mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2025-02-28 15:30:32 +01:00
ITFUCKIGNWORKS ????
This commit is contained in:
parent
9481cb8c3b
commit
ab57a1c8f1
41
src/custom_sfml_audio/al_resource.cpp
Normal file
41
src/custom_sfml_audio/al_resource.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "al_resource.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_device.hpp"
|
||||
|
||||
namespace {
|
||||
// OpenAL resources counter and its mutex
|
||||
unsigned int count = 0;
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
// The audio device is instantiated on demand rather than at global startup,
|
||||
// which solves a lot of weird crashes and errors.
|
||||
// It is destroyed when it is no longer needed.
|
||||
std::unique_ptr<AudioDevice> globalDevice;
|
||||
}
|
||||
|
||||
AlResource::AlResource() {
|
||||
// Protect from concurrent access
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
// If this is the very first resource, trigger the global device initialization
|
||||
if (count == 0)
|
||||
globalDevice = std::make_unique<AudioDevice>();
|
||||
|
||||
// Increment the resources counter
|
||||
++count;
|
||||
}
|
||||
|
||||
AlResource::~AlResource() {
|
||||
// Protect from concurrent access
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
// Decrement the resources counter
|
||||
--count;
|
||||
|
||||
// If there's no more resource alive, we can destroy the device
|
||||
if (count == 0)
|
||||
globalDevice.reset();
|
||||
}
|
9
src/custom_sfml_audio/al_resource.hpp
Normal file
9
src/custom_sfml_audio/al_resource.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/Audio/Export.hpp>
|
||||
|
||||
class AlResource {
|
||||
protected:
|
||||
AlResource();
|
||||
~AlResource();
|
||||
};
|
@ -1,20 +1,26 @@
|
||||
#include "clap_player.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../better_note.hpp"
|
||||
|
||||
ClapPlayer::ClapPlayer(
|
||||
const better::Notes* notes_,
|
||||
const better::Timing* timing_,
|
||||
const sf::SoundBuffer& note_clap_,
|
||||
const sf::SoundBuffer& chord_clap_
|
||||
const std::filesystem::path& assets
|
||||
) :
|
||||
notes(notes_),
|
||||
timing(timing_),
|
||||
note_clap(note_clap_),
|
||||
chord_clap(chord_clap_)
|
||||
note_clap(),
|
||||
chord_clap()
|
||||
{
|
||||
if (not note_clap.loadFromFile(assets / "sounds" / "note.wav")) {
|
||||
throw std::runtime_error("Could not load note clap audio file");
|
||||
}
|
||||
if (not chord_clap.loadFromFile(assets / "sounds" / "chord.wav")) {
|
||||
throw std::runtime_error("Could not load chord clap audio file");
|
||||
}
|
||||
sf::SoundStream::initialize(note_clap.getChannelCount(), note_clap.getSampleRate());
|
||||
samples.resize(note_clap.getChannelCount() * note_clap.getSampleRate(), 0);
|
||||
}
|
||||
|
@ -8,15 +8,14 @@
|
||||
|
||||
#include "../better_notes.hpp"
|
||||
#include "../better_timing.hpp"
|
||||
#include "open_sound_stream.hpp"
|
||||
#include "precise_sound_stream.hpp"
|
||||
|
||||
class ClapPlayer: public OpenSoundStream {
|
||||
class ClapPlayer: public PreciseSoundStream {
|
||||
public:
|
||||
ClapPlayer(
|
||||
const better::Notes* notes_,
|
||||
const better::Timing* timing_,
|
||||
const sf::SoundBuffer& note_clap_,
|
||||
const sf::SoundBuffer& chord_clap_
|
||||
const std::filesystem::path& assets
|
||||
);
|
||||
|
||||
void set_notes_and_timing(const better::Notes* notes, const better::Timing* timing);
|
||||
@ -35,6 +34,6 @@ private:
|
||||
|
||||
const better::Notes* notes;
|
||||
const better::Timing* timing;
|
||||
const sf::SoundBuffer& note_clap;
|
||||
const sf::SoundBuffer& chord_clap;
|
||||
sf::SoundBuffer note_clap;
|
||||
sf::SoundBuffer chord_clap;
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
sources += files([
|
||||
'al_check.cpp',
|
||||
'al_resource.cpp',
|
||||
'audio_device.cpp',
|
||||
'beat_tick_player.cpp',
|
||||
'clap_player.cpp',
|
||||
'open_music.cpp',
|
||||
'open_soud_stream.cpp',
|
||||
'open_sound_stream.cpp',
|
||||
'precise_sound_stream.cpp',
|
||||
'synced_sound_streams.cpp'
|
||||
])
|
@ -8,6 +8,7 @@
|
||||
#include <SFML/Audio/Music.hpp>
|
||||
#include <SFML/System/Err.hpp>
|
||||
#include <SFML/System/Time.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "al_check.hpp"
|
||||
|
||||
@ -19,10 +20,13 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
OpenMusic::OpenMusic() :
|
||||
OpenMusic::OpenMusic(const std::filesystem::path& filename) :
|
||||
m_file(),
|
||||
m_loopSpan(0, 0) {
|
||||
|
||||
m_loopSpan(0, 0)
|
||||
{
|
||||
if (not openFromFile(filename)) {
|
||||
throw std::runtime_error("Could not open "+filename.string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -47,36 +51,6 @@ bool OpenMusic::openFromFile(const std::filesystem::path& filename) {
|
||||
}
|
||||
|
||||
|
||||
bool OpenMusic::openFromMemory(const void* data, std::size_t sizeInBytes) {
|
||||
// First stop the music if it was already running
|
||||
stop();
|
||||
|
||||
// Open the underlying sound file
|
||||
if (!m_file.openFromMemory(data, sizeInBytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform common initializations
|
||||
initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool OpenMusic::openFromStream(sf::InputStream& stream) {
|
||||
// First stop the music if it was already running
|
||||
stop();
|
||||
|
||||
// Open the underlying sound file
|
||||
if (!m_file.openFromStream(stream))
|
||||
return false;
|
||||
|
||||
// Perform common initializations
|
||||
initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
sf::Time OpenMusic::getDuration() const {
|
||||
return m_file.getDuration();
|
||||
}
|
||||
|
@ -15,11 +15,9 @@
|
||||
class OpenMusic : public PreciseSoundStream {
|
||||
public:
|
||||
using TimeSpan = sf::Music::Span<sf::Time>;
|
||||
OpenMusic();
|
||||
OpenMusic(const std::filesystem::path& filename);
|
||||
~OpenMusic() override;
|
||||
[[nodiscard]] bool openFromFile(const std::filesystem::path& filename);
|
||||
[[nodiscard]] bool openFromMemory(const void* data, std::size_t sizeInBytes);
|
||||
[[nodiscard]] bool openFromStream(sf::InputStream& stream);
|
||||
sf::Time getDuration() const;
|
||||
TimeSpan getLoopPoints() const;
|
||||
void setLoopPoints(TimeSpan timePoints);
|
||||
|
@ -1,11 +1,14 @@
|
||||
#include <SFML/Audio/SoundStream.hpp>
|
||||
#include "precise_sound_stream.hpp"
|
||||
|
||||
PreciseSoundStream::PreciseSoundStream() {
|
||||
initialize_open_al_extension();
|
||||
}
|
||||
|
||||
void PreciseSoundStream::initialize_open_al_extension() {
|
||||
if (not alIsExtensionPresent("AL_SOFT_source_latency")) {
|
||||
throw std::runtime_error("Error: AL_SOFT_source_latency not supported");
|
||||
}
|
||||
|
||||
alGetSourcedvSOFT = reinterpret_cast<LPALGETSOURCEDVSOFT>(alGetProcAddress("alGetSourcedvSOFT"));
|
||||
}
|
||||
|
||||
@ -22,10 +25,10 @@ void PreciseSoundStream::play() {
|
||||
|
||||
sf::Time PreciseSoundStream::getPrecisePlayingOffset() const {
|
||||
if (getStatus() != sf::SoundStream::Playing) {
|
||||
return sf::SoundStream::getPlayingOffset();
|
||||
return getPlayingOffset();
|
||||
} else {
|
||||
return (
|
||||
sf::SoundStream::getPlayingOffset()
|
||||
getPlayingOffset()
|
||||
- (alSecOffsetLatencySoft()[1] * getPitch())
|
||||
+ (lag * getPitch())
|
||||
);
|
||||
|
@ -8,14 +8,12 @@
|
||||
|
||||
#include "open_sound_stream.hpp"
|
||||
|
||||
class PreciseSoundStream : public OpenSoundStream {
|
||||
public:
|
||||
struct PreciseSoundStream : public OpenSoundStream {
|
||||
PreciseSoundStream();
|
||||
sf::Time getPrecisePlayingOffset() const;
|
||||
void play();
|
||||
protected:
|
||||
void initialize_open_al_extension();
|
||||
LPALGETSOURCEDVSOFT alGetSourcedvSOFT;
|
||||
private:
|
||||
std::array<sf::Time, 2> alSecOffsetLatencySoft() const;
|
||||
sf::Time lag = sf::Time::Zero;
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
#include "synced_sound_streams.hpp"
|
||||
|
||||
#include <boost/math/constants/constants.hpp>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
@ -12,6 +13,7 @@
|
||||
|
||||
#include "al_check.hpp"
|
||||
#include "audio_device.hpp"
|
||||
#include "src/custom_sfml_audio/precise_sound_stream.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable: 4355) // 'this' used in base member initializer list
|
||||
@ -56,7 +58,7 @@ SyncedSoundStreams::~SyncedSoundStreams() {
|
||||
}
|
||||
|
||||
|
||||
void SyncedSoundStreams::add_stream(const std::string& name, std::shared_ptr<OpenSoundStream> s) {
|
||||
void SyncedSoundStreams::add_stream(const std::string& name, std::shared_ptr<PreciseSoundStream> s) {
|
||||
InternalStream internal_stream{s, {}};
|
||||
internal_stream.buffers.m_channelCount = s->getChannelCount();
|
||||
internal_stream.buffers.m_sampleRate = s->getSampleRate();
|
||||
@ -201,12 +203,23 @@ sf::Time SyncedSoundStreams::getPlayingOffset() const {
|
||||
|
||||
ALfloat secs = 0.f;
|
||||
alCheck(alGetSourcef(s.stream->get_source(), AL_SEC_OFFSET, &secs));
|
||||
return sf::seconds(
|
||||
auto base = sf::seconds(
|
||||
secs
|
||||
+ static_cast<float>(s.buffers.m_samplesProcessed)
|
||||
/ static_cast<float>(s.buffers.m_sampleRate)
|
||||
/ static_cast<float>(s.buffers.m_channelCount)
|
||||
);
|
||||
auto correction = (
|
||||
(s.stream->alSecOffsetLatencySoft()[1] * s.stream->getPitch())
|
||||
- (s.stream->lag * s.stream->getPitch())
|
||||
);
|
||||
return base - correction;
|
||||
}
|
||||
|
||||
void SyncedSoundStreams::setPitch(float pitch) {
|
||||
for (auto& [_, s] : streams) {
|
||||
s.stream->setPitch(pitch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
#include <SFML/Audio/SoundSource.hpp>
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include "open_sound_stream.hpp"
|
||||
#include "al_resource.hpp"
|
||||
#include "precise_sound_stream.hpp"
|
||||
|
||||
|
||||
// Number of audio buffers used by the streaming loop
|
||||
@ -38,17 +39,18 @@ struct Buffers {
|
||||
};
|
||||
|
||||
struct InternalStream {
|
||||
std::shared_ptr<OpenSoundStream> stream;
|
||||
std::shared_ptr<PreciseSoundStream> stream;
|
||||
Buffers buffers;
|
||||
|
||||
void clear_queue();
|
||||
};
|
||||
|
||||
class SyncedSoundStreams {
|
||||
class SyncedSoundStreams : public AlResource {
|
||||
public:
|
||||
SyncedSoundStreams();
|
||||
~SyncedSoundStreams();
|
||||
|
||||
void add_stream(const std::string& name, std::shared_ptr<OpenSoundStream> s);
|
||||
void add_stream(const std::string& name, std::shared_ptr<PreciseSoundStream> s);
|
||||
void remove_stream(const std::string& name);
|
||||
|
||||
void play();
|
||||
@ -60,11 +62,12 @@ public:
|
||||
void setPlayingOffset(sf::Time timeOffset);
|
||||
sf::Time getPlayingOffset() const;
|
||||
|
||||
void setPitch(float pitch);
|
||||
|
||||
void setLoop(bool loop);
|
||||
bool getLoop() const;
|
||||
|
||||
protected:
|
||||
SyncedSoundStreams();
|
||||
void setProcessingInterval(sf::Time interval);
|
||||
|
||||
private:
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <memory>
|
||||
#include <nowide/fstream.hpp>
|
||||
#include <sstream>
|
||||
#include <SFML/System/Time.hpp>
|
||||
@ -34,7 +35,7 @@
|
||||
#include "variant_visitor.hpp"
|
||||
|
||||
EditorState::EditorState(const std::filesystem::path& assets_) :
|
||||
note_claps(assets_),
|
||||
clap_player(std::make_shared<ClapPlayer>(nullptr, nullptr, assets_)),
|
||||
playfield(assets_),
|
||||
linear_view(assets_),
|
||||
applicable_timing(song.timing),
|
||||
@ -42,6 +43,7 @@ EditorState::EditorState(const std::filesystem::path& assets_) :
|
||||
{
|
||||
reload_music();
|
||||
reload_jacket();
|
||||
audio.add_stream(note_clap_stream, clap_player);
|
||||
};
|
||||
|
||||
EditorState::EditorState(
|
||||
@ -51,7 +53,7 @@ EditorState::EditorState(
|
||||
) :
|
||||
song(song_),
|
||||
song_path(song_path),
|
||||
note_claps(assets_),
|
||||
clap_player(std::make_shared<ClapPlayer>(nullptr, nullptr, assets_)),
|
||||
playfield(assets_),
|
||||
linear_view(assets_),
|
||||
applicable_timing(song.timing),
|
||||
@ -63,6 +65,7 @@ EditorState::EditorState(
|
||||
}
|
||||
reload_music();
|
||||
reload_jacket();
|
||||
audio.add_stream(note_clap_stream, clap_player);
|
||||
};
|
||||
|
||||
int EditorState::get_volume() const {
|
||||
@ -71,7 +74,9 @@ int EditorState::get_volume() const {
|
||||
|
||||
void EditorState::set_volume(int newMusicVolume) {
|
||||
volume = std::clamp(newMusicVolume, 0, 10);
|
||||
audio.setMusicVolume(Toolbox::convertVolumeToNormalizedDB(volume)*100.f);
|
||||
if (music.has_value()) {
|
||||
(**music).setVolume(Toolbox::convertVolumeToNormalizedDB(volume)*100.f);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorState::volume_up() {
|
||||
@ -117,7 +122,7 @@ void EditorState::stop() {
|
||||
audio.stop();
|
||||
}
|
||||
|
||||
SyncedSoundStreams::Status EditorState::get_status() {
|
||||
sf::SoundSource::Status EditorState::get_status() {
|
||||
return audio.getStatus();
|
||||
}
|
||||
|
||||
@ -158,7 +163,11 @@ void EditorState::set_playback_position(std::variant<sf::Time, Fraction> newPosi
|
||||
};
|
||||
|
||||
sf::Time EditorState::get_playback_position() {
|
||||
return audio.getPrecisePlayingOffset();
|
||||
if (music.has_value()) {
|
||||
return audio.getPlayingOffset();
|
||||
} else {
|
||||
return current_time();
|
||||
}
|
||||
}
|
||||
|
||||
Fraction EditorState::current_exact_beats() const {
|
||||
@ -384,7 +393,7 @@ void EditorState::display_properties() {
|
||||
if (feis::InputTextColored(
|
||||
"Audio",
|
||||
&song.metadata.audio,
|
||||
audio.music_is_loaded(),
|
||||
music.has_value(),
|
||||
"Invalid Audio Path"
|
||||
)) {
|
||||
reload_music();
|
||||
@ -417,9 +426,9 @@ void EditorState::display_properties() {
|
||||
Decimal{0},
|
||||
song.metadata.preview_loop.start
|
||||
);
|
||||
if (audio.music_is_loaded()) {
|
||||
if (music.has_value()) {
|
||||
song.metadata.preview_loop.start = std::min(
|
||||
Decimal{audio.getMusicDuration().asMicroseconds()} / 1000000,
|
||||
Decimal{(**music).getDuration().asMicroseconds()} / 1000000,
|
||||
song.metadata.preview_loop.start
|
||||
);
|
||||
}
|
||||
@ -429,12 +438,12 @@ void EditorState::display_properties() {
|
||||
Decimal{0},
|
||||
song.metadata.preview_loop.duration
|
||||
);
|
||||
if (audio.music_is_loaded()) {
|
||||
if (music.has_value()) {
|
||||
song.metadata.preview_loop.start = std::min(
|
||||
(
|
||||
Decimal{
|
||||
audio
|
||||
.getMusicDuration()
|
||||
(**music)
|
||||
.getDuration()
|
||||
.asMicroseconds()
|
||||
} / 1000000
|
||||
- song.metadata.preview_loop.start
|
||||
@ -457,7 +466,7 @@ status of the editor. Will appear in the "Editor Status" window
|
||||
void EditorState::display_status() {
|
||||
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
{
|
||||
if (not audio.music_is_loaded()) {
|
||||
if (not music.has_value()) {
|
||||
if (not song.metadata.audio.empty()) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1, 0.42, 0.41, 1),
|
||||
@ -520,10 +529,10 @@ void EditorState::display_playback_status() {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(fmt::format("{:.3f}", static_cast<double>(current_exact_beats())).c_str());
|
||||
ImGui::SameLine();
|
||||
if (audio.music_is_loaded()) {
|
||||
if (music.has_value()) {
|
||||
ImGui::TextDisabled("Music File Offset :");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(Toolbox::to_string(audio.getPrecisePlayingOffset()).c_str());
|
||||
ImGui::TextUnformatted(Toolbox::to_string(audio.getPlayingOffset()).c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::TextDisabled("Timeline Position :");
|
||||
@ -786,7 +795,7 @@ void EditorState::open_chart(const std::string& name) {
|
||||
chart_state.emplace(chart, name_ref, assets);
|
||||
reload_editable_range();
|
||||
reload_applicable_timing();
|
||||
note_claps.set_notes_and_timing(&chart.notes, &applicable_timing);
|
||||
clap_player->set_notes_and_timing(&chart.notes, &applicable_timing);
|
||||
};
|
||||
|
||||
void EditorState::update_visible_notes() {
|
||||
@ -809,10 +818,10 @@ void EditorState::reload_editable_range() {
|
||||
|
||||
Interval<sf::Time> EditorState::choose_editable_range() {
|
||||
Interval<sf::Time> new_range{sf::Time::Zero, sf::Time::Zero};
|
||||
if (audio.music_is_loaded()) {
|
||||
if (music.has_value()) {
|
||||
// If there is music, allow editing up to the end, but no further
|
||||
// You've put notes *after* the end of the music ? fuck 'em.
|
||||
new_range += audio.getMusicDuration();
|
||||
new_range += (**music).getDuration();
|
||||
return new_range;
|
||||
} else {
|
||||
// If there is no music :
|
||||
@ -857,14 +866,16 @@ void EditorState::reload_jacket() {
|
||||
*/
|
||||
void EditorState::reload_music() {
|
||||
if (not song_path.has_value() or song.metadata.audio.empty()) {
|
||||
audio.clear_music();
|
||||
music.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto absolute_music_path = song_path->parent_path() / song.metadata.audio;
|
||||
if (not audio.openFromFile(absolute_music_path)) {
|
||||
audio.clear_music();
|
||||
};
|
||||
try {
|
||||
music.emplace(std::make_shared<OpenMusic>(absolute_music_path));
|
||||
} catch (const std::exception& e) {
|
||||
music.reset();
|
||||
}
|
||||
|
||||
reload_editable_range();
|
||||
playback_position = std::clamp(
|
||||
@ -874,6 +885,11 @@ void EditorState::reload_music() {
|
||||
);
|
||||
previous_playback_position = playback_position;
|
||||
set_speed(speed);
|
||||
if (music.has_value()) {
|
||||
audio.add_stream(music_stream, *music);
|
||||
} else {
|
||||
audio.remove_stream(music_stream);
|
||||
}
|
||||
};
|
||||
|
||||
void EditorState::reload_preview_audio() {
|
||||
|
@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <SFML/Audio.hpp>
|
||||
#include <SFML/Audio/SoundSource.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <optional>
|
||||
|
||||
|
||||
#include "custom_sfml_audio/clap_player.hpp"
|
||||
#include "custom_sfml_audio/open_music.hpp"
|
||||
#include "custom_sfml_audio/synced_sound_streams.hpp"
|
||||
#include "widgets/linear_view.hpp"
|
||||
#include "better_note.hpp"
|
||||
#include "better_song.hpp"
|
||||
#include "chart_state.hpp"
|
||||
@ -15,11 +21,11 @@
|
||||
#include "notes_clipboard.hpp"
|
||||
#include "notifications_queue.hpp"
|
||||
#include "playfield.hpp"
|
||||
#include "custom_sfml_audio/synced_sound_streams.hpp"
|
||||
#include "custom_sfml_audio/clap_player.hpp"
|
||||
#include "widgets/linear_view.hpp"
|
||||
|
||||
|
||||
const std::string music_stream = "music";
|
||||
const std::string note_clap_stream = "note_clap";
|
||||
|
||||
/*
|
||||
* The god class, holds everything there is to know about the currently open
|
||||
* file
|
||||
@ -40,7 +46,8 @@ public:
|
||||
std::optional<ChartState> chart_state;
|
||||
|
||||
SyncedSoundStreams audio;
|
||||
ClapPlayer clap_player;
|
||||
std::shared_ptr<ClapPlayer> clap_player;
|
||||
std::optional<std::shared_ptr<OpenMusic>> music = {};
|
||||
|
||||
int get_volume() const;
|
||||
void set_volume(int newMusicVolume);
|
||||
|
@ -418,6 +418,7 @@ int main() {
|
||||
editor_state->update_visible_notes();
|
||||
if (editor_state->playing) {
|
||||
editor_state->previous_playback_position = editor_state->playback_position;
|
||||
editor_state->playback_position = editor_state->current_time() + delta * (editor_state->get_speed() / 10.f);
|
||||
switch (editor_state->get_status()) {
|
||||
case sf::Music::Stopped:
|
||||
case sf::Music::Paused:
|
||||
@ -441,7 +442,7 @@ int main() {
|
||||
editor_state->playing = false;
|
||||
editor_state->playback_position = editor_state->get_editable_range().end;
|
||||
}
|
||||
} else if (editor_state->get_status() == SyncedSoundStreams::Playing) {
|
||||
} else if (editor_state->get_status() == sf::SoundSource::Playing) {
|
||||
editor_state->pause();
|
||||
}
|
||||
}
|
||||
|
@ -21,13 +21,10 @@ sources += files(
|
||||
'marker.cpp',
|
||||
'mp3_reader.cpp',
|
||||
'note.cpp',
|
||||
'note_claps.cpp',
|
||||
'notes_clipboard.cpp',
|
||||
'notification.cpp',
|
||||
'notifications_queue.cpp',
|
||||
'playfield.cpp',
|
||||
'precise_music.cpp',
|
||||
'precise_sound_stream.cpp',
|
||||
'preferences.cpp',
|
||||
'sound_effect.cpp',
|
||||
'special_numeric_types.cpp',
|
||||
|
@ -1,105 +0,0 @@
|
||||
#include "note_claps.hpp"
|
||||
|
||||
NoteClaps::NoteClaps(const std::filesystem::path& assets) {
|
||||
const auto path = assets / "sounds" / "note.wav";
|
||||
if (not clap.loadFromFile(path)) {
|
||||
throw std::runtime_error("Could not open "+path.string());
|
||||
}
|
||||
output_buffer.resize(clap.getSampleRate()*clap.getChannelCount(), 0);
|
||||
initialize(clap.getChannelCount(), clap.getSampleRate());
|
||||
initialize_open_al_extension();
|
||||
}
|
||||
|
||||
void NoteClaps::set_notes_and_timing(const better::Notes* notes_, const better::Timing* timing_) {
|
||||
notes = notes_;
|
||||
timing = timing_;
|
||||
}
|
||||
|
||||
bool NoteClaps::onGetData(sf::SoundStream::Chunk& data) {
|
||||
output_buffer.assign(output_buffer.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>(output_buffer.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);
|
||||
|
||||
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));
|
||||
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>(clap.getSampleCount());
|
||||
if (it->first <= last_audible_start) {
|
||||
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>(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>(output_buffer.size())) {
|
||||
clap_finished_playing_in_current_buffer = false;
|
||||
slice_end_in_buffer = static_cast<std::int64_t>(output_buffer.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>(clap.getSampleCount()) - slice_start_in_clap
|
||||
);
|
||||
for (std::int64_t i = 0; i < slice_size; i++) {
|
||||
output_buffer[slice_start_in_buffer + i] = clap.getSamples()[slice_start_in_clap + i];
|
||||
}
|
||||
if (clap_finished_playing_in_current_buffer) {
|
||||
it = notes_at_sample.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.samples = output_buffer.data();
|
||||
data.sampleCount = output_buffer.size();
|
||||
current_sample += output_buffer.size();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NoteClaps::onSeek(sf::Time timeOffset) {
|
||||
current_sample = timeToSamples(timeOffset);
|
||||
notes_at_sample.clear();
|
||||
}
|
||||
|
||||
std::int64_t NoteClaps::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()) * getSampleRate() * getChannelCount()) + 500000) / 1000000;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
sf::Time NoteClaps::samplesToTime(std::int64_t samples) const
|
||||
{
|
||||
sf::Time position = sf::Time::Zero;
|
||||
|
||||
// Make sure we don't divide by 0
|
||||
if (clap.getSampleRate() != 0 && clap.getChannelCount() != 0)
|
||||
position = sf::microseconds(static_cast<std::int64_t>((samples * 1000000) / (getChannelCount() * getSampleRate())));
|
||||
|
||||
return position;
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
#include <SFML/Audio.hpp>
|
||||
#include <SFML/Config.hpp>
|
||||
|
||||
#include "better_notes.hpp"
|
||||
#include "better_timing.hpp"
|
||||
#include "src/precise_sound_stream.hpp"
|
||||
|
||||
class NoteClaps: public PreciseSoundStream {
|
||||
public:
|
||||
NoteClaps(const std::filesystem::path& assets);
|
||||
void set_notes_and_timing(const better::Notes* notes, const better::Timing* timing);
|
||||
protected:
|
||||
bool onGetData(sf::SoundStream::Chunk& data) override;
|
||||
void onSeek(sf::Time timeOffset) override;
|
||||
|
||||
std::int64_t timeToSamples(sf::Time position) const;
|
||||
sf::Time samplesToTime(std::int64_t samples) const;
|
||||
|
||||
const better::Notes* notes = nullptr;
|
||||
const better::Timing* timing = nullptr;
|
||||
private:
|
||||
sf::SoundBuffer clap;
|
||||
std::vector<sf::Int16> output_buffer;
|
||||
|
||||
std::int64_t current_sample = 0;
|
||||
std::map<std::int64_t, unsigned int> notes_at_sample;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user