Implemented Sine, Sawtooth, Triangle, and Square wave generators for Sound class
This commit is contained in:
@@ -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).
|
||||
|
14
main.cpp
14
main.cpp
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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, ¤t_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, ¤t_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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user