1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-01-09 17:00:32 +00:00

Merge pull request #7565 from haklc/dev

Change pitch by semitones
This commit is contained in:
Stypox 2022-02-27 09:58:38 +01:00 committed by GitHub
commit 3f7ba2e3d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 349 additions and 17 deletions

View File

@ -9,6 +9,7 @@ import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.RelativeLayout;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
@ -37,6 +38,7 @@ public class PlaybackParameterDialog extends DialogFragment {
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 = 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_TWENTY_FIVE_PERCENT_VALUE;
private static final boolean DEFAULT_SKIP_SILENCE = false; private static final boolean DEFAULT_SKIP_SILENCE = false;
@ -64,9 +66,11 @@ public class PlaybackParameterDialog extends DialogFragment {
private double initialTempo = DEFAULT_TEMPO; private double initialTempo = DEFAULT_TEMPO;
private double initialPitch = DEFAULT_PITCH; private double initialPitch = DEFAULT_PITCH;
private int initialSemitones = DEFAULT_SEMITONES;
private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
private double tempo = DEFAULT_TEMPO; private double tempo = DEFAULT_TEMPO;
private double pitch = DEFAULT_PITCH; private double pitch = DEFAULT_PITCH;
private int semitones = DEFAULT_SEMITONES;
private double stepSize = DEFAULT_STEP; private double stepSize = DEFAULT_STEP;
@Nullable @Nullable
@ -86,9 +90,19 @@ public class PlaybackParameterDialog extends DialogFragment {
@Nullable @Nullable
private TextView pitchStepUpText; private TextView pitchStepUpText;
@Nullable @Nullable
private SeekBar semitoneSlider;
@Nullable
private TextView semitoneCurrentText;
@Nullable
private TextView semitoneStepDownText;
@Nullable
private TextView semitoneStepUpText;
@Nullable
private CheckBox unhookingCheckbox; private CheckBox unhookingCheckbox;
@Nullable @Nullable
private CheckBox skipSilenceCheckbox; private CheckBox skipSilenceCheckbox;
@Nullable
private CheckBox adjustBySemitonesCheckbox;
public static PlaybackParameterDialog newInstance(final double playbackTempo, public static PlaybackParameterDialog newInstance(final double playbackTempo,
final double playbackPitch, final double playbackPitch,
@ -101,6 +115,7 @@ public class PlaybackParameterDialog extends DialogFragment {
dialog.tempo = playbackTempo; dialog.tempo = playbackTempo;
dialog.pitch = playbackPitch; dialog.pitch = playbackPitch;
dialog.semitones = dialog.percentToSemitones(playbackPitch);
dialog.initialSkipSilence = playbackSkipSilence; dialog.initialSkipSilence = playbackSkipSilence;
return dialog; return dialog;
@ -127,9 +142,11 @@ public class PlaybackParameterDialog extends DialogFragment {
if (savedInstanceState != null) { if (savedInstanceState != null) {
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
initialSemitones = percentToSemitones(initialPitch);
tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO); tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO);
pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH); pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH);
semitones = percentToSemitones(pitch);
stepSize = savedInstanceState.getDouble(STEP_SIZE_KEY, DEFAULT_STEP); stepSize = savedInstanceState.getDouble(STEP_SIZE_KEY, DEFAULT_STEP);
} }
} }
@ -160,9 +177,11 @@ public class PlaybackParameterDialog extends DialogFragment {
.setView(view) .setView(view)
.setCancelable(true) .setCancelable(true)
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> .setNegativeButton(R.string.cancel, (dialogInterface, i) ->
setPlaybackParameters(initialTempo, initialPitch, initialSkipSilence)) setPlaybackParameters(initialTempo, initialPitch,
initialSemitones, initialSkipSilence))
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> .setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, DEFAULT_SKIP_SILENCE)) setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH,
DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE))
.setPositiveButton(R.string.ok, (dialogInterface, i) -> .setPositiveButton(R.string.ok, (dialogInterface, i) ->
setCurrentPlaybackParameters()); setCurrentPlaybackParameters());
@ -176,12 +195,47 @@ public class PlaybackParameterDialog extends DialogFragment {
private void setupControlViews(@NonNull final View rootView) { private void setupControlViews(@NonNull final View rootView) {
setupHookingControl(rootView); setupHookingControl(rootView);
setupSkipSilenceControl(rootView); setupSkipSilenceControl(rootView);
setupAdjustBySemitonesControl(rootView);
setupTempoControl(rootView); setupTempoControl(rootView);
setupPitchControl(rootView); setupPitchControl(rootView);
setupSemitoneControl(rootView);
togglePitchSliderType(rootView);
setStepSize(stepSize); setStepSize(stepSize);
setupStepSizeSelector(rootView); }
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 void setupTempoControl(@NonNull final View rootView) { private void setupTempoControl(@NonNull final View rootView) {
@ -234,23 +288,40 @@ public class PlaybackParameterDialog extends DialogFragment {
} }
} }
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);
if (semitoneCurrentText != null) {
semitoneCurrentText.setText(getSignedSemitonesString(semitones));
}
if (semitoneSlider != null) {
setSemitoneSlider(semitones);
semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener());
}
}
private void setupHookingControl(@NonNull final View rootView) { private void setupHookingControl(@NonNull final View rootView) {
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
if (unhookingCheckbox != null) { if (unhookingCheckbox != null) {
// restore whether pitch and tempo are unhooked or not // restores whether pitch and tempo are unhooked or not
unhookingCheckbox.setChecked(PreferenceManager unhookingCheckbox.setChecked(PreferenceManager
.getDefaultSharedPreferences(requireContext()) .getDefaultSharedPreferences(requireContext())
.getBoolean(getString(R.string.playback_unhook_key), true)); .getBoolean(getString(R.string.playback_unhook_key), true));
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
// save whether pitch and tempo are unhooked or not // saves whether pitch and tempo are unhooked or not
PreferenceManager.getDefaultSharedPreferences(requireContext()) PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit() .edit()
.putBoolean(getString(R.string.playback_unhook_key), isChecked) .putBoolean(getString(R.string.playback_unhook_key), isChecked)
.apply(); .apply();
if (!isChecked) { if (!isChecked) {
// when unchecked, slide back to the minimum of current tempo or pitch // when unchecked, slides back to the minimum of current tempo or pitch
final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); final double minimum = Math.min(getCurrentPitch(), getCurrentTempo());
setSliders(minimum); setSliders(minimum);
setCurrentPlaybackParameters(); setCurrentPlaybackParameters();
@ -268,6 +339,46 @@ public class PlaybackParameterDialog extends DialogFragment {
} }
} }
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
.getDefaultSharedPreferences(requireContext())
.getBoolean(getString(R.string.playback_adjust_by_semitones_key), true));
// stores whether semitone adjustment is used or not
adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putBoolean(getString(R.string.playback_adjust_by_semitones_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()));
}
});
}
}
private void setupStepSizeSelector(@NonNull final View rootView) { private void setupStepSizeSelector(@NonNull final View rootView) {
final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
@ -310,6 +421,22 @@ public class PlaybackParameterDialog extends DialogFragment {
} }
} }
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 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) { private void setStepSize(final double stepSize) {
this.stepSize = stepSize; this.stepSize = stepSize;
@ -344,6 +471,20 @@ public class PlaybackParameterDialog extends DialogFragment {
setCurrentPlaybackParameters(); setCurrentPlaybackParameters();
}); });
} }
if (semitoneStepDownText != null) {
semitoneStepDownText.setOnClickListener(view -> {
onSemitoneSliderUpdated(getCurrentSemitones() - 1);
setCurrentPlaybackParameters();
});
}
if (semitoneStepUpText != null) {
semitoneStepUpText.setOnClickListener(view -> {
onSemitoneSliderUpdated(getCurrentSemitones() + 1);
setCurrentPlaybackParameters();
});
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -398,10 +539,34 @@ public class PlaybackParameterDialog extends DialogFragment {
}; };
} }
private void onTempoSliderUpdated(final double newTempo) { private SeekBar.OnSeekBarChangeListener getOnSemitoneChangedListener() {
if (unhookingCheckbox == null) { return new SeekBar.OnSeekBarChangeListener() {
return; @Override
public void onProgressChanged(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();
} }
}
@Override
public void onStartTrackingTouch(final SeekBar seekBar) {
// Do Nothing.
}
@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
// Do Nothing.
}
};
}
private void onTempoSliderUpdated(final double newTempo) {
if (!unhookingCheckbox.isChecked()) { if (!unhookingCheckbox.isChecked()) {
setSliders(newTempo); setSliders(newTempo);
} else { } else {
@ -410,9 +575,6 @@ public class PlaybackParameterDialog extends DialogFragment {
} }
private void onPitchSliderUpdated(final double newPitch) { private void onPitchSliderUpdated(final double newPitch) {
if (unhookingCheckbox == null) {
return;
}
if (!unhookingCheckbox.isChecked()) { if (!unhookingCheckbox.isChecked()) {
setSliders(newPitch); setSliders(newPitch);
} else { } else {
@ -420,6 +582,10 @@ public class PlaybackParameterDialog extends DialogFragment {
} }
} }
private void onSemitoneSliderUpdated(final int newSemitone) {
setSemitoneSlider(newSemitone);
}
private void setSliders(final double newValue) { private void setSliders(final double newValue) {
setTempoSlider(newValue); setTempoSlider(newValue);
setPitchSlider(newValue); setPitchSlider(newValue);
@ -439,25 +605,49 @@ public class PlaybackParameterDialog extends DialogFragment {
pitchSlider.setProgress(strategy.progressOf(newPitch)); pitchSlider.setProgress(strategy.progressOf(newPitch));
} }
private void setSemitoneSlider(final int newSemitone) {
if (semitoneSlider == null) {
return;
}
semitoneSlider.setProgress(newSemitone + 12);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Helper // Helper
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void setCurrentPlaybackParameters() { private void setCurrentPlaybackParameters() {
setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence()); if (getCurrentAdjustBySemitones()) {
setPlaybackParameters(
getCurrentTempo(),
semitonesToPercent(getCurrentSemitones()),
getCurrentSemitones(),
getCurrentSkipSilence()
);
} else {
setPlaybackParameters(
getCurrentTempo(),
getCurrentPitch(),
percentToSemitones(getCurrentPitch()),
getCurrentSkipSilence()
);
}
} }
private void setPlaybackParameters(final double newTempo, final double newPitch, private void setPlaybackParameters(final double newTempo, final double newPitch,
final boolean skipSilence) { final int newSemitones, final boolean skipSilence) {
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) { if (callback != null && tempoCurrentText != null
&& pitchCurrentText != null && semitoneCurrentText != null) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Setting playback parameters to " Log.d(TAG, "Setting playback parameters to "
+ "tempo=[" + newTempo + "], " + "tempo=[" + newTempo + "], "
+ "pitch=[" + newPitch + "]"); + "pitch=[" + newPitch + "], "
+ "semitones=[" + newSemitones + "]");
} }
tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo)); tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo));
pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch)); pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch));
semitoneCurrentText.setText(getSignedSemitonesString(newSemitones));
callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence); callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence);
} }
} }
@ -470,6 +660,11 @@ public class PlaybackParameterDialog extends DialogFragment {
return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); 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 double getCurrentStepSize() { private double getCurrentStepSize() {
return stepSize; return stepSize;
} }
@ -478,6 +673,10 @@ public class PlaybackParameterDialog extends DialogFragment {
return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
} }
private boolean getCurrentAdjustBySemitones() {
return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked();
}
@NonNull @NonNull
private static String getStepUpPercentString(final double percent) { private static String getStepUpPercentString(final double percent) {
return STEP_UP_SIGN + getPercentString(percent); return STEP_UP_SIGN + getPercentString(percent);
@ -493,8 +692,21 @@ public class PlaybackParameterDialog extends DialogFragment {
return PlayerHelper.formatPitch(percent); return PlayerHelper.formatPitch(percent);
} }
@NonNull
private static String getSignedSemitonesString(final int semitones) {
return semitones > 0 ? "+" + semitones : "" + semitones;
}
public interface Callback { public interface Callback {
void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
boolean playbackSkipSilence); 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));
}
} }

View File

@ -261,11 +261,115 @@
tools:text="+5%" /> tools:text="+5%" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/semitoneControl"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_below="@id/pitchControlText"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneStepDown"
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="♭"
android:textColor="?attr/colorAccent"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<RelativeLayout
android:id="@+id/semitoneDisplay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_toStartOf="@+id/semitoneStepUp"
android:layout_toLeftOf="@+id/semitoneStepUp"
android:layout_toEndOf="@+id/semitoneStepDown"
android:layout_toRightOf="@+id/semitoneStepDown"
android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneMinimumText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:gravity="center"
android:text="-12"
android:textColor="?attr/colorAccent"
tools:ignore="HardcodedText"/>
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneCurrentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
tools:text="0" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneMaximumText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:gravity="center"
android:text="+12"
android:textColor="?attr/colorAccent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/semitoneSeekbar"
style="@style/Widget.AppCompat.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/semitoneCurrentText"
android:max="24"
android:paddingBottom="4dp"
android:progress="12" />
</RelativeLayout>
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneStepUp"
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="♯"
android:textColor="?attr/colorAccent"
android:textSize="20sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
</RelativeLayout>
<View <View
android:id="@+id/separatorStepSizeSelector" android:id="@+id/separatorStepSizeSelector"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_below="@+id/pitchControl" android:layout_below="@+id/semitoneControl"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
@ -280,6 +384,7 @@
android:orientation="horizontal"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/playback_step_type"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
@ -380,6 +485,18 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:text="@string/skip_silence_checkbox" /> android:text="@string/skip_silence_checkbox" />
<CheckBox
android:id="@+id/adjustBySemitonesCheckbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/skipSilenceCheckbox"
android:layout_centerHorizontal="true"
android:checked="false"
android:clickable="true"
android:focusable="true"
android:maxLines="1"
android:text="@string/adjust_by_semitones_checkbox" />
</LinearLayout> </LinearLayout>
<!-- END HERE --> <!-- END HERE -->

View File

@ -238,6 +238,7 @@
<item>@string/local_search_suggestions</item> <item>@string/local_search_suggestions</item>
<item>@string/remote_search_suggestions</item> <item>@string/remote_search_suggestions</item>
</string-array> </string-array>
<string name="playback_adjust_by_semitones_key">playback_adjust_by_semitones_key</string>
<string name="show_play_with_kodi_key">show_play_with_kodi</string> <string name="show_play_with_kodi_key">show_play_with_kodi</string>
<string name="show_comments_key">show_comments</string> <string name="show_comments_key">show_comments</string>
<string name="show_next_video_key">show_next_video</string> <string name="show_next_video_key">show_next_video</string>

View File

@ -486,7 +486,9 @@
<string name="playback_pitch">Pitch</string> <string name="playback_pitch">Pitch</string>
<string name="unhook_checkbox">Unhook (may cause distortion)</string> <string name="unhook_checkbox">Unhook (may cause distortion)</string>
<string name="skip_silence_checkbox">Fast-forward during silence</string> <string name="skip_silence_checkbox">Fast-forward during silence</string>
<string name="adjust_by_semitones_checkbox">Adjust pitch by musical semitones</string>
<string name="playback_step">Step</string> <string name="playback_step">Step</string>
<string name="playback_tempo_step">Tempo step</string>
<string name="playback_reset">Reset</string> <string name="playback_reset">Reset</string>
<!-- GDPR dialog --> <!-- GDPR dialog -->
<string name="start_accept_privacy_policy">In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully. <string name="start_accept_privacy_policy">In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully.