ITFUCKIGNWORKS ????

This commit is contained in:
Stepland 2022-06-01 00:26:36 +02:00
parent 9481cb8c3b
commit ab57a1c8f1
18 changed files with 157 additions and 230 deletions

View 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();
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <SFML/Audio/Export.hpp>
class AlResource {
protected:
AlResource();
~AlResource();
};

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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'
])

View File

@ -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();
}

View File

@ -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);

View File

@ -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())
);

View File

@ -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;
};

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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() {

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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',

View File

@ -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;
}

View File

@ -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;
};