audio_stream

Overview

This page documents q_io, the optional audio and MIDI I/O library, not q_lib. See q_io.

audio_stream connects a processing function to an audio device. It opens the device with the channel counts asked for, and once started it calls process from the device’s own thread, once per buffer of frames samples, until stopped. process reads the input buffers and fills the output buffers; the stream does the rest.

You use it by deriving a class from audio_stream, choosing the constructor arguments for the channels you need, and overriding the one process overload that matches, of the three audio_stream_base declares:

  • process(out_channels const& out): output only, a synthesizer or a player. Construct with zero input channels.

  • process(in_channels const& in): input only, an analyzer or a recorder. Construct with zero output channels.

  • process(in_channels const& in, out_channels const& out): an effect.

in and out are Multi Buffer views: out[ch] is channel ch, and out.frames the range of frame indices in this buffer. Whatever process leaves in out is what the device plays, so an output-only stream must write every frame of every channel, silence included.

process runs on a real-time thread. It must not allocate, lock, block or do I/O; anything of that kind belongs on the thread that called start.

Use Case

A sine wave to the default output device: derive, fill out in process, then start, keep the program alive, and stop. Nothing plays until start, and the callbacks end when stop returns. This is the Sine Oscillator tutorial in full.

struct sin_synth : q::audio_stream
{
   sin_synth(q::frequency freq)
    : audio_stream(0, 2)                          // no input, stereo out
    , phase(freq, this->sampling_rate())
   {}

   void process(out_channels const& out) override
   {
      auto left = out[0];
      auto right = out[1];
      for (auto frame : out.frames)
         right[frame] = left[frame] = q::sin(phase++);
   }

   q::phase_iterator phase;
};

int main()
{
   sin_synth synth{440_Hz};
   if (!synth.is_valid())
      return 1;                                   // synth.error() says why

   synth.start();
   q::sleep(5_s);                                 // the audio thread plays
   synth.stop();
}

Include

#include <q_io/audio_stream.hpp>

Declaration

class audio_stream : public audio_stream_base
{
public:
                           audio_stream(
                              std::size_t input_channels
                            , std::size_t output_channels
                            , double sps = -1
                            , int frames = -1
                           );

                           audio_stream(
                              audio_device const& device
                            , std::size_t input_channels
                            , std::size_t output_channels
                            , double sps = -1
                            , int frames = -1
                           );

   virtual                 ~audio_stream();

   void                    start();
   void                    stop();

   bool                    is_valid() const;
   duration                time() const;
   double                  cpu_load() const;
   char const*             error() const;

   duration                input_latency() const;
   duration                output_latency() const;
   double                  sampling_rate() const;
   std::size_t             input_channels() const;
   std::size_t             output_channels() const;
};

Expressions

As a subclass of audio_stream_base, audio_stream inherits all the publicly accessible member functions, member variables, and types of its base class.

In addition to valid expressions for audio_stream_base, audio_stream allows these expressions.

Notation

as

Object of type audio_stream.

ad

Object of type audio_device.

ic

Number of input channels.

oc

Number of output channels.

sps

Floating point value for the desired sample rate. A value of -1, the default, indicates a request to use the device’s default sampling rate.

fr

Number of frames: the buffer size per channel. A value of -1, the default, requests 256. The device may use another size; the buffers passed to process carry the size of each call in their frames.

Constructors

Expression Semantics

audio_stream(ic, oc, sps, fr)

Construct an audio_stream using the default audio_device, given the number of input and output channels, ic and oc, optional sample rate, sps, and optional number of frames, fr.

audio_stream(ad, ic, oc, sps, fr)

Construct an audio_stream using the the audio_device, ad, given the number of input and output channels, ic and oc, optional sample rate, sps, and optional number of frames, fr.

Take note that audio_stream_base is non-copyable, and therefore audio_stream is also non-copyable.

Accessors

Expression Semantics Return Type

as.is_valid()

Check if the audio_stream is valid. Do this before any operation on the audio_stream. Any operation on an invalid stream is undefined behavior.

bool

as.time()

Get the stream’s time in seconds: the audio it has processed since it was opened. It starts at 0, advances one buffer per call to process, and does not move while the stream is stopped.

duration

as.cpu_load()

Get stream’s CPU load: A floating point value, typically between 0.0 and 1.0, where 1.0 indicates that the stream is consuming the maximum number of CPU cycles possible to maintain real-time operation.

double

as.error()

A human readable error message, or an empty string. On as.is_valid() == false, the reason the audio_stream could not be opened. If the device goes away, the audio system closes the stream: as.is_valid() stays true, and after as.start() or as.stop() this reports that the stream is closed.

char const*

as.input_latency()

Get the stream’s latency, or 0 if it has no inputs.

duration

as.output_latency()

Get the stream’s latency, or 0 if it has no outputs.

duration

as.input_channels()

Get the number of input channels.

std::size_t

as.output_channels()

Get the number of output channels.

std::size_t

as.sampling_rate()

The sampling rate the stream opened at, which is the device’s default unless the constructor asked for another.

double

The audio backend reports one latency for the whole stream. On a duplex stream, input_latency() and output_latency() both return it: the input and output latencies added together.

Mutators

Expression Semantics

as.start()

Start the stream: from here process is called on the device’s thread, once per buffer.

as.stop()

Stop the stream. Returns once the device has stopped calling process, so after it the derived object may be destroyed safely.

Example

An effect, with both overloads' arguments: 1 input channel (mono) and two output channels (stereo). Route the mono input to both the left and right output channels.

struct my_processor : q::audio_stream                             (1)
{
   my_processor()
    : audio_stream(1, 2)                                          (2)
   {}

   void process(in_channels const& in, out_channels const& out)   (3)
   {
      auto left = out[0];
      auto right = out[1];
      auto mono_in = in[0];
      for (auto frame : out.frames)
      {
         // Get the next input sample
         auto s = mono_in[frame];

         // Output
         left[frame] = s;
         right[frame] = s;
      }
   }
};
1 Here we declare my_processor. A user-defined class derived from audio_stream.
2 Construct the base audio_stream with 1 input channel (mono) and two output channels (stereo) using the default audio device.
3 Implement the process member function.

See Audio Stream Client Interface for more info.