diff options
author | Chris Robinson <[email protected]> | 2015-10-13 11:43:25 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2015-10-13 11:43:25 -0700 |
commit | c18e23477c5efc0183fa809622085b1ed413cb6b (patch) | |
tree | e4e455598e1a1726a75abc418df1edf28674f66d | |
parent | d7ec9d355f25c4072954f7447c3e5d0f100ef8d6 (diff) |
Add a tone generator test program
Currently used to test the general output, particularly the resampler, by
checking the results with a spectrum analyzer and/or oscilloscope (for example
using PulseAudio's "Monitor of ..." devices feeding an external app).
-rw-r--r-- | CMakeLists.txt | 19 | ||||
-rw-r--r-- | examples/altonegen.c | 250 |
2 files changed, 269 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index af9e961a..822649ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ OPTION(ALSOFT_UTILS "Build and install utility programs" ON) OPTION(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF) OPTION(ALSOFT_EXAMPLES "Build and install example programs" ON) +OPTION(ALSOFT_TESTS "Build and install test programs" ON) OPTION(ALSOFT_CONFIG "Install alsoft.conf sample configuration file" ON) OPTION(ALSOFT_HRTF_DEFS "Install HRTF definition files" ON) @@ -1226,6 +1227,24 @@ IF(ALSOFT_UTILS) MESSAGE(STATUS "") ENDIF() +IF(ALSOFT_TESTS) + ADD_LIBRARY(test-common STATIC examples/common/alhelpers.c) + + ADD_EXECUTABLE(altonegen examples/altonegen.c) + TARGET_LINK_LIBRARIES(altonegen test-common ${LIBNAME}) + + IF(ALSOFT_INSTALL) + INSTALL(TARGETS altonegen + RUNTIME DESTINATION bin + LIBRARY DESTINATION "lib${LIB_SUFFIX}" + ARCHIVE DESTINATION "lib${LIB_SUFFIX}" + ) + ENDIF() + + MESSAGE(STATUS "Building test programs") + MESSAGE(STATUS "") +ENDIF() + IF(ALSOFT_EXAMPLES) IF(SDL2_FOUND AND SDL_SOUND_FOUND) ADD_LIBRARY(ex-common STATIC examples/common/alhelpers.c diff --git a/examples/altonegen.c b/examples/altonegen.c new file mode 100644 index 00000000..d8601f7e --- /dev/null +++ b/examples/altonegen.c @@ -0,0 +1,250 @@ +/* + * OpenAL Tone Generator Test + * + * Copyright (c) 2015 by Chris Robinson <[email protected]> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains a test for generating waveforms and plays them for a + * given length of time. Intended to inspect the behavior of the mixer by + * checking the output with a spectrum analyzer and oscilloscope. + * + * TODO: This would actually be nicer as a GUI app with buttons to start and + * stop individual waveforms, include additional whitenoise and pinknoise + * generators, and have the ability to hook up EFX filters and effects. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <math.h> + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "common/alhelpers.h" + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +enum WaveType { + WT_Sine, + WT_Square, + WT_Sawtooth, + WT_Triangle, +}; + +static void ApplySin(ALfloat *data, ALdouble g, ALuint srate, ALuint freq) +{ + ALdouble smps_per_cycle = (ALdouble)srate / freq; + ALuint i; + for(i = 0;i < srate;i++) + data[i] += sin(i/smps_per_cycle * 2.0*M_PI) * g; +} + +/* Generates waveforms using additive synthesis. Each waveform is constructed + * by summing one or more sine waves, up to (and excluding) nyquist. + */ +static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate) +{ + ALint data_size; + ALfloat *data; + ALuint buffer; + ALenum err; + ALuint i; + + data_size = srate * sizeof(ALfloat); + data = calloc(1, data_size); + if(type == WT_Sine) + ApplySin(data, 1.0, srate, freq); + else if(type == WT_Square) + for(i = 1;freq*i < srate/2;i+=2) + ApplySin(data, 4.0/M_PI * 1.0/i, srate, freq*i); + else if(type == WT_Sawtooth) + for(i = 1;freq*i < srate/2;i++) + ApplySin(data, 2.0/M_PI * ((i&1)*2 - 1.0) / i, srate, freq*i); + else if(type == WT_Triangle) + for(i = 1;freq*i < srate/2;i+=2) + ApplySin(data, 8.0/(M_PI*M_PI) * (1.0 - (i&2)) / (i*i), srate, freq*i); + + /* Buffer the audio data into a new buffer object. */ + buffer = 0; + alGenBuffers(1, &buffer); + alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, data_size, srate); + free(data); + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); + if(alIsBuffer(buffer)) + alDeleteBuffers(1, &buffer); + return 0; + } + + return buffer; +} + + +int main(int argc, char *argv[]) +{ + enum WaveType wavetype = WT_Sine; + ALuint source, buffer; + ALint last_pos, num_loops; + ALint max_loops = 4; + ALint srate = 44100; + ALint tone_freq = 1000; + ALenum state; + int i; + + for(i = 1;i < argc;i++) + { + if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + fprintf(stderr, "OpenAL Tone Generator\n" +"\n" +"Usage: %s <options>\n" +"\n" +"Available options:\n" +" --help/-h This help text\n" +" -t <seconds> Time to play a tone (default 5 seconds)\n" +" --waveform/-w <type> Waveform type: sine (default), square, sawtooth,\n" +" triangle\n" +" --freq/-f <hz> Tone frequency (default 1000 hz)\n" +" --srate/-s <sample rate> Sampling rate (default 44100 hz)\n", + argv[0] + ); + return 1; + } + else if(i+1 < argc && strcmp(argv[i], "-t") == 0) + { + i++; + max_loops = atoi(argv[i]) - 1; + } + else if(i+1 < argc && (strcmp(argv[i], "--waveform") == 0 || strcmp(argv[i], "-w") == 0)) + { + i++; + if(strcmp(argv[i], "sine") == 0) + wavetype = WT_Sine; + else if(strcmp(argv[i], "square") == 0) + wavetype = WT_Square; + else if(strcmp(argv[i], "sawtooth") == 0) + wavetype = WT_Sawtooth; + else if(strcmp(argv[i], "triangle") == 0) + wavetype = WT_Triangle; + else + fprintf(stderr, "Unhandled waveform: %s\n", argv[i]); + } + else if(i+1 < argc && (strcmp(argv[i], "--freq") == 0 || strcmp(argv[i], "-f") == 0)) + { + i++; + tone_freq = atoi(argv[i]); + if(tone_freq < 20) + { + fprintf(stderr, "Invalid tone frequency: %s (min: 20hz)\n", argv[i]); + tone_freq = 20; + } + } + else if(i+1 < argc && (strcmp(argv[i], "--srate") == 0 || strcmp(argv[i], "-s") == 0)) + { + i++; + srate = atoi(argv[i]); + if(srate < 40) + { + fprintf(stderr, "Invalid sample rate: %s (min: 40hz)\n", argv[i]); + srate = 40; + } + } + } + + InitAL(); + + if(!alIsExtensionPresent("AL_EXT_FLOAT32")) + { + fprintf(stderr, "Required AL_EXT_FLOAT32 extension not supported on this device!\n"); + CloseAL(); + return 1; + } + + /* Load the sound into a buffer. */ + buffer = CreateWave(wavetype, tone_freq, srate); + if(!buffer) + { + CloseAL(); + return 1; + } + + { + ALCdevice *device; + ALCint dev_rate; + ALint buf_rate; + + device = alcGetContextsDevice(alcGetCurrentContext()); + alcGetIntegerv(device, ALC_FREQUENCY, 1, &dev_rate); + assert(alcGetError(device)==ALC_NO_ERROR && "Failed to get device sample rate"); + + alGetBufferi(buffer, AL_FREQUENCY, &buf_rate); + assert(alGetError()==AL_NO_ERROR && "Failed to get buffer sample rate"); + + printf("Playing %dhz tone with %dhz sample rate and %dhz output, for %d second%s...\n", + tone_freq, buf_rate, dev_rate, max_loops+1, max_loops?"s":""); + fflush(stdout); + } + + /* Create the source to play the sound with. */ + source = 0; + alGenSources(1, &source); + alSourcei(source, AL_BUFFER, buffer); + assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); + + /* Play the sound for a while. */ + num_loops = 0; + last_pos = 0; + alSourcei(source, AL_LOOPING, (max_loops > 0) ? AL_TRUE : AL_FALSE); + alSourcePlay(source); + do { + ALint pos; + Sleep(10); + alGetSourcei(source, AL_SAMPLE_OFFSET, &pos); + alGetSourcei(source, AL_SOURCE_STATE, &state); + if(pos < last_pos && state == AL_PLAYING) + { + ++num_loops; + if(num_loops >= max_loops) + alSourcei(source, AL_LOOPING, AL_FALSE); + printf("%d...\n", max_loops - num_loops + 1); + fflush(stdout); + } + last_pos = pos; + } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); + + /* All done. Delete resources, and close OpenAL. */ + alDeleteSources(1, &source); + alDeleteBuffers(1, &buffer); + + /* Close up OpenAL. */ + CloseAL(); + + return 0; +} |