mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2025-02-28 15:30:32 +01:00
Working beat tick + simpler stream change method
This commit is contained in:
parent
51c2bd8c37
commit
f09f6b66b3
116
src/custom_sfml_audio/beat_ticks.cpp
Normal file
116
src/custom_sfml_audio/beat_ticks.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include "beat_ticks.hpp"
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../better_note.hpp"
|
||||
|
||||
BeatTicks::BeatTicks(
|
||||
const better::Timing* timing_,
|
||||
const std::filesystem::path& assets
|
||||
) :
|
||||
timing(timing_),
|
||||
beat_tick()
|
||||
{
|
||||
if (not beat_tick.loadFromFile(assets / "sounds" / "beat.wav")) {
|
||||
throw std::runtime_error("Could not load beat tick audio file");
|
||||
}
|
||||
sf::SoundStream::initialize(beat_tick.getChannelCount(), beat_tick.getSampleRate());
|
||||
samples.resize(timeToSamples(sf::milliseconds(200)), 0);
|
||||
}
|
||||
|
||||
void BeatTicks::set_timing(const better::Timing* timing_) {
|
||||
timing = timing_;
|
||||
}
|
||||
|
||||
bool BeatTicks::onGetData(sf::SoundStream::Chunk& data) {
|
||||
samples.assign(samples.size(), 0);
|
||||
if (timing != 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 start_beat = timing->beats_at(start_time);
|
||||
const auto end_beat = timing->beats_at(end_time);
|
||||
|
||||
auto first_beat = static_cast<std::int64_t>(start_beat);
|
||||
while (first_beat < start_beat) {
|
||||
first_beat++;
|
||||
}
|
||||
for (std::int64_t beat = first_beat; beat < end_beat; beat++) {
|
||||
const auto time = timing->time_at(beat);
|
||||
const auto sample = static_cast<std::int64_t>(timeToSamples(time));
|
||||
beat_at_sample.insert(sample);
|
||||
}
|
||||
|
||||
for (auto it = beat_at_sample.begin(); it != beat_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>(beat_tick.getSampleCount());
|
||||
if (*it <= last_audible_start) {
|
||||
it = beat_at_sample.erase(it);
|
||||
} else {
|
||||
const auto full_tick_start_in_buffer = *it - static_cast<std::int64_t>(start_sample);
|
||||
const auto slice_start_in_buffer = std::max(std::int64_t(0), full_tick_start_in_buffer);
|
||||
const auto full_tick_end_in_buffer = full_tick_start_in_buffer + static_cast<std::int64_t>(beat_tick.getSampleCount());
|
||||
auto slice_end_in_buffer = full_tick_end_in_buffer;
|
||||
bool tick_finished_playing_in_current_buffer = true;
|
||||
if (next != beat_at_sample.end()) {
|
||||
slice_end_in_buffer = std::min(
|
||||
slice_end_in_buffer,
|
||||
*next - static_cast<std::int64_t>(start_sample)
|
||||
);
|
||||
} else if (slice_end_in_buffer > static_cast<std::int64_t>(samples.size())) {
|
||||
tick_finished_playing_in_current_buffer = false;
|
||||
slice_end_in_buffer = static_cast<std::int64_t>(samples.size());
|
||||
}
|
||||
auto slice_start_in_tick = slice_start_in_buffer - full_tick_start_in_buffer;
|
||||
auto slice_size = std::min(
|
||||
slice_end_in_buffer - slice_start_in_buffer,
|
||||
static_cast<std::int64_t>(beat_tick.getSampleCount()) - slice_start_in_tick
|
||||
);
|
||||
const auto tick_pointer = beat_tick.getSamples() + slice_start_in_tick;
|
||||
std::copy(
|
||||
tick_pointer,
|
||||
tick_pointer + slice_size,
|
||||
samples.begin() + slice_start_in_buffer
|
||||
);
|
||||
if (tick_finished_playing_in_current_buffer) {
|
||||
it = beat_at_sample.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.samples = samples.data();
|
||||
data.sampleCount = samples.size();
|
||||
current_sample += samples.size();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
void BeatTicks::onSeek(sf::Time timeOffset) {
|
||||
current_sample = timeToSamples(timeOffset);
|
||||
beat_at_sample.clear();
|
||||
};
|
||||
|
||||
std::int64_t BeatTicks::timeToSamples(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.asMicroseconds()) * beat_tick.getSampleRate() * beat_tick.getChannelCount()) + 500000) / 1000000;
|
||||
}
|
||||
|
||||
sf::Time BeatTicks::samplesToTime(std::int64_t samples) const {
|
||||
sf::Time position = sf::Time::Zero;
|
||||
|
||||
// Make sure we don't divide by 0
|
||||
if (beat_tick.getSampleRate() != 0 && beat_tick.getChannelCount() != 0)
|
||||
position = sf::microseconds((samples * 1000000) / (beat_tick.getChannelCount() * beat_tick.getSampleRate()));
|
||||
|
||||
return position;
|
||||
}
|
34
src/custom_sfml_audio/beat_ticks.hpp
Normal file
34
src/custom_sfml_audio/beat_ticks.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <SFML/Audio/SoundBuffer.hpp>
|
||||
|
||||
#include "../better_timing.hpp"
|
||||
#include "precise_sound_stream.hpp"
|
||||
|
||||
class BeatTicks: public PreciseSoundStream {
|
||||
public:
|
||||
BeatTicks(
|
||||
const better::Timing* timing_,
|
||||
const std::filesystem::path& assets
|
||||
);
|
||||
|
||||
void set_timing(const better::Timing* timing);
|
||||
std::atomic<bool> play_chords = true;
|
||||
|
||||
protected:
|
||||
bool onGetData(Chunk& data) override;
|
||||
void onSeek(sf::Time timeOffset) override;
|
||||
|
||||
private:
|
||||
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::set<std::int64_t> beat_at_sample;
|
||||
|
||||
const better::Timing* timing;
|
||||
sf::SoundBuffer beat_tick;
|
||||
};
|
@ -2,6 +2,7 @@ sources += files([
|
||||
'al_check.cpp',
|
||||
'al_resource.cpp',
|
||||
'audio_device.cpp',
|
||||
'beat_ticks.cpp',
|
||||
'note_claps.cpp',
|
||||
'open_music.cpp',
|
||||
'open_sound_stream.cpp',
|
||||
|
@ -61,48 +61,46 @@ SyncedSoundStreams::~SyncedSoundStreams() {
|
||||
|
||||
|
||||
void SyncedSoundStreams::add_stream(const std::string& name, std::shared_ptr<PreciseSoundStream> s) {
|
||||
stream_change_requests.try_enqueue(AddStream{name, s});
|
||||
if (not m_isStreaming) {
|
||||
// if we are not currently playing audio there should be no problem
|
||||
// changing the streams right now
|
||||
unsafe_update_streams();
|
||||
const auto oldStatus = getStatus();
|
||||
const auto position = getPrecisePlayingOffset();
|
||||
stop();
|
||||
{
|
||||
InternalStream internal_stream{s, {}};
|
||||
internal_stream.buffers.m_channelCount = s->getChannelCount();
|
||||
internal_stream.buffers.m_sampleRate = s->getSampleRate();
|
||||
internal_stream.buffers.m_format = AudioDevice::getFormatFromChannelCount(s->getChannelCount());
|
||||
streams.emplace(name, internal_stream);
|
||||
}
|
||||
reload_sources();
|
||||
if (oldStatus != sf::SoundSource::Stopped) {
|
||||
setPlayingOffset(position);
|
||||
}
|
||||
if (oldStatus == sf::SoundSource::Playing) {
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
void SyncedSoundStreams::remove_stream(const std::string& name) {
|
||||
stream_change_requests.try_enqueue(RemoveStream{name});
|
||||
if (not m_isStreaming) {
|
||||
// if we are not currently playing audio there should be no problem
|
||||
// changing the streams right now
|
||||
unsafe_update_streams();
|
||||
const auto oldStatus = getStatus();
|
||||
const auto position = getPrecisePlayingOffset();
|
||||
stop();
|
||||
{
|
||||
if (streams.contains(name)) {
|
||||
streams.at(name).clear_queue();
|
||||
}
|
||||
streams.erase(name);
|
||||
}
|
||||
reload_sources();
|
||||
if (oldStatus != sf::SoundSource::Stopped) {
|
||||
setPlayingOffset(position);
|
||||
}
|
||||
if (oldStatus == sf::SoundSource::Playing) {
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
void SyncedSoundStreams::unsafe_update_streams() {
|
||||
ChangeStreamsCommand c;
|
||||
bool modified_stuff = false;
|
||||
auto _do_request = VariantVisitor {
|
||||
[this](const AddStream& a) {
|
||||
InternalStream internal_stream{a.stream, {}};
|
||||
internal_stream.buffers.m_channelCount = a.stream->getChannelCount();
|
||||
internal_stream.buffers.m_sampleRate = a.stream->getSampleRate();
|
||||
internal_stream.buffers.m_format = AudioDevice::getFormatFromChannelCount(a.stream->getChannelCount());
|
||||
streams.emplace(a.name, internal_stream);
|
||||
},
|
||||
[this](const RemoveStream& r) {
|
||||
if (streams.contains(r.name)) {
|
||||
streams.at(r.name).clear_queue();
|
||||
}
|
||||
streams.erase(r.name);
|
||||
},
|
||||
};
|
||||
while (stream_change_requests.try_dequeue(c)) {
|
||||
std::visit(_do_request, c);
|
||||
modified_stuff = true;
|
||||
}
|
||||
if (modified_stuff) {
|
||||
reload_sources();
|
||||
}
|
||||
bool SyncedSoundStreams::contains_stream(const std::string& name) {
|
||||
return streams.contains(name);
|
||||
}
|
||||
|
||||
void SyncedSoundStreams::play() {
|
||||
@ -398,9 +396,6 @@ void SyncedSoundStreams::streamData() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Process stream change requests
|
||||
unsafe_update_streams();
|
||||
|
||||
// Leave some time for the other threads if the stream is still playing
|
||||
if (std::any_of(streams.begin(), streams.end(), [](auto& s){
|
||||
return s.second.stream->getStatus() != sf::SoundSource::Stopped;
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
#include "al_resource.hpp"
|
||||
#include "precise_sound_stream.hpp"
|
||||
#include "readerwriterqueue.h"
|
||||
#include "src/history_item.hpp"
|
||||
|
||||
|
||||
@ -65,6 +64,7 @@ public:
|
||||
|
||||
void add_stream(const std::string& name, std::shared_ptr<PreciseSoundStream> s);
|
||||
void remove_stream(const std::string& name);
|
||||
bool contains_stream(const std::string& name);
|
||||
|
||||
void play();
|
||||
void pause();
|
||||
@ -92,7 +92,6 @@ private:
|
||||
void launchStreamingThread(sf::SoundSource::Status threadStartState);
|
||||
void awaitStreamingThread();
|
||||
|
||||
moodycamel::ReaderWriterQueue<ChangeStreamsCommand> stream_change_requests{10};
|
||||
void unsafe_update_streams();
|
||||
void reload_sources();
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
EditorState::EditorState(const std::filesystem::path& assets_) :
|
||||
note_claps(std::make_shared<NoteClaps>(nullptr, nullptr, assets_)),
|
||||
beat_ticks(std::make_shared<BeatTicks>(nullptr, assets_)),
|
||||
playfield(assets_),
|
||||
linear_view(assets_),
|
||||
applicable_timing(song.timing),
|
||||
@ -54,6 +55,7 @@ EditorState::EditorState(
|
||||
song(song_),
|
||||
song_path(song_path),
|
||||
note_claps(std::make_shared<NoteClaps>(nullptr, nullptr, assets_)),
|
||||
beat_ticks(std::make_shared<BeatTicks>(nullptr, assets_)),
|
||||
playfield(assets_),
|
||||
linear_view(assets_),
|
||||
applicable_timing(song.timing),
|
||||
@ -118,6 +120,14 @@ void EditorState::toggle_playback() {
|
||||
}
|
||||
}
|
||||
|
||||
void EditorState::toggle_beat_ticks() {
|
||||
if (audio.contains_stream(beat_tick_stream)) {
|
||||
audio.remove_stream(beat_tick_stream);
|
||||
} else {
|
||||
audio.add_stream(beat_tick_stream, beat_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorState::play() {
|
||||
audio.play();
|
||||
}
|
||||
@ -804,6 +814,7 @@ void EditorState::open_chart(const std::string& name) {
|
||||
reload_editable_range();
|
||||
reload_applicable_timing();
|
||||
note_claps->set_notes_and_timing(&chart.notes, &applicable_timing);
|
||||
beat_ticks->set_timing(&applicable_timing);
|
||||
};
|
||||
|
||||
void EditorState::update_visible_notes() {
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
#include "custom_sfml_audio/beat_ticks.hpp"
|
||||
#include "custom_sfml_audio/note_claps.hpp"
|
||||
#include "custom_sfml_audio/open_music.hpp"
|
||||
#include "custom_sfml_audio/synced_sound_streams.hpp"
|
||||
@ -25,6 +26,7 @@
|
||||
|
||||
const std::string music_stream = "music";
|
||||
const std::string note_clap_stream = "note_clap";
|
||||
const std::string beat_tick_stream = "beat_tick";
|
||||
|
||||
/*
|
||||
* The god class, holds everything there is to know about the currently open
|
||||
@ -47,6 +49,7 @@ public:
|
||||
|
||||
SyncedSoundStreams audio;
|
||||
std::shared_ptr<NoteClaps> note_claps;
|
||||
std::shared_ptr<BeatTicks> beat_ticks;
|
||||
std::optional<std::shared_ptr<OpenMusic>> music = {};
|
||||
|
||||
int get_volume() const;
|
||||
@ -77,6 +80,7 @@ public:
|
||||
const Interval<sf::Time>& get_editable_range();
|
||||
|
||||
void toggle_playback();
|
||||
void toggle_beat_ticks();
|
||||
void play();
|
||||
void pause();
|
||||
void stop();
|
||||
|
14
src/main.cpp
14
src/main.cpp
@ -481,12 +481,14 @@ int main() {
|
||||
}
|
||||
if (editor_state->showSoundSettings) {
|
||||
ImGui::Begin("Sound Settings", &editor_state->showSoundSettings, ImGuiWindowFlags_AlwaysAutoResize); {
|
||||
if (ImGui::TreeNode("Note Clap")) {
|
||||
static auto play_chords = editor_state->note_claps->play_chords.load();
|
||||
if (ImGui::Checkbox("Play on chords", &play_chords)) {
|
||||
editor_state->note_claps->play_chords.store(play_chords);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
ImGui::Text("Note Clap");
|
||||
static auto play_chords = editor_state->note_claps->play_chords.load();
|
||||
if (ImGui::Checkbox("Play on chords", &play_chords)) {
|
||||
editor_state->note_claps->play_chords.store(play_chords);
|
||||
}
|
||||
ImGui::Text("Beat Tick");
|
||||
if (ImGui::Button("Toggle")) {
|
||||
editor_state->toggle_beat_ticks();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
Loading…
x
Reference in New Issue
Block a user