mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2025-02-28 15:30:32 +01:00
ah yes ... an audio callback
This commit is contained in:
parent
317616c86e
commit
58de43883d
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user