blob: 713e2c5cef4fc0ddbc10c44ea816eb25f5c3be14 [file] [log] [blame]
/* Copyright (c) 2011, Gabriel M. Beddingfield <gabrbedd@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* tone-generator.c
*
* Utility for generating an accurate waveform to an audio output.
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include <limits.h>
#include <tinyalsa/asoundlib.h>
#include "oscillator-table.h"
#include "config.h"
#include "tone-generator.h"
/* LOAD ALL THE WAVE TABLES
*
* WAVE TABLE ASSUMPTIONS:
*
* - All lengths are a power of 2 (8, 16, 32, 64, ...)
*
* - The wave tables are sufficiently large such that
* interpolation is not necessary. For example, at a
* sample rate of 48000 Hz, a wave table length 4096
* should be sufficient for all frequencies above
* 24 Hz (48000/4096 = 11.72 Hz).
*/
static int16_t g_table_square_wave_data[] = {
#include "table_square.c"
};
static int16_t g_table_sine_wave_data[] = {
#include "table_sine.c"
};
static int16_t g_table_triangle_wave_data[] = {
#include "table_triangle.c"
};
static int16_t g_table_sawtooth_wave_data[] = {
#include "table_sawtooth.c"
};
static struct wave_table g_wave_tables[] = {
DECLARE_TABLE("square", g_table_square_wave_data),
DECLARE_TABLE("sine", g_table_sine_wave_data),
DECLARE_TABLE("triangle", g_table_triangle_wave_data),
DECLARE_TABLE("sawtooth", g_table_sawtooth_wave_data),
{ 0 }
};
static int check_wave_tables()
{
assert( STATIC_ARRAY_SIZE(g_table_square_wave_data) <= 0xFFFF );
assert( STATIC_ARRAY_SIZE(g_table_sine_wave_data) <= 0xFFFF );
assert( STATIC_ARRAY_SIZE(g_table_triangle_wave_data) <= 0xFFFF );
assert( STATIC_ARRAY_SIZE(g_table_sawtooth_wave_data) <= 0xFFFF );
return 0;
}
struct tone_generator_config {
int card;
int device;
struct wave_table *wave_table;
struct wave_scale wave_scale;
struct pcm_config pcm_config;
uint32_t duration;
int16_t volume; /* binary fraction / USHRT_MAX */
uint32_t chan_mask;
int bits;
};
static int inner_main(struct tone_generator_config config)
{
struct pcm_config *pcm_config = &config.pcm_config;
struct pcm *pcm;
unsigned pos;
void *buf;
pcm = pcm_open(config.card, config.device, PCM_OUT, pcm_config);
if (!pcm) {
fprintf(stderr, "Could not open sound card\n");
fprintf(stderr, "%s\n", pcm_get_error(pcm));
return 1;
}
if (!pcm_is_ready(pcm)) {
fprintf(stderr, "Sound card not ready\n");
fprintf(stderr, "%s\n", pcm_get_error(pcm));
return 1;
}
buf = calloc(config.bits / 8,
pcm_config->period_size * pcm_config->channels);
if (!buf) {
fprintf(stderr, "Could not allocate memory for buffer\n");
return 1;
}
for (pos=0 ; (!config.duration || (pos < config.duration)) ; pos += pcm_config->period_size) {
oscillator_table_render(buf,
config.wave_table,
pos,
pcm_config->period_size,
config.wave_scale,
pcm_config->channels,
config.chan_mask, /* write to all channels */
config.volume,
config.bits);
if (pcm_write(pcm,
buf,
pcm_config->channels * pcm_config->period_size * (config.bits/8))) {
fprintf(stderr, "Error writing to sound card\n");
fprintf(stderr, "%s\n", pcm_get_error(pcm));
break;
}
}
pcm_close(pcm);
return 0;
}
static void usage()
{
struct wave_table *ptr;
printf("Usage: audio-tool [options] tone <wave_type> <frequency> [<vol_db>]\n");
printf("\n");
printf("wave_type:\n");
for (ptr=g_wave_tables ; ptr->name ; ++ptr) {
printf(" %s\n", ptr->name);
}
printf("frequency: non-negative real number\n");
printf("vol_db: (optional) Volume attenuation in dB FS (implied negative, must be >= 0, default=0)\n");
}
int tone_generator_main(const struct audio_tool_config *at_config, int argc, char* argv[])
{
struct tone_generator_config config = {
.card = 0,
.device = 0,
.chan_mask = ~0,
};
struct pcm_config pcm_config;
struct wave_table *ptr, *table;
struct wave_scale wave_scale;
double freq;
char *arg_wave_type, *arg_freq, *arg_voldb;
double tmp;
if ((argc < 3) || (argc > 4)) {
usage();
return 1;
}
if (check_wave_tables())
return 1;
arg_wave_type = argv[1];
arg_freq = argv[2];
if (argc > 3)
arg_voldb = argv[3];
else
arg_voldb = "0";
/* Set sane defaults */
memset(&pcm_config, 0, sizeof(struct pcm_config));
switch (at_config->bits) {
case 8: pcm_config.format = PCM_FORMAT_S8; break;
case 16: pcm_config.format = PCM_FORMAT_S16_LE; break;
case 24: pcm_config.format = PCM_FORMAT_S24_LE; break;
case 32: pcm_config.format = PCM_FORMAT_S32_LE; break;
default:
assert(0);
}
config.device = at_config->device;
config.card = at_config->card;
pcm_config.period_size = at_config->period_size;
pcm_config.period_count = at_config->num_periods;
pcm_config.rate = at_config->rate;
pcm_config.channels = at_config->channels;
config.chan_mask = at_config->channel_mask;
config.duration = at_config->duration * pcm_config.rate;
config.bits = at_config->bits;
for (ptr = g_wave_tables ; ptr->name ; ++ptr) {
if (strcmp(arg_wave_type, ptr->name) == 0) {
table = ptr;
assert( IS_POWER_OF_TWO(table->length) );
assert( table->mask == table->length - 1 );
break;
}
}
if (ptr->name == 0) {
fprintf(stderr, "Invalied wave_type parameter\n");
return 1;
}
tmp = atof(arg_freq);
if (tmp < 10.0) {
fprintf(stderr, "Error: frequency must be > 10Hz\n");
return 1;
}
freq = tmp;
tmp = atof(arg_voldb);
if (tmp < 0 ) {
fprintf(stderr, "Volume attenuation must be greater than 0 dB FS\n");
return 1;
}
/* Convert db to fraction */
tmp = -tmp;
tmp = pow(10.0, tmp/10.0);
config.volume = (unsigned short) (tmp * ((double)USHRT_MAX));
tmp = ((double)pcm_config.rate) / freq;
wave_scale.length = tmp;
tmp = (tmp - wave_scale.length) * 0xFFF;
wave_scale.sub = tmp;
wave_scale.sub_den = 0xFFF;
wave_scale.sub_shift = 12;
/* This restriction prevents overflows in render()
*/
{
uint16_t bits = 0;
while ((1<<bits) < table->length) ++bits;
if (wave_scale.sub_shift + bits > 24) {
fprintf(stderr, "bits(wave_scale) + bits(table.length) "
" must be less than or equal to 24\n");
return 1;
}
}
memcpy(&config.pcm_config, &pcm_config, sizeof(pcm_config));
memcpy(&config.wave_scale, &wave_scale, sizeof(wave_scale));
config.wave_table = table;
return inner_main(config);
return 0;
}