1 module pixelperfectengine.audio.m2.seq;
2 
3 public import pixelperfectengine.audio.m2.types;
4 public import pixelperfectengine.audio.base.midiseq : Sequencer;
5 public import pixelperfectengine.audio.base.modulebase;
6 import collections.treemap;
7 
8 import std.typecons : BitFlags;
9 import core.time : MonoTime;
10 import midi2.types.structs;
11 import midi2.types.enums;
12 
13 public class SequencerM2 : Sequencer {
14 	enum ErrorFlags : uint {
15 		badOpcode		=		1<<0,
16 		illegalJump		=		1<<1,
17 		unrecognizedDevice=		1<<2,
18 		unrecognizedCode=		1<<3,
19 		outOfPatternSlots=		1<<4,
20 		unrecognizedPattern=	1<<5,
21 		illegalMIDICmd	=		1<<6,
22 	}
23 	enum StatusFlags : uint {
24 		play			=		1<<0,
25 		pause			=		1<<1,
26 		endReached		=		1<<8,
27 
28 		cfg_StopOnError	=		1<<16,
29 	}
30 	public BitFlags!ErrorFlags errors;
31 	protected BitFlags!StatusFlags status;
32 	protected Duration timePos;
33 	public TreeMap!(uint, AudioModule) modTrgt;
34 	public M2Song songdata;
35 
36 	public void lapseTime(Duration amount) @nogc nothrow {
37 		if (!status.play) return;
38 		timePos += amount;
39 		foreach (ref M2PatternSlot ptrnSl ; songdata.ptrnSl) {
40 			if (ptrnSl.status.isRunning && !ptrnSl.status.suspend && status.play) {
41 				advancePattern(ptrnSl, amount);
42 			}
43 		}
44 	}
45 	/** 
46 	 * Advances the supplied pattern by the given `amount`, then processes commands if needed.
47 	 */
48 	private void advancePattern(ref M2PatternSlot ptrn, Duration amount) @nogc nothrow {
49 		///Returns the current timebase
50 		ulong getTimeBase() @nogc @safe pure nothrow const {
51 			return (((songdata.timebase * songdata.globTimeMult)>>16) * ptrn.timeMult)>>16;
52 		}
53 		if ((ptrn.timeToWait -= amount) <= hnsecs(0)) {	//If enough time has passed, then proceed to compute commands
54 			uint[] patternData = songdata.ptrnData[ptrn.id];
55 			while (patternData.length > ptrn.position && status.play) {	//Loop until end is reached or certain opcode is reached (handle that within the switch statement)
56 				DataReaderHelper data = DataReaderHelper(patternData[ptrn.position]);
57 				switch (data.bytes[0]) {		//Process commands
58 					case OpCode.nullcmd:		//Null command (do nothing)
59 						break;
60 					case OpCode.lnwait:			//Long wait
61 						const ulong tics = (((data.bytes[1]<<16) | data.hwords[1])<<24) | patternData[ptrn.position + 1];	//Get amount of tics for this wait command
62 						const ulong timeBase = getTimeBase();
63 						ptrn.timeToWait += nsecs(timeBase * tics);		//calculate new wait amount, plus amount for any inaccuracy from sequencer steping.
64 						ptrn.position += 2;
65 						if (!ptrn.timeToWait.isNegative) goto exitLoop;	//hazard case: even after wait time is l
66 						break;
67 					case OpCode.shwait:			//Short wait
68 						const uint tics = (data.bytes[1]<<16) | data.hwords[1];
69 						const ulong timeBase = getTimeBase();
70 						ptrn.timeToWait += nsecs(timeBase * tics);
71 						ptrn.position += 1;
72 						if (!ptrn.timeToWait.isNegative) goto exitLoop;
73 						break;
74 					case OpCode.emit:			//MIDI data emit
75 						const device = data.hwords[1];
76 						const dataAm = data.bytes[1];
77 						emitMIDIData(patternData[ptrn.position..ptrn.position + dataAm], device);
78 						ptrn.position += 1 + dataAm;
79 						break;
80 					case OpCode.jmp:			//Conditional jump
81 						switch (data.bytes[1]) {
82 							case JmpCode.nc:	//Always jump
83 								ptrn.position += cast(int)patternData[ptrn.position + 2];
84 								break;
85 							case JmpCode.eq:	//Jump if condition code and condition register are equal
86 								if (ptrn.localReg[CR] == patternData[ptrn.position + 1]) {
87 									ptrn.position += cast(int)patternData[ptrn.position + 2];
88 								} else {
89 									ptrn.position += 3;
90 								}
91 								break;
92 							case JmpCode.ne:	//Jump if condition code and condition register are not equal
93 								if (ptrn.localReg[CR] != patternData[ptrn.position + 1]) {
94 									ptrn.position += cast(int)patternData[ptrn.position + 2];
95 								} else {
96 									ptrn.position += 3;
97 								}
98 								break;
99 							case JmpCode.sh:
100 								if (ptrn.localReg[CR] & patternData[ptrn.position + 1]) {
101 									ptrn.position += cast(int)patternData[ptrn.position + 2];
102 								} else {
103 									ptrn.position += 3;
104 								}
105 								break;
106 							case JmpCode.op:	//Jump if condition code bits are opposite of CR
107 								if (ptrn.localReg[CR] == ~patternData[ptrn.position + 1]) {
108 									ptrn.position += cast(int)patternData[ptrn.position + 2];
109 								} else {
110 									ptrn.position += 3;
111 								}
112 								break;
113 							default:
114 								ptrn.position += 3;
115 								errors.unrecognizedCode = true;
116 								if (status.cfg_StopOnError) {
117 									status.play = false;
118 									return;
119 								}
120 								break;
121 						}
122 						if (ptrn.position > patternData.length) {
123 							errors.illegalJump = true;
124 							ptrn.status.isRunning = false;
125 							ptrn.status.hasEnded = true;
126 							if (status.cfg_StopOnError) {
127 								status.play = false;
128 								return;
129 							}
130 						}
131 						break;
132 					case OpCode.chain_par:
133 						initNewPattern((data.bytes[1]<<24) | data.hwords[1], PATTERN_SLOT_INACTIVE_ID);
134 						ptrn.position++;
135 						break;
136 					case OpCode.chain_ser:
137 						initNewPattern((data.bytes[1]<<24) | data.hwords[1], ptrn.id);
138 						ptrn.status.suspend = true;
139 						ptrn.position++;
140 						return;
141 					//Math operations on registers begin
142 					case OpCode.add: .. case OpCode.satmuls:
143 						T saturate(T)(const long src) @nogc nothrow pure {
144 							if (src < T.min) return T.min;
145 							if (src > T.max) return T.max;
146 							return cast(T)src;
147 						}
148 						const uint ra = data.bytes[1] & 0x80 ? songdata.globalReg[data.bytes[1]&0x7F] : ptrn.localReg[data.bytes[1]];
149 						const uint rb = data.bytes[2] & 0x80 ? songdata.globalReg[data.bytes[2]&0x7F] : ptrn.localReg[data.bytes[2]];
150 						uint rd;
151 						switch (data.bytes[0]) {
152 							case OpCode.add:
153 								rd = ra + rb;
154 								break;
155 							case OpCode.sub:
156 								rd = ra - rb;
157 								break;
158 							case OpCode.mul:
159 								rd = ra * rb;
160 								break;
161 							case OpCode.div:
162 								rd = ra / rb;
163 								break;
164 							case OpCode.mod:
165 								rd = ra % rb;
166 								break;
167 							case OpCode.and:
168 								rd = ra & rb;
169 								break;
170 							case OpCode.or:
171 								rd = ra | rb;
172 								break;
173 							case OpCode.xor:
174 								rd = ra ^ rb;
175 								break;
176 							case OpCode.not:
177 								rd = ~ra;
178 								break;
179 							case OpCode.lshi:
180 								rd = ra << data.bytes[2];
181 								break;
182 							case OpCode.rshi:
183 								rd = ra >> data.bytes[2];
184 								break;
185 							case OpCode.rasi:
186 								rd = cast(int)ra >> data.bytes[2];
187 								break;
188 							case OpCode.adds:
189 								rd = cast(int)ra + cast(int)rb;
190 								break;
191 							case OpCode.subs:
192 								rd = cast(int)ra - cast(int)rb;
193 								break;
194 							case OpCode.muls:
195 								rd = cast(int)ra * cast(int)rb;
196 								break;
197 							case OpCode.divs:
198 								rd = cast(int)ra / cast(int)rb;
199 								break;
200 							case OpCode.lsh:
201 								rd = ra << rb;
202 								break;
203 							case OpCode.rsh:
204 								rd = ra << rb;
205 								break;
206 							case OpCode.ras:
207 								rd = cast(int)ra << cast(int)rb;
208 								break;
209 							case OpCode.mov:
210 								rd = ra;
211 								break;
212 							case OpCode.satadd:
213 								rd = saturate!uint(cast(long)ra + rb);
214 								break;
215 							case OpCode.satsub:
216 								rd = saturate!uint(cast(long)ra - rb);
217 								break;
218 							case OpCode.satmul:
219 								rd = saturate!uint(cast(long)ra * rb);
220 								break;
221 							case OpCode.satadds:
222 								rd = saturate!int(cast(long)(cast(int)ra) + cast(int)rb);
223 								break;
224 							case OpCode.satsubs:
225 								rd = saturate!int(cast(long)(cast(int)ra) - cast(int)rb);
226 								break;
227 							case OpCode.satmuls:
228 								rd = saturate!int(cast(long)(cast(int)ra) * cast(int)rb);
229 								break;
230 							default:
231 								errors.badOpcode = true;
232 								if (status.cfg_StopOnError) {
233 									status.play = false;
234 									return;
235 								}
236 								break;
237 						}
238 						if (data.bytes[3] & 0x80) {
239 							songdata.globalReg[data.bytes[3] & 0x7F] = rd;
240 						} else {
241 							ptrn.localReg[data.bytes[3]] = rd;
242 						}
243 						ptrn.position++;
244 						break;
245 					//Math operations on registers end
246 					case OpCode.cmp:		//compare instruction
247 						bool cmpRes;
248 						const uint ra = data.bytes[2] & 0x80 ? songdata.globalReg[data.bytes[2]&0x7F] : ptrn.localReg[data.bytes[2]];
249 						const uint rb = data.bytes[3] & 0x80 ? songdata.globalReg[data.bytes[3]&0x7F] : ptrn.localReg[data.bytes[3]];
250 						switch (data.bytes[1]) {
251 							case CmpCode.eq:
252 								cmpRes = ra == rb;
253 								break;
254 							case CmpCode.ne:
255 								cmpRes = ra != rb;
256 								break;
257 							case CmpCode.gt:
258 								cmpRes = ra > rb;
259 								break;
260 							case CmpCode.ge:
261 								cmpRes = ra >= rb;
262 								break;
263 							case CmpCode.lt:
264 								cmpRes = ra < rb;
265 								break;
266 							case CmpCode.le:
267 								cmpRes = ra <= rb;
268 								break;
269 							case CmpCode.ze:
270 								cmpRes = ra == 0;
271 								break;
272 							case CmpCode.nz:
273 								cmpRes = ra != 0;
274 								break;
275 							case CmpCode.ng:
276 								cmpRes = cast(int)ra < 0;
277 								break;
278 							case CmpCode.po:
279 								cmpRes = cast(int)ra > 0;
280 								break;
281 							case CmpCode.sgt:
282 								cmpRes = cast(int)ra > cast(int)rb;
283 								break;
284 							case CmpCode.sge:
285 								cmpRes = cast(int)ra >= cast(int)rb;
286 								break;
287 							case CmpCode.slt:
288 								cmpRes = cast(int)ra < cast(int)rb;
289 								break;
290 							case CmpCode.sle:
291 								cmpRes = cast(int)ra <= cast(int)rb;
292 								break;
293 							default:
294 								errors.unrecognizedCode = true;
295 								if (status.cfg_StopOnError) {
296 									status.play = false;
297 									return;
298 								}
299 								break;
300 						}
301 						ptrn.localReg[CR]<<=1;		//Shift in new bit depending on compare result;
302 						if (cmpRes) ptrn.localReg[CR] |= 1;
303 						ptrn.position++;
304 						break;
305 					case OpCode.chain:
306 						ptrn.status.isRunning = false;
307 						ptrn.status.hasEnded = true;
308 						initNewPattern((data.bytes[1]<<24) | data.hwords[1], PATTERN_SLOT_INACTIVE_ID);
309 						return;
310 					case OpCode.cue:
311 						ptrn.lastCue = (data.bytes[1]<<24) | data.hwords[1];
312 						break;
313 					default:
314 						errors.badOpcode = true;
315 						if (status.cfg_StopOnError) {
316 							status.play = false;
317 							return;
318 						}
319 						break;
320 				}
321 			}
322 			exitLoop:
323 			if (patternData.length >= ptrn.position && !hasUsefulDataLeft(patternData[ptrn.position..$])) {	//Free up pattern slot if ended or has no useful data left.
324 				ptrn.status.isRunning = false;
325 				ptrn.status.hasEnded = true;
326 				if (ptrn.backLink != PATTERN_SLOT_INACTIVE_ID) {
327 					foreach (size_t i, ref M2PatternSlot ptrnSl ; songdata.ptrnSl) {
328 						if (ptrnSl.id == ptrn.backLink) {
329 							ptrn.status.suspend = false;
330 						}
331 					}
332 				}
333 			}
334 		}
335 	}
336 	///Initializes new pattern with the given ID
337 	private void initNewPattern(uint patternID, uint backLink) @nogc nothrow {
338 		foreach (size_t i, ref M2PatternSlot ptrnSl ; songdata.ptrnSl) {
339 			if (ptrnSl.id == PATTERN_SLOT_INACTIVE_ID || ptrnSl.status.hasEnded) {	//Search for lowest unused pattern slot
340 				ptrnSl.reset;
341 				uint[] ptrnData = songdata.ptrnData[patternID];
342 				if (ptrnData.length) {
343 					ptrnSl.id = patternID;
344 					ptrnSl.backLink = backLink;
345 				} else {								//Handle errors for potential references to nonexistent patterns
346 					errors.unrecognizedPattern = true;
347 					if (status.cfg_StopOnError) {
348 						status.play = false;
349 					}
350 				}
351 				return;
352 			}
353 		}
354 		errors.outOfPatternSlots = true;
355 		if (status.cfg_StopOnError) {
356 			status.play = false;
357 		}
358 	}
359 	///Returns true if there's still useful for the sequencer.
360 	private bool hasUsefulDataLeft(uint[] patternData) @nogc nothrow pure const {
361 		if (patternData.length == 0) return false;
362 		foreach (key; patternData) {
363 			DataReaderHelper data = DataReaderHelper(key);
364 			if (data.bytes[0] != 0x00 || data.bytes[0] != 0xff) return true;    //Has at least one potential command.
365 		}
366 		return false;
367 	}
368 	/** 
369 	 * Emits MIDI data to the target module. Takes care of data length, transposing (TODO: implement), etc.
370 	 */
371 	private void emitMIDIData(uint[] data, uint targetID) @nogc nothrow {
372 		uint pos;
373 		AudioModule am = modTrgt[targetID];
374 		if (am is null) {
375 			errors.unrecognizedCode = true;
376 			if (status.cfg_StopOnError) status.play = false;
377 			return;
378 		}
379 		while (pos < data.length) {
380 			UMP midiPck;
381 			midiPck.base = data[pos];
382 			const uint cmdSize = umpSizes[midiPck.msgType]>>4;
383 			if (cmdSize + pos >= data.length) {
384 				errors.illegalMIDICmd = true;
385 				if (status.cfg_StopOnError) status.play = false;
386 				return;
387 			} else {
388 				switch (cmdSize) {
389 					case 2:
390 						am.midiReceive(midiPck, data[pos + 1]);
391 						pos += 2;
392 						break;
393 					case 3:
394 						am.midiReceive(midiPck, data[pos + 1], data[pos + 2]);
395 						pos += 3;
396 						break;
397 					case 4:
398 						am.midiReceive(midiPck, data[pos + 1], data[pos + 2], data[pos + 3]);
399 						pos += 4;
400 						break;
401 					default:
402 						am.midiReceive(midiPck);
403 						pos += 1;
404 						break;
405 				}
406 			}
407 		}
408 	}
409 }