2 * OpenAL Tone Generator Test
4 * Copyright (c) 2015 by Chris Robinson <chris.kcat@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 /* This file contains a test for generating waveforms and plays them for a
26 * given length of time. Intended to inspect the behavior of the mixer by
27 * checking the output with a spectrum analyzer and oscilloscope.
29 * TODO: This would actually be nicer as a GUI app with buttons to start and
30 * stop individual waveforms, include additional whitenoise and pinknoise
31 * generators, and have the ability to hook up EFX filters and effects.
45 #include "common/alhelpers.h"
47 #include "win_main_utf8.h"
50 #define M_PI (3.14159265358979323846)
62 static const char *GetWaveTypeName(enum WaveType type
)
66 case WT_Sine
: return "sine";
67 case WT_Square
: return "square";
68 case WT_Sawtooth
: return "sawtooth";
69 case WT_Triangle
: return "triangle";
70 case WT_Impulse
: return "impulse";
71 case WT_WhiteNoise
: return "noise";
76 static inline ALuint
dither_rng(ALuint
*seed
)
78 *seed
= (*seed
* 96314165) + 907633515;
82 static void ApplySin(ALfloat
*data
, ALdouble g
, ALuint srate
, ALuint freq
)
84 ALdouble smps_per_cycle
= (ALdouble
)srate
/ freq
;
86 for(i
= 0;i
< srate
;i
++)
89 data
[i
] += (ALfloat
)(sin(modf(i
/smps_per_cycle
, &ival
) * 2.0*M_PI
) * g
);
93 /* Generates waveforms using additive synthesis. Each waveform is constructed
94 * by summing one or more sine waves, up to (and excluding) nyquist.
96 static ALuint
CreateWave(enum WaveType type
, ALuint freq
, ALuint srate
, ALfloat gain
)
105 data_size
= (ALuint
)(srate
* sizeof(ALfloat
));
106 data
= calloc(1, data_size
);
110 ApplySin(data
, 1.0, srate
, freq
);
113 for(i
= 1;freq
*i
< srate
/2;i
+=2)
114 ApplySin(data
, 4.0/M_PI
* 1.0/i
, srate
, freq
*i
);
117 for(i
= 1;freq
*i
< srate
/2;i
++)
118 ApplySin(data
, 2.0/M_PI
* ((i
&1)*2 - 1.0) / i
, srate
, freq
*i
);
121 for(i
= 1;freq
*i
< srate
/2;i
+=2)
122 ApplySin(data
, 8.0/(M_PI
*M_PI
) * (1.0 - (i
&2)) / (i
*i
), srate
, freq
*i
);
125 /* NOTE: Impulse isn't handled using additive synthesis, and is
126 * instead just a non-0 sample at a given rate. This can still be
127 * useful to test (other than resampling, the ALSOFT_DEFAULT_REVERB
128 * environment variable can prove useful here to test the reverb
131 for(i
= 0;i
< srate
;i
++)
132 data
[i
] = (i
%(srate
/freq
)) ? 0.0f
: 1.0f
;
135 /* NOTE: WhiteNoise is just uniform set of uncorrelated values, and
136 * is not influenced by the waveform frequency.
138 for(i
= 0;i
< srate
;i
++)
140 ALuint rng0
= dither_rng(&seed
);
141 ALuint rng1
= dither_rng(&seed
);
142 data
[i
] = (ALfloat
)(rng0
*(1.0/UINT_MAX
) - rng1
*(1.0/UINT_MAX
));
149 for(i
= 0;i
< srate
;i
++)
153 /* Buffer the audio data into a new buffer object. */
155 alGenBuffers(1, &buffer
);
156 alBufferData(buffer
, AL_FORMAT_MONO_FLOAT32
, data
, (ALsizei
)data_size
, (ALsizei
)srate
);
159 /* Check if an error occured, and clean up if so. */
161 if(err
!= AL_NO_ERROR
)
163 fprintf(stderr
, "OpenAL Error: %s\n", alGetString(err
));
164 if(alIsBuffer(buffer
))
165 alDeleteBuffers(1, &buffer
);
173 int main(int argc
, char *argv
[])
175 enum WaveType wavetype
= WT_Sine
;
176 const char *appname
= argv
[0];
177 ALuint source
, buffer
;
178 ALint last_pos
, num_loops
;
181 ALint tone_freq
= 1000;
188 if(InitAL(&argv
, &argc
) != 0)
191 if(!alIsExtensionPresent("AL_EXT_FLOAT32"))
193 fprintf(stderr
, "Required AL_EXT_FLOAT32 extension not supported on this device!\n");
198 for(i
= 0;i
< argc
;i
++)
200 if(strcmp(argv
[i
], "-h") == 0 || strcmp(argv
[i
], "-?") == 0
201 || strcmp(argv
[i
], "--help") == 0)
203 fprintf(stderr
, "OpenAL Tone Generator\n"
205 "Usage: %s [-device <name>] <options>\n"
207 "Available options:\n"
208 " --help/-h This help text\n"
209 " -t <seconds> Time to play a tone (default 5 seconds)\n"
210 " --waveform/-w <type> Waveform type: sine (default), square, sawtooth,\n"
211 " triangle, impulse, noise\n"
212 " --freq/-f <hz> Tone frequency (default 1000 hz)\n"
213 " --gain/-g <gain> gain 0.0 to 1 (default 1)\n"
214 " --srate/-s <sample rate> Sampling rate (default output rate)\n",
220 else if(i
+1 < argc
&& strcmp(argv
[i
], "-t") == 0)
223 max_loops
= atoi(argv
[i
]) - 1;
225 else if(i
+1 < argc
&& (strcmp(argv
[i
], "--waveform") == 0 || strcmp(argv
[i
], "-w") == 0))
228 if(strcmp(argv
[i
], "sine") == 0)
230 else if(strcmp(argv
[i
], "square") == 0)
231 wavetype
= WT_Square
;
232 else if(strcmp(argv
[i
], "sawtooth") == 0)
233 wavetype
= WT_Sawtooth
;
234 else if(strcmp(argv
[i
], "triangle") == 0)
235 wavetype
= WT_Triangle
;
236 else if(strcmp(argv
[i
], "impulse") == 0)
237 wavetype
= WT_Impulse
;
238 else if(strcmp(argv
[i
], "noise") == 0)
239 wavetype
= WT_WhiteNoise
;
241 fprintf(stderr
, "Unhandled waveform: %s\n", argv
[i
]);
243 else if(i
+1 < argc
&& (strcmp(argv
[i
], "--freq") == 0 || strcmp(argv
[i
], "-f") == 0))
246 tone_freq
= atoi(argv
[i
]);
249 fprintf(stderr
, "Invalid tone frequency: %s (min: 1hz)\n", argv
[i
]);
253 else if(i
+1 < argc
&& (strcmp(argv
[i
], "--gain") == 0 || strcmp(argv
[i
], "-g") == 0))
256 gain
= (ALfloat
)atof(argv
[i
]);
257 if(gain
< 0.0f
|| gain
> 1.0f
)
259 fprintf(stderr
, "Invalid gain: %s (min: 0.0, max 1.0)\n", argv
[i
]);
263 else if(i
+1 < argc
&& (strcmp(argv
[i
], "--srate") == 0 || strcmp(argv
[i
], "-s") == 0))
266 srate
= atoi(argv
[i
]);
269 fprintf(stderr
, "Invalid sample rate: %s (min: 40hz)\n", argv
[i
]);
276 ALCdevice
*device
= alcGetContextsDevice(alcGetCurrentContext());
277 alcGetIntegerv(device
, ALC_FREQUENCY
, 1, &dev_rate
);
278 assert(alcGetError(device
)==ALC_NO_ERROR
&& "Failed to get device sample rate");
283 /* Load the sound into a buffer. */
284 buffer
= CreateWave(wavetype
, (ALuint
)tone_freq
, (ALuint
)srate
, gain
);
291 printf("Playing %dhz %s-wave tone with %dhz sample rate and %dhz output, for %d second%s...\n",
292 tone_freq
, GetWaveTypeName(wavetype
), srate
, dev_rate
, max_loops
+1, max_loops
?"s":"");
295 /* Create the source to play the sound with. */
297 alGenSources(1, &source
);
298 alSourcei(source
, AL_BUFFER
, (ALint
)buffer
);
299 assert(alGetError()==AL_NO_ERROR
&& "Failed to setup sound source");
301 /* Play the sound for a while. */
304 alSourcei(source
, AL_LOOPING
, (max_loops
> 0) ? AL_TRUE
: AL_FALSE
);
305 alSourcePlay(source
);
308 al_nssleep(10000000);
309 alGetSourcei(source
, AL_SAMPLE_OFFSET
, &pos
);
310 alGetSourcei(source
, AL_SOURCE_STATE
, &state
);
311 if(pos
< last_pos
&& state
== AL_PLAYING
)
314 if(num_loops
>= max_loops
)
315 alSourcei(source
, AL_LOOPING
, AL_FALSE
);
316 printf("%d...\n", max_loops
- num_loops
+ 1);
320 } while(alGetError() == AL_NO_ERROR
&& state
== AL_PLAYING
);
322 /* All done. Delete resources, and close OpenAL. */
323 alDeleteSources(1, &source
);
324 alDeleteBuffers(1, &buffer
);
326 /* Close up OpenAL. */