[Python-checkins] python/nondist/sandbox/audiotest Makefile,NONE,1.1 audiotest.c,NONE,1.1

gward@users.sourceforge.net gward@users.sourceforge.net
Sun, 01 Jun 2003 14:49:13 -0700


Update of /cvsroot/python/python/nondist/sandbox/audiotest
In directory sc8-pr-cvs1:/tmp/cvs-serv6377

Added Files:
	Makefile audiotest.c 
Log Message:
Initial checkin of pure C OSS test program.

--- NEW FILE: Makefile ---
CC = gcc
CFLAGS = -g -Wall

audiotest: audiotest.o
	$(CC) -o $@ -lm $<


clean:
	rm audiotest *.o

--- NEW FILE: audiotest.c ---
/*
 * Pure C OSS test program.  Useful as a quick sanity check to make
 * sure that the audio hardware and device driver are behaving
 * correctly when the ossaudiodev module is not.
 *
 * GPW 2003/03/11
 *
 * $Id: audiotest.c,v 1.1 2003/06/01 21:49:10 gward Exp $
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <math.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>


typedef struct {
    char * name;                        /* eg. "/dev/dsp" */
    int    fd;                          /* fd returned by open() */
} audiodev_t;

/* Describes audio sampling parameters: needed to initialize audio
 * hardware, and to generate a buffer.
 */
typedef struct {
    int rate;                           /* sampling rate in Hz */
    int size;                           /* sample size in bits (8 or 16) */
    int sign;                           /* are sample values signed? */
} audiosample_t;

/* Describes physical characteristics of an audio signal such as a
 * sine wave.
 */
typedef struct {
    int freq;                           /* frequency of signal in Hz */
    double ampl;                        /* amplitude of signal (0.0 .. 1.0) */
    double dur;                         /* duration of signal in sec */
} audiosignal_t;

/* A buffer containing an audio signal ready to play (assuming the hardware
 * has been initialized with the right sampling parameters).
 */
typedef struct {
    audiosample_t * sample;
    audiosignal_t * signal;

    int cycle_len;                      /* number of samples in a cycle */
    int buff_len;                       /* number of samples in buff */
    int buff_size;                      /* size of buff in bytes */
    char * buff;
} audiobuffer_t;


static void
writenow (char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(stdout, fmt, args);
    va_end(args);
    fflush(stdout);
}

static void
error (char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    fprintf(stderr, "audiotest: error: ");
    vfprintf(stderr, fmt, args);
    fputc('\n', stderr);
    va_end(args);
    exit(1);
}

static void
_sampling_ioctl (audiodev_t * device, char * param, int cmd, int value)
{
    int requested_value = value;

    if (ioctl(device->fd, cmd, &value) == -1)
        error("%s: unable to set %s: %s",
              device->name, param, strerror(errno));
    if (value != requested_value)
        error("%s: unable to set %s to %d (got %d)",
              device->name, param, requested_value, value);
}

static void
setparameters (audiodev_t * device, audiosample_t * sample)
{
    int fmt;

    if (sample->size == 8) {
        if (sample->sign)
            fmt = AFMT_S8;
        else
            fmt = AFMT_U8;
    }
    else if (sample->size == 16) {
        if (sample->sign)
            fmt = AFMT_S16_LE;          /* XXX not handling endianness */
        else
            fmt = AFMT_U16_LE;
    }

    _sampling_ioctl(device, "sample format", SNDCTL_DSP_SETFMT, fmt);
    _sampling_ioctl(device, "number of channels", SNDCTL_DSP_CHANNELS, 1);
    _sampling_ioctl(device, "sampling rate", SNDCTL_DSP_SPEED, sample->rate);
}

static void
sync_audio (audiodev_t * device)
{
    if (ioctl(device->fd, SNDCTL_DSP_SYNC, 0) == -1)
        error("%s: unable to sync: %s", device->name, strerror(errno));
}


static audiosample_t *
mksample (int rate, int size, int sign)
{
    audiosample_t * sample;

    /* sanity-check args */
    if (rate < 1 || rate > 1000000)
        return NULL;
    if (size % 8 != 0)
        return NULL;        

    sample = (audiosample_t *) malloc(sizeof(audiosample_t));
    sample->rate = rate;
    sample->size = size;
    sample->sign = sign;
    return sample;
}

static void
printsample(FILE * f, audiosample_t * sample)
{
    fprintf(f, "<audiosample at %p: rate=%d, size=%d, sign=%c>",
            sample, sample->rate, sample->size, (sample->sign ? 'Y' : 'N'));
}

static void
freesample (audiosample_t * sample)
{
    free(sample);
}

static audiosignal_t *
mksignal (int freq, double ampl, double dur)
{
    audiosignal_t * signal;

    if (freq < 1 || freq > 100000)
        return NULL;
    if (ampl < 0.0 || ampl > 1.0)
        return NULL;
    if (dur < 0 || dur > 3600)
        return NULL;

    signal = (audiosignal_t *) malloc(sizeof(audiosignal_t));
    signal->freq = freq;
    signal->ampl = ampl;
    signal->dur = dur;
    return signal;
}

static void
printsignal(FILE * f, audiosignal_t * signal)
{
    fprintf(f, "<audiosignal at %p: freq=%d, ampl=%g, dur=%g>",
            signal, signal->freq, signal->ampl, signal->dur);
}

static void
freesignal (audiosignal_t * signal)
{
    free(signal);
}

static audiobuffer_t *
mkbuffer (audiosample_t * sample, audiosignal_t * signal)
{
    audiobuffer_t * buffer = (audiobuffer_t *) malloc(sizeof(audiobuffer_t));
    buffer->sample = sample;
    buffer->signal = signal;

    buffer->cycle_len = sample->rate / signal->freq;
    buffer->buff_len = sample->rate * signal->dur;
    buffer->buff_size = buffer->buff_len * sample->size / 8;
    buffer->buff = (char *) malloc(buffer->buff_size);
    memset(buffer->buff, 0, buffer->buff_size);
    return buffer;
}

static void
printbuffer (FILE * f, audiobuffer_t * buffer)
{
    fprintf(f, "<audiobuffer at %p: cycle_len=%d, buff_len=%d, buff_size=%d>",
            buffer, buffer->cycle_len, buffer->buff_len, buffer->buff_size);
}

static void
dumpbuffer (FILE * f, audiobuffer_t * buffer)
{
    printbuffer(f, buffer);
    fputc('\n', f);
    
    fprintf(f, "  sample: ");
    printsample(f, buffer->sample);
    fputc('\n', f);

    fprintf(f, "  signal: ");
    printsignal(f, buffer->signal);
    fputc('\n', f);
}

static void
freebuffer (audiobuffer_t * buffer)
{
    free(buffer->buff);
    free(buffer);
}


static void
gen_sine (audiobuffer_t * buffer)
{
    double step;
    int offset;
    int i;

    /* Fill the first cycle of the buffer. */
    step = 2*M_PI / buffer->cycle_len;
    offset = 0;                         /* offset into buff */
    for (i = 0; i < buffer->cycle_len; i++) {
        double val_f;
        //int    val_i;

        assert(offset < buffer->buff_len);
        val_f = buffer->signal->ampl * sin(i * step);
        if (buffer->sample->size == 8) {
            /* first, convert for signed 8-bit:
               -1.0 .. 1.0 -> -128 .. 127 (0x80 .. 0x7f) */ 
            int val = (int) floor(val_f * 128);
            if (val == 128)             /* clamp 1.0 down to 127 */
                val--;

            /* store, or adjust-and-store */
            if (buffer->sample->sign)
                buffer->buff[offset] = (signed char) val;
            else {
                val += 128;             /* -128 .. 127 -> 0 .. 255 */
                buffer->buff[offset] = (unsigned char) val;
            }
            offset += 1;
        }
        else if (buffer->sample->size == 16) {
            int val = (int) floor(val_f * 32768);
            if (val == 32768)
                val--;

            if (buffer->sample->sign) {
                ((short *) buffer->buff)[offset] = (signed short) val;
            }
            else {
                val += 32768;
                ((short *) buffer->buff)[offset] = (unsigned short) val;
            }
            offset += 2;
        }
    }

    /* Now duplicate that data until 'buff' is full. */
    while (offset < buffer->buff_size) {
        for (i = 0; i < buffer->cycle_len; i++) {
            buffer->buff[offset++] = buffer->buff[i];
            if (offset == buffer->buff_size)
                break;
        }
    }
}    


static void
wait_user (int i, char * description)
{
    char buf[2];

    fprintf(stdout, "test %d: %s: press enter", i, description);
    fflush(stdout);
    fgets(buf, 2, stdin);
    return;
}

static void
test_one_tone (int testnum, audiosample_t * sample, audiodev_t * device)
{
    audiosignal_t * signal;
    audiobuffer_t * buffer;
    int nbytes;

    signal = mksignal(1000, 0.75, 1.0);
    buffer = mkbuffer(sample, signal);
    gen_sine(buffer);

    wait_user(testnum, "1 kHz sine wave, ampl 0.75, 1 sec");
    nbytes = write(device->fd, buffer->buff, buffer->buff_size);
    sync_audio(device);
    writenow("%d bytes played\n", nbytes);

    freesignal(signal);
    freebuffer(buffer);
}

static void
test_two_tones (int testnum, audiosample_t * sample, audiodev_t * device)
{
    audiosignal_t * signal1, * signal2;
    audiobuffer_t * buffer1, * buffer2;
    int nbytes;

    signal1 = mksignal(523, 0.75, 1.0);
    signal2 = mksignal(523*2, 0.75, 1.0);
    buffer1 = mkbuffer(sample, signal1);
    buffer2 = mkbuffer(sample, signal2);
    gen_sine(buffer1);
    gen_sine(buffer2);

    wait_user(testnum, "middle C (523 Hz) for 1 sec, then jump an octave");
    nbytes = write(device->fd, buffer1->buff, buffer1->buff_size);
    nbytes += write(device->fd, buffer2->buff, buffer2->buff_size);
    sync_audio(device);
    writenow("%d bytes played\n", nbytes);

    freesignal(signal1);
    freesignal(signal2);
    freebuffer(buffer1);
    freebuffer(buffer2);
}

static void
play_scale (audiodev_t * device,
            audiobuffer_t *buffers[],
            int nbuffers)
{
    int i;
    int nbytes = 0;

    writenow("frequencies: ");
    for (i = 0; i < nbuffers; i++) {
        writenow("%d ", buffers[i]->signal->freq);
        nbytes += write(device->fd, buffers[i]->buff, buffers[i]->buff_size);
        sync_audio(device);
    }
    //sync_audio(device);
    writenow("%d bytes played\n", nbytes);
}

static void
free_buffers (audiobuffer_t *buffers[],
              int nbuffers)
{
    int i;

    for (i = 0; i < nbuffers; i++) {
        freesignal(buffers[i]->signal);
        freebuffer(buffers[i]);
    }
}

static void test_chromatic (int testnum,
                            audiosample_t * sample,
                            audiodev_t * device)
{
    audiosignal_t * signal;
    audiobuffer_t * buffers[13];
    int i;
    double factor = pow(2, (float) 1/12);
    double freq = 523.25;               /* middle C */

    /* First create all the buffers */
    for (i = 0; i < 13; i++) {
        signal = mksignal((int) rint(freq), 0.75, 0.5);
        /*printf("generating signal %d: ", i);
        printsignal(stdout, signal);
        fputc('\n', stdout);*/
        buffers[i] = mkbuffer(sample, signal);
        gen_sine(buffers[i]);
        freq *= factor;
    }

    /* Now play them all, ie. play the chromatic scale. */
    wait_user(testnum,
              "chromatic (12-tone) scale from middle C, 0.5 sec per note");
    play_scale(device, buffers, 13);

    /* And free everything up. */
    free_buffers(buffers, 13);
}

static void test_major_scale (int testnum,
                              audiosample_t * sample,
                              audiodev_t * device)
{
    audiosignal_t * signal;
    audiobuffer_t * buffers[8];
    int i;
    double factor = pow(2, (float) 1/12);
    double steps[] = { factor*factor,
                       factor*factor,
                       factor,
                       factor*factor,
                       factor*factor,
                       factor*factor,
                       factor };
    double freq = 523.25;               /* middle C */

    /* Generating the samples is a bit trickier for a major scale then
       for a chromatic scale, because it's not a simple geometric
       progression.  The rule is: W W H W W W H where W is a whole step
       (freq*factor*factor) and H is a half-step (freq*factor). */
    for (i = 0; i < 8; i++) {
        signal = mksignal((int) rint(freq), 0.75, 0.5);
        buffers[i] = mkbuffer(sample, signal);
        gen_sine(buffers[i]);
        if (i < 7)
            freq *= steps[i];
    }

    /* Play the scale and free it all up. */
    wait_user(testnum, "C-major scale, 0.5 sec per note");
    play_scale(device, buffers, 8);
    free_buffers(buffers, 8);
}


static void
run_tests (audiodev_t * device)
{
    audiosample_t * sample;
    int testnum = 1;

    /* Lowest-common denominator sampling parameters for now:
       unsigned mono 8-bit samples at 8000 samples/sec. */
    sample = mksample(8000, 8, 0);
    setparameters(device, sample);

    /* First test: simple 1kHz tone */
    test_one_tone(testnum++, sample, device);

    /* Second test: middle C (523 Hz), then jump an octave. */
    test_two_tones(testnum++, sample, device);

    /* For playing scales, 8000 samples/sec isn't good enough.
       Hopefully most hardware can support 32000! */
    sample->rate = 32000;
    setparameters(device, sample);

    /* Third test: chromatic (12-tone) scale, starting at middle C */
    test_chromatic(testnum++, sample, device);

    /* Fourth test: C-major scale */
    test_major_scale(testnum++, sample, device);

}


int
main (int argc, char **argv)
{
    audiodev_t device;
    char usage[] = "usage: audiotest [device]\n";

    if (argc == 1) {                    /* no args */
        device.name = "/dev/dsp";
    }
    else if (argc == 2) {               /* one arg */
        device.name = argv[1];
    }
    else {
        fprintf(stderr, usage);
        exit(1);
    }

    printf("opening %s ...", device.name);
    fflush(stdout);
    device.fd = open(device.name, O_WRONLY, 0);
    if (device.fd == -1) {
        printf(" failed\n");
        perror(device.name);
        exit(2);
    }
    printf(" done\n");

    run_tests(&device);
    return 0;
}