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 }