diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 2cd48e7d6..0cf9b1e53 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -618,7 +618,7 @@ void CSound::UnloadSample(int SampleID) m_aSamples[SampleID].m_pData = nullptr; } -float CSound::GetSampleDuration(int SampleID) +float CSound::GetSampleTotalTime(int SampleID) { if(SampleID == -1 || SampleID >= NUM_SAMPLES) return 0.0f; @@ -626,6 +626,50 @@ float CSound::GetSampleDuration(int SampleID) return (m_aSamples[SampleID].m_NumFrames / m_aSamples[SampleID].m_Rate); } +float CSound::GetSampleCurrentTime(int SampleID) +{ + if(SampleID == -1 || SampleID >= NUM_SAMPLES) + return 0.0f; + + CSample *pSample = &m_aSamples[SampleID]; + if(IsPlaying(SampleID)) + { + for(auto &Voice : m_aVoices) + { + if(Voice.m_pSample == pSample) + { + return (Voice.m_Tick / pSample->m_Rate); + } + } + } + else + { + return (pSample->m_PausedAt / pSample->m_Rate); + } +} + +void CSound::SetSampleCurrentTime(int SampleID, float Time) +{ + if(SampleID == -1 || SampleID >= NUM_SAMPLES) + return; + + CSample *pSample = &m_aSamples[SampleID]; + if(IsPlaying(SampleID)) + { + for(auto &Voice : m_aVoices) + { + if(Voice.m_pSample == pSample) + { + Voice.m_Tick = pSample->m_NumFrames * Time; + } + } + } + else + { + pSample->m_PausedAt = pSample->m_NumFrames * Time; + } +} + void CSound::SetChannel(int ChannelID, float Vol, float Pan) { m_aChannels[ChannelID].m_Vol = (int)(Vol * 255.0f); @@ -772,9 +816,18 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float m_aVoices[VoiceID].m_pSample = &m_aSamples[SampleID]; m_aVoices[VoiceID].m_pChannel = &m_aChannels[ChannelID]; if(Flags & FLAG_LOOP) + { m_aVoices[VoiceID].m_Tick = m_aSamples[SampleID].m_PausedAt; + } + else if(Flags & FLAG_PREVIEW) + { + m_aVoices[VoiceID].m_Tick = m_aSamples[SampleID].m_PausedAt; + m_aSamples[SampleID].m_PausedAt = 0; + } else + { m_aVoices[VoiceID].m_Tick = 0; + } m_aVoices[VoiceID].m_Vol = 255; m_aVoices[VoiceID].m_Flags = Flags; m_aVoices[VoiceID].m_X = (int)x; @@ -799,6 +852,21 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags) return Play(ChannelID, SampleID, Flags, 0, 0); } +void CSound::Pause(int SampleID) +{ + // TODO: a nice fade out + std::unique_lock Lock(m_SoundLock); + CSample *pSample = &m_aSamples[SampleID]; + for(auto &Voice : m_aVoices) + { + if(Voice.m_pSample == pSample) + { + Voice.m_pSample->m_PausedAt = Voice.m_Tick; + Voice.m_pSample = 0; + } + } +} + void CSound::Stop(int SampleID) { // TODO: a nice fade out diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 03ce2c925..7ea40cca3 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -96,7 +96,9 @@ public: int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; void UnloadSample(int SampleID) override; - float GetSampleDuration(int SampleID) override; // in s + float GetSampleTotalTime(int SampleID) override; // in s + float GetSampleCurrentTime(int SampleID) override; // in s + void SetSampleCurrentTime(int SampleID, float Time) override; void SetChannel(int ChannelID, float Vol, float Pan) override; void SetListenerPos(float x, float y) override; @@ -112,6 +114,7 @@ public: CVoiceHandle Play(int ChannelID, int SampleID, int Flags, float x, float y); CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) override; CVoiceHandle Play(int ChannelID, int SampleID, int Flags) override; + void Pause(int SampleID) override; void Stop(int SampleID) override; void StopAll() override; void StopVoice(CVoiceHandle Voice) override; diff --git a/src/engine/sound.h b/src/engine/sound.h index 17afbdea0..d9a3f9591 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -15,7 +15,8 @@ public: FLAG_LOOP = 1 << 0, FLAG_POS = 1 << 1, FLAG_NO_PANNING = 1 << 2, - FLAG_ALL = FLAG_LOOP | FLAG_POS | FLAG_NO_PANNING, + FLAG_PREVIEW = 1 << 3, + FLAG_ALL = FLAG_LOOP | FLAG_POS | FLAG_NO_PANNING | FLAG_PREVIEW, }; enum @@ -68,7 +69,9 @@ public: virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; virtual void UnloadSample(int SampleID) = 0; - virtual float GetSampleDuration(int SampleID) = 0; // in s + virtual float GetSampleTotalTime(int SampleID) = 0; // in s + virtual float GetSampleCurrentTime(int SampleID) = 0; // in s + virtual void SetSampleCurrentTime(int SampleID, float Time) = 0; virtual void SetChannel(int ChannelID, float Volume, float Panning) = 0; virtual void SetListenerPos(float x, float y) = 0; @@ -83,6 +86,7 @@ public: virtual CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) = 0; virtual CVoiceHandle Play(int ChannelID, int SampleID, int Flags) = 0; + virtual void Pause(int SampleID) = 0; virtual void Stop(int SampleID) = 0; virtual void StopAll() = 0; virtual void StopVoice(CVoiceHandle Voice) = 0; diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 4c121c851..a67e0140d 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1257,35 +1257,106 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) void CEditor::DoToolbarSounds(CUIRect ToolBar) { - CUIRect ToolBarTop, ToolBarBottom, Button; + CUIRect ToolBarTop, ToolBarBottom, Button, SeekBar; ToolBar.HSplitMid(&ToolBarTop, &ToolBarBottom, 5.0f); if(m_SelectedSound >= 0 && (size_t)m_SelectedSound < m_Map.m_vpSounds.size()) { const std::shared_ptr pSelectedSound = m_Map.m_vpSounds[m_SelectedSound]; - // play/stop button + // play/pause button { ToolBarBottom.VSplitLeft(ToolBarBottom.h, &Button, &ToolBarBottom); - static int s_PlayStopButton; - if(DoButton_FontIcon(&s_PlayStopButton, Sound()->IsPlaying(pSelectedSound->m_SoundID) ? FONT_ICON_STOP : FONT_ICON_PLAY, 0, &Button, 0, "Play/stop audio preview", IGraphics::CORNER_ALL) || + static int s_PlayPauseButton; + if(DoButton_FontIcon(&s_PlayPauseButton, Sound()->IsPlaying(pSelectedSound->m_SoundID) ? FONT_ICON_PAUSE : FONT_ICON_PLAY, 0, &Button, 0, "Play/pause audio preview", IGraphics::CORNER_ALL) || (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_SPACE))) { if(Sound()->IsPlaying(pSelectedSound->m_SoundID)) - Sound()->Stop(pSelectedSound->m_SoundID); + Sound()->Pause(pSelectedSound->m_SoundID); else - Sound()->Play(CSounds::CHN_GUI, pSelectedSound->m_SoundID, 0); + Sound()->Play(CSounds::CHN_GUI, pSelectedSound->m_SoundID, ISound::FLAG_PREVIEW); } } - - // duration + // stop button + { + ToolBarBottom.VSplitLeft(2.0f, nullptr, &ToolBarBottom); + ToolBarBottom.VSplitLeft(ToolBarBottom.h, &Button, &ToolBarBottom); + static int s_StopButton; + if(DoButton_FontIcon(&s_StopButton, FONT_ICON_STOP, 0, &Button, 0, "Stop audio preview", IGraphics::CORNER_ALL)) + { + Sound()->Stop(pSelectedSound->m_SoundID); + } + } + // do seekbar { ToolBarBottom.VSplitLeft(5.0f, nullptr, &ToolBarBottom); - char aDuration[32]; - char aDurationLabel[64]; - str_time_float(Sound()->GetSampleDuration(pSelectedSound->m_SoundID), TIME_HOURS, aDuration, sizeof(aDuration)); - str_format(aDurationLabel, sizeof(aDurationLabel), "Duration: %s", aDuration); - UI()->DoLabel(&ToolBarBottom, aDurationLabel, 12.0f, TEXTALIGN_ML); + ToolBarBottom.VSplitLeft(200.0f, &SeekBar, &ToolBarBottom); + const float Rounding = 5.0f; + + static int s_SeekBarID = 0; + void *pId = &s_SeekBarID; + + char aBuffer[64]; + float CurrentTime = Sound()->GetSampleCurrentTime(pSelectedSound->m_SoundID); + float TotalTime = Sound()->GetSampleTotalTime(pSelectedSound->m_SoundID); + + // draw seek bar + SeekBar.Draw(ColorRGBA(0, 0, 0, 0.5f), IGraphics::CORNER_ALL, Rounding); + + // draw filled bar + float Amount = CurrentTime / TotalTime; + CUIRect FilledBar = SeekBar; + FilledBar.w = 2 * Rounding + (FilledBar.w - 2 * Rounding) * Amount; + FilledBar.Draw(ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, Rounding); + + // draw time + char aCurrentTime[32]; + str_time_float(CurrentTime, TIME_HOURS, aCurrentTime, sizeof(aCurrentTime)); + char aTotalTime[32]; + str_time_float(TotalTime, TIME_HOURS, aTotalTime, sizeof(aTotalTime)); + str_format(aBuffer, sizeof(aBuffer), "%s / %s", aCurrentTime, aTotalTime); + UI()->DoLabel(&SeekBar, aBuffer, SeekBar.h * 0.70f, TEXTALIGN_MC); + + // do the logic + const bool Inside = UI()->MouseInside(&SeekBar); + + if(UI()->CheckActiveItem(pId)) + { + if(!UI()->MouseButton(0)) + UI()->SetActiveItem(nullptr); + else + { + static float s_PrevAmount = 0.0f; + float AmountSeek = clamp((UI()->MouseX() - SeekBar.x - Rounding) / (float)(SeekBar.w - 2 * Rounding), 0.0f, 1.0f); + + if(Input()->ShiftIsPressed()) + { + AmountSeek = s_PrevAmount + (AmountSeek - s_PrevAmount) * 0.05f; + if(AmountSeek >= 0.0f && AmountSeek <= 1.0f && absolute(s_PrevAmount - AmountSeek) >= 0.0001f) + { + Sound()->SetSampleCurrentTime(pSelectedSound->m_SoundID, AmountSeek); + } + } + else + { + if(AmountSeek >= 0.0f && AmountSeek <= 1.0f && absolute(s_PrevAmount - AmountSeek) >= 0.001f) + { + s_PrevAmount = AmountSeek; + Sound()->SetSampleCurrentTime(pSelectedSound->m_SoundID, AmountSeek); + } + } + } + } + else if(UI()->HotItem() == pId) + { + if(UI()->MouseButton(0)) + { + UI()->SetActiveItem(pId); + } + } + + if(Inside) + UI()->SetHotItem(pId); } } } @@ -4689,11 +4760,11 @@ void CEditor::RenderFileDialog() Sound()->Play(CSounds::CHN_GUI, m_FilePreviewSound, 0); } - char aDuration[32]; - char aDurationLabel[64]; - str_time_float(Sound()->GetSampleDuration(m_FilePreviewSound), TIME_HOURS, aDuration, sizeof(aDuration)); - str_format(aDurationLabel, sizeof(aDurationLabel), "Duration: %s", aDuration); - UI()->DoLabel(&Preview, aDurationLabel, 12.0f, TEXTALIGN_ML); + char aTotalTime[32]; + char aTotalTimeLabel[64]; + str_time_float(Sound()->GetSampleTotalTime(m_FilePreviewSound), TIME_HOURS, aTotalTime, sizeof(aTotalTime)); + str_format(aTotalTimeLabel, sizeof(aTotalTimeLabel), "Duration: %s", aTotalTime); + UI()->DoLabel(&Preview, aTotalTimeLabel, 12.0f, TEXTALIGN_ML); } else if(m_FilePreviewState == PREVIEW_ERROR) {