ah yes ... an audio callback

This commit is contained in:
Stepland 2022-10-02 01:42:25 +02:00
parent 317616c86e
commit 58de43883d
3 changed files with 75 additions and 63 deletions

View File

@ -2,6 +2,7 @@
#include <SFML/Audio/SoundBuffer.hpp>
#include <SFML/System/Time.hpp>
#include <limits>
#include <memory>
#include <stdexcept>
@ -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> 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<std::int64_t>(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<std::int64_t>(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<std::int64_t>(timeToSamples(time));
const auto sample = static_cast<std::int64_t>(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<std::int64_t>(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<std::int64_t>(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<std::int64_t>::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<std::int64_t>(it->first) - static_cast<std::int64_t>(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<std::int64_t>(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<std::int64_t>(next->first) - static_cast<std::int64_t>(start_sample)
);
} else if (slice_end_in_buffer > static_cast<std::int64_t>(samples.size())) {
clap_finished_playing_in_current_buffer = false;
slice_end_in_buffer = static_cast<std::int64_t>(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<std::int64_t>(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<std::int64_t>((position / pitch).asMicroseconds()) * note_clap->getSampleRate() * note_clap->getChannelCount()) + 500000) / 1000000;
return ((static_cast<std::int64_t>(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;
}

View File

@ -38,8 +38,10 @@ private:
float pitch = 1.f;
std::vector<sf::Int16> 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<std::int64_t, unsigned int> notes_at_sample;

View File

@ -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<float>(s.buffers.m_samplesProcessed)
/ static_cast<float>(s.buffers.m_sampleRate)
/ static_cast<float>(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) {