247 строки
6.1 KiB
C++
247 строки
6.1 KiB
C++
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_audio.h>
|
|
#include <queue>
|
|
#include <cmath>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#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<typename T>
|
|
void generateSamples(T *stream, int length);
|
|
void wait();
|
|
|
|
std::queue<BeepObject> 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<typename T>
|
|
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;
|
|
}
|