Implemented Sine, Sawtooth, Triangle, and Square wave generators for Sound class

This commit is contained in:
2024-09-06 13:45:58 -04:00
parent dfaf7150ea
commit 994bfc97d2
4 changed files with 176 additions and 23 deletions

View File

@@ -18,6 +18,7 @@
#include <iterator>
#include <vorbis/vorbisfile.h>
#include <algorithm>
#include <cmath>
int mix(int a, int b);
@@ -30,9 +31,6 @@ namespace ReMixer {
class Sound {
public:
/// The default constructor for a Sound object is intentionally left undefined.
/// It is intended that you use the static constructors below to load Sound objects.
Sound() = default;
@@ -61,6 +59,27 @@ namespace ReMixer {
static Sound
FromPCMBuffer(std::vector<char> buffer, uint sampleRate = 44.1_kHz, StreamMode streamMode = StreamMode::STEREO);
/// Returns a single-channel sound object generated from a trigonometric sine function.
/// @see https://en.wikipedia.org/wiki/Sine_wave
static Sound FromSineWave(float length, float wavelength, float amplitude, float phase);
/// Returns a single-channel sound object generated from a sawtooth wave function.
/// Sawtooth waves of constant period contain odd and even harmonics that decrease at -6 dB / octave.
/// @see https://en.wikipedia.org/wiki/Sawtooth_wave
static Sound FromSawtoothWave(float length, float wavelength, float amplitude, float phase);
/// Returns a single-channel sound object generated from a triangle wave function.
/// Triangle waves contain odd harmonics that decrease at -12 dB / octave.
/// @see https://en.wikipedia.org/wiki/Triangle_wave
static Sound FromTriangleWave(float length, float wavelength, float amplitude, float phase);
/// Returns a single-channel sound object generated from a square wave function.
/// Square waves of constant period contain odd harmonics that decrease at -6 dB / octave.
/// @see https://en.wikipedia.org/wiki/Square_wave
static Sound FromSquareWave(float length, float wavelength, float amplitude, float phase);
/// Fills this sound instance with data from a file encoded with OGG vorbis, usually with an .ogg extension.
/// @param file_name The full path relative to the executable in which the file is located.
/// @see https://en.wikipedia.org/wiki/Vorbis
@@ -83,6 +102,11 @@ namespace ReMixer {
void
LoadPCMBuffer(std::vector<char> buffer, uint sampleRate = 44.1_kHz, StreamMode streamMode = StreamMode::STEREO);
void LoadSineWave(float length, float wavelength, float amplitude, float phase);
void LoadSawtoothWave(float length, float wavelength, float amplitude, float phase);
void LoadTriangleWave(float length, float wavelength, float amplitude, float phase);
void LoadSquareWave(float length, float wavelength, float amplitude, float phase);
/// Returns the sample count of the sound.
uint SampleCount() const;;
@@ -120,6 +144,8 @@ namespace ReMixer {
uint sample_rate;
std::vector<char> audio_data;
StreamMode stream_mode;
};
/// Class that holds a short sound that can fit in memory and requires no latency. (i.e. footsteps or gun shots).

View File

@@ -38,20 +38,18 @@
PulseStream test_stream = test.CreateStream("Test Stream");
//PulseStream test_stream2 = test.CreateStream("Another Test Stream");
Sound test_sound = Sound::FromPCMFile("output.raw");
Sound ogg_test_sound = Sound::FromOGGVorbisFile("wind.ogg");
//Sound test_sound = Sound::FromPCMFile("output.raw");
Sound test_sound = Sound::FromOGGVorbisFile("wind.ogg");
Sound wave_test = Sound::FromSquareWave(10.f, 0.01f, 4000, 0);
test_sound = test_sound + ogg_test_sound;
//test_sound = test_sound + ogg_test_sound;
auto sample_depth = 2;
std::cout << "Sound Playback Length: " << ogg_test_sound.PlaybackLength() << " seconds." << std::endl;
std::cout << "Sound Playback Length: " << wave_test.PlaybackLength() << " seconds." << std::endl;
std::thread playback_thread([](PulseAudioSubsystem system, PulseStream stream, Sound sound){
system.Play(stream, sound);
}, test, test_stream, test_sound);
}, test, test_stream, wave_test);
while (true) {
std::this_thread::sleep_for(25ms);

View File

@@ -11,7 +11,6 @@ void ReMixer::PulseAudioSubsystem::Play(ReMixer::PulseStream &stream, const ReMi
stream.SetSampleRate(sound.SampleRate());
}
pa_threaded_mainloop_lock(pa->mainloop);
size_t length = sound.SampleCount();
@@ -60,7 +59,7 @@ ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::strin
// TODO: Expose to the API later
static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = 44100,
.rate = 44.1_kHz,
.channels = 2
};
@@ -122,29 +121,30 @@ ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::strin
ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_name) : SoundSubsystem()
{
ctx_name = context_name;
const char *server = NULL;
pa = pa_xnew(pa_impl, 1);
const char *server = NULL;
int error = PA_ERR_INTERNAL;
if (!(pa->mainloop = pa_threaded_mainloop_new()))
{
// Attempt to create a self-threaded PulseAudio instance.
if (!(pa->mainloop = pa_threaded_mainloop_new())) {
throw std::runtime_error("Failed to create a PulseAudio mainloop.");
}
// Grab our API handle.
pa->api = pa_threaded_mainloop_get_api(pa->mainloop);
if (!(pa->context = pa_context_new(pa->api, context_name.c_str())))
{
// Attempt to setup our context.
if (!(pa->context = pa_context_new(pa->api, context_name.c_str()))) {
throw std::runtime_error("Failed to create a PulseAudio context.");
}
pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN;
pa_context_set_state_callback(pa->context, OnCtxStateChange, this);
// Attempt to connect to the PulseAudio server.
if (pa_context_connect(pa->context, server, flags, NULL) < 0)
{
error = pa_context_errno(pa->context);
@@ -155,10 +155,12 @@ ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_nam
pa_threaded_mainloop_lock(pa->mainloop);
// Attempt to Start the PulseAudio Loop.
if (pa_threaded_mainloop_start(pa->mainloop) < 0) {
throw std::runtime_error("Failed to start thread mainloop.");
}
// Wait until the context is ready, listen for updates from the server.
for (;;) {
pa_context_state_t state;
state = pa_context_get_state(pa->context);
@@ -172,9 +174,10 @@ ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_nam
throw std::runtime_error(std::format("Context was not good: {}", error));
}
// Wait until the context is ready.
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_threaded_mainloop_unlock(pa->mainloop);
pa_context_get_server_info(pa->context, OnListServerInfo, this);

View File

@@ -86,7 +86,7 @@ void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) {
}
sample_rate = vi->rate;
//vi->
//std::cout << vi->bitrate_nominal << std::endl;
//std::cout << vi->bitrate_lower << std::endl;
@@ -100,7 +100,12 @@ void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) {
int current_section;
long bytes;
while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, &current_section)) > 0) {
/// Corresponds to PA_SAMPLE_S16LE
int is_big_endian = false;
int word_size = 2;
int is_signed = true;
while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), is_big_endian, word_size, is_signed, &current_section)) > 0) {
audio_data.insert(audio_data.end(), pcmout, pcmout + bytes);
}
}
@@ -108,6 +113,7 @@ void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) {
uint ReMixer::Sound::Channels() const {
if (stream_mode == StreamMode::MONO) return 1;
if (stream_mode == StreamMode::STEREO) return 2;
return 0;
}
ReMixer::StreamMode ReMixer::Sound::GetStreamMode() const { return stream_mode;}
@@ -167,6 +173,126 @@ ReMixer::Sound ReMixer::Sound::operator+(const ReMixer::Sound &rhs) const {
return Sound::FromPCMBuffer(sum);
}
void ReMixer::Sound::LoadSineWave(float length, float wavelength, float amplitude, float phase)
{
stream_mode = StreamMode::MONO;
// TODO: Allow sample_rate as a parameter.
sample_rate = 44.1_kHz;
uint samples = length * SampleRate();
for (int i = 0; i < samples; i++)
{
float time = (float)i / (float)sample_rate;
float input = (2.f*M_PI*time) - phase;
float wave = amplitude * std::sin(input / wavelength);
short sample = (short)(wave);
char low_half = sample;
char high_half = (sample >> 8);
audio_data.push_back(low_half);
audio_data.push_back(high_half);
}
}
void ReMixer::Sound::LoadSawtoothWave(float length, float wavelength, float amplitude, float phase) {
stream_mode = StreamMode::MONO;
// TODO: Allow sample_rate as a parameter.
sample_rate = 44.1_kHz;
uint samples = length * SampleRate();
for (int i = 0; i < samples; i++) {
float time = (float) i / (float)sample_rate;
float input = (2.f*M_PI*time) - phase;
float coefficient = 2.f*amplitude / M_PI;
float wave = coefficient * std::atan(std::tan( input / (2.f*wavelength)));
short sample = (short)(wave);
char low_half = sample;
char high_half = (sample >> 8);
audio_data.push_back(low_half);
audio_data.push_back(high_half);
}
}
void ReMixer::Sound::LoadTriangleWave(float length, float wavelength, float amplitude, float phase) {
stream_mode = StreamMode::MONO;
// TODO: Allow sample_rate as a parameter.
sample_rate = 44.1_kHz;
uint samples = length * SampleRate();
for (int i = 0; i < samples; i++) {
float time = (float) i / (float)sample_rate;
float input = (2.f*M_PI*time) - phase;
float coefficient = 2.f*amplitude / M_PI;
float wave = coefficient * std::asin(std::sin(input / wavelength));
short sample = (short)(wave);
char low_half = sample;
char high_half = (sample >> 8);
audio_data.push_back(low_half);
audio_data.push_back(high_half);
}
}
void ReMixer::Sound::LoadSquareWave(float length, float wavelength, float amplitude, float phase) {
stream_mode = StreamMode::MONO;
// TODO: Allow sample_rate as a parameter.
sample_rate = 44.1_kHz;
uint samples = length * SampleRate();
float duty = 0.5f*wavelength;
for (int i = 0; i < samples; i++) {
float time = (float) i / (float)sample_rate;
float wave = -amplitude;
if (std::fmod(time - phase, wavelength) < duty) {
wave = amplitude;
}
short sample = (short)(wave);
char low_half = sample;
char high_half = (sample >> 8);
audio_data.push_back(low_half);
audio_data.push_back(high_half);
}
}
ReMixer::Sound ReMixer::Sound::FromSineWave(float length, float wavelength, float amplitude, float phase) {
Sound sound;
sound.LoadSineWave(length, wavelength, amplitude, phase);
return sound;
}
ReMixer::Sound ReMixer::Sound::FromSawtoothWave(float length, float wavelength, float amplitude, float phase) {
Sound sound;
sound.LoadSawtoothWave(length, wavelength, amplitude, phase);
return sound;
}
ReMixer::Sound ReMixer::Sound::FromTriangleWave(float length, float wavelength, float amplitude, float phase) {
Sound sound;
sound.LoadTriangleWave(length, wavelength, amplitude, phase);
return sound;
}
ReMixer::Sound ReMixer::Sound::FromSquareWave(float length, float wavelength, float amplitude, float phase) {
Sound sound;
sound.LoadSquareWave(length, wavelength, amplitude, phase);
return sound;
}
int mix(int a, int b) {
return std::clamp(a + b, -128, 128);
}