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;
10 import core.time;
11 
12 import std.conv : to;
13 import std.string : fromStringz;
14 import std.bitmanip : bitfields;
15 
16 import pixelperfectengine.system.exc;
17 //import pixelperfectengine.system.etc : isPowerOf2;
18 import pixelperfectengine.audio.base.modulebase;
19 import pixelperfectengine.audio.base.midiseq;
20 
21 import iota.audio.output;
22 import iota.audio.device;
23 public import iota.audio.types;
24 
25 /**
26  * Manages and initializes audio devices.
27  *
28  * Important: Only one instance should be made.
29  */
30 public class AudioDeviceHandler {
31 	package AudioDevice				device;			///Contains the initialized audio device
32 	protected AudioSpecs			specs;			///Contains the requested/given audio specs
33 	package int						blockSize;		///Requested/given buffer base size / block length (in samples)
34 	package int						nOfBlocks;		///Requested/given number of blocks before they get sent to the output
35 	/** 
36 	 * Creates an instance, and detects all drivers.
37 	 *Params: 
38 	 * specs: Requested audio specifications. If not available, a nearby will be used instead.
39 	 * channels: Number of channels
40 	 * buffSize: The size of the buffer in samples
41 	 *
42 	 * Throws an AudioInitException if audio is failed to be initialized.
43 	 */
44 	public this(AudioSpecs specs, int blockSize, int nOfBlocks) {
45 		//context = soundio_create();
46 		this.specs = specs;
47 		this.blockSize = blockSize;
48 		this.nOfBlocks = nOfBlocks;
49 		//req.callback = &callbacksFromSDL;
50 	}
51 	///Destructor
52 	~this() {
53 		
54 	}
55 	/**
56 	 * Initializes an audio driver by ID.
57 	 *
58 	 * Throws an AudioInitException if audio failed to be initialized.
59 	 */
60 	public static void initAudioDriver(DriverType backend) {
61 		int errCode = initDriver(backend);
62 		if (errCode) throw new AudioInitException("Failed to initialize audio driver. Error code: " ~ errCode.to!string);
63 	}
64 	/**
65 	 * Opens a specific audio device for audio playback by ID, then sets the values for buffer sizes etc.
66 	 *
67 	 * Throws an AudioInitException if audio failed to be initialized
68 	 */
69 	public void initAudioDevice(int id = -1) {
70 		int errCode = openDevice(id, device);
71 		if (errCode) throw new AudioInitException("Failed to initialize audio device. Error code: " ~ errCode.to!string);
72 		int recSlmpRate = device.getRecommendedSampleRate();
73 		if (recSlmpRate > 0) {
74 			specs.sampleRate = recSlmpRate;
75 		}
76 		specs = device.requestSpecs(specs);
77 		// Recalculate block sizes if buffer size changed
78 		if (specs.bufferSize_slmp != blockSize * nOfBlocks) {
79 			if (!(specs.bufferSize_slmp % 4)) {
80 				blockSize = specs.bufferSize_slmp / 4;
81 				nOfBlocks = 4;
82 			} else {
83 				blockSize = specs.bufferSize_slmp;
84 				nOfBlocks = 1;
85 			}
86 		}
87 	}
88 	/**
89 	 * Return an array with the names of the available audio devices.
90 	 */
91 	public string[] getDevices() {
92 		return getOutputDeviceNames();
93 	}
94 	/**
95 	 * Returns the available sampling frequency.
96 	 */
97 	public int getSamplingFrequency() @nogc @safe pure nothrow const {
98 		return specs.sampleRate;
99 	}
100 	/**
101 	 * Returns the number of audio channels.
102 	 */
103 	public int getChannels() @nogc @safe pure nothrow const {
104 		return specs.outputChannels;
105 	}
106 	/**
107 	 * Returns the buffer size in units.
108 	 */
109 	public size_t getBufferSize() @nogc @safe pure nothrow const {
110 		return blockSize * nOfBlocks;
111 	}
112 	/** 
113 	 * Returns the buffer length in time, in `Duration` format.
114 	 */
115 	public Duration getBufferDelay() @nogc @safe pure nothrow const {
116 		return specs.bufferSize_time;
117 	}
118 }
119 /**
120  * Manages all audio modules complete with routing, MIDI2.0, etc.
121  */
122 public class ModuleManager {
123 	protected int			blockSize;		///Rendering buffer size in samples, also the length of a single frame.
124 	protected int			nOfBlocks;		///Number of maximum frames that can be put into the output buffer.
125 	protected int			currBlock;		///Current audio frame.
126 	///Pointer to the audio device handler.
127 	public AudioDeviceHandler	devHandler;
128 	///Pointer to a MIDI sequencer, for synchronizing it with the audio stream.
129 	public Sequencer		midiSeq;
130 	protected OutputStream	outStrm;		///Output stream handling.
131 	/**
132 	 * List of modules.
133 	 *
134 	 * Ran in order, should be ordered in such way to ensure that routing is correct, and the modules that need the
135 	 * input will get some.
136 	 */
137 	public AudioModule[]	moduleList;
138 	/**
139 	 * List of pointers to input buffers.
140 	 *
141 	 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs.
142 	 * If a specific plugin doesn't have any inputs, then an array with zero elements must be added.
143 	 */
144 	protected float*[][]	inBufferList;
145 	/**
146 	 * List of pointers to output buffers.
147 	 *
148 	 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs.
149 	 * If a specific plugin doesn't have any outputs, then an array with zero elements must be added.
150 	 */
151 	protected float*[][]	outBufferList;
152 	/**
153 	 * List of the buffers themselves.
154 	 *
155 	 * One buffer can be shared between multiple input and/or output for mixing, etc.
156 	 * All buffers must have the same size, defined by the variable `blockSize`
157 	 * The first buffers are used for output rendering.
158 	 */
159 	protected float[][]		buffers;
160 	/**
161 	 * Final output buffer.
162 	 *
163 	 * Words are in LRLRLR... order, or similar, depending on number of channels.
164 	 */
165 	protected float[]		finalBuffer;
166 	protected int			channels;
167 	/** 
168 	 * Creates an instance of a module handler.
169 	 *Params:
170 	 * handler = The AudioDeviceHandler that contains the data about the audio device.
171 	 * blockSize = The size of the buffer in samples.
172 	 * nOfBlocks = The number of frames before they get queued to the audio device.
173 	 */
174 	public this(AudioDeviceHandler handler) {
175 		import pixelperfectengine.audio.base.func : resetBuffer;
176 		devHandler = handler;
177 		this.blockSize = handler.blockSize;
178 		outStrm = handler.device.createOutputStream();
179 		if (outStrm is null) throw new AudioInitException("Audio stream couldn't be opened.");
180 		this.nOfBlocks = handler.nOfBlocks;
181 		finalBuffer.length = handler.getChannels() * blockSize * nOfBlocks;
182 		this.channels = handler.getChannels();
183 		resetBuffer(finalBuffer);
184 
185 		buffers.length = handler.getChannels();
186 		for (int i ; i < buffers.length ; i++) {
187 			buffers[i].length = blockSize;
188 			resetBuffer(buffers[i]);
189 		}
190 		outStrm.callback_buffer = &audioCallback;
191 		//super(&render);
192 	}
193 	/**
194 	 * Audio callback function.
195 	 * Renders the audio, then it copies to the destination buffer. If needed, it'll do floating point to integer conversion.
196 	 */
197 	protected void audioCallback(ubyte[] destbuffer) @nogc nothrow {
198 		import pixelperfectengine.audio.base.func : interleave, resetBuffer;
199 		import core.stdc.string : memcpy;
200 
201 		while (currBlock < nOfBlocks) {
202 			foreach (ref key; buffers) {
203 				resetBuffer(key);
204 			}
205 			foreach (size_t i, AudioModule am; moduleList) {
206 				am.renderFrame(inBufferList[i], outBufferList[i]);
207 			}
208 			if (midiSeq !is null)
209 				midiSeq.lapseTime(devHandler.getBufferDelay);
210 			const size_t offset = currBlock * blockSize * channels;
211 			interleave(blockSize, buffers[0].ptr, buffers[1].ptr, finalBuffer.ptr + offset);
212 			currBlock++;
213 		}
214 		currBlock = 0;
215 		
216 		memcpy(destbuffer.ptr, finalBuffer.ptr, destbuffer.length);
217 		
218 	}
219 	/**
220 	 * MIDI commands are received here from modules.
221 	 *
222 	 * data: up to 128 bits of MIDI 2.0 commands. Any packets that are shorter should be padded with zeros.
223 	 * offset: time offset of the command. This can reduce jitter caused by the asynchronous operation of the 
224 	 * sequencer and the audio plugin system.
225 	 */
226 	public void midiReceive(uint[4] data, uint offset) @nogc nothrow {
227 		
228 	}
229 	/**
230 	 * Sets up a specific number of buffers.
231 	 */
232 	public void setBuffers(size_t num) @safe nothrow {
233 		import pixelperfectengine.audio.base.func : resetBuffer;
234 		buffers.length = num;
235 		for (size_t i ; i < buffers.length ; i++) {
236 			buffers[i].length = blockSize;
237 			resetBuffer(buffers[i]);
238 		}
239 	}
240 	public int getChannels() const @nogc @safe pure nothrow {
241 		return channels;
242 	}
243 	/**
244 	 * Adds a plugin to the list.
245 	 *Params: 
246 	 * md = The audio module to be added. Automatic set-up is done upon addition.
247 	 * inBuffs = list of the audio inputs to be added, or null if none.
248 	 * inCfg = list of audio input IDs to be used. Must be matched with `inBuffs`
249 	 * outBuffs - list of the audio outputs to be added, of null if none.
250 	 * outCfg = list of audio output IDs to be used. Must be matched with `outBuffs`
251 	 */
252 	public void addModule(AudioModule md, size_t[] inBuffs, ubyte[] inCfg, size_t[] outBuffs, ubyte[] outCfg) nothrow {
253 		md.moduleSetup(inCfg, outCfg, devHandler.getSamplingFrequency, blockSize, this);
254 		moduleList ~= md;
255 		float*[] buffList0, buffList1;
256 		buffList0.length = inBuffs.length;
257 		for (size_t i ; i < inBuffs.length ; i++) {
258 			buffList0[i] = buffers[inBuffs[i]].ptr;
259 		}
260 		buffList1.length = outBuffs.length;
261 		for (size_t i ; i < outBuffs.length ; i++) {
262 			buffList1[i] = buffers[outBuffs[i]].ptr;
263 		}
264 		inBufferList ~= buffList0;
265 		outBufferList ~= buffList1;
266 	}
267 	public void reset() {
268 		moduleList.length = 0;
269 		inBufferList.length = 0;
270 		outBufferList.length = 0;
271 		setBuffers(0);
272 	}
273 	/**
274 	 * Runs the audio thread and starts the audio output.
275 	 */
276 	public int runAudioThread() @nogc nothrow {
277 		return outStrm.runAudioThread();
278 	}
279 	/**
280 	 * Stops all audio output.
281 	 */
282 	public int suspendAudioThread() {
283 		return outStrm.suspendAudioThread();
284 	}
285 }
286 
287 /**
288  * Thrown on audio initialization errors.
289  */
290 public class AudioInitException : PPEException {
291 	///
292 	@nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
293     {
294         super(msg, file, line, nextInChain);
295     }
296 	///
297     @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
298     {
299         super(msg, file, line, nextInChain);
300     }
301 }