diff --git a/src/custom_sfml_audio/note_claps.cpp b/src/custom_sfml_audio/note_claps.cpp index 2792776..831f701 100644 --- a/src/custom_sfml_audio/note_claps.cpp +++ b/src/custom_sfml_audio/note_claps.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -22,7 +23,7 @@ NoteClaps::NoteClaps( throw std::runtime_error("Could not load note clap audio file"); } sf::SoundStream::initialize(note_clap->getChannelCount(), note_clap->getSampleRate()); - samples.resize(timeToSamples(sf::seconds(1)), 0); + samples.resize(openAL_time_to_samples(sf::seconds(1)), 0); } NoteClaps::NoteClaps( @@ -37,7 +38,7 @@ NoteClaps::NoteClaps( note_clap(note_clap_) { sf::SoundStream::initialize(note_clap->getChannelCount(), note_clap->getSampleRate()); - samples.resize(timeToSamples(sf::seconds(1)), 0); + samples.resize(openAL_time_to_samples(sf::seconds(1)), 0); } void NoteClaps::set_notes_and_timing(const better::Notes* notes_, const better::Timing* timing_) { @@ -57,57 +58,62 @@ std::shared_ptr NoteClaps::with_pitch(float pitch) { bool NoteClaps::onGetData(sf::SoundStream::Chunk& data) { samples.assign(samples.size(), 0); if (timing != nullptr and notes != nullptr) { - const auto start_sample = current_sample; - const auto end_sample = current_sample + static_cast(samples.size()); - const auto start_time = samplesToTime(start_sample); - const auto end_time = samplesToTime(end_sample); + const auto absolute_buffer_start = current_sample; + const std::int64_t absolute_buffer_end = current_sample + static_cast(samples.size()); + const auto start_time = samples_to_music_time(absolute_buffer_start); + const auto end_time = samples_to_music_time(absolute_buffer_end); const auto start_beat = timing->beats_at(start_time); const auto end_beat = timing->beats_at(end_time); notes->in(start_beat, end_beat, [&](const better::Notes::const_iterator& it){ const auto beat = it->second.get_time(); const auto time = timing->time_at(beat); - const auto sample = static_cast(timeToSamples(time)); + const auto sample = static_cast(music_time_to_samples(time)); notes_at_sample[sample] += 1; }); for (auto it = notes_at_sample.begin(); it != notes_at_sample.end();) { - // Should we still be playing the clap ? - const auto next = std::next(it); - const auto last_audible_start = start_sample - static_cast(note_clap->getSampleCount()); - if (it->first <= last_audible_start) { + const std::int64_t absolute_clap_start = it->first; + const std::int64_t absolute_clap_end = absolute_clap_start + static_cast(note_clap->getSampleCount()); + const std::int64_t absolute_clap_slice_start = std::max( + absolute_clap_start, + absolute_buffer_start + ); + const std::int64_t absolute_clap_slice_end = std::min({ + absolute_clap_end, + absolute_buffer_end, + [&](const auto& it){ + const auto next = std::next(it); + if (next != notes_at_sample.end()) { + return next->first; + } else { + return std::numeric_limits::max(); + } + }(it) + }); + const std::int64_t slice_size = absolute_clap_slice_end - absolute_clap_slice_start; + const std::int64_t slice_start_relative_to_clap_start = absolute_clap_slice_start - absolute_clap_start; + const std::int64_t slice_start_relative_to_buffer_start = absolute_clap_slice_start - absolute_buffer_start; + const auto input_start = note_clap->getSamples() + slice_start_relative_to_clap_start; + const auto input_end = input_start + slice_size; + const auto output_start = samples.begin() + slice_start_relative_to_buffer_start; + // this code is SURPRISINGLY hard to get right for how little it + // seems to be doing. + // the slice size SHOULD always be positive but if for whatever + // reason the code is still wrong and the computation returns + // a bogus value we just give up playing the samples instead of + // risking a segfault + if (slice_size > 0) { + std::copy( + input_start, + input_end, + output_start + ); + } + if (absolute_clap_end <= absolute_buffer_end) { it = notes_at_sample.erase(it); } else { - const auto full_clap_start_in_buffer = static_cast(it->first) - static_cast(start_sample); - const auto slice_start_in_buffer = std::max(std::int64_t(0), full_clap_start_in_buffer); - const auto full_clap_end_in_buffer = full_clap_start_in_buffer + static_cast(note_clap->getSampleCount()); - auto slice_end_in_buffer = full_clap_end_in_buffer; - bool clap_finished_playing_in_current_buffer = true; - if (next != notes_at_sample.end()) { - slice_end_in_buffer = std::min( - slice_end_in_buffer, - static_cast(next->first) - static_cast(start_sample) - ); - } else if (slice_end_in_buffer > static_cast(samples.size())) { - clap_finished_playing_in_current_buffer = false; - slice_end_in_buffer = static_cast(samples.size()); - } - auto slice_start_in_clap = slice_start_in_buffer - full_clap_start_in_buffer; - auto slice_size = std::min( - slice_end_in_buffer - slice_start_in_buffer, - static_cast(note_clap->getSampleCount()) - slice_start_in_clap - ); - const auto clap_pointer = note_clap->getSamples() + slice_start_in_clap; - std::copy( - clap_pointer, - clap_pointer + slice_size, - samples.begin() + slice_start_in_buffer - ); - if (clap_finished_playing_in_current_buffer) { - it = notes_at_sample.erase(it); - } else { - ++it; - } + ++it; } } } @@ -120,24 +126,32 @@ bool NoteClaps::onGetData(sf::SoundStream::Chunk& data) { }; void NoteClaps::onSeek(sf::Time timeOffset) { - current_sample = timeToSamples(timeOffset); + current_sample = music_time_to_samples(timeOffset); notes_at_sample.clear(); }; -std::int64_t NoteClaps::timeToSamples(sf::Time position) const { +std::int64_t NoteClaps::openAL_time_to_samples(sf::Time position) const { // Always ROUND, no unchecked truncation, hence the addition in the numerator. // This avoids most precision errors arising from "samples => Time => samples" conversions // Original rounding calculation is ((Micros * Freq * Channels) / 1000000) + 0.5 // We refactor it to keep Int64 as the data type throughout the whole operation. - return ((static_cast((position / pitch).asMicroseconds()) * note_clap->getSampleRate() * note_clap->getChannelCount()) + 500000) / 1000000; + return ((static_cast(position.asMicroseconds()) * note_clap->getSampleRate() * note_clap->getChannelCount()) + 500000) / 1000000; } -sf::Time NoteClaps::samplesToTime(std::int64_t samples) const { +sf::Time NoteClaps::samples_to_openAL_time(std::int64_t samples) const { sf::Time position = sf::Time::Zero; // Make sure we don't divide by 0 if (note_clap->getSampleRate() != 0 && note_clap->getChannelCount() != 0) position = sf::microseconds((samples * 1000000) / (note_clap->getChannelCount() * note_clap->getSampleRate())); - return position * pitch; + return position; +} + +std::int64_t NoteClaps::music_time_to_samples(sf::Time position) const { + return openAL_time_to_samples(position / pitch); +} + +sf::Time NoteClaps::samples_to_music_time(std::int64_t samples) const { + return samples_to_openAL_time(samples) * pitch; } \ No newline at end of file diff --git a/src/custom_sfml_audio/note_claps.hpp b/src/custom_sfml_audio/note_claps.hpp index 7d40fcc..4a44848 100644 --- a/src/custom_sfml_audio/note_claps.hpp +++ b/src/custom_sfml_audio/note_claps.hpp @@ -38,8 +38,10 @@ private: float pitch = 1.f; std::vector samples; std::int64_t current_sample = 0; - std::int64_t timeToSamples(sf::Time position) const; - sf::Time samplesToTime(std::int64_t samples) const; + std::int64_t openAL_time_to_samples(sf::Time position) const; + sf::Time samples_to_openAL_time(std::int64_t samples) const; + std::int64_t music_time_to_samples(sf::Time position) const; + sf::Time samples_to_music_time(std::int64_t samples) const; std::map notes_at_sample; diff --git a/src/custom_sfml_audio/synced_sound_streams.cpp b/src/custom_sfml_audio/synced_sound_streams.cpp index cd43968..9921ed4 100644 --- a/src/custom_sfml_audio/synced_sound_streams.cpp +++ b/src/custom_sfml_audio/synced_sound_streams.cpp @@ -217,11 +217,7 @@ void SyncedSoundStreams::setPlayingOffset(sf::Time timeOffset) { // Let the derived class update the current position for (auto& [_, s]: streams) { - auto stream_pitch = 1.f; - if (s.reconstruct_on_pitch_change) { - stream_pitch = pitch; - } - s.stream->public_seek_callback(timeOffset * stream_pitch); + s.stream->public_seek_callback(timeOffset); // Restart streaming s.buffers.m_samplesProcessed = timeToSamples(timeOffset, s.buffers.m_sampleRate, s.buffers.m_channelCount); } @@ -245,16 +241,16 @@ sf::Time SyncedSoundStreams::getPlayingOffset() const { ALfloat secs = 0.f; alCheck(alGetSourcef(s.stream->get_source(), AL_SEC_OFFSET, &secs)); - const auto unpitched_seconds = sf::seconds( + 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) ); if (s.reconstruct_on_pitch_change) { - return unpitched_seconds * pitch; + return openal_seconds * pitch; } else { - return unpitched_seconds; + return openal_seconds; } } @@ -267,15 +263,15 @@ sf::Time SyncedSoundStreams::getPrecisePlayingOffset() const { if (not (s.buffers.m_sampleRate && s.buffers.m_channelCount)) { return base; } - auto stream_pitch = s.stream->getPitch(); - if (s.reconstruct_on_pitch_change) { - stream_pitch = 1.f; - } const auto correction = ( - (s.stream->alSecOffsetLatencySoft()[1] * stream_pitch) - - (s.stream->lag * stream_pitch) + s.stream->alSecOffsetLatencySoft()[1] - s.stream->lag ); - return base - correction; + if (s.reconstruct_on_pitch_change) { + return base - (correction * pitch); + } else { + return base - correction; + } + } void SyncedSoundStreams::setPitch(float new_pitch) {