1 module pixelperfectengine.audio.base.handler; 2 3 /* 4 * Copyright (C) 2015-2021, by Laszlo Szeremi under the Boost license. 5 * 6 * Pixel Perfect Engine, audio.base.handler module. 7 */ 8 9 import core.thread.osthread; 10 11 import std.conv : to; 12 import std.string : fromStringz; 13 import std.bitmanip : bitfields; 14 15 import pixelperfectengine.system.exc; 16 import pixelperfectengine.audio.base.modulebase; 17 18 import bindbc.sdl.bind.sdlaudio; 19 import bindbc.sdl.bind.sdlerror : SDL_GetError; 20 21 /** 22 * Manages and initializes audio devices. 23 * 24 * Only one instance should be made. 25 */ 26 public class AudioDeviceHandler { 27 protected const(char)*[] devices; ///Names of the devices 28 protected const(char)*[] drivers; ///Names of the drivers 29 protected SDL_AudioDeviceID openedDevice; ///The ID of the opened audio device 30 protected SDL_AudioSpec req; ///Requested audio specs 31 protected SDL_AudioSpec given; ///Given audio specs 32 /** 33 * Creates an instance, and detects all drivers. 34 * 35 * slmpFreq: Sampling frequency 36 * channels: Number of channels 37 * buffSize: The size of the buffer in samples 38 * 39 * Throws an AudioInitException if audio is failed to be initialized. 40 */ 41 public this(int slmpFreq, ubyte channels, ushort buffSize) { 42 const int nOfAudioDrivers = SDL_GetNumAudioDrivers(); 43 //deviceNames.length = SDL_GetNumAudioDevices(0); 44 if (nOfAudioDrivers > 0) { 45 drivers.reserve(nOfAudioDrivers); 46 for (int i ; i < nOfAudioDrivers ; i++) { 47 drivers ~= SDL_GetAudioDriver(i); 48 } 49 } else throw new AudioInitException("No audio drivers were found on this system!"); 50 req.freq = slmpFreq; 51 req.format = SDL_AudioFormat.AUDIO_F32; 52 req.channels = channels; 53 req.samples = buffSize; 54 req.callback = &callbacksFromSDL; 55 } 56 ///Destructor 57 ~this() { 58 SDL_AudioQuit(); 59 } 60 /** 61 * Initializes an audio driver by ID. 62 * 63 * Throws an AudioInitException if audio failed to be initialized. 64 */ 65 public void initAudioDriver(int id) { 66 if (id >= drivers.length) throw new AudioInitException("Audio driver not found!"); 67 const int audioStatusCode = SDL_AudioInit(id >= 0 ? drivers[id] : null); 68 if (audioStatusCode) throw new AudioInitException("Audio driver failed to be initialized. Error code: " ~ 69 to!string(audioStatusCode) ~ " ; SDL Error message: " ~ fromStringz(SDL_GetError()).idup); 70 const int nOfAudioDevices = SDL_GetNumAudioDevices(0); 71 if (nOfAudioDevices > 0) { 72 devices.reserve(nOfAudioDevices); 73 for (int i ; i < nOfAudioDevices ; i++) { 74 devices ~= SDL_GetAudioDeviceName(i, 0); 75 } 76 } else throw new AudioInitException("No audio devices found!"); 77 } 78 /** 79 * Opens a specific audio device for audio playback by ID, then sets the values for buffer sizes etc. 80 * 81 * Throws an AudioInitException if audio failed to be initialized 82 */ 83 public void initAudioDevice(int id) { 84 if (id >= devices.length) throw new AudioInitException("Audio device not found"); 85 openedDevice = SDL_OpenAudioDevice(id >= 0 ? devices[id] : null, 0, &req, &given, 86 SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); 87 if (openedDevice < 0) throw new AudioInitException("Audio device couldn't be opened. Error code: " ~ 88 to!string(openedDevice) ~ " ; SDL Error message: " ~ fromStringz(SDL_GetError()).idup); 89 } 90 /** 91 * Returns an array with the names of the available audio drivers. 92 */ 93 public string[] getDrivers() @trusted pure nothrow const { 94 string[] result; 95 result.reserve(drivers.length); 96 for (int i ; i < drivers.length ; i++) { 97 result ~= fromStringz(drivers[i]).idup; 98 } 99 return result; 100 } 101 /** 102 * Return an array with the names of the available audio devices. 103 */ 104 public string[] getDevices() @trusted pure nothrow const { 105 string[] result; 106 result.reserve(devices.length); 107 for (int i ; i < devices.length ; i++) { 108 result ~= fromStringz(devices[i]).idup; 109 } 110 return result; 111 } 112 /** 113 * Returns the available sampling frequency. 114 */ 115 public int getSamplingFrequency() @nogc @safe pure nothrow const { 116 return given.freq; 117 } 118 /** 119 * Returns the available format. 120 */ 121 public SDL_AudioFormat getFormat() @nogc @safe pure nothrow const { 122 return given.format; 123 } 124 /** 125 * Returns the number of audio channels. 126 */ 127 public ubyte getChannels() @nogc @safe pure nothrow const { 128 return given.channels; 129 } 130 } 131 /** 132 * Manages all audio modules complete with routing, MIDI2.0, etc. 133 */ 134 public class ModuleManager : Thread { 135 /** 136 * Output buffer size in samples. 137 * 138 * Must be set upon initialization. 139 */ 140 protected int outBufferSize; 141 /** 142 * Rendering buffer size in samples, also the length of a single frame. 143 * 144 * Must be less than outBufferSize, and power of two. 145 */ 146 protected int bufferSize; 147 /** 148 * Number of maximum frames that can be put into the output buffer. 149 */ 150 protected int nOfFrames; 151 /** 152 * Current audio frame. 153 */ 154 protected int currFrame; 155 ///Pointer to the audio device handler. 156 public AudioDeviceHandler devHandler; 157 /** 158 * List of modules. 159 * 160 * Ran in order, should be ordered in such way to ensure that routing is correct, and the modules that need the 161 * input will get some. 162 */ 163 protected AudioModule[] moduleList; 164 /** 165 * List of pointers to input buffers. 166 * 167 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 168 * If a specific plugin doesn't have any inputs, then an array with zero elements must be added. 169 */ 170 protected float*[][] inBufferList; 171 /** 172 * List of pointers to output buffers. 173 * 174 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 175 * If a specific plugin doesn't have any outputs, then an array with zero elements must be added. 176 */ 177 protected float*[][] outBufferList; 178 /** 179 * List of the buffers themselves. 180 * 181 * One buffer can be shared between multiple input and/or output for mixing, etc. 182 * All buffers must have the same size, defined by the variable `bufferSize` 183 * The first buffers are used for output rendering. 184 */ 185 protected float[][] buffers; 186 /** 187 * Final buffers. 188 * 189 * May be null, if frame length is equal with output buffer length. 190 */ 191 protected float[][] finalBuffers; 192 193 /** 194 * Puts the output to the final destination. 195 * 196 * Currently only stereo output is supported. 197 */ 198 public void put(void* userdata, ubyte* stream, int len) @nogc nothrow { 199 import pixelperfectengine.audio.base.func : interleave; 200 while (currFrame < nOfFrames) 201 renderFrame(); 202 if (finalBuffers.length == 2) { 203 interleave(len, finalBuffers[0].ptr, finalBuffers[1].ptr, cast(float*)stream); 204 } 205 currFrame = 0; 206 } 207 /** 208 * Renders a single frame of audio. 209 */ 210 public void renderFrame() @nogc nothrow { 211 import core.stdc.string : memcpy; 212 if (currFrame >= nOfFrames) 213 return; 214 foreach (size_t i, AudioModule am; moduleList) { 215 am.renderFrame(inBufferList[i], outBufferList[i]); 216 } 217 const size_t offset = currFrame * bufferSize; 218 for (int i ; i < devHandler.getChannels() ; i++) { 219 memcpy(finalBuffers[i].ptr + offset, buffers[i].ptr, bufferSize * float.sizeof); 220 } 221 currFrame++; 222 } 223 /** 224 * MIDI commands are received here from modules. 225 * 226 * data: up to 128 bits of MIDI 2.0 commands. Any packets that are shorter should be padded with zeros. 227 * offset: time offset of the command. This can reduce jitter caused by the asynchronous operation of the 228 * sequencer and the audio plugin system. 229 */ 230 public void midiReceive(uint[4] data, uint offset) @nogc nothrow { 231 232 } 233 /** 234 * Sets up a specific number of buffers. 235 */ 236 public void setBuffers(size_t num) nothrow { 237 buffers.length = num; 238 for (size_t i ; i < buffers.length ; i++) { 239 buffers[i].length = bufferSize; 240 } 241 } 242 /** 243 * Adds a plugin to the list. 244 */ 245 public void addModule(AudioModule md, size_t[] inBuffs, size_t[] outBuffs) nothrow { 246 moduleList ~= md; 247 float*[] buffList0, buffList1; 248 buffList0.length = inBuffs.length; 249 for (size_t i ; i < inBuffs.length ; i++) { 250 buffList0[i] = buffers[inBuffs[i]].ptr; 251 } 252 buffList1.length = outBuffs.length; 253 for (size_t i ; i < outBuffs.length ; i++) { 254 buffList1[i] = buffers[outBuffs[i]].ptr; 255 } 256 inBufferList ~= buffList0; 257 outBufferList ~= buffList1; 258 } 259 /** 260 * Locks the manager and all audio modules within it to avoid interference from GC. 261 * 262 * This will however disable any further memory allocation until thread is unlocked. 263 */ 264 public void lockAudioThread() { 265 266 } 267 /** 268 * Unlocks the manager and all audio modules within it to allow GC allocation, which is needed for loading, etc. 269 * 270 * Note that this will probably result in the GC regularly stopping the audio thread, resulting in audio glitches, 271 * etc. 272 */ 273 public void unlockAudioThread() { 274 275 } 276 } 277 alias CallBackDeleg = void delegate(void* userdata, ubyte* stream, int len) @nogc nothrow; 278 ///Privides a way for delegates to be called from SDL2. 279 ///Must be set up before audio device initialization. 280 static CallBackDeleg audioCallbackDeleg; 281 /** 282 * A function that handles callbacks from SDL2's audio system. 283 */ 284 extern(C) void callbacksFromSDL(void* userdata, ubyte* stream, int len) @nogc nothrow { 285 audioCallbackDeleg(userdata, stream, len); 286 } 287 288 /** 289 * Thrown on audio initialization errors. 290 */ 291 public class AudioInitException : PPEException { 292 /// 293 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 294 { 295 super(msg, file, line, nextInChain); 296 } 297 /// 298 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 299 { 300 super(msg, file, line, nextInChain); 301 } 302 }