diff --git a/README.md b/README.md index c8cd663..c431d7d 100644 --- a/README.md +++ b/README.md @@ -10,34 +10,148 @@ exposed. **This library is a work-in-progress.** -## Alternatives +## Features - * [PortAudio](http://www.portaudio.com/) - - Does not support [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/). - - Logs messages to stdio and you can't turn that off. - - Does not support channel layouts / channel maps. - - Does not support emitting an event when available devices change. - - Does not let you connect to multiple backends at once. - - Not written by me. - * [rtaudio](https://www.music.mcgill.ca/~gary/rtaudio/) - - It is not a C library. - - It uses [exceptions](http://stackoverflow.com/questions/1736146/why-is-exception-handling-bad). - - It does not support channel layouts / channel maps. - - Does not support emitting an event when available devices change. - - Does not let you connect to multiple backends at once. - - Not written by me. - * [SDL](https://www.libsdl.org/) - - Comes with baggage: display, windowing, input handling, and lots more. - - Not designed with real-time low latency audio in mind. - - Listing audio devices is [broken](https://github.com/andrewrk/node-groove/issues/13). - - Does not support recording devices. - - Does not support channel layouts / channel maps. - - Does not support emitting an event when available devices change. - - Does not let you connect to multiple backends at once. - - Not written by me. + * Supports: + - [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/) + - [ALSA](http://www.alsa-project.org/) + - Dummy Backend (silence) + - (planned) [JACK](http://jackaudio.org/) + - (planned) [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) + - (planned) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx) + - (planned) [ASIO](http://www.asio4all.com/) + * C library. Depends only on the respective backend API libraries and libc. + Does *not* depend on libstdc++, and does *not* have exceptions, run-time type + information, or [setjmp](http://latentcontent.net/2007/12/05/libpng-worst-api-ever/). + * Does not write anything to stdio. I'm looking at you, + [PortAudio](http://www.portaudio.com/). + * Supports channel layouts (also known as channel maps), important for + surround sound applications. + * Ability to monitor devices and get an event when available devices change. + * Ability to connect to multiple backends at once. For example you could have + an ALSA device open and a JACK device open at the same time. + * Meticulously checks all return codes and memory allocations and uses + meaningful error codes. -## How It Works +## Synopsis +Complete program to emit a sine wave over the default device using the best +backend: + +```c +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include + +#include +#include +#include +#include +#include + +__attribute__ ((cold)) +__attribute__ ((noreturn)) +__attribute__ ((format (printf, 1, 2))) +static void panic(const char *format, ...) { + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + va_end(ap); + abort(); +} + +static const float PI = 3.1415926535f; +static float seconds_offset = 0.0f; +static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) { + float float_sample_rate = outstream->sample_rate; + float seconds_per_frame = 1.0f / float_sample_rate; + int err; + + int frame_count = requested_frame_count; + for (;;) { + struct SoundIoChannelArea *areas; + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) + panic("%s", soundio_strerror(err)); + + if (!frame_count) + break; + + const struct SoundIoChannelLayout *layout = &outstream->layout; + + float pitch = 440.0f; + float radians_per_second = pitch * 2.0f * PI; + for (int frame = 0; frame < frame_count; frame += 1) { + float sample = sinf((seconds_offset + frame * seconds_per_frame) * radians_per_second); + for (int channel = 0; channel < layout->channel_count; channel += 1) { + float *ptr = (float*)(areas[channel].ptr + areas[channel].step * frame); + *ptr = sample; + } + } + seconds_offset += seconds_per_frame * frame_count; + + if ((err = soundio_outstream_write(outstream, frame_count))) + panic("%s", soundio_strerror(err)); + } +} + +static void error_callback(struct SoundIoOutStream *device, int err) { + if (err == SoundIoErrorUnderflow) { + static int count = 0; + fprintf(stderr, "underrun %d\n", count++); + } else { + panic("%s", soundio_strerror(err)); + } +} + +int main(int argc, char **argv) { + struct SoundIo *soundio = soundio_create(); + if (!soundio) + panic("out of memory"); + + int err; + if ((err = soundio_connect(soundio))) + panic("error connecting: %s", soundio_strerror(err)); + + int default_out_device_index = soundio_get_default_output_device_index(soundio); + if (default_out_device_index < 0) + panic("no output device found"); + + struct SoundIoDevice *device = soundio_get_output_device(soundio, default_out_device_index); + if (!device) + panic("out of memory"); + + fprintf(stderr, "Output device: %s: %s\n", device->name, device->description); + + struct SoundIoOutStream *outstream = soundio_outstream_create(device); + outstream->format = SoundIoFormatFloat32NE; + outstream->write_callback = write_callback; + outstream->error_callback = error_callback; + + if ((err = soundio_outstream_open(outstream))) + panic("unable to open device: %s", soundio_strerror(err)); + + if ((err = soundio_outstream_start(outstream))) + panic("unable to start device: %s", soundio_strerror(err)); + + for (;;) + soundio_wait_events(soundio); + + soundio_outstream_destroy(outstream); + soundio_device_unref(device); + soundio_destroy(soundio); + return 0; +} +``` + +### "Best Backend" + +When you use `soundio_connect', it tries these backends in order. libsoundio tries these backends in order. If unable to connect to that backend, due to the backend not being installed, or the server not running, or the platform is wrong, the next backend is tried. @@ -47,10 +161,15 @@ platform is wrong, the next backend is tried. 0. ALSA (Linux) 0. CoreAudio (OSX) 0. ASIO (Windows) - 0. DirectSound (Windows) - 0. OSS (BSD) + 0. WASAPI (Windows) 0. Dummy +If you don't like this order, you can use `soundio_connect_backend` to +explicitly choose a backend to connect to. You can use `soundio_backend_count` +and `soundio_get_backend` to get the list of available backends. + +For complete API documentation, see `src/soundio.h`. + ## Contributing libsoundio is programmed in a tiny subset of C++11: @@ -62,7 +181,7 @@ libsoundio is programmed in a tiny subset of C++11: * No references. * No linking against libstdc++. -Don't get tricked - this is a *C library*, not a C++ library. We just take +Do not be fooled - this is a *C library*, not a C++ library. We just take advantage of a select few C++11 compiler features such as templates, and then link against libc. @@ -128,7 +247,7 @@ view `coverage/index.html` in a browser. 0. pipe record to playback example working with dummy linux, osx, windows 0. pipe record to playback example working with pulseaudio linux 0. implement CoreAudio (OSX) backend, get examples working - 0. implement DirectSound (Windows) backend, get examples working + 0. implement WASAPI (Windows) backend, get examples working 0. implement JACK backend, get examples working 0. Avoid calling `panic` in PulseAudio. 0. implement ASIO (Windows) backend, get examples working diff --git a/src/soundio.h b/src/soundio.h index 1a050a8..c82bf07 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -390,7 +390,7 @@ int soundio_backend_count(struct SoundIo *soundio); enum SoundIoBackend soundio_get_backend(struct SoundIo *soundio, int index); // when you call this, the on_devices_change and on_events_signal callbacks -// might be called. This is the only time those functions will be called. +// might be called. This is the only time those callbacks will be called. void soundio_flush_events(struct SoundIo *soundio); // flushes events as they occur, blocks until you call soundio_wakeup @@ -462,10 +462,10 @@ int soundio_get_output_device_count(struct SoundIo *soundio); struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index); struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index); -// returns the index of the default input device, or -1 on error +// returns the index of the default input device int soundio_get_default_input_device_index(struct SoundIo *soundio); -// returns the index of the default output device, or -1 on error +// returns the index of the default output device int soundio_get_default_output_device_index(struct SoundIo *soundio); void soundio_device_ref(struct SoundIoDevice *device);