#include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846f #endif #ifdef EMSCRIPTEN #include "emscripten/emscripten.h" #endif #ifdef main #undef main #endif const int tone_duration = 1000; struct BeepObject { double toneFrequency; int samplesLeft; }; class Beeper { private: double phase; int frequency; int numChannels; int mutedChannel; public: Beeper(int frequency, int numChannels, int sdlAudioFormat); ~Beeper(); void beep(double toneFrequency, int durationMSecs); template void generateSamples(T *stream, int length); void wait(); std::queue beeps; int sdlAudioFormat; }; void audio_callback(void*, Uint8*, int); Beeper::Beeper(int frequency_, int numChannels_, int sdlAudioFormat_) { phase = 0.0; mutedChannel = 1; SDL_AudioSpec desiredSpec; desiredSpec.freq = frequency_; desiredSpec.format = sdlAudioFormat_; desiredSpec.channels = numChannels_; desiredSpec.samples = 1024; // This is samples per channel. desiredSpec.callback = audio_callback; desiredSpec.userdata = this; SDL_AudioSpec obtainedSpec; // you might want to look for errors here SDL_OpenAudio(&desiredSpec, &obtainedSpec); // In this test, we require *exactly* the identical SDL result that we provide, since we test // all various configurations individually. if (obtainedSpec.freq != desiredSpec.freq || obtainedSpec.format != desiredSpec.format || obtainedSpec.channels != desiredSpec.channels || obtainedSpec.samples != desiredSpec.samples) { SDL_CloseAudio(); throw std::runtime_error("Failed to initialize desired SDL_OpenAudio!"); } frequency = obtainedSpec.freq; numChannels = obtainedSpec.channels; sdlAudioFormat = obtainedSpec.format; // Immediately start producing audio. SDL_PauseAudio(0); } Beeper::~Beeper() { SDL_CloseAudio(); } template void Beeper::generateSamples(T *stream, int length) { const int AMPLITUDE = (sizeof(T) == 2) ? 28000 : 120; const int offset = (sdlAudioFormat == AUDIO_U8) ? 120 : 0; int i = 0; length /= numChannels; while (i < length) { if (beeps.empty()) { memset(stream + numChannels*i, 0, sizeof(T)*numChannels*(length-i)); return; } BeepObject& bo = beeps.front(); // In Stereo tests, mute one of the channels to be able to distinguish that Stereo output works. if (bo.samplesLeft > tone_duration * frequency / 2 / 1000) { mutedChannel = 1; } else { mutedChannel = 0; } int samplesToDo = std::min(i + bo.samplesLeft, length); bo.samplesLeft -= samplesToDo - i; while (i < samplesToDo) { for(int j = 0; j < numChannels; ++j) { stream[numChannels*i+j] = (T)(offset + (int)(AMPLITUDE * std::sin(phase * 2 * M_PI / frequency))); if (numChannels > 1 && j == mutedChannel) { stream[numChannels*i+j] = 0; } } phase += bo.toneFrequency; i++; } if (bo.samplesLeft == 0) { beeps.pop(); } } } void Beeper::beep(double toneFrequency, int durationMSecs) { BeepObject bo; bo.toneFrequency = toneFrequency; bo.samplesLeft = durationMSecs * frequency / 1000; SDL_LockAudio(); beeps.push(bo); SDL_UnlockAudio(); } Beeper *beep = 0; // Test all kinds of various possible formats. Not all are supported, but running this // test will report you which work. const int freqs[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 }; const int channels[] = { 1, 2 }; const int sdlAudioFormats[] = { AUDIO_U8, AUDIO_S16LSB /*, AUDIO_S8, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S16MSB */ }; const char *SdlAudioFormatToString(int sdlAudioType) { switch(sdlAudioType) { case AUDIO_U8: return "AUDIO_U8"; case AUDIO_S8: return "AUDIO_S8"; case AUDIO_U16LSB: return "AUDIO_U16LSB"; case AUDIO_U16MSB: return "AUDIO_U16MSB"; case AUDIO_S16LSB: return "AUDIO_S16LSB"; case AUDIO_S16MSB: return "AUDIO_S16MSB"; default: return "(unknown)"; } } #define NUM_ELEMS(x) (sizeof(x)/sizeof((x)[0])) // Indices to the currently running test. int f = -1; int c = 0; int s = 0; void nextTest(void *unused = 0) { ++f; if (f >= NUM_ELEMS(freqs)) { f = 0; ++c; if (c >= NUM_ELEMS(channels)) { c = 0; ++s; if (s >= NUM_ELEMS(sdlAudioFormats)) { printf("All tests done. Quit.\n"); #ifdef EMSCRIPTEN emscripten_cancel_main_loop(); #ifdef REPORT_RESULT int result = 1; REPORT_RESULT(); #endif #endif return; } } } double Hz = 440; try { beep = new Beeper(freqs[f], channels[c], sdlAudioFormats[s]); } catch(...) { printf("FAILED to play beep for %d msecs at %d Hz tone with audio format %s, %d channels, and %d samples/sec.\n", tone_duration, (int)Hz, SdlAudioFormatToString(sdlAudioFormats[s]), channels[c], freqs[f]); nextTest(); return; } printf("Playing back a beep for %d msecs at %d Hz tone with audio format %s, %d channels, and %d samples/sec.\n", tone_duration, (int)Hz, SdlAudioFormatToString(sdlAudioFormats[s]), channels[c], freqs[f]); beep->beep(Hz, tone_duration); } void update() { SDL_LockAudio(); int size = beep->beeps.size(); SDL_UnlockAudio(); if (size == 0 && beep) { delete beep; beep = 0; #ifdef EMSCRIPTEN emscripten_async_call(nextTest, 0, 1500); #else SDL_Delay(1500); nextTest(); #endif } } void audio_callback(void *_beeper, Uint8 *_stream, int _length) { Beeper* beeper = (Beeper*) _beeper; if (beeper->sdlAudioFormat == AUDIO_U8) { Uint8 *stream = (Uint8*) _stream; beeper->generateSamples(stream, _length); } else if (beeper->sdlAudioFormat == AUDIO_S16LSB) { Sint16 *stream = (Sint16*) _stream; int length = _length / 2; beeper->generateSamples(stream, length); } else { assert(false && "Audio sample generation not implemented for current format!\n"); } } int main(int argc, char** argv) { SDL_Init(SDL_INIT_AUDIO); nextTest(); #ifdef EMSCRIPTEN emscripten_set_main_loop(update, 60, 0); #else while(beep) { SDL_Delay(20); update(); } #endif return 0; }