Skip to content

Commit

Permalink
Modify sound system to use single AudioStream
Browse files Browse the repository at this point in the history
  • Loading branch information
aceiii committed Apr 26, 2024
1 parent 63a26e6 commit e630daf
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 100 deletions.
32 changes: 6 additions & 26 deletions src/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ void Interface::initialize() {
int monitor_height = GetMonitorHeight(monitor);
spdlog::trace("Monitor resolution: {}x{}", monitor_width, monitor_height);

sounds.push_back(std::make_unique<WaveGeneratorSource>());
sounds.initialize();
sounds.add_source(std::make_unique<WaveGeneratorSource>());

if (settings.lock_fps) {
SetTargetFPS(kDefaultFPS);
Expand Down Expand Up @@ -227,9 +228,7 @@ bool Interface::update() {
keyboard.update();

bool is_playing = regs->st > 0;
for (auto &sound : sounds) {
sound->update(is_playing || force_play_all_sounds);
}
sounds.update(is_playing || force_play_all_sounds);

screen.update();

Expand Down Expand Up @@ -531,33 +530,14 @@ bool Interface::update() {
}

ImGui::Checkbox("Force Play All Sounds", &force_play_all_sounds);

ImGui::NewLine();

int sid_to_remove = -1;
for (int sid = 0; sid < sounds.size(); sid += 1) {
ImGui::PushID(sid);
ImGui::BeginChild("##Sound", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY);
ImGui::Text("Sound #%d", (sid + 1));

sounds[sid]->render();

if (ImGui::Button("Remove")) {
sid_to_remove = sid;
}

ImGui::EndChild();
ImGui::PopID();
}

if (sid_to_remove != -1) {
sounds.erase(sounds.begin() + sid_to_remove);
}
sounds.render();
}

ImGui::NewLine();
if (ImGui::Button("Add sound")) {
sounds.push_back(std::make_unique<WaveGeneratorSource>());
sounds.add_source(std::make_unique<WaveGeneratorSource>());
}

ImGui::End();
Expand Down Expand Up @@ -702,7 +682,7 @@ void Interface::cleanup() {
settings.window_height = GetScreenHeight();

spdlog::info("Cleaning up interface");
sounds.clear();
sounds.cleanup();
rlImGuiShutdown();
CloseAudioDevice();
CloseWindow();
Expand Down
2 changes: 1 addition & 1 deletion src/interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class Interface {
AssemblyViewer assembly;
Keyboard keyboard;
Config<interface_settings> config;
SoundManager sounds;
std::vector<uint8_t> rom;
std::shared_ptr<registers> regs;
std::vector<std::unique_ptr<SoundSource>> sounds;
};
173 changes: 113 additions & 60 deletions src/sound.cpp
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
#include "sound.h"

#include <array>
#include <cmath>
#include <cstring>
#include <spdlog/spdlog.h>
#include <raylib.h>
#include <imgui.h>
#include <random>

SoundSource::SoundSource() {
spdlog::trace("Initializing SoundSource");
SetAudioStreamBufferSizeDefault(kMaxSamplesPerUpdate);
stream = LoadAudioStream(kAudioSampleRate, kAudioSampleSize, kAudioNumChannels);
PlayAudioStream(stream);
static inline float noise(double x) {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<> dist(-1.0f, 1.0f);
return dist(gen);
}

SoundSource::~SoundSource() {
spdlog::trace("Destructing SoundSource");
StopAudioStream(stream);
UnloadAudioStream(stream);
static inline float sine_wave(double x) {
return std::sin(2 * PI * x);
}

void SoundSource::update(bool play_sound) {
if (!IsAudioStreamProcessed(stream)) {
return;
static inline float square_wave(double x) {
float s = sine_wave(x);
if (std::abs(s) <= 0.001f) {
return 0.f;
}
return std::abs(s) / s;
}

gen_sound_data(play_sound, buffer.data(), buffer.size());
UpdateAudioStream(stream, buffer.data(), buffer.size());
static inline float triangle_wave(double x) {
return std::abs(fmod(x, 1.0f) - 0.5f) * 4 - 1;
}

static inline float sawtooth_wave(double x) {
return std::abs(fmod(x, 1.0f)) * 2 - 1;
}

void WaveGeneratorSource::render() {
Expand All @@ -50,17 +56,26 @@ void WaveGeneratorSource::render() {
}
ImGui::EndCombo();
}
ImGui::PlotLines("Sound", samples.data(), samples.size(), 0, nullptr, -1.5f, 1.5f, ImVec2(0, 80.0f));
ImGui::SliderFloat("Volume", &volume, 0.0f, 100.0f);
ImGui::SliderFloat("Frequency", &frequency, 20.0f, 15000.0f);
if (ImGui::Button("20Hz")) {
frequency = 20.0f;
ImGui::PlotLines("Sound", samples.data(), samples.size(), 0, nullptr, -1.2f, 1.2f, ImVec2(0, 50.0f));
ImGui::SliderFloat("Volume", &volume, 0.0f, 100.0f, "%.0f");
ImGui::SliderFloat("Offset", &offset, 0.0f, 1.0f, "%0.2f");
if (ImGui::Button("+0.0")) {
offset = 0.0f;
}
ImGui::SameLine();
if (ImGui::Button("50Hz")) {
frequency = 50.0f;
if (ImGui::Button("+0.25")) {
offset = 0.25f;
}
ImGui::SameLine();
if (ImGui::Button("+0.5")) {
offset = 0.50f;
}
ImGui::SameLine();
if (ImGui::Button("+0.75")) {
offset = 0.75f;
}

ImGui::SliderFloat("Frequency", &frequency, 20.0f, 15000.0f, "%.0f");
if (ImGui::Button("100Hz")) {
frequency = 100.0f;
}
Expand All @@ -83,42 +98,12 @@ void WaveGeneratorSource::render() {
ImGui::Checkbox("Force Play", &force_play);
}

static inline float noise(float idx) {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<> dist(-1.0f, 1.0f);
return dist(gen);
}

static inline float sine_wave(float idx) {
return std::sin(2 * PI * idx);
}

static inline float square_wave(float idx) {
float s = sine_wave(idx);
if (std::abs(s) <= 0.001f) {
return 0.f;
}
return std::abs(s) / s;
}

static inline float triangle_wave(float idx) {
return std::abs(fmod(idx, 1.0f) - 0.5f) * 4 - 1;
}

static inline float sawtooth_wave(float idx) {
return std::abs(fmod(idx, 1.0f)) * 2 - 1;
}

void WaveGeneratorSource::gen_sound_data(bool play_sound, short buffer[], size_t buffer_size) {
void WaveGeneratorSource::update(bool play_sound, double time) {
if (!play_sound && !force_play) {
memset(buffer, 0, sizeof(short) * buffer_size);
memset(&samples, 0, sizeof(float) * samples.size());
return;
}

float incr = frequency / static_cast<float>(kAudioSampleRate);

auto wave_func = ([this]() {
switch (wave_type) {
case 0: return sine_wave;
Expand All @@ -129,16 +114,84 @@ void WaveGeneratorSource::gen_sound_data(bool play_sound, short buffer[], size_t
}
})();

for (int i = 0; i < buffer_size; i++) {
float sample = wave_func(wave_idx) * (volume / 100.0f);
buffer[i] = static_cast<short>(((1 << 15) - 1) * sample);
double incr = 1.0 / kAudioSampleRate;

for (int i = 0; i < samples.size(); i++) {
samples[i] = wave_func((time * frequency) + offset) * (volume / 100.0f);
time += incr;
}
}

void SoundManager::initialize() {
spdlog::trace("Initializing SoundSource");

memset(buffer.data(), 0, buffer.size() * sizeof(short));
memset(samples.data(), 0, samples.size() * sizeof(float));

SetAudioStreamBufferSizeDefault(kMaxSamplesPerUpdate);
stream = LoadAudioStream(kAudioSampleRate, kAudioSampleSize, kAudioNumChannels);
PlayAudioStream(stream);
}

void SoundManager::cleanup() {
spdlog::trace("Destructing SoundSource");
sources.clear();
StopAudioStream(stream);
UnloadAudioStream(stream);
}

void SoundManager::render() {
ImGui::PlotLines("Sound", samples.data(), samples.size(), 0, nullptr, -1.5f, 1.5f, ImVec2(0, 80.0f));

int sid_to_remove = -1;
for (int sid = 0; sid < sources.size(); sid += 1) {
ImGui::PushID(sid);
ImGui::BeginChild("##Sound", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY);
ImGui::Text("Sound #%d", (sid + 1));

if (i < samples.size()) {
samples[i] = sample;
sources[sid]->render();

if (ImGui::Button("Remove")) {
sid_to_remove = sid;
}

wave_idx += incr;
if (wave_idx > 1.0f)
wave_idx -= 1.0f;
ImGui::EndChild();
ImGui::PopID();
}

if (sid_to_remove != -1) {
remove_source_at(sid_to_remove);
}
}

void SoundManager::update(bool play_sound) {
if (!IsAudioStreamProcessed(stream)) {
return;
}

memset(samples.data(), 0, samples.size() * sizeof(float));

for (auto &source : sources) {
source->update(play_sound, time);
const float* source_samples = source->get_samples();
for (int i = 0; i < buffer.size(); i += 1) {
samples[i] += source_samples[i];
}
}

for (int i = 0; i < buffer.size(); i += 1) {
buffer[i] = static_cast<short>(((1 << 15) - 1) * samples[i]);
}

time = fmod(time + (1.0 / kAudioSampleRate) * buffer.size(), 1.0);

UpdateAudioStream(stream, buffer.data(), buffer.size());
}

void SoundManager::add_source(std::unique_ptr<SoundSource> source) {
sources.push_back(std::move(source));
}

void SoundManager::remove_source_at(size_t index) {
sources.erase(sources.begin() + index);
}
45 changes: 32 additions & 13 deletions src/sound.h
Original file line number Diff line number Diff line change
@@ -1,40 +1,59 @@
#pragma once

#include <memory>
#include <raylib.h>
#include <array>
#include <vector>

constexpr int kMaxSamplesPerUpdate = 4096;
constexpr int kMaxSamplesPerUpdate = 1024;
constexpr int kAudioSampleRate = 44100;
constexpr int kAudioSampleSize = 16;
constexpr int kAudioNumChannels = 1;

class SoundSource {
public:
SoundSource();
virtual ~SoundSource();
virtual ~SoundSource() = default;

virtual void render() = 0;
virtual void gen_sound_data(bool play_sound, short buffer[], size_t buffer_size) = 0;
virtual void update(bool play_sound, double time) = 0;

void update(bool play_sound);
auto get_samples() const {
return samples.data();
}

private:
AudioStream stream;
std::array<short, kMaxSamplesPerUpdate> buffer;
protected:
std::array<float, kMaxSamplesPerUpdate> samples;
};

class WaveGeneratorSource : public SoundSource {
class WaveGeneratorSource final : public SoundSource {
public:
virtual ~WaveGeneratorSource() = default;

virtual void render() override;
virtual void gen_sound_data(bool play_sound, short buffer[], size_t buffer_size) override;
virtual void update(bool play_sound, double time) override;

private:
std::array<float, 1000> samples;
bool force_play = false;
float frequency = 440.0f;
float volume = 100.0f;
float wave_idx = 0.0f;
float offset = 0.0f;
float volume = 50.0f;
int wave_type = 0;
};

class SoundManager final {
public:
void initialize();
void render();
void update(bool play_sound);
void cleanup();

void add_source(std::unique_ptr<SoundSource> source);
void remove_source_at(size_t index);

private:
double time = 0;
AudioStream stream;
std::array<float, kMaxSamplesPerUpdate> samples;
std::array<short, kMaxSamplesPerUpdate> buffer;
std::vector<std::unique_ptr<SoundSource>> sources;
};

0 comments on commit e630daf

Please sign in to comment.