Mozzi  version v1.1.0
sound synthesis library for Arduino
AudioOutput.h
1 /** @file AudioOutput
2  *
3  * Platform independent audio output and adding support for new platforms or output methods.
4  *
5  * Mozzi provides support for audio ouput on a range of different boards and CPUs. This page is about the following related topics:
6  *
7  * - adding a custom output method (importantly using external DACs) to your sketch
8  * - writing sketches that will work on different platforms / with different output methods
9  * - extending Mozzi for a new architecture
10  *
11  * For all of these topics, it is helpful to have a basic understanding of the basic output steps in Mozzi:
12  *
13  * 1. Inside the loop() function in your sketch you call audioHook(). This function is responsible for calling updateAudio(), whenever there is room in the output buffer,
14  * adding the generated sample to the output buffer, calling updateControl() at an appropriate rate, and a few other details that are not important for this discussion.
15  *
16  * 2. A platform-specific timer is triggered at audio rate (usually), takes a sample from the output buffer and sends it to audioOutput().
17  *
18  * 3. The audioOutput() function - usually predefined inside Mozzi - takes care of sending the sample to the hardware.
19  *
20  * This basic output pipeline can be customized in several ways. First, defining EXTERNAL_AUDIO_OUTPUT to true in mozzi_config.h will allow you to define your own audioOutput()
21  * fuction. The library ships with some sample sketches for output to external DACs using this mechanism.
22  *
23  * In some cases, you will additionally want to bypass Mozzis output buffer, for example, if your board, or your external DAC already comes with an efficient built-in buffer.
24  * In this case, define BYPASS_MOZZI_OUTPUT_BUFFER to true. You will then have to provide a custom definition of canBufferAudioOutput(), returning true whenever your hardware
25  * is ready toaccept a new sample of output. This is called from inside audioHook(), and whenever there is room for a new sample, it is generated and sent to audioOutput(),
26  * immediately. In this case, should you need a timer running at AUDIO_RATE, you will have to set up one, yourself, if needed.
27  *
28  * In custom code, setting BYPASS_MOZZI_OUTPUT_BUFFER does not make much sense without EXTERNAL_AUDIO_OUTPUT also set to true. However, some platform ports (e.g. ESP32) actually
29  * use this combination, internally.
30  *
31  * Different output methods often support a different resolution of output samples. To provide best performance on slow boards, Mozzi expects your updateAudio() function to
32  * return samples in exactly the width that is needed at the output stage. Thus, defining this naively, an updateAudio() function designed for 8 bit output will produce very
33  * low volume output on a 16 bit DAC, while the other way around overflows will result in way too loud and heavily distored output. Fortunately, all that is needed to write
34  * portable sketches is to specify how many bits your updateAudio() function provides. The (inline) functions in the AudioOutput namespace do just that. Using them makes sure
35  * your audio output is shifted if, and as much as needed on all platforms.
36  */
37 
38 #ifndef AUDIOOUTPUT
39 #define AUDIOOUTPUT
40 
41 #include "MozziGuts.h"
42 
43 /** The type used to store a single channel of a single frame, internally. For compatibility with earlier versions of Mozzi this is defined as int.
44  * If you do not care about keeping old sketches working, you may be able to save some RAM by using int16_t, instead (on boards where int is larger
45  * than 16 bits). */
46 #define AudioOutputStorage_t int
47 
48 #if IS_AVR() && ((AUDIO_MODE == STANDARD_PLUS) || (AUDIO_MODE == STANDARD))
49 #define SCALE_AUDIO(x,bits) (bits > 8 ? (x) >> (bits - 8) : (x) << (8 - bits))
50 #define SCALE_AUDIO_NEAR(x,bits) (bits > 9 ? (x) >> (bits - 9) : (x) << (9 - bits))
51 #define CLIP_AUDIO(x) constrain((x), -244,243)
52 #else
53 #define SCALE_AUDIO(x,bits) (bits > AUDIO_BITS ? (x) >> (bits - AUDIO_BITS) : (x) << (AUDIO_BITS - bits))
54 #define SCALE_AUDIO_NEAR(x,bits) SCALE_AUDIO(x,bits)
55 #define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) AUDIO_BIAS), (AudioOutputStorage_t) AUDIO_BIAS-1)
56 #endif
57 
58 #if (AUDIO_CHANNELS == STEREO)
59 #define AudioOutput StereoOutput
60 #if (STEREO_HACK == true)
61 #define AudioOutput_t void
62 #else
63 #define AudioOutput_t StereoOutput
64 #endif
65 #else
66 /** Representation of an single audio output sample/frame. For mono output, this is really just a single zero-centered int,
67  * but for stereo it's a struct containing two ints.
68  *
69  * While it may be tempting (and is possible) to use an int, directly, using AudioOutput_t and the functions AudioOutput::from8Bit(),
70  * AudioOutput::fromNBit(), or AudioOutput::fromAlmostNBit() will allow you to write code that will work across different platforms, even
71  * when those use a different output resolution.
72  *
73  * @note The only place where you should be using AudioOutput_t, directly, is in your updateAudio() function signature. It's really just a
74  * dummy used to make the "same" function signature work across different configurations (importantly mono/stereo). It's true value
75  * might be subject to change, and it may even be void. Use either MonoOutput or StereoOutput to represent a piece of audio output.
76  */
77 #define AudioOutput_t AudioOutputStorage_t
78 /** Representation of an single audio output sample/frame. This #define maps to either MonoOutput or StereoOutput, depending on what is configured
79  * in mozzi_config.h. Since the two are source compatible to a large degree, it often isn't even necessary to test, which it is, in your code. E.g.
80  * both have functions l() and r(), to return "two" audio channels (which will be the same in case of mono).
81  *
82  * You will not usually use or encounter this definition, unless using EXTERNAL_AUDIO_OUTPUT.
83  */
84 #define AudioOutput MonoOutput
85 #endif
86 
87 /** This struct encapsulates one frame of mono audio output. Internally, it really just boils down to two int values, but the struct provides
88  * useful API an top of that. For more detail @see MonoOutput . */
89 struct StereoOutput {
90  /** Construct an audio frame from raw values (zero-centered) */
92  /** Default contstructor */
93  StereoOutput() : _l(0), _r(0) {};
94 #if (AUDIO_CHANNELS != STEREO)
95  /** Conversion to int operator: If used in a mono config, returns only the left channel (and gives a compile time warning).
96  This _could_ be turned into an operator for implicit conversion in this case. For now we chose to apply conversion on demand, only, as most of the time
97  using StereoOutput in a mono config, is not intended. */
98  inline AudioOutput_t portable() const __attribute__((deprecated("Sketch generates stereo output, but Mozzi is configured for mono. Check mozzi_config.h."))) { return _l; };
99 #endif
100  AudioOutputStorage_t l() const { return _l; };
101  AudioOutputStorage_t r() const { return _r; };
102  /** @see MonoOutput::clip(). Clips both channels. */
103  StereoOutput& clip() { _l = CLIP_AUDIO(_l); _r = CLIP_AUDIO(_r); return *this; };
104 
105  /** @see MonoOutput::fromNBit(), stereo variant */
106 template<typename T> static inline StereoOutput fromNBit(uint8_t bits, T l, T r) { return StereoOutput(SCALE_AUDIO(l, bits), SCALE_AUDIO(r, bits)); }
107  /** @see MonoOutput::from8Bit(), stereo variant */
108  static inline StereoOutput from8Bit(int16_t l, int16_t r) { return fromNBit(8, l, r); }
109  /** @see MonoOutput::from16Bit(), stereo variant */
110  static inline StereoOutput from16Bit(int16_t l, int16_t r) { return fromNBit(16, l, r); }
111  /** @see MonoOutput::fromAlmostNBit(), stereo variant */
112  template<typename A, typename B> static inline StereoOutput fromAlmostNBit(A bits, B l, B r) { return StereoOutput(SCALE_AUDIO_NEAR(l, bits), SCALE_AUDIO_NEAR(r, bits)); }
113 private:
116 };
117 
118 /** This struct encapsulates one frame of mono audio output. Internally, it really just boils down to a single int value, but the struct provides
119  * useful API an top of that, for the following:
120  *
121  * a) To construct an output frame, you should use one of the from8Bit(), fromNBit(), etc. functions. Given a raw input value, at a known resolution (number of bits),
122  * this scales the output efficiently to whatever is needed on the target platform. Using this, your updateAudio() function will be portable across different CPU and
123  * different output methods, including external DACs.
124  * b) The struct provides some convenience API on top of this. Right now, this is the function clip(), replacing the more verbose, and non-portable constrain(x, -244, 243)
125  * found in some old sketches.
126  * c) The struct provides accessors l() and r() that are source-compatible with StereoOutput, making it easy to e.g. implement support for an external DAC in both mono
127  * and stereo.
128  * d) Finally, an automatic conversion operator to int aka AudioOutput_t provides backward compatibility with old Mozzi sketches. Internally, the compiler will actually
129  * do away with this wholw struct, leaving just the same basic fast integer operations as in older Mozzi sketches. However, now, you don't have to rewrite those for
130  * different configurations.
131  */
132 struct MonoOutput {
133  /** Construct an audio frame from raw values (zero-centered) */
135 #if (AUDIO_CHANNELS > 1)
136  /** Conversion to stereo operator: If used in a stereo config, returns identical channels (and gives a compile time warning).
137  This _could_ be turned into an operator for implicit conversion in this case. For now we chose to apply conversion on demand, only, as most of the time
138  using StereoOutput in a mono config, is not intended. */
139  inline StereoOutput portable() const __attribute__((deprecated("Sketch generates mono output, but Mozzi is configured for stereo. Check mozzi_config.h."))) { return StereoOutput(_l, _l); };
140 #else
141  /** Conversion to int operator. */
142  inline operator AudioOutput_t() const { return _l; };
143 #endif
144  AudioOutputStorage_t l() const { return _l; };
145  AudioOutputStorage_t r() const { return _l; };
146  /** Clip frame to supported range. This is useful when at times, but only rarely, the signal may exceed the usual range. Using this function does not avoid
147  * artifacts, entirely, but gives much better results than an overflow. */
148  MonoOutput& clip() { _l = CLIP_AUDIO(_l); return *this; };
149 
150  /** Construct an audio frame a zero-centered value known to be in the N bit range. Appropriate left- or right-shifting will be performed, based on the number of output
151  * bits available. While this function takes care of the shifting, beware of potential overflow issues, if your intermediary results exceed the 16 bit range. Use proper
152  * casts to int32_t or larger in that case (and the compiler will automatically pick the 32 bit overload in this case) */
153  template<typename T> static inline MonoOutput fromNBit(uint8_t bits, T l) { return MonoOutput(SCALE_AUDIO(l, bits)); }
154  /** Construct an audio frame from a zero-centered value known to be in the 8 bit range. On AVR, STANDADR or STANDARD_PLUS mode, this is effectively the same as calling the
155  * constructor, directly (no scaling gets applied). On platforms/configs using more bits, an appropriate left-shift will be performed. */
156  static inline MonoOutput from8Bit(int16_t l) { return fromNBit(8, l); }
157  /** Construct an audio frame a zero-centered value known to be in the 16 bit range. This is jsut a shortcut for fromNBit(16, ...) provided for convenience. */
158  static inline MonoOutput from16Bit(int16_t l) { return fromNBit(16, l); }
159  /** Construct an audio frame a zero-centered value known to be above at almost but not quite the N bit range, e.g. at N=8 bits and a litte. On most platforms, this is
160  * exactly the same as fromNBit(), shifting up or down to the platforms' available resolution.
161  *
162  * However, on AVR, STANDARD(_PLUS) (where about 8.5 bits are usable), the value will be shifted to the (almost) 9 bit range, instead of to the 8 bit range. allowing to
163  * make use of that extra half bit of resolution. In many cases it is useful to follow up this call with clip().
164  *
165  * @example fromAlmostNBit(10, oscilA.next() + oscilB.next() + oscilC.next());
166  */
167  template<typename A, typename B> static inline MonoOutput fromAlmostNBit(A bits, B l) { return MonoOutput(SCALE_AUDIO_NEAR(l, bits)); }
168 
169 private:
171 };
172 
173 
174 /** When setting EXTERNAL_AUDIO_OUTPUT to true, implement this function to take care of writing samples to the hardware. */
175 void audioOutput(const AudioOutput f);
176 #if BYPASS_MOZZI_OUTPUT_BUFFER
177 /** When setting BYPASS_MOZZI_OUTPUT_BUFFER to true, implement this function to return true, if and only if your hardware (or custom buffer) is ready to accept the next sample. */
178 inline bool canBufferAudioOutput();
179 #endif
180 
181 /** Perform one step of (fast) pdm encoding, returning 8 "bits" (i.e. 8 ones and zeros).
182  * You will usually call this at least four or eight times for a single input sample.
183  *
184  * The return type is defined as uint32_t to avoid conversion steps. Actually, only the 8 lowest
185  * bits of the return value are set. */
186 inline uint32_t pdmCode8(uint16_t sample) {
187  // lookup table for fast pdm coding on 8 output bits at a time
188  static const byte fast_pdm_table[]{0, 0b00010000, 0b01000100,
189  0b10010010, 0b10101010, 0b10110101,
190  0b11011101, 0b11110111, 0b11111111};
191 
192  static uint32_t lastwritten = 0;
193  static uint32_t nexttarget = 0;
194  // in each iteration, code the highest 3-and-a-little bits.
195  // Note that sample only has 16 bits, while the
196  // highest bit we consider for writing is bit 17.
197  // Thus, if the highest bit is set, the next
198  // three bits cannot be.
199  nexttarget += sample;
200  nexttarget -= lastwritten;
201  lastwritten = nexttarget & 0b11110000000000000;
202  return fast_pdm_table[lastwritten >> 13];
203 }
204 
205 /** Convenience function to perform four iterations of pdmCode8() */
206 inline uint32_t pdmCode32(uint16_t sample) {
207  uint32_t outbits = 0;
208  for (uint8_t i = 0; i < 4; ++i) {
209  outbits = outbits << 8;
210  outbits |= pdmCode8(sample);
211  }
212  return outbits;
213 }
214 
215 #if (EXTERNAL_AUDIO_OUTPUT == true)
216 #warning "Mozzi is configured to use an external void 'audioOutput(const AudioOutput f)' function. Please define one in your sketch"
217 #endif
218 
219 #endif
static MonoOutput from16Bit(int16_t l)
Construct an audio frame a zero-centered value known to be in the 16 bit range.
Definition: AudioOutput.h:158
StereoOutput & clip()
Definition: AudioOutput.h:103
#define SCALE_AUDIO_NEAR(x, bits)
Definition: AudioOutput.h:54
static StereoOutput fromNBit(uint8_t bits, T l, T r)
Definition: AudioOutput.h:106
StereoOutput()
Default contstructor.
Definition: AudioOutput.h:93
static MonoOutput from8Bit(int16_t l)
Construct an audio frame from a zero-centered value known to be in the 8 bit range.
Definition: AudioOutput.h:156
#define AUDIO_MODE
AUDIO_MODE holds the audio mode setting.
Definition: mozzi_config.h:28
#define AUDIO_CHANNELS
This sets allows to change from a single/mono audio output channel to stereo output.
Definition: mozzi_config.h:92
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:132
MonoOutput(AudioOutputStorage_t l=0)
Construct an audio frame from raw values (zero-centered)
Definition: AudioOutput.h:134
static StereoOutput from8Bit(int16_t l, int16_t r)
Definition: AudioOutput.h:108
AudioOutput_t portable() const __attribute__((deprecated("Sketch generates stereo output
Conversion to int operator: If used in a mono config, returns only the left channel (and gives a comp...
#define STANDARD_PLUS
Definition: MozziGuts.h:165
MonoOutput & clip()
Clip frame to supported range.
Definition: AudioOutput.h:148
#define AudioOutput
Representation of an single audio output sample/frame.
Definition: AudioOutput.h:84
#define IS_AVR()
#define SCALE_AUDIO(x, bits)
Definition: AudioOutput.h:53
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:89
static StereoOutput from16Bit(int16_t l, int16_t r)
Definition: AudioOutput.h:110
operator AudioOutput_t() const
Conversion to int operator.
Definition: AudioOutput.h:142
#define STEREO
Definition: MozziGuts.h:170
StereoOutput(AudioOutputStorage_t l, AudioOutputStorage_t r)
Construct an audio frame from raw values (zero-centered)
Definition: AudioOutput.h:91
#define AudioOutput_t
Representation of an single audio output sample/frame.
Definition: AudioOutput.h:77
#define STANDARD
Used to set AUDIO_MODE to STANDARD, STANDARD_PLUS, or HIFI.
Definition: MozziGuts.h:164
#define EXTERNAL_AUDIO_OUTPUT
Defining this option as true in mozzi_config.h allows to completely customize the audio output...
Definition: mozzi_config.h:99
static StereoOutput fromAlmostNBit(A bits, B l, B r)
Definition: AudioOutput.h:112
static MonoOutput fromNBit(uint8_t bits, T l)
Construct an audio frame a zero-centered value known to be in the N bit range.
Definition: AudioOutput.h:153
#define CLIP_AUDIO(x)
Definition: AudioOutput.h:55
#define AudioOutputStorage_t
The type used to store a single channel of a single frame, internally.
Definition: AudioOutput.h:46