mirror of https://github.com/TeamNewPipe/NewPipe
605 lines
23 KiB
Java
605 lines
23 KiB
Java
package org.schabi.newpipe.player.helper;
|
|
|
|
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
|
|
import static org.schabi.newpipe.player.Player.DEBUG;
|
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable;
|
|
|
|
import android.app.Dialog;
|
|
import android.content.Context;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.graphics.drawable.LayerDrawable;
|
|
import android.os.Bundle;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.widget.CheckBox;
|
|
import android.widget.SeekBar;
|
|
import android.widget.TextView;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.StringRes;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.fragment.app.DialogFragment;
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
import org.schabi.newpipe.R;
|
|
import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
|
|
import org.schabi.newpipe.player.Player;
|
|
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
|
|
import org.schabi.newpipe.util.SliderStrategy;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.DoubleConsumer;
|
|
import java.util.function.DoubleFunction;
|
|
import java.util.function.DoubleSupplier;
|
|
|
|
import icepick.Icepick;
|
|
import icepick.State;
|
|
|
|
public class PlaybackParameterDialog extends DialogFragment {
|
|
private static final String TAG = "PlaybackParameterDialog";
|
|
|
|
// Minimum allowable range in ExoPlayer
|
|
private static final double MIN_PITCH_OR_SPEED = 0.10f;
|
|
private static final double MAX_PITCH_OR_SPEED = 3.00f;
|
|
|
|
private static final boolean PITCH_CTRL_MODE_PERCENT = false;
|
|
private static final boolean PITCH_CTRL_MODE_SEMITONE = true;
|
|
|
|
private static final double STEP_1_PERCENT_VALUE = 0.01f;
|
|
private static final double STEP_5_PERCENT_VALUE = 0.05f;
|
|
private static final double STEP_10_PERCENT_VALUE = 0.10f;
|
|
private static final double STEP_25_PERCENT_VALUE = 0.25f;
|
|
private static final double STEP_100_PERCENT_VALUE = 1.00f;
|
|
|
|
private static final double DEFAULT_TEMPO = 1.00f;
|
|
private static final double DEFAULT_PITCH_PERCENT = 1.00f;
|
|
private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE;
|
|
private static final boolean DEFAULT_SKIP_SILENCE = false;
|
|
|
|
private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic(
|
|
MIN_PITCH_OR_SPEED,
|
|
MAX_PITCH_OR_SPEED,
|
|
1.00f,
|
|
10_000);
|
|
|
|
private static final SliderStrategy SEMITONE_STRATEGY = new SliderStrategy() {
|
|
@Override
|
|
public int progressOf(final double value) {
|
|
return PlayerSemitoneHelper.percentToSemitones(value) + 12;
|
|
}
|
|
|
|
@Override
|
|
public double valueOf(final int progress) {
|
|
return PlayerSemitoneHelper.semitonesToPercent(progress - 12);
|
|
}
|
|
};
|
|
|
|
@Nullable
|
|
private Callback callback;
|
|
|
|
@State
|
|
double initialTempo = DEFAULT_TEMPO;
|
|
@State
|
|
double initialPitchPercent = DEFAULT_PITCH_PERCENT;
|
|
@State
|
|
boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
|
|
|
|
@State
|
|
double tempo = DEFAULT_TEMPO;
|
|
@State
|
|
double pitchPercent = DEFAULT_PITCH_PERCENT;
|
|
@State
|
|
boolean skipSilence = DEFAULT_SKIP_SILENCE;
|
|
|
|
private DialogPlaybackParameterBinding binding;
|
|
|
|
public static PlaybackParameterDialog newInstance(
|
|
final double playbackTempo,
|
|
final double playbackPitch,
|
|
final boolean playbackSkipSilence,
|
|
final Callback callback
|
|
) {
|
|
final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
|
dialog.callback = callback;
|
|
|
|
dialog.initialTempo = playbackTempo;
|
|
dialog.initialPitchPercent = playbackPitch;
|
|
dialog.initialSkipSilence = playbackSkipSilence;
|
|
|
|
dialog.tempo = dialog.initialTempo;
|
|
dialog.pitchPercent = dialog.initialPitchPercent;
|
|
dialog.skipSilence = dialog.initialSkipSilence;
|
|
|
|
return dialog;
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
// Lifecycle
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
@Override
|
|
public void onAttach(@NonNull final Context context) {
|
|
super.onAttach(context);
|
|
if (context instanceof Callback) {
|
|
callback = (Callback) context;
|
|
} else if (callback == null) {
|
|
dismiss();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
Icepick.saveInstanceState(this, outState);
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
// Dialog
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
@NonNull
|
|
@Override
|
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
|
assureCorrectAppLanguage(getContext());
|
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
|
|
|
binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext()));
|
|
initUI();
|
|
|
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
|
.setView(binding.getRoot())
|
|
.setCancelable(true)
|
|
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
|
|
setAndUpdateTempo(initialTempo);
|
|
setAndUpdatePitch(initialPitchPercent);
|
|
setAndUpdateSkipSilence(initialSkipSilence);
|
|
updateCallback();
|
|
})
|
|
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> {
|
|
setAndUpdateTempo(DEFAULT_TEMPO);
|
|
setAndUpdatePitch(DEFAULT_PITCH_PERCENT);
|
|
setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE);
|
|
updateCallback();
|
|
})
|
|
.setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback());
|
|
|
|
return dialogBuilder.create();
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
// UI Initialization and Control
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
private void initUI() {
|
|
// Tempo
|
|
setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PITCH_OR_SPEED);
|
|
setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PITCH_OR_SPEED);
|
|
|
|
binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED));
|
|
setAndUpdateTempo(tempo);
|
|
binding.tempoSeekbar.setOnSeekBarChangeListener(
|
|
getTempoOrPitchSeekbarChangeListener(
|
|
QUADRATIC_STRATEGY,
|
|
this::onTempoSliderUpdated));
|
|
|
|
registerOnStepClickListener(
|
|
binding.tempoStepDown,
|
|
() -> tempo,
|
|
-1,
|
|
this::onTempoSliderUpdated);
|
|
registerOnStepClickListener(
|
|
binding.tempoStepUp,
|
|
() -> tempo,
|
|
1,
|
|
this::onTempoSliderUpdated);
|
|
|
|
// Pitch
|
|
binding.pitchToogleControlModes.setOnClickListener(v -> {
|
|
final boolean isCurrentlyVisible =
|
|
binding.pitchControlModeTabs.getVisibility() == View.GONE;
|
|
binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible
|
|
? View.VISIBLE
|
|
: View.GONE);
|
|
animateRotation(binding.pitchToogleControlModes,
|
|
Player.DEFAULT_CONTROLS_DURATION,
|
|
isCurrentlyVisible ? 180 : 0);
|
|
});
|
|
|
|
getPitchControlModeComponentMappings()
|
|
.forEach(this::setupPitchControlModeTextView);
|
|
changePitchControlMode(isCurrentPitchControlModeSemitone());
|
|
|
|
// Pitch - Percent
|
|
setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PITCH_OR_SPEED);
|
|
setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PITCH_OR_SPEED);
|
|
|
|
binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED));
|
|
setAndUpdatePitch(pitchPercent);
|
|
binding.pitchPercentSeekbar.setOnSeekBarChangeListener(
|
|
getTempoOrPitchSeekbarChangeListener(
|
|
QUADRATIC_STRATEGY,
|
|
this::onPitchPercentSliderUpdated));
|
|
|
|
registerOnStepClickListener(
|
|
binding.pitchPercentStepDown,
|
|
() -> pitchPercent,
|
|
-1,
|
|
this::onPitchPercentSliderUpdated);
|
|
registerOnStepClickListener(
|
|
binding.pitchPercentStepUp,
|
|
() -> pitchPercent,
|
|
1,
|
|
this::onPitchPercentSliderUpdated);
|
|
|
|
// Pitch - Semitone
|
|
binding.pitchSemitoneSeekbar.setOnSeekBarChangeListener(
|
|
getTempoOrPitchSeekbarChangeListener(
|
|
SEMITONE_STRATEGY,
|
|
this::onPitchPercentSliderUpdated));
|
|
|
|
registerOnSemitoneStepClickListener(
|
|
binding.pitchSemitoneStepDown,
|
|
-1,
|
|
this::onPitchPercentSliderUpdated);
|
|
registerOnSemitoneStepClickListener(
|
|
binding.pitchSemitoneStepUp,
|
|
1,
|
|
this::onPitchPercentSliderUpdated);
|
|
|
|
// Steps
|
|
getStepSizeComponentMappings()
|
|
.forEach(this::setupStepTextView);
|
|
// Initialize UI
|
|
setStepSizeToUI(getCurrentStepSize());
|
|
|
|
// Bottom controls
|
|
bindCheckboxWithBoolPref(
|
|
binding.unhookCheckbox,
|
|
R.string.playback_unhook_key,
|
|
true,
|
|
isChecked -> {
|
|
if (!isChecked) {
|
|
// when unchecked, slide back to the minimum of current tempo or pitch
|
|
ensureHookIsValidAndUpdateCallBack();
|
|
}
|
|
});
|
|
|
|
setAndUpdateSkipSilence(skipSilence);
|
|
binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
|
skipSilence = isChecked;
|
|
updateCallback();
|
|
});
|
|
}
|
|
|
|
// -- General formatting --
|
|
|
|
private void setText(
|
|
final TextView textView,
|
|
final DoubleFunction<String> formatter,
|
|
final double value
|
|
) {
|
|
Objects.requireNonNull(textView).setText(formatter.apply(value));
|
|
}
|
|
|
|
// -- Steps --
|
|
|
|
private void registerOnStepClickListener(
|
|
final TextView stepTextView,
|
|
final DoubleSupplier currentValueSupplier,
|
|
final double direction, // -1 for step down, +1 for step up
|
|
final DoubleConsumer newValueConsumer
|
|
) {
|
|
stepTextView.setOnClickListener(view -> {
|
|
newValueConsumer.accept(
|
|
currentValueSupplier.getAsDouble() + 1 * getCurrentStepSize() * direction);
|
|
updateCallback();
|
|
});
|
|
}
|
|
|
|
private void registerOnSemitoneStepClickListener(
|
|
final TextView stepTextView,
|
|
final int direction, // -1 for step down, +1 for step up
|
|
final DoubleConsumer newValueConsumer
|
|
) {
|
|
stepTextView.setOnClickListener(view -> {
|
|
newValueConsumer.accept(PlayerSemitoneHelper.semitonesToPercent(
|
|
PlayerSemitoneHelper.percentToSemitones(this.pitchPercent) + direction));
|
|
updateCallback();
|
|
});
|
|
}
|
|
|
|
// -- Pitch --
|
|
|
|
private void setupPitchControlModeTextView(
|
|
final boolean semitones,
|
|
final TextView textView
|
|
) {
|
|
textView.setOnClickListener(view -> {
|
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
.edit()
|
|
.putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones)
|
|
.apply();
|
|
|
|
changePitchControlMode(semitones);
|
|
});
|
|
}
|
|
|
|
private Map<Boolean, TextView> getPitchControlModeComponentMappings() {
|
|
final Map<Boolean, TextView> mappings = new HashMap<>();
|
|
mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent);
|
|
mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone);
|
|
return mappings;
|
|
}
|
|
|
|
private void changePitchControlMode(final boolean semitones) {
|
|
// Bring all textviews into a normal state
|
|
final Map<Boolean, TextView> pitchCtrlModeComponentMapping =
|
|
getPitchControlModeComponentMappings();
|
|
pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground(
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
|
|
|
|
// Mark the selected textview
|
|
final TextView textView = pitchCtrlModeComponentMapping.get(semitones);
|
|
if (textView != null) {
|
|
textView.setBackground(new LayerDrawable(new Drawable[]{
|
|
resolveDrawable(requireContext(), R.attr.dashed_border),
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
|
|
}));
|
|
}
|
|
|
|
// Show or hide component
|
|
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
|
|
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
|
|
|
|
if (semitones) {
|
|
// Recalculate pitch percent when changing to semitone
|
|
// (as it could be an invalid semitone value)
|
|
final double newPitchPercent = calcValidPitch(pitchPercent);
|
|
|
|
// If the values differ set the new pitch
|
|
if (this.pitchPercent != newPitchPercent) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
|
|
+ "currentPitchPercent = " + pitchPercent + ", "
|
|
+ "newPitchPercent = " + newPitchPercent
|
|
);
|
|
}
|
|
this.onPitchPercentSliderUpdated(newPitchPercent);
|
|
updateCallback();
|
|
}
|
|
} else if (!binding.unhookCheckbox.isChecked()) {
|
|
// When changing to percent it's possible that tempo is != pitch
|
|
ensureHookIsValidAndUpdateCallBack();
|
|
}
|
|
}
|
|
|
|
private boolean isCurrentPitchControlModeSemitone() {
|
|
return PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
.getBoolean(
|
|
getString(R.string.playback_adjust_by_semitones_key),
|
|
PITCH_CTRL_MODE_PERCENT);
|
|
}
|
|
|
|
// -- Steps (Set) --
|
|
|
|
private void setupStepTextView(
|
|
final double stepSizeValue,
|
|
final TextView textView
|
|
) {
|
|
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue);
|
|
textView.setOnClickListener(view -> {
|
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
.edit()
|
|
.putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue)
|
|
.apply();
|
|
|
|
setStepSizeToUI(stepSizeValue);
|
|
});
|
|
}
|
|
|
|
private Map<Double, TextView> getStepSizeComponentMappings() {
|
|
final Map<Double, TextView> mappings = new HashMap<>();
|
|
mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent);
|
|
mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent);
|
|
mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent);
|
|
mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent);
|
|
mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent);
|
|
return mappings;
|
|
}
|
|
|
|
private void setStepSizeToUI(final double newStepSize) {
|
|
// Bring all textviews into a normal state
|
|
final Map<Double, TextView> stepSiteComponentMapping = getStepSizeComponentMappings();
|
|
stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground(
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
|
|
|
|
// Mark the selected textview
|
|
final TextView textView = stepSiteComponentMapping.get(newStepSize);
|
|
if (textView != null) {
|
|
textView.setBackground(new LayerDrawable(new Drawable[]{
|
|
resolveDrawable(requireContext(), R.attr.dashed_border),
|
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
|
|
}));
|
|
}
|
|
|
|
// Bind to the corresponding control components
|
|
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
|
|
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
|
|
|
|
binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize));
|
|
binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize));
|
|
}
|
|
|
|
private double getCurrentStepSize() {
|
|
return PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
.getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP);
|
|
}
|
|
|
|
// -- Additional options --
|
|
|
|
private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
|
|
this.skipSilence = newSkipSilence;
|
|
binding.skipSilenceCheckbox.setChecked(newSkipSilence);
|
|
}
|
|
|
|
@SuppressWarnings("SameParameterValue") // this method was written to be reusable
|
|
private void bindCheckboxWithBoolPref(
|
|
@NonNull final CheckBox checkBox,
|
|
@StringRes final int resId,
|
|
final boolean defaultValue,
|
|
@NonNull final Consumer<Boolean> onInitialValueOrValueChange
|
|
) {
|
|
final boolean prefValue = PreferenceManager
|
|
.getDefaultSharedPreferences(requireContext())
|
|
.getBoolean(getString(resId), defaultValue);
|
|
|
|
checkBox.setChecked(prefValue);
|
|
|
|
onInitialValueOrValueChange.accept(prefValue);
|
|
|
|
checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
|
// save whether pitch and tempo are unhooked or not
|
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
.edit()
|
|
.putBoolean(getString(resId), isChecked)
|
|
.apply();
|
|
|
|
onInitialValueOrValueChange.accept(isChecked);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ensures that the slider hook is valid and if not sets and updates the sliders accordingly.
|
|
* <br/>
|
|
* You have to ensure by yourself that the hooking is active.
|
|
*/
|
|
private void ensureHookIsValidAndUpdateCallBack() {
|
|
if (tempo != pitchPercent) {
|
|
setSliders(Math.min(tempo, pitchPercent));
|
|
updateCallback();
|
|
}
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
// Sliders
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener(
|
|
final SliderStrategy sliderStrategy,
|
|
final DoubleConsumer newValueConsumer
|
|
) {
|
|
return new SimpleOnSeekBarChangeListener() {
|
|
@Override
|
|
public void onProgressChanged(@NonNull final SeekBar seekBar,
|
|
final int progress,
|
|
final boolean fromUser) {
|
|
if (fromUser) { // ensure that the user triggered the change
|
|
newValueConsumer.accept(sliderStrategy.valueOf(progress));
|
|
updateCallback();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private void onTempoSliderUpdated(final double newTempo) {
|
|
if (!binding.unhookCheckbox.isChecked()) {
|
|
setSliders(newTempo);
|
|
} else {
|
|
setAndUpdateTempo(newTempo);
|
|
}
|
|
}
|
|
|
|
private void onPitchPercentSliderUpdated(final double newPitch) {
|
|
if (!binding.unhookCheckbox.isChecked()) {
|
|
setSliders(newPitch);
|
|
} else {
|
|
setAndUpdatePitch(newPitch);
|
|
}
|
|
}
|
|
|
|
private void setSliders(final double newValue) {
|
|
setAndUpdateTempo(newValue);
|
|
setAndUpdatePitch(newValue);
|
|
}
|
|
|
|
private void setAndUpdateTempo(final double newTempo) {
|
|
this.tempo = calcValidTempo(newTempo);
|
|
|
|
binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo));
|
|
setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo);
|
|
}
|
|
|
|
private void setAndUpdatePitch(final double newPitch) {
|
|
this.pitchPercent = calcValidPitch(newPitch);
|
|
|
|
binding.pitchPercentSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitchPercent));
|
|
binding.pitchSemitoneSeekbar.setProgress(SEMITONE_STRATEGY.progressOf(pitchPercent));
|
|
setText(binding.pitchPercentCurrentText,
|
|
PlayerHelper::formatPitch,
|
|
pitchPercent);
|
|
setText(binding.pitchSemitoneCurrentText,
|
|
PlayerSemitoneHelper::formatPitchSemitones,
|
|
pitchPercent);
|
|
}
|
|
|
|
private double calcValidTempo(final double newTempo) {
|
|
return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo));
|
|
}
|
|
|
|
private double calcValidPitch(final double newPitch) {
|
|
final double calcPitch =
|
|
Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch));
|
|
|
|
if (!isCurrentPitchControlModeSemitone()) {
|
|
return calcPitch;
|
|
}
|
|
|
|
return PlayerSemitoneHelper.semitonesToPercent(
|
|
PlayerSemitoneHelper.percentToSemitones(calcPitch));
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
// Helper
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
private void updateCallback() {
|
|
if (callback == null) {
|
|
return;
|
|
}
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Updating callback: "
|
|
+ "tempo = " + tempo + ", "
|
|
+ "pitchPercent = " + pitchPercent + ", "
|
|
+ "skipSilence = " + skipSilence
|
|
);
|
|
}
|
|
callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence);
|
|
}
|
|
|
|
@NonNull
|
|
private static String getStepUpPercentString(final double percent) {
|
|
return '+' + getPercentString(percent);
|
|
}
|
|
|
|
@NonNull
|
|
private static String getStepDownPercentString(final double percent) {
|
|
return '-' + getPercentString(percent);
|
|
}
|
|
|
|
@NonNull
|
|
private static String getPercentString(final double percent) {
|
|
return PlayerHelper.formatPitch(percent);
|
|
}
|
|
|
|
public interface Callback {
|
|
void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
|
boolean playbackSkipSilence);
|
|
}
|
|
}
|