Reworked/Implemented PlaybackParameterDialog functionallity
* Add support for semitones * Fixed some minor bugs * Improved some methods
This commit is contained in:
parent
dae5aa38a8
commit
762cdc812c
|
@ -8,11 +8,14 @@ import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
@ -22,8 +25,10 @@ import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
|
||||||
import org.schabi.newpipe.util.SliderStrategy;
|
import org.schabi.newpipe.util.SliderStrategy;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.DoubleConsumer;
|
import java.util.function.DoubleConsumer;
|
||||||
import java.util.function.DoubleFunction;
|
import java.util.function.DoubleFunction;
|
||||||
|
import java.util.function.DoubleSupplier;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
@ -32,8 +37,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
private static final String TAG = "PlaybackParameterDialog";
|
private static final String TAG = "PlaybackParameterDialog";
|
||||||
|
|
||||||
// Minimum allowable range in ExoPlayer
|
// Minimum allowable range in ExoPlayer
|
||||||
private static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
|
private static final double MIN_PLAYBACK_VALUE = 0.10f;
|
||||||
private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
|
private static final double MAX_PLAYBACK_VALUE = 3.00f;
|
||||||
|
|
||||||
private static final double STEP_1_PERCENT_VALUE = 0.01f;
|
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_5_PERCENT_VALUE = 0.05f;
|
||||||
|
@ -42,30 +47,42 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
private static final double STEP_100_PERCENT_VALUE = 1.00f;
|
private static final double STEP_100_PERCENT_VALUE = 1.00f;
|
||||||
|
|
||||||
private static final double DEFAULT_TEMPO = 1.00f;
|
private static final double DEFAULT_TEMPO = 1.00f;
|
||||||
private static final double DEFAULT_PITCH = 1.00f;
|
private static final double DEFAULT_PITCH_PERCENT = 1.00f;
|
||||||
private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE;
|
private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE;
|
||||||
private static final boolean DEFAULT_SKIP_SILENCE = false;
|
private static final boolean DEFAULT_SKIP_SILENCE = false;
|
||||||
|
|
||||||
private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic(
|
private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic(
|
||||||
MINIMUM_PLAYBACK_VALUE,
|
MIN_PLAYBACK_VALUE,
|
||||||
MAXIMUM_PLAYBACK_VALUE,
|
MAX_PLAYBACK_VALUE,
|
||||||
1.00f,
|
1.00f,
|
||||||
10_000);
|
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
|
@Nullable
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
|
||||||
@State
|
@State
|
||||||
double initialTempo = DEFAULT_TEMPO;
|
double initialTempo = DEFAULT_TEMPO;
|
||||||
@State
|
@State
|
||||||
double initialPitch = DEFAULT_PITCH;
|
double initialPitchPercent = DEFAULT_PITCH_PERCENT;
|
||||||
@State
|
@State
|
||||||
boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
|
boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
|
||||||
|
|
||||||
@State
|
@State
|
||||||
double tempo = DEFAULT_TEMPO;
|
double tempo = DEFAULT_TEMPO;
|
||||||
@State
|
@State
|
||||||
double pitch = DEFAULT_PITCH;
|
double pitchPercent = DEFAULT_PITCH_PERCENT;
|
||||||
@State
|
@State
|
||||||
double stepSize = DEFAULT_STEP;
|
double stepSize = DEFAULT_STEP;
|
||||||
@State
|
@State
|
||||||
|
@ -83,11 +100,11 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
dialog.callback = callback;
|
dialog.callback = callback;
|
||||||
|
|
||||||
dialog.initialTempo = playbackTempo;
|
dialog.initialTempo = playbackTempo;
|
||||||
dialog.initialPitch = playbackPitch;
|
dialog.initialPitchPercent = playbackPitch;
|
||||||
dialog.initialSkipSilence = playbackSkipSilence;
|
dialog.initialSkipSilence = playbackSkipSilence;
|
||||||
|
|
||||||
dialog.tempo = dialog.initialTempo;
|
dialog.tempo = dialog.initialTempo;
|
||||||
dialog.pitch = dialog.initialPitch;
|
dialog.pitchPercent = dialog.initialPitchPercent;
|
||||||
dialog.skipSilence = dialog.initialSkipSilence;
|
dialog.skipSilence = dialog.initialSkipSilence;
|
||||||
|
|
||||||
return dialog;
|
return dialog;
|
||||||
|
@ -125,20 +142,19 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
|
|
||||||
binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext()));
|
binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext()));
|
||||||
initUI();
|
initUI();
|
||||||
initUIData();
|
|
||||||
|
|
||||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
||||||
.setView(binding.getRoot())
|
.setView(binding.getRoot())
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
|
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
|
||||||
setAndUpdateTempo(initialTempo);
|
setAndUpdateTempo(initialTempo);
|
||||||
setAndUpdatePitch(initialPitch);
|
setAndUpdatePitch(initialPitchPercent);
|
||||||
setAndUpdateSkipSilence(initialSkipSilence);
|
setAndUpdateSkipSilence(initialSkipSilence);
|
||||||
updateCallback();
|
updateCallback();
|
||||||
})
|
})
|
||||||
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> {
|
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> {
|
||||||
setAndUpdateTempo(DEFAULT_TEMPO);
|
setAndUpdateTempo(DEFAULT_TEMPO);
|
||||||
setAndUpdatePitch(DEFAULT_PITCH);
|
setAndUpdatePitch(DEFAULT_PITCH_PERCENT);
|
||||||
setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE);
|
setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE);
|
||||||
updateCallback();
|
updateCallback();
|
||||||
})
|
})
|
||||||
|
@ -153,12 +169,63 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
|
|
||||||
private void initUI() {
|
private void initUI() {
|
||||||
// Tempo
|
// Tempo
|
||||||
setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE);
|
setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE);
|
||||||
setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE);
|
setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE);
|
||||||
|
|
||||||
// Pitch
|
binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE));
|
||||||
setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE);
|
setAndUpdateTempo(tempo);
|
||||||
setText(binding.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE);
|
binding.tempoSeekbar.setOnSeekBarChangeListener(
|
||||||
|
getTempoOrPitchSeekbarChangeListener(
|
||||||
|
QUADRATIC_STRATEGY,
|
||||||
|
this::onTempoSliderUpdated));
|
||||||
|
|
||||||
|
registerOnStepClickListener(
|
||||||
|
binding.tempoStepDown,
|
||||||
|
() -> tempo,
|
||||||
|
-1,
|
||||||
|
this::onTempoSliderUpdated);
|
||||||
|
registerOnStepClickListener(
|
||||||
|
binding.tempoStepUp,
|
||||||
|
() -> tempo,
|
||||||
|
1,
|
||||||
|
this::onTempoSliderUpdated);
|
||||||
|
|
||||||
|
// Pitch - Percent
|
||||||
|
setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE);
|
||||||
|
setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE);
|
||||||
|
|
||||||
|
binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE));
|
||||||
|
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
|
// Steps
|
||||||
setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE);
|
setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE);
|
||||||
|
@ -166,6 +233,34 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE);
|
setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE);
|
||||||
setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE);
|
setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE);
|
||||||
setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE);
|
setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE);
|
||||||
|
|
||||||
|
setAndUpdateStepSize(stepSize);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
setSliders(Math.min(pitchPercent, tempo));
|
||||||
|
updateCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setAndUpdateSkipSilence(skipSilence);
|
||||||
|
binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
||||||
|
skipSilence = isChecked;
|
||||||
|
updateCallback();
|
||||||
|
});
|
||||||
|
|
||||||
|
bindCheckboxWithBoolPref(
|
||||||
|
binding.adjustBySemitonesCheckbox,
|
||||||
|
R.string.playback_adjust_by_semitones_key,
|
||||||
|
false,
|
||||||
|
this::showPitchSemitonesOrPercent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextView setText(
|
private TextView setText(
|
||||||
|
@ -177,6 +272,31 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
return textView;
|
return textView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 * stepSize * 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupStepTextView(
|
private void setupStepTextView(
|
||||||
final TextView textView,
|
final TextView textView,
|
||||||
final double stepSizeValue
|
final double stepSizeValue
|
||||||
|
@ -185,77 +305,14 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
.setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue));
|
.setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initUIData() {
|
|
||||||
// Tempo
|
|
||||||
binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
|
||||||
setAndUpdateTempo(tempo);
|
|
||||||
binding.tempoSeekbar.setOnSeekBarChangeListener(
|
|
||||||
getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated));
|
|
||||||
|
|
||||||
registerOnStepClickListener(
|
|
||||||
binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated);
|
|
||||||
registerOnStepClickListener(
|
|
||||||
binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated);
|
|
||||||
|
|
||||||
// Pitch
|
|
||||||
binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
|
||||||
setAndUpdatePitch(pitch);
|
|
||||||
binding.pitchSeekbar.setOnSeekBarChangeListener(
|
|
||||||
getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated));
|
|
||||||
|
|
||||||
registerOnStepClickListener(
|
|
||||||
binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated);
|
|
||||||
registerOnStepClickListener(
|
|
||||||
binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated);
|
|
||||||
|
|
||||||
// Steps
|
|
||||||
setAndUpdateStepSize(stepSize);
|
|
||||||
|
|
||||||
// Bottom controls
|
|
||||||
// restore whether pitch and tempo are unhooked or not
|
|
||||||
binding.unhookCheckbox.setChecked(PreferenceManager
|
|
||||||
.getDefaultSharedPreferences(requireContext())
|
|
||||||
.getBoolean(getString(R.string.playback_unhook_key), true));
|
|
||||||
|
|
||||||
binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
|
||||||
// save 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, slide back to the minimum of current tempo or pitch
|
|
||||||
setSliders(Math.min(pitch, tempo));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setAndUpdateSkipSilence(skipSilence);
|
|
||||||
binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
|
||||||
skipSilence = isChecked;
|
|
||||||
updateCallback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 setAndUpdateStepSize(final double newStepSize) {
|
private void setAndUpdateStepSize(final double newStepSize) {
|
||||||
this.stepSize = newStepSize;
|
this.stepSize = newStepSize;
|
||||||
|
|
||||||
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
|
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
|
||||||
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
|
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
|
||||||
|
|
||||||
binding.pitchStepUp.setText(getStepUpPercentString(newStepSize));
|
binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize));
|
||||||
binding.pitchStepDown.setText(getStepDownPercentString(newStepSize));
|
binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
|
private void setAndUpdateSkipSilence(final boolean newSkipSilence) {
|
||||||
|
@ -263,19 +320,72 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
binding.skipSilenceCheckbox.setChecked(newSkipSilence);
|
binding.skipSilenceCheckbox.setChecked(newSkipSilence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void bindCheckboxWithBoolPref(
|
||||||
|
@NonNull final CheckBox checkBox,
|
||||||
|
@StringRes final int resId,
|
||||||
|
final boolean defaultValue,
|
||||||
|
@Nullable final Consumer<Boolean> onInitialValueOrValueChange
|
||||||
|
) {
|
||||||
|
final boolean prefValue = PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(requireContext())
|
||||||
|
.getBoolean(getString(resId), defaultValue);
|
||||||
|
|
||||||
|
checkBox.setChecked(prefValue);
|
||||||
|
|
||||||
|
if (onInitialValueOrValueChange != null) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (onInitialValueOrValueChange != null) {
|
||||||
|
onInitialValueOrValueChange.accept(isChecked);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPitchSemitonesOrPercent(final boolean semitones) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Sliders
|
// Sliders
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener(
|
private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener(
|
||||||
|
final SliderStrategy sliderStrategy,
|
||||||
final DoubleConsumer newValueConsumer
|
final DoubleConsumer newValueConsumer
|
||||||
) {
|
) {
|
||||||
return new SeekBar.OnSeekBarChangeListener() {
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
||||||
final boolean fromUser) {
|
final boolean fromUser) {
|
||||||
if (fromUser) { // this change is first in chain
|
if (fromUser) { // ensure that the user triggered the change
|
||||||
newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress));
|
newValueConsumer.accept(sliderStrategy.valueOf(progress));
|
||||||
updateCallback();
|
updateCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,7 +410,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPitchSliderUpdated(final double newPitch) {
|
private void onPitchPercentSliderUpdated(final double newPitch) {
|
||||||
if (!binding.unhookCheckbox.isChecked()) {
|
if (!binding.unhookCheckbox.isChecked()) {
|
||||||
setSliders(newPitch);
|
setSliders(newPitch);
|
||||||
} else {
|
} else {
|
||||||
|
@ -314,15 +424,39 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAndUpdateTempo(final double newTempo) {
|
private void setAndUpdateTempo(final double newTempo) {
|
||||||
this.tempo = newTempo;
|
this.tempo = calcValidTempo(newTempo);
|
||||||
|
|
||||||
binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo));
|
binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo));
|
||||||
setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo);
|
setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAndUpdatePitch(final double newPitch) {
|
private void setAndUpdatePitch(final double newPitch) {
|
||||||
this.pitch = newPitch;
|
this.pitchPercent = calcValidPitch(newPitch);
|
||||||
binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch));
|
|
||||||
setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch);
|
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_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double calcValidPitch(final double newPitch) {
|
||||||
|
final double calcPitch =
|
||||||
|
Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch));
|
||||||
|
|
||||||
|
if (!binding.adjustBySemitonesCheckbox.isChecked()) {
|
||||||
|
return calcPitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlayerSemitoneHelper.semitonesToPercent(
|
||||||
|
PlayerSemitoneHelper.percentToSemitones(calcPitch));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -335,12 +469,12 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Updating callback: "
|
Log.d(TAG, "Updating callback: "
|
||||||
+ "tempo = [" + tempo + "], "
|
+ "tempo = " + tempo + ", "
|
||||||
+ "pitch = [" + pitch + "], "
|
+ "pitchPercent = " + pitchPercent + ", "
|
||||||
+ "skipSilence = [" + skipSilence + "]"
|
+ "skipSilence = " + skipSilence
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence);
|
callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.schabi.newpipe.player.helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts between percent and 12-tone equal temperament semitones.
|
||||||
|
* <br/>
|
||||||
|
* @see
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/Equal_temperament#Twelve-tone_equal_temperament">
|
||||||
|
* Wikipedia: Equal temperament#Twelve-tone equal temperament
|
||||||
|
* </a>
|
||||||
|
*/
|
||||||
|
public final class PlayerSemitoneHelper {
|
||||||
|
public static final int TONES = 12;
|
||||||
|
|
||||||
|
private PlayerSemitoneHelper() {
|
||||||
|
// No impl
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatPitchSemitones(final double percent) {
|
||||||
|
return formatPitchSemitones(percentToSemitones(percent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatPitchSemitones(final int semitones) {
|
||||||
|
return semitones > 0 ? "+" + semitones : "" + semitones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double semitonesToPercent(final int semitones) {
|
||||||
|
return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int percentToSemitones(final double percent) {
|
||||||
|
return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ensureSemitonesInRange(final int semitones) {
|
||||||
|
return Math.max(-TONES, Math.min(TONES, semitones));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue