diff --git a/src/custom_sfml_audio/beat_ticks.cpp b/src/custom_sfml_audio/beat_ticks.cpp index 2512a95..fe8ba0b 100644 --- a/src/custom_sfml_audio/beat_ticks.cpp +++ b/src/custom_sfml_audio/beat_ticks.cpp @@ -10,18 +10,18 @@ BeatTicks::BeatTicks( const better::Timing* timing_, const std::filesystem::path& assets, - float pitch + float pitch_ ) : - FakePitchedSoundStream(assets / "sounds" / "beat.wav", pitch), + FakePitchedSoundStream(assets / "sounds" / "beat.wav", pitch_), timing(timing_) {} BeatTicks::BeatTicks( const better::Timing* timing_, std::shared_ptr beat_tick, - float pitch + float pitch_ ) : - FakePitchedSoundStream(beat_tick, pitch), + FakePitchedSoundStream(beat_tick, pitch_), timing(timing_) {} @@ -29,11 +29,11 @@ void BeatTicks::set_timing(const better::Timing* timing_) { timing = timing_; } -std::shared_ptr BeatTicks::with_pitch(float pitch) { +std::shared_ptr BeatTicks::with_pitch(float new_pitch) { return std::make_shared( timing, sample, - pitch + new_pitch ); } diff --git a/src/custom_sfml_audio/precise_sound_stream.hpp b/src/custom_sfml_audio/precise_sound_stream.hpp index 6f7c30f..5af7a33 100644 --- a/src/custom_sfml_audio/precise_sound_stream.hpp +++ b/src/custom_sfml_audio/precise_sound_stream.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -19,6 +20,16 @@ struct PreciseSoundStream : public OpenSoundStream { }; template -sf::Uint64 timeToSamples(sf::Time position, T sample_rate, T channel_count) { +sf::Uint64 time_to_samples(sf::Time position, T sample_rate, T channel_count) { return ((static_cast(position.asMicroseconds()) * sample_rate * channel_count) + 500000) / 1000000; +}; + +template +sf::Time samples_to_time(std::int64_t samples, T sample_rate, T channel_count) { + // Make sure we don't divide by 0 + if (sample_rate != 0 && channel_count != 0) { + return sf::microseconds((samples * 1000000) / (channel_count * sample_rate)); + } else { + return sf::Time::Zero; + } }; \ No newline at end of file diff --git a/src/custom_sfml_audio/synced_sound_streams.cpp b/src/custom_sfml_audio/synced_sound_streams.cpp index cfe46aa..9bd1e8c 100644 --- a/src/custom_sfml_audio/synced_sound_streams.cpp +++ b/src/custom_sfml_audio/synced_sound_streams.cpp @@ -14,8 +14,10 @@ #include #include "../variant_visitor.hpp" +#include "../toolbox.hpp" #include "al_check.hpp" #include "audio_device.hpp" +#include "imgui.h" #include "precise_sound_stream.hpp" #ifdef _MSC_VER @@ -155,6 +157,11 @@ void SyncedSoundStreams::play() { // Start updating the stream in a separate thread to avoid blocking the application launchStreamingThread(sf::SoundSource::Playing); + + // Set lag values + for (const auto& [_, s]: streams) { + s.stream->lag = s.stream->alSecOffsetLatencySoft()[1]; + } } @@ -219,7 +226,11 @@ void SyncedSoundStreams::setPlayingOffset(sf::Time timeOffset) { for (auto& [_, s]: streams) { s.stream->public_seek_callback(timeOffset); // Restart streaming - s.buffers.m_samplesProcessed = timeToSamples(timeOffset, s.buffers.m_sampleRate, s.buffers.m_channelCount); + if (s.reconstruct_on_pitch_change) { + timeOffset /= pitch; + } + + s.buffers.m_samplesProcessed = time_to_samples(timeOffset, s.buffers.m_sampleRate, s.buffers.m_channelCount); } if (oldStatus == sf::SoundSource::Stopped) { @@ -234,18 +245,19 @@ sf::Time SyncedSoundStreams::getPlayingOffset() const { if (streams.empty()) { return sf::Time::Zero; } - const auto& s = streams.begin()->second; + return getPlayingOffset(streams.begin()->second); +} + +sf::Time SyncedSoundStreams::getPlayingOffset(const InternalStream& s) const { if (not (s.buffers.m_sampleRate && s.buffers.m_channelCount)) { return sf::Time::Zero; } - ALfloat secs = 0.f; alCheck(alGetSourcef(s.stream->get_source(), AL_SEC_OFFSET, &secs)); - const auto openal_seconds = sf::seconds( - secs - + static_cast(s.buffers.m_samplesProcessed) - / static_cast(s.buffers.m_sampleRate) - / static_cast(s.buffers.m_channelCount) + const auto openal_seconds = sf::seconds(secs) + samples_to_time( + s.buffers.m_samplesProcessed, + s.buffers.m_sampleRate, + s.buffers.m_channelCount ); if (s.reconstruct_on_pitch_change) { return openal_seconds * pitch; @@ -255,14 +267,14 @@ sf::Time SyncedSoundStreams::getPlayingOffset() const { } sf::Time SyncedSoundStreams::getPrecisePlayingOffset() const { - const auto base = getPlayingOffset(); if (streams.empty()) { - return base; - } - const auto& s = streams.begin()->second; - if (not (s.buffers.m_sampleRate && s.buffers.m_channelCount)) { - return base; + return sf::Time::Zero; } + return getPrecisePlayingOffset(streams.begin()->second); +} + +sf::Time SyncedSoundStreams::getPrecisePlayingOffset(const InternalStream& s) const { + const auto base = getPlayingOffset(s); const auto correction = ( s.stream->alSecOffsetLatencySoft()[1] - s.stream->lag ); @@ -288,6 +300,94 @@ bool SyncedSoundStreams::getLoop() const { return m_loop; } +void SyncedSoundStreams::display_debug() const { + if (ImGui::BeginTable("SSS debug props", streams.size() + 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn(""); + for (const auto& [name, _] : streams) { + ImGui::TableSetupColumn(name.c_str()); + } + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("reconstruct on pitch change"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(fmt::format("{}", s.reconstruct_on_pitch_change).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("SyncedSoundStreams offset"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Toolbox::to_string(getPlayingOffset(s)).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("internal stream offset"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Toolbox::to_string(s.stream->getPlayingOffset()).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("SyncedSoundStreams offset (precise)"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Toolbox::to_string(getPrecisePlayingOffset(s)).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("internal stream offset (precise)"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Toolbox::to_string(s.stream->getPrecisePlayingOffset()).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("stream info in .buffers"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(fmt::format("{}Hz * {}ch", s.buffers.m_sampleRate, s.buffers.m_channelCount).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("stream info in .stream"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(fmt::format("{}Hz * {}ch", s.stream->getSampleRate(), s.stream->getChannelCount()).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("internal stream pitch"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(fmt::format("x{}", s.stream->getPitch()).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("AL_SEC_OFFSET"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + ALfloat secs = 0.f; + alCheck(alGetSourcef(s.stream->get_source(), AL_SEC_OFFSET, &secs)); + ImGui::TextUnformatted(Toolbox::to_string(sf::seconds(secs)).c_str()); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("samples_to_time()"); + for (const auto& [_, s] : streams) { + ImGui::TableNextColumn(); + const auto time = samples_to_time( + s.buffers.m_samplesProcessed, + s.buffers.m_sampleRate, + s.buffers.m_channelCount + ); + ImGui::TextUnformatted(Toolbox::to_string(time).c_str()); + } + ImGui::EndTable(); + } +} + void SyncedSoundStreams::setProcessingInterval(sf::Time interval) { m_processingInterval = interval; diff --git a/src/custom_sfml_audio/synced_sound_streams.hpp b/src/custom_sfml_audio/synced_sound_streams.hpp index d902e54..eb99903 100644 --- a/src/custom_sfml_audio/synced_sound_streams.hpp +++ b/src/custom_sfml_audio/synced_sound_streams.hpp @@ -77,6 +77,8 @@ public: void setLoop(bool loop); bool getLoop() const; + void display_debug() const; + protected: void setProcessingInterval(sf::Time interval); @@ -94,6 +96,9 @@ private: void unsafe_update_streams(); void reload_sources(); + sf::Time getPlayingOffset(const InternalStream& s) const; + sf::Time getPrecisePlayingOffset(const InternalStream& s) const; + float pitch = 1.f; std::thread m_thread; // Thread running the background tasks mutable std::recursive_mutex m_threadMutex; // Thread mutex diff --git a/src/editor_state.cpp b/src/editor_state.cpp index 169352c..320f263 100644 --- a/src/editor_state.cpp +++ b/src/editor_state.cpp @@ -124,6 +124,7 @@ void EditorState::toggle_beat_ticks() { if (audio.contains_stream(beat_tick_stream)) { audio.remove_stream(beat_tick_stream); } else { + beat_ticks = beat_ticks->with_pitch(get_pitch()); audio.add_stream(beat_tick_stream, {beat_ticks, true}); } } @@ -158,6 +159,10 @@ void EditorState::set_pitch(float pitch) { audio.setPitch(pitch); } +float EditorState::get_pitch() const { + return speed / 10.f; +} + void EditorState::set_playback_position(std::variant newPosition) { const auto clamp_ = VariantVisitor { [this](const sf::Time& seconds) { @@ -515,6 +520,8 @@ void EditorState::display_status() { "No jacket loaded"); } } + + audio.display_debug(); } ImGui::End(); }; diff --git a/src/editor_state.hpp b/src/editor_state.hpp index 694cc50..ad5253d 100644 --- a/src/editor_state.hpp +++ b/src/editor_state.hpp @@ -25,7 +25,7 @@ const std::string music_stream = "music"; -const std::string note_clap_stream = "note_clap"; +const std::string note_clap_stream = "aaa_note_clap"; const std::string beat_tick_stream = "beat_tick"; /* @@ -86,6 +86,7 @@ public: void stop(); sf::SoundSource::Status get_status(); void set_pitch(float pitch); + float get_pitch() const; void set_playback_position(std::variant newPosition); sf::Time get_precise_playback_position();