diff --git a/Buzzer.cpp b/Buzzer.cpp new file mode 100644 index 0000000..5f72a2f --- /dev/null +++ b/Buzzer.cpp @@ -0,0 +1,71 @@ +#include "Buzzer.hpp" + +#include +#include +#include +#include + +void SDLCALL Buzzer::audioCallback(void *userdata, Uint8 *stream, int len) { + static_cast(userdata)->copySamples(stream, len); +} + +Buzzer::Buzzer(unsigned buzzerFrequency) { + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + + SDL_zero(desired); + desired.channels = 1; + desired.freq = 44000; + desired.format = AUDIO_S16SYS; + desired.samples = 4096; + desired.callback = audioCallback; + desired.userdata = this; + + mAudioDevice = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, &obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if(mAudioDevice == 0) { + throw std::runtime_error(SDL_GetError()); + } + + // period of the sine wave, in samples, to obtained the desired + // buffer buzzer frequency at a particular audio sample rate + double waveLength = double(obtained.freq) / buzzerFrequency; + // buffer size that fits a whole number of sine waves and is roughly the same size of obtained.samples + size_t numSamples = waveLength * std::max(std::floor(obtained.samples / waveLength), 1.0); + mSamples.resize(numSamples); + + // generate the sine wave + auto amplitude = std::numeric_limits::max(); + auto angularFreq = 2.0 * M_PI / waveLength; + for(size_t iSample = 0; iSample < numSamples; iSample++) { + mSamples[iSample] = amplitude * std::sin(angularFreq * iSample); + } + miCurrentSample = 0; +} + +Buzzer::~Buzzer() { + SDL_CloseAudioDevice(mAudioDevice); +} + +void Buzzer::on() { + SDL_PauseAudioDevice(mAudioDevice, SDL_FALSE); +} + +void Buzzer::off() { + SDL_PauseAudioDevice(mAudioDevice, SDL_TRUE); +} + +void Buzzer::copySamples(Uint8 *stream, int len) { + // Copy len samples from the current position of the buffer, + // looping back to the start of the buffer when needed. + while(len > 0) { + size_t numBytes = std::min(sizeof(sample_t) * (mSamples.size() - miCurrentSample), size_t(len)); + size_t numSamples = numBytes / sizeof(sample_t); + std::memcpy(stream, mSamples.data() + miCurrentSample, numBytes); + stream += numBytes; + len -= numBytes; + miCurrentSample += numSamples; + if(miCurrentSample >= mSamples.size()) { + miCurrentSample = 0; + } + } +} diff --git a/Buzzer.hpp b/Buzzer.hpp new file mode 100644 index 0000000..a789893 --- /dev/null +++ b/Buzzer.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class Buzzer { +public: + using sample_t = Sint16; + + Buzzer(unsigned frequency); + ~Buzzer(); + + void on(); + void off(); + +private: + static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len); + void copySamples(Uint8 *stream, int len); + +private: + std::vector mSamples; + SDL_AudioDeviceID mAudioDevice; + size_t miCurrentSample; +}; diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e1078f6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.16) +project(chocochip8) +SET(CMAKE_BUILD_TYPE Debug) + +add_subdirectory(submodules/sdl2) + +SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) +add_executable(chocochip8 main.cpp Buzzer.cpp) +target_link_libraries(chocochip8 SDL2::SDL2-static) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..217d9e8 --- /dev/null +++ b/main.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "Buzzer.hpp" + +int main(int argc, char* args[]) { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { + std::cerr << "Couldn't initialize SDL: " << SDL_GetError() << '\n'; + return 1; + } + + Buzzer buzzer(440); + buzzer.on(); + + SDL_Event event; + while(SDL_WaitEvent(&event) && event.type != SDL_QUIT) { + } + + buzzer.off(); + SDL_Quit(); + return 0; +} +