diff --git a/Telegram/Resources/icons/voice2x.png b/Telegram/Resources/icons/voice2x.png new file mode 100644 index 0000000000..f01a811168 Binary files /dev/null and b/Telegram/Resources/icons/voice2x.png differ diff --git a/Telegram/Resources/icons/voice2x@2x.png b/Telegram/Resources/icons/voice2x@2x.png new file mode 100644 index 0000000000..a37c17097f Binary files /dev/null and b/Telegram/Resources/icons/voice2x@2x.png differ diff --git a/Telegram/Resources/icons/voice2x@3x.png b/Telegram/Resources/icons/voice2x@3x.png new file mode 100644 index 0000000000..6f60123cee Binary files /dev/null and b/Telegram/Resources/icons/voice2x@3x.png differ diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 0b83ee97ab..749d4098db 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -649,6 +649,7 @@ struct Data { bool SuggestEmoji = true; bool SuggestStickersByEmoji = true; base::Observable ReplaceEmojiChanged; + float VoiceMsgPlaybackSpeed = 1.f; bool SoundNotify = true; bool DesktopNotify = true; bool RestoreSoundNotifyFromTray = false; @@ -778,6 +779,7 @@ DefineVar(Global, bool, ReplaceEmoji); DefineVar(Global, bool, SuggestEmoji); DefineVar(Global, bool, SuggestStickersByEmoji); DefineRefVar(Global, base::Observable, ReplaceEmojiChanged); +DefineVar(Global, float, VoiceMsgPlaybackSpeed); DefineVar(Global, bool, SoundNotify); DefineVar(Global, bool, DesktopNotify); DefineVar(Global, bool, RestoreSoundNotifyFromTray); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index e1d2d1f30b..b257918b02 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -304,6 +304,7 @@ DeclareVar(bool, ReplaceEmoji); DeclareVar(bool, SuggestEmoji); DeclareVar(bool, SuggestStickersByEmoji); DeclareRefVar(base::Observable, ReplaceEmojiChanged); +DeclareVar(float, VoiceMsgPlaybackSpeed); DeclareVar(bool, SoundNotify); DeclareVar(bool, DesktopNotify); DeclareVar(bool, RestoreSoundNotifyFromTray); diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index fb46a00481..a7496755c5 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/media_audio_track.h" #include "platform/platform_audio.h" #include "messenger.h" +#include "facades.h" #include #include @@ -44,6 +45,13 @@ namespace { Player::Mixer *MixerInstance = nullptr; +struct PlaybackSpeedData { + ALuint uiEffectSlot = 0; + ALuint uiEffect = 0; + ALuint uiFilter = 0; +}; +PlaybackSpeedData _playbackSpeedData; + // Thread: Any. bool ContextErrorHappened() { ALenum errCode; @@ -144,6 +152,24 @@ bool CreatePlaybackDevice() { alListener3f(AL_VELOCITY, 0.f, 0.f, 0.f); alListenerfv(AL_ORIENTATION, v); + // playback speed related init + // generate an effect slot and an effect + alGenAuxiliaryEffectSlots(1, &_playbackSpeedData.uiEffectSlot); + alGenEffects(1, &_playbackSpeedData.uiEffect); + // initialize the pitch shifter effect + alEffecti(_playbackSpeedData.uiEffect, AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER); + // 12 semitones = 1 octave + alEffecti(_playbackSpeedData.uiEffect, AL_PITCH_SHIFTER_COARSE_TUNE, -12); + // connect the effect with the effect slot + alAuxiliaryEffectSloti(_playbackSpeedData.uiEffectSlot, AL_EFFECTSLOT_EFFECT, _playbackSpeedData.uiEffect); + // initialize a filter to disable the direct (dry) path + alGenFilters(1, &_playbackSpeedData.uiFilter); + alFilteri(_playbackSpeedData.uiFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + // disable all frequencies + alFilterf(_playbackSpeedData.uiFilter, AL_LOWPASS_GAIN, 0.f); + // to use the modified playback speed: + // connect both the effect slot and filter with the stream source and set AL_PITCH + alDistanceModel(AL_NONE); return true; @@ -154,6 +180,15 @@ void ClosePlaybackDevice() { if (!AudioDevice) return; LOG(("Audio Info: Closing audio playback device.")); + + // playback speed related + alDeleteFilters(1, &_playbackSpeedData.uiFilter); + alDeleteEffects(1, &_playbackSpeedData.uiEffect); + alDeleteAuxiliaryEffectSlots(1, &_playbackSpeedData.uiEffectSlot); + _playbackSpeedData.uiFilter = 0; + _playbackSpeedData.uiEffect = 0; + _playbackSpeedData.uiEffectSlot = 0; + if (Player::mixer()) { Player::mixer()->detachTracks(); } @@ -290,6 +325,7 @@ void Mixer::Track::createStream() { alSource3f(stream.source, AL_VELOCITY, 0, 0, 0); alSourcei(stream.source, AL_LOOPING, 0); alGenBuffers(3, stream.buffers); + mixer()->updatePlaybackSpeed(); } void Mixer::Track::destroyStream() { @@ -983,6 +1019,29 @@ void Mixer::stop(const AudioMsgId &audio, State state) { if (current) emit updated(current); } +void Mixer::updatePlaybackSpeed() +{ + const auto track = trackForType(AudioMsgId::Type::Voice); + if (!track || track->state.id.type() != AudioMsgId::Type::Voice || !track->isStreamCreated()) { + return; + } + const auto src = track->stream.source; + // Note: This alters the playback speed AND the pitch + alSourcef(src, AL_PITCH, Global::VoiceMsgPlaybackSpeed()); + // fix the pitch using effects and filters + if (Global::VoiceMsgPlaybackSpeed() > 1.f) { + // connect the effect slot with the stream + alSource3i(src, AL_AUXILIARY_SEND_FILTER, Media::Audio::_playbackSpeedData.uiEffectSlot, 0, 0); + // connect the filter with the stream + alSourcei(src, AL_DIRECT_FILTER, Media::Audio::_playbackSpeedData.uiFilter); + } else { + // disconnect the effect slot + alSource3i(src, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, 0); + // disconnect the filter + alSourcei(src, AL_DIRECT_FILTER, AL_FILTER_NULL); + } +} + void Mixer::stopAndClear() { Track *current_audio = nullptr, *current_song = nullptr; { diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index fe1921281b..016c26e85c 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -117,6 +117,8 @@ public: void stop(const AudioMsgId &audio); void stop(const AudioMsgId &audio, State state); + void updatePlaybackSpeed(); + // Video player audio stream interface. void feedFromVideo(VideoSoundPart &&part); int64 getVideoCorrectedTime(const AudioMsgId &id, TimeMs frameMs, TimeMs systemMs); diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index f012f67b71..f6bc35d9a9 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -72,6 +72,32 @@ mediaPlayerRepeatInactiveIcon: icon { { "player_repeat", mediaPlayerInactiveFg, point(9px, 11px)} }; +mediaPlayerSpeedButton: IconButton { + width: 31px; + height: 30px; + + icon: icon { + { "voice2x", mediaPlayerActiveFg, point(8px, 11px) } + }; + iconPosition: point(0px, 0px); + + rippleAreaPosition: point(3px, 5px); + rippleAreaSize: 25px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgOver; + } +} +mediaPlayerSpeedDisabledIcon: icon { + { "voice2x", menuIconFg, point(8px, 11px)} +}; +mediaPlayerSpeedDisabledIconOver: icon { + { "voice2x", menuIconFgOver, point(8px, 11px)} +}; +mediaPlayerSpeedDisabledRippleBg: windowBgOver; +mediaPlayerSpeedInactiveIcon: icon { + { "voice2x", mediaPlayerInactiveFg, point(8px, 11px)} +}; + mediaPlayerVolumeIcon0: icon { { "player_volume0", mediaPlayerActiveFg }, }; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index f91d24095d..9baa44ee79 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -22,7 +22,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_media_player.h" #include "styles/style_mediaview.h" #include "history/history_item.h" +#include "storage/localstorage.h" #include "layout.h" +#include "facades.h" namespace Media { namespace Player { @@ -80,6 +82,7 @@ Widget::Widget(QWidget *parent) : RpWidget(parent) , _playPause(this) , _volumeToggle(this, st::mediaPlayerVolumeToggle) , _repeatTrack(this, st::mediaPlayerRepeatButton) +, _playbackSpeed(this, st::mediaPlayerSpeedButton) , _close(this, st::mediaPlayerClose) , _shadow(this) , _playbackSlider(this, st::mediaPlayerPlayback) @@ -128,6 +131,14 @@ Widget::Widget(QWidget *parent) : RpWidget(parent) instance()->toggleRepeat(AudioMsgId::Type::Song); }); + updatePlaybackSpeedIcon(); + _playbackSpeed->setClickedCallback([=] { + Global::SetVoiceMsgPlaybackSpeed(Global::VoiceMsgPlaybackSpeed() == 1.f ? 2.f : 1.f); + mixer()->updatePlaybackSpeed(); + updatePlaybackSpeedIcon(); + Local::writeSettings(); + }); + subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) { if (type == _type) { updateRepeatTrackIcon(); @@ -247,6 +258,9 @@ void Widget::handleSeekFinished(float64 progress) { void Widget::resizeEvent(QResizeEvent *e) { auto right = st::mediaPlayerCloseRight; _close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width(); + if (_type == AudioMsgId::Type::Voice) { + _playbackSpeed->moveToRight(right, st::mediaPlayerPlayTop); right += _playbackSpeed->width(); + } _repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width(); _volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width(); @@ -330,6 +344,8 @@ int Widget::getLabelsRight() const { auto result = st::mediaPlayerCloseRight + _close->width(); if (_type == AudioMsgId::Type::Song) { result += _repeatTrack->width() + _volumeToggle->width(); + } else if (_type == AudioMsgId::Type::Voice) { + result += _playbackSpeed->width(); } result += st::mediaPlayerPadding; return result; @@ -353,6 +369,14 @@ void Widget::updateRepeatTrackIcon() { _repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg); } +void Widget::updatePlaybackSpeedIcon() +{ + const auto playbackSpeed = Global::VoiceMsgPlaybackSpeed(); + const auto isDefaultSpeed = playbackSpeed == 1.f; + _playbackSpeed->setIconOverride(isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIcon : nullptr, isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIconOver : nullptr); + _playbackSpeed->setRippleColorOverride(isDefaultSpeed ? &st::mediaPlayerSpeedDisabledRippleBg : nullptr); +} + void Widget::checkForTypeChange() { auto hasActiveType = [](AudioMsgId::Type type) { auto current = instance()->current(type); @@ -372,6 +396,7 @@ void Widget::setType(AudioMsgId::Type type) { _type = type; _repeatTrack->setVisible(_type == AudioMsgId::Type::Song); _volumeToggle->setVisible(_type == AudioMsgId::Type::Song); + _playbackSpeed->setVisible(_type == AudioMsgId::Type::Voice); if (!_shadow->isHidden()) { _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); } @@ -384,6 +409,9 @@ void Widget::setType(AudioMsgId::Type type) { ) | rpl::start_with_next([=] { handlePlaylistUpdate(); }); + // maybe the type change causes a change of the button layout + QResizeEvent event = { size(), size() }; + resizeEvent(&event); } } diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index a715a61a19..6e2160f697 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -66,6 +66,7 @@ private: void updatePlayPrevNextPositions(); void updateLabelsGeometry(); void updateRepeatTrackIcon(); + void updatePlaybackSpeedIcon(); void createPrevNextButtons(); void destroyPrevNextButtons(); @@ -103,6 +104,7 @@ private: object_ptr _nextTrack = { nullptr }; object_ptr _volumeToggle; object_ptr _repeatTrack; + object_ptr _playbackSpeed; object_ptr _close; object_ptr _shadow = { nullptr }; object_ptr _playbackSlider; diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index fac293a1e4..22f3540645 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -598,6 +598,7 @@ enum { dbiCacheSettings = 0x56, dbiAnimationsDisabled = 0x57, dbiScalePercent = 0x58, + dbiPlaybackSpeed = 0x59, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -1749,6 +1750,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting Global::SetVideoVolume(snap(v / 1e6, 0., 1.)); } break; + case dbiPlaybackSpeed: { + quint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetVoiceMsgPlaybackSpeed(v); + } break; + default: LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); return false; @@ -2596,6 +2605,7 @@ void writeSettings() { data.stream << quint32(dbiLoggedPhoneNumber) << cLoggedPhoneNumber(); data.stream << quint32(dbiTxtDomainString) << Global::TxtDomainString(); data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0); + data.stream << quint32(dbiPlaybackSpeed) << quint32(Global::VoiceMsgPlaybackSpeed()); data.stream << quint32(dbiConnectionType) << qint32(dbictProxiesList); data.stream << qint32(proxies.size());