Major refactoring of PlaybackParameterDialog

* Removed/Renamed methods
* Use ``IcePick``
* Better structuring
* Keep skipSilence when rotating the device (PlayQueueActivity only)
This commit is contained in:
litetex 2022-01-01 19:02:03 +01:00
parent 6e81f2430b
commit 652d50173e
1 changed files with 224 additions and 502 deletions

View File

@ -9,10 +9,10 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
@ -20,104 +20,88 @@ import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
import org.schabi.newpipe.util.SliderStrategy;
import java.util.Objects;
import java.util.function.DoubleConsumer;
import java.util.function.DoubleFunction;
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 MINIMUM_PLAYBACK_VALUE = 0.10f;
private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
private static final char STEP_UP_SIGN = '+';
private static final char STEP_DOWN_SIGN = '-';
private static final double STEP_ONE_PERCENT_VALUE = 0.01f;
private static final double STEP_FIVE_PERCENT_VALUE = 0.05f;
private static final double STEP_TEN_PERCENT_VALUE = 0.10f;
private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f;
private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f;
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 = 1.00f;
private static final int DEFAULT_SEMITONES = 0;
private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE;
private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE;
private static final boolean DEFAULT_SKIP_SILENCE = false;
@NonNull
private static final String TAG = "PlaybackParameterDialog";
@NonNull
private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
@NonNull
private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
@NonNull
private static final String TEMPO_KEY = "tempo_key";
@NonNull
private static final String PITCH_KEY = "pitch_key";
@NonNull
private static final String STEP_SIZE_KEY = "step_size_key";
@NonNull
private final SliderStrategy strategy = new SliderStrategy.Quadratic(
MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE,
/*centerAt=*/1.00f, /*sliderGranularity=*/10000);
private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic(
MINIMUM_PLAYBACK_VALUE,
MAXIMUM_PLAYBACK_VALUE,
1.00f,
10_000);
@Nullable
private Callback callback;
private double initialTempo = DEFAULT_TEMPO;
private double initialPitch = DEFAULT_PITCH;
private int initialSemitones = DEFAULT_SEMITONES;
private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
private double tempo = DEFAULT_TEMPO;
private double pitch = DEFAULT_PITCH;
private int semitones = DEFAULT_SEMITONES;
@State
double initialTempo = DEFAULT_TEMPO;
@State
double initialPitch = DEFAULT_PITCH;
@State
boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
@State
double tempo = DEFAULT_TEMPO;
@State
double pitch = DEFAULT_PITCH;
@State
double stepSize = DEFAULT_STEP;
@State
boolean skipSilence = DEFAULT_SKIP_SILENCE;
@Nullable
private SeekBar tempoSlider;
@Nullable
private TextView tempoCurrentText;
@Nullable
private TextView tempoStepDownText;
@Nullable
private TextView tempoStepUpText;
@Nullable
private SeekBar pitchSlider;
@Nullable
private TextView pitchCurrentText;
@Nullable
private TextView pitchStepDownText;
@Nullable
private TextView pitchStepUpText;
@Nullable
private SeekBar semitoneSlider;
@Nullable
private TextView semitoneCurrentText;
@Nullable
private TextView semitoneStepDownText;
@Nullable
private TextView semitoneStepUpText;
@Nullable
private CheckBox unhookingCheckbox;
@Nullable
private CheckBox skipSilenceCheckbox;
@Nullable
private CheckBox adjustBySemitonesCheckbox;
public static PlaybackParameterDialog newInstance(final double playbackTempo,
final double playbackPitch,
final boolean playbackSkipSilence,
final Callback callback) {
private SeekBar pitchSlider;
private TextView pitchCurrentText;
private TextView pitchStepDownText;
private TextView pitchStepUpText;
private CheckBox unhookingCheckbox;
private CheckBox skipSilenceCheckbox;
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.initialPitch = playbackPitch;
dialog.tempo = playbackTempo;
dialog.pitch = playbackPitch;
dialog.semitones = dialog.percentToSemitones(playbackPitch);
dialog.initialSkipSilence = playbackSkipSilence;
dialog.tempo = dialog.initialTempo;
dialog.pitch = dialog.initialPitch;
dialog.skipSilence = dialog.initialSkipSilence;
return dialog;
}
@ -126,7 +110,7 @@ public class PlaybackParameterDialog extends DialogFragment {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(@NonNull final Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
if (context instanceof Callback) {
callback = (Callback) context;
@ -136,28 +120,9 @@ public class PlaybackParameterDialog extends DialogFragment {
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
assureCorrectAppLanguage(getContext());
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
initialSemitones = percentToSemitones(initialPitch);
tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO);
pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH);
semitones = percentToSemitones(pitch);
}
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
outState.putDouble(TEMPO_KEY, getCurrentTempo());
outState.putDouble(PITCH_KEY, getCurrentPitch());
Icepick.saveInstanceState(this, outState);
}
/*//////////////////////////////////////////////////////////////////////////
@ -168,20 +133,28 @@ public class PlaybackParameterDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
assureCorrectAppLanguage(getContext());
Icepick.restoreInstanceState(this, savedInstanceState);
final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null);
setupControlViews(view);
initUI(view);
initUIData();
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
setPlaybackParameters(initialTempo, initialPitch,
initialSemitones, initialSkipSilence))
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH,
DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE))
.setPositiveButton(R.string.ok, (dialogInterface, i) ->
setCurrentPlaybackParameters());
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
setAndUpdateTempo(initialTempo);
setAndUpdatePitch(initialPitch);
setAndUpdateSkipSilence(initialSkipSilence);
updateCallback();
})
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> {
setAndUpdateTempo(DEFAULT_TEMPO);
setAndUpdatePitch(DEFAULT_PITCH);
setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE);
updateCallback();
})
.setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback());
return dialogBuilder.create();
}
@ -190,353 +163,171 @@ public class PlaybackParameterDialog extends DialogFragment {
// Control Views
//////////////////////////////////////////////////////////////////////////*/
private void setupControlViews(@NonNull final View rootView) {
setupHookingControl(rootView);
setupSkipSilenceControl(rootView);
setupAdjustBySemitonesControl(rootView);
private void initUI(@NonNull final View rootView) {
// Tempo
tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar));
tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText));
tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp));
tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown));
setupTempoControl(rootView);
setupPitchControl(rootView);
setupSemitoneControl(rootView);
setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE);
setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE);
togglePitchSliderType(rootView);
// Pitch
pitchSlider = Objects.requireNonNull(rootView.findViewById(R.id.pitchSeekbar));
pitchCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.pitchCurrentText));
pitchStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepUp));
pitchStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepDown));
setupStepSizeSelector(rootView);
setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE);
setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE);
// Steps
setupStepTextView(rootView, R.id.stepSizeOnePercent, STEP_1_PERCENT_VALUE);
setupStepTextView(rootView, R.id.stepSizeFivePercent, STEP_5_PERCENT_VALUE);
setupStepTextView(rootView, R.id.stepSizeTenPercent, STEP_10_PERCENT_VALUE);
setupStepTextView(rootView, R.id.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE);
setupStepTextView(rootView, R.id.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE);
// Bottom controls
unhookingCheckbox =
Objects.requireNonNull(rootView.findViewById(R.id.unhookCheckbox));
skipSilenceCheckbox =
Objects.requireNonNull(rootView.findViewById(R.id.skipSilenceCheckbox));
}
private void togglePitchSliderType(@NonNull final View rootView) {
final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl);
final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl);
final View separatorStepSizeSelector =
rootView.findViewById(R.id.separatorStepSizeSelector);
final RelativeLayout.LayoutParams params =
(RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams();
if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) {
if (getCurrentAdjustBySemitones()) {
// replaces pitchControl slider with semitoneControl slider
pitchControl.setVisibility(View.GONE);
semitoneControl.setVisibility(View.VISIBLE);
params.addRule(RelativeLayout.BELOW, R.id.semitoneControl);
// forces unhook for semitones
unhookingCheckbox.setChecked(true);
unhookingCheckbox.setEnabled(false);
setupTempoStepSizeSelector(rootView);
} else {
semitoneControl.setVisibility(View.GONE);
pitchControl.setVisibility(View.VISIBLE);
params.addRule(RelativeLayout.BELOW, R.id.pitchControl);
// (re)enables hooking selection
unhookingCheckbox.setEnabled(true);
setupCombinedStepSizeSelector(rootView);
}
}
private TextView setText(
final TextView textView,
final DoubleFunction<String> formatter,
final double value
) {
Objects.requireNonNull(textView).setText(formatter.apply(value));
return textView;
}
private void setupTempoControl(@NonNull final View rootView) {
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
if (tempoCurrentText != null) {
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
}
if (tempoMaximumText != null) {
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
}
if (tempoMinimumText != null) {
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
}
if (tempoSlider != null) {
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
tempoSlider.setProgress(strategy.progressOf(tempo));
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
}
private TextView setText(
final View rootView,
@IdRes final int idRes,
final DoubleFunction<String> formatter,
final double value
) {
final TextView textView = rootView.findViewById(idRes);
setText(textView, formatter, value);
return textView;
}
private void setupPitchControl(@NonNull final View rootView) {
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
if (pitchCurrentText != null) {
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
}
if (pitchMaximumText != null) {
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
}
if (pitchMinimumText != null) {
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
}
if (pitchSlider != null) {
pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
pitchSlider.setProgress(strategy.progressOf(pitch));
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
}
private void setupStepTextView(
final View rootView,
@IdRes final int idRes,
final double stepSizeValue
) {
setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue)
.setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue));
}
private void setupSemitoneControl(@NonNull final View rootView) {
semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar);
semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText);
semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown);
semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp);
private void initUIData() {
// Tempo
tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE));
setAndUpdateTempo(tempo);
tempoSlider.setOnSeekBarChangeListener(
getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated));
if (semitoneCurrentText != null) {
semitoneCurrentText.setText(getSignedSemitonesString(semitones));
}
registerOnStepClickListener(
tempoStepDownText, tempo, -1, this::onTempoSliderUpdated);
registerOnStepClickListener(
tempoStepUpText, tempo, 1, this::onTempoSliderUpdated);
if (semitoneSlider != null) {
setSemitoneSlider(semitones);
semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener());
}
// Pitch
pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE));
setAndUpdatePitch(pitch);
pitchSlider.setOnSeekBarChangeListener(
getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated));
}
registerOnStepClickListener(
pitchStepDownText, pitch, -1, this::onPitchSliderUpdated);
registerOnStepClickListener(
pitchStepUpText, pitch, 1, this::onPitchSliderUpdated);
private void setupHookingControl(@NonNull final View rootView) {
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
if (unhookingCheckbox != null) {
// restores whether pitch and tempo are unhooked or not
unhookingCheckbox.setChecked(PreferenceManager
.getDefaultSharedPreferences(requireContext())
.getBoolean(getString(R.string.playback_unhook_key), true));
// Steps
setAndUpdateStepSize(stepSize);
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
// saves whether pitch and tempo are unhooked or not
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
.apply();
if (!isChecked) {
// when unchecked, slides back to the minimum of current tempo or pitch
final double minimum = Math.min(getCurrentPitch(), getCurrentTempo());
setSliders(minimum);
setCurrentPlaybackParameters();
}
});
}
}
private void setupSkipSilenceControl(@NonNull final View rootView) {
skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox);
if (skipSilenceCheckbox != null) {
skipSilenceCheckbox.setChecked(initialSkipSilence);
skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) ->
setCurrentPlaybackParameters());
}
}
private void setupAdjustBySemitonesControl(@NonNull final View rootView) {
adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox);
if (adjustBySemitonesCheckbox != null) {
// restores whether semitone adjustment is used or not
adjustBySemitonesCheckbox.setChecked(PreferenceManager
// Bottom controls
// restore whether pitch and tempo are unhooked or not
unhookingCheckbox.setChecked(PreferenceManager
.getDefaultSharedPreferences(requireContext())
.getBoolean(getString(R.string.playback_adjust_by_semitones_key), true));
.getBoolean(getString(R.string.playback_unhook_key), true));
// stores whether semitone adjustment is used or not
adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
PreferenceManager.getDefaultSharedPreferences(requireContext())
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
// save whether pitch and tempo are unhooked or not
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked)
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
.apply();
togglePitchSliderType(rootView);
if (isChecked) {
setPlaybackParameters(
getCurrentTempo(),
getCurrentPitch(),
Integer.min(12,
Integer.max(-12, percentToSemitones(getCurrentPitch())
)),
getCurrentSkipSilence()
);
setSemitoneSlider(Integer.min(12,
Integer.max(-12, percentToSemitones(getCurrentPitch()))
));
} else {
setPlaybackParameters(
getCurrentTempo(),
semitonesToPercent(getCurrentSemitones()),
getCurrentSemitones(),
getCurrentSkipSilence()
);
setPitchSlider(semitonesToPercent(getCurrentSemitones()));
}
});
}
if (!isChecked) {
// when unchecked, slide back to the minimum of current tempo or pitch
setSliders(Math.min(pitch, tempo));
}
});
setAndUpdateSkipSilence(skipSilence);
skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
skipSilence = isChecked;
updateCallback();
});
}
private void setupStepSizeSelector(@NonNull final View rootView) {
setStepSize(PreferenceManager
.getDefaultSharedPreferences(requireContext())
.getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP));
final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
final TextView stepSizeTwentyFivePercentText = rootView
.findViewById(R.id.stepSizeTwentyFivePercent);
final TextView stepSizeOneHundredPercentText = rootView
.findViewById(R.id.stepSizeOneHundredPercent);
if (stepSizeOnePercentText != null) {
stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
stepSizeOnePercentText
.setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE));
}
if (stepSizeFivePercentText != null) {
stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE));
stepSizeFivePercentText
.setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE));
}
if (stepSizeTenPercentText != null) {
stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE));
stepSizeTenPercentText
.setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE));
}
if (stepSizeTwentyFivePercentText != null) {
stepSizeTwentyFivePercentText
.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
stepSizeTwentyFivePercentText
.setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE));
}
if (stepSizeOneHundredPercentText != null) {
stepSizeOneHundredPercentText
.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE));
stepSizeOneHundredPercentText
.setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE));
}
private void registerOnStepClickListener(
final TextView stepTextView,
final double currentValue,
final double direction, // -1 for step down, +1 for step up
final DoubleConsumer newValueConsumer
) {
stepTextView.setOnClickListener(view ->
newValueConsumer.accept(currentValue * direction)
);
}
private void setupTempoStepSizeSelector(@NonNull final View rootView) {
final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type);
if (playbackStepTypeText != null) {
playbackStepTypeText.setText(R.string.playback_tempo_step);
}
setupStepSizeSelector(rootView);
private void setAndUpdateStepSize(final double newStepSize) {
this.stepSize = newStepSize;
tempoStepUpText.setText(getStepUpPercentString(newStepSize));
tempoStepDownText.setText(getStepDownPercentString(newStepSize));
pitchStepUpText.setText(getStepUpPercentString(newStepSize));
pitchStepDownText.setText(getStepDownPercentString(newStepSize));
}
private void setupCombinedStepSizeSelector(@NonNull final View rootView) {
final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type);
if (playbackStepTypeText != null) {
playbackStepTypeText.setText(R.string.playback_step);
}
setupStepSizeSelector(rootView);
}
private void setStepSize(final double stepSize) {
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putFloat(getString(R.string.adjustment_step_key), (float) stepSize)
.apply();
if (tempoStepUpText != null) {
tempoStepUpText.setText(getStepUpPercentString(stepSize));
tempoStepUpText.setOnClickListener(view -> {
onTempoSliderUpdated(getCurrentTempo() + stepSize);
setCurrentPlaybackParameters();
});
}
if (tempoStepDownText != null) {
tempoStepDownText.setText(getStepDownPercentString(stepSize));
tempoStepDownText.setOnClickListener(view -> {
onTempoSliderUpdated(getCurrentTempo() - stepSize);
setCurrentPlaybackParameters();
});
}
if (pitchStepUpText != null) {
pitchStepUpText.setText(getStepUpPercentString(stepSize));
pitchStepUpText.setOnClickListener(view -> {
onPitchSliderUpdated(getCurrentPitch() + stepSize);
setCurrentPlaybackParameters();
});
}
if (pitchStepDownText != null) {
pitchStepDownText.setText(getStepDownPercentString(stepSize));
pitchStepDownText.setOnClickListener(view -> {
onPitchSliderUpdated(getCurrentPitch() - stepSize);
setCurrentPlaybackParameters();
});
}
if (semitoneStepDownText != null) {
semitoneStepDownText.setOnClickListener(view -> {
onSemitoneSliderUpdated(getCurrentSemitones() - 1);
setCurrentPlaybackParameters();
});
}
if (semitoneStepUpText != null) {
semitoneStepUpText.setOnClickListener(view -> {
onSemitoneSliderUpdated(getCurrentSemitones() + 1);
setCurrentPlaybackParameters();
});
}
private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
this.skipSilence = newSkipSilence;
skipSilenceCheckbox.setChecked(newSkipSilence);
}
/*//////////////////////////////////////////////////////////////////////////
// Sliders
//////////////////////////////////////////////////////////////////////////*/
private SimpleOnSeekBarChangeListener getOnTempoChangedListener() {
return new SimpleOnSeekBarChangeListener() {
private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener(
final DoubleConsumer newValueConsumer
) {
return new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress,
public void onProgressChanged(final SeekBar seekBar, final int progress,
final boolean fromUser) {
final double currentTempo = strategy.valueOf(progress);
if (fromUser) {
onTempoSliderUpdated(currentTempo);
setCurrentPlaybackParameters();
if (fromUser) { // this change is first in chain
newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress));
updateCallback();
}
}
};
}
private SimpleOnSeekBarChangeListener getOnPitchChangedListener() {
return new SimpleOnSeekBarChangeListener() {
@Override
public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress,
final boolean fromUser) {
final double currentPitch = strategy.valueOf(progress);
if (fromUser) { // this change is first in chain
onPitchSliderUpdated(currentPitch);
setCurrentPlaybackParameters();
}
public void onStartTrackingTouch(final SeekBar seekBar) {
// Do nothing
}
};
}
private SimpleOnSeekBarChangeListener getOnSemitoneChangedListener() {
return new SimpleOnSeekBarChangeListener() {
@Override
public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress,
final boolean fromUser) {
// semitone slider supplies values 0 to 24, subtraction by 12 is required
final int currentSemitones = progress - 12;
if (fromUser) { // this change is first in chain
onSemitoneSliderUpdated(currentSemitones);
// line below also saves semitones as pitch percentages
onPitchSliderUpdated(semitonesToPercent(currentSemitones));
setCurrentPlaybackParameters();
}
public void onStopTrackingTouch(final SeekBar seekBar) {
// Do nothing
}
};
}
@ -545,7 +336,7 @@ public class PlaybackParameterDialog extends DialogFragment {
if (!unhookingCheckbox.isChecked()) {
setSliders(newTempo);
} else {
setTempoSlider(newTempo);
setAndUpdateTempo(newTempo);
}
}
@ -553,109 +344,53 @@ public class PlaybackParameterDialog extends DialogFragment {
if (!unhookingCheckbox.isChecked()) {
setSliders(newPitch);
} else {
setPitchSlider(newPitch);
setAndUpdatePitch(newPitch);
}
}
private void onSemitoneSliderUpdated(final int newSemitone) {
setSemitoneSlider(newSemitone);
}
private void setSliders(final double newValue) {
setTempoSlider(newValue);
setPitchSlider(newValue);
setAndUpdateTempo(newValue);
setAndUpdatePitch(newValue);
}
private void setTempoSlider(final double newTempo) {
if (tempoSlider == null) {
return;
}
tempoSlider.setProgress(strategy.progressOf(newTempo));
private void setAndUpdateTempo(final double newTempo) {
this.tempo = newTempo;
tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo));
setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo);
}
private void setPitchSlider(final double newPitch) {
if (pitchSlider == null) {
return;
}
pitchSlider.setProgress(strategy.progressOf(newPitch));
}
private void setSemitoneSlider(final int newSemitone) {
if (semitoneSlider == null) {
return;
}
semitoneSlider.setProgress(newSemitone + 12);
private void setAndUpdatePitch(final double newPitch) {
this.pitch = newPitch;
pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch));
setText(pitchCurrentText, PlayerHelper::formatPitch, pitch);
}
/*//////////////////////////////////////////////////////////////////////////
// Helper
//////////////////////////////////////////////////////////////////////////*/
private void setCurrentPlaybackParameters() {
if (getCurrentAdjustBySemitones()) {
setPlaybackParameters(
getCurrentTempo(),
semitonesToPercent(getCurrentSemitones()),
getCurrentSemitones(),
getCurrentSkipSilence()
);
} else {
setPlaybackParameters(
getCurrentTempo(),
getCurrentPitch(),
percentToSemitones(getCurrentPitch()),
getCurrentSkipSilence()
private void updateCallback() {
if (callback == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "Updating callback: "
+ "tempo = [" + tempo + "], "
+ "pitch = [" + pitch + "], "
+ "skipSilence = [" + skipSilence + "]"
);
}
}
private void setPlaybackParameters(final double newTempo, final double newPitch,
final int newSemitones, final boolean skipSilence) {
if (callback != null && tempoCurrentText != null
&& pitchCurrentText != null && semitoneCurrentText != null) {
if (DEBUG) {
Log.d(TAG, "Setting playback parameters to "
+ "tempo=[" + newTempo + "], "
+ "pitch=[" + newPitch + "], "
+ "semitones=[" + newSemitones + "]");
}
tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo));
pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch));
semitoneCurrentText.setText(getSignedSemitonesString(newSemitones));
callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence);
}
}
private double getCurrentTempo() {
return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress());
}
private double getCurrentPitch() {
return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress());
}
private int getCurrentSemitones() {
// semitoneSlider is absolute, that's why - 12
return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12;
}
private boolean getCurrentSkipSilence() {
return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
}
private boolean getCurrentAdjustBySemitones() {
return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked();
callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence);
}
@NonNull
private static String getStepUpPercentString(final double percent) {
return STEP_UP_SIGN + getPercentString(percent);
return '+' + getPercentString(percent);
}
@NonNull
private static String getStepDownPercentString(final double percent) {
return STEP_DOWN_SIGN + getPercentString(percent);
return '-' + getPercentString(percent);
}
@NonNull
@ -663,21 +398,8 @@ public class PlaybackParameterDialog extends DialogFragment {
return PlayerHelper.formatPitch(percent);
}
@NonNull
private static String getSignedSemitonesString(final int semitones) {
return semitones > 0 ? "+" + semitones : "" + semitones;
}
public interface Callback {
void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
boolean playbackSkipSilence);
}
public double semitonesToPercent(final int inSemitones) {
return Math.pow(2, inSemitones / 12.0);
}
public int percentToSemitones(final double inPercent) {
return (int) Math.round(12 * Math.log(inPercent) / Math.log(2));
}
}