1 module pixelperfectengine.audio.m2.rw_text;
2 
3 public import pixelperfectengine.audio.m2.types;
4 import std.string;
5 import std.exception;
6 import std.array : split;
7 import std.uni : isWhite;
8 import std.math : isNaN;
9 import std.format.read : formattedRead;
10 import std.conv : to;
11 import std.algorithm.searching : canFind, startsWith, countUntil;
12 import collections.sortedlist;
13 
14 package string toAllCaps(string s) @safe nothrow {
15 	string result;
16 	result.reserve = s.length;
17 	for (int i = 0; i < s.length ; i++) {
18 		result ~= s[i] >= 0x61 && s[i] <= 0x7A ? s[i] & 0x5F : s[i];
19 	}
20 	return result;
21 }
22 package long parsenum(string s) {
23 	if (s.startsWith("0x")) {
24 		return s[2..$].to!long(16);
25 	} else {
26 		return s.to!long;
27 	}
28 }
29 package string removeComment(string s) {
30 	const ptrdiff_t commentPos = countUntil(s, ";");
31 	if (commentPos == -1) return s;
32 	return s[0..commentPos];
33 }
34 package int parseNote(string n) {
35 	//return cast(int)countUntil(NOTE_LOOKUP_TABLE, n);
36 	n = toAllCaps(n);
37 	for (int i = 0; i < 128 ; i++) {
38 		if (NOTE_LOOKUP_TABLE[i] == n) return i;
39 	}
40 	return cast(int)parsenum(n);
41 }
42 package int parseRegister(const string s) {
43 	if (s[0] == 'R') {
44 		try
45 			return s[1..$].to!int(16);
46 		catch (Exception e) {}
47 	}
48 	return -1;
49 }
50 package double getRhythmDur(const char c) @nogc @safe pure nothrow {
51 	switch (c) {
52 		case 'W': return 1.0;
53 		case 'H': return 1.0 / 2;
54 		case 'Q': return 1.0 / 4;
55 		case 'O': return 1.0 / 8;
56 		case 'S': return 1.0 / 16;
57 		case 'T': return 1.0 / 32;
58 		case 'I': return 1.0 / 64;
59 		case 'X': return 1.0 / 128;
60 		case 'Y': return 1.0 / 256;
61 		case 'Z': return 1.0 / 512;
62 		case 'U': return 1.0 / 1024;
63 		default: return double.nan;
64 	}
65 }
66 package double getTupletDiv(const string c, const double base) @nogc @safe pure nothrow {
67 	switch (c) {
68 		default: return base;
69 		case "2": return base * 3 / 2;
70 		case "3": return base * 2 / 3;
71 		case "4": return base * 3 / 4;
72 		case "5": return base * 4 / 5;
73 		case "6": return base * 4 / 6;
74 		case "7": return base * 4 / 7;
75 		case "8": return base * 6 / 8;
76 		case "9": return base * 8 / 9;
77 		case "10": return base * 8 / 10;
78 		case "11": return base * 8 / 11;
79 		case "12": return base * 8 / 12;
80 		case "13": return base * 8 / 13;
81 		case "14": return base * 8 / 14;
82 		case "15": return base * 8 / 15;
83 	}
84 }
85 package ulong parseRhythm(string n, float bpm, long timebase) {
86 	const long whNoteLen = cast(long)((1_000_000_000 / cast(double)timebase) * (15 / bpm));
87 	double duration = 0.0;
88 	n = toAllCaps(n);
89 	string[] indiv = n.split("+");
90 	foreach (string part ; indiv) {
91 		int pos;
92 		double currDur = 0.0;
93 		if (part[0] >= '1' || part[0] <= '9') {
94 			if (part[1] >= '0' || part[1] <= '5') {
95 				currDur = getTupletDiv(part[0..2], getRhythmDur(part[2]));
96 				pos = 3;
97 			} else {
98 				currDur = getTupletDiv(part[0..1], getRhythmDur(part[1]));
99 				pos = 2;
100 			}
101 		} else {
102 			currDur = getRhythmDur(part[0]);
103 			pos = 1;
104 		}
105 		for (int i = 1 ; pos < part.length ; i++, pos++) {
106 			if(part[pos] == '.')
107 				currDur *= 1 + (0.5 * i);
108 			else
109 				currDur = double.nan;
110 		}
111 		duration += currDur;
112 	}
113 	enforce (!isNaN(duration), "Rhythm syntax error");
114 	return cast(ulong)(duration * whNoteLen);
115 }
116 ///Reads textual M2 files and compiles them into binary.
117 public M2File loadM2FromText(string src) {
118 	
119 	enum Context {
120 		init,
121 		headerParse,
122 		metadataParse,
123 		deviceParse,
124 		patternParse,
125 		stringParse,
126 	}
127 	struct PatternData {
128 		string name;
129 		size_t lineNum;
130 		uint lineLen;
131 		float currBPM = 120;
132 		size_t[string] positionLabels;
133 	}
134 	struct NoteData {
135 		uint device;
136 		ubyte ch;
137 		ubyte note;
138 		ushort velocity;
139 		long durFrom;
140 		long durTo;
141 		bool opEquals(const NoteData other) @nogc @safe pure nothrow const {
142 			return this.device == other.device && this.ch == other.ch && this.note == other.note && 
143 					this.velocity == other.velocity && this.durFrom == other.durFrom && this.durTo == other.durTo;
144 		}
145 		int opCmp(const NoteData other) @nogc @safe pure nothrow const {
146 			if (this.durTo > other.durTo) return 1;
147 			if (this.durTo < other.durTo) return -1;
148 			return 0;
149 		}
150 		size_t toHash() const @nogc @safe pure nothrow {
151 			return durFrom ^ durTo;
152 		}
153 	}
154 	M2File result;
155 	Context context, prevContext;
156 	string parsedString;
157 	string[] lines = src.splitLines;
158 	PatternData[] ptrnData;
159 	sizediff_t searchPatternByName(const string name) @nogc @safe pure nothrow const {
160 		for (sizediff_t i = 0; i < ptrnData.length ; i++) {
161 			if (ptrnData[i].name == name)
162 				return i;
163 		}
164 		return -1;
165 	}
166 	//Validate file
167 	enforce(lines[0][0..12] == "MIDI2.0 VER " && lines[0][12] == '1', "Wrong version or file!");
168 	//First pass: parse header, etc.
169 	for (size_t lineNum = 1 ; lineNum < lines.length ; lineNum++) {
170 		string[] words = removeComment(lines[lineNum]).split!isWhite();
171 		if (!words.length) continue;
172 		switch (context) {
173 			case Context.patternParse:
174 				if (words[0] == "END") {	//Calculate line numbers then close current pattern parsing.
175 					ptrnData[$-1].lineLen = cast(uint)(lineNum - ptrnData[$-1].lineNum - 1);
176 					context = Context.init;
177 				} else if (startsWith(words[0], "@")) {
178 					ptrnData[$-1].positionLabels[words[0][1..$]] = lineNum;
179 				}
180 				break;
181 			case Context.headerParse:
182 				switch (words[0]) {
183 					case "timeFormatID":
184 						switch (words[1]) {
185 							case "ms", "fmt0":
186 								result.timeFormat = M2TimeFormat.ms;
187 								break;
188 							case "us", "fmt1":
189 								result.timeFormat = M2TimeFormat.us;
190 								break;
191 							case "hns", "fmt2":
192 								result.timeFormat = M2TimeFormat.hns;
193 								break;
194 							case "fmt3":
195 								result.timeFormat = M2TimeFormat.fmt3;
196 								break;
197 							case "fmt4":
198 								result.timeFormat = M2TimeFormat.fmt4;
199 								break;
200 							case "fmt5":
201 								result.timeFormat = M2TimeFormat.fmt5;
202 								break;
203 							default:
204 								throw new Exception("Unrecognized format!");
205 						}
206 						break;
207 					case "timeFormatPeriod":
208 						result.timeFrmtPer = cast(uint)parsenum(words[1]);
209 						break;
210 					case "timeFormatRes":
211 						result.timeFrmtRes = cast(uint)parsenum(words[1]);
212 						break;
213 					case "maxPattern":
214 						result.patternNum = cast(ushort)parsenum(words[1]);
215 						break;
216 					case "END":
217 						context = Context.init;
218 						break;
219 					default:
220 						break;
221 				}
222 				break;
223 			case Context.metadataParse:
224 				if (words[0] == "END") context = Context.init;
225 				break;
226 			case Context.deviceParse:
227 				if (words[0] == "END") context = Context.init;
228 				else {
229 					const ushort devID = cast(ushort)parsenum(words[$ - 1]);
230 					result.devicelist[devID] = words[0][0..$ - 1];
231 				}
232 				break;
233 			default:
234 				switch (words[0]) {	
235 					case "HEADER":
236 						context = Context.headerParse;
237 						break;
238 					case "METADATA":
239 						context = Context.metadataParse;
240 						break;
241 					case "DEVLIST":
242 						context = Context.deviceParse;
243 						break;
244 					case "PATTERN":
245 						context = Context.patternParse;
246 						ptrnData ~= PatternData(words[1], lineNum, 0);
247 						break;
248 
249 					default:
250 						break;
251 				}
252 				break;
253 		}
254 			
255 	}
256 	//check if a main pattern is present, raise an error if it doesn't exist, or set it as the first if it's not already
257 	if (ptrnData[0].name != "main") {
258 		bool mainExists;
259 		for (int i = 1 ; i < ptrnData.length ; i++) {
260 			if (ptrnData[i].name == "main") {
261 				mainExists = true;
262 				ptrnData = ptrnData[i] ~ ptrnData[0..i] ~ ptrnData[i + 1..$];
263 				break;
264 			}
265 		}
266 		enforce(mainExists, "Entry point pattern doesn't exists");
267 	}
268 	//Initialize song data
269 	result.songdata = M2Song(result.patternNum, result.timeFormat, result.timeFrmtPer, result.timeFrmtRes);
270 	//Second pass: parse patterns
271 	foreach (size_t i, PatternData key; ptrnData) {
272 		//NoteData[] noteMacroHandler;
273 		SortedList!(NoteData) noteMacroHandler;
274 		uint[] currEmitStr;
275 		uint currDevNum;
276 		float bpm = 120;
277 		ulong timepos;
278 		void flushEmitStr() {
279 			if (currEmitStr.length) {
280 				auto ptrnData = result.songdata.ptrnData.ptrOf(cast(uint)i);
281 				*ptrnData ~= [0x03_00_00_00 | (cast(uint)(currEmitStr.length<<16)) | currDevNum] ~ currEmitStr;
282 				currEmitStr.length = 0;
283 			}
284 		}
285 		void insertWaitCmd(ulong amount) {
286 			if (amount) {
287 				auto ptrnData = result.songdata.ptrnData.ptrOf(cast(uint)i);
288 				if (amount <= 0xFF_FF_FF) {	//Short wait
289 					*ptrnData ~= 0x01_00_00_00 | cast(uint)amount;
290 				} else {					//Long wait
291 					*ptrnData ~= [0x02_00_00_00 | cast(uint)(amount>>24), cast(uint)amount];
292 				}
293 			}
294 		}
295 		void insertCmd(uint[] cmdStr) {
296 			if (cmdStr.length) {
297 				auto ptrnData = result.songdata.ptrnData.ptrOf(cast(uint)i);
298 				*ptrnData ~= cmdStr;
299 			}
300 		}
301 		void insertJmpCmd(const sizediff_t currLineNum, uint cmdCode, string[] words) {
302 			const int targetAm = cast(int)(key.positionLabels[words[1]] - currLineNum);
303 			const uint conditionMask = cast(uint)parsenum(words[0]);
304 			insertCmd([cmdCode, conditionMask, targetAm]);
305 		}
306 		void insertMathCmd(const uint cmdCode, string[] words) {
307 			enforce(words.length == 3, "Incorrect number of registers");
308 			const int ra = parseRegister(words[0]);
309 			const int rb = parseRegister(words[1]);
310 			const int rd = parseRegister(words[2]);
311 			enforce((ra|rb|rd) <= -1, "Bad register number");
312 			insertCmd([cmdCode | (ra<<16) | (rb<<8) | rd]);
313 		}
314 		void insertShImmCmd(const uint cmdCode, string[] words) {
315 			enforce(words.length == 3, "Incorrect number of registers");
316 			const int ra = parseRegister(words[0]);
317 			const int rb = cast(int)parsenum(words[1]);
318 			const int rd = parseRegister(words[2]);
319 			enforce((ra|rd) <= -1, "Bad register number");
320 			enforce(rb <= 31 && rb >= 0, "Bad immediate amount");
321 			insertCmd([cmdCode | (ra<<16) | (rb<<8) | rd]);
322 		}
323 		void insertTwoOpCmd(const uint cmdCode, string[] words) {
324 			enforce(words.length == 2, "Incorrect number of registers");
325 			const int ra = parseRegister(words[0]);
326 			const int rd = parseRegister(words[1]);
327 			enforce((ra|rd) <= -1, "Bad register number");
328 			insertCmd([cmdCode | (ra<<16) | rd]);
329 		}
330 		void insertCmpInstr(const uint cmdCode, string[] words) {
331 			enforce(words.length == 2, "Incorrect number of registers");
332 			const int ra = parseRegister(words[0]);
333 			const int rb = parseRegister(words[1]);
334 			enforce((ra|rb) <= -1, "Bad register number");
335 			insertCmd([cmdCode | (ra<<8) | rb]);
336 		}
337 		void insertMIDI2Cmd(bool longfield, bool note)(const uint cmdCode, string chField, string upperField, 
338 				string lowerField, string valueField, string aux) {
339 			uint emitWithRegVal;
340 			int rCh, rNote, rValue, rAux = -1;
341 			uint value, upper, lower, channel;
342 			static if (note) {
343 				rValue = parseRegister(valueField);
344 				if (rValue != -1) emitWithRegVal |= 0x08;
345 				else value = (cast(uint)parsenum(valueField))<<16;
346 				if (aux.length) {
347 					if (aux.length >= 4) {
348 						switch (aux[0..2]) {
349 							case "ms": lower = 0x01; break;
350 							case "ps": lower = 0x02; break;
351 							case "pt": lower = 0x03; break;
352 							default: break;
353 						}
354 						rAux = parseRegister(aux[3..$]);
355 						if (rAux != -1) emitWithRegVal |= 0x01;
356 						else value |= cast(uint)parsenum(aux[3..$]);
357 					}
358 				}
359 			} else {
360 				rValue = parseRegister(valueField);
361 				if (rValue != -1) emitWithRegVal |= 0x08;
362 				else value = cast(uint)parsenum(valueField);
363 			}
364 			static if (longfield) {
365 				rNote = parseRegister(upperField);
366 				if (rNote != -1) emitWithRegVal |= 0x04;
367 				else {
368 					const uint lf = cast(uint)parsenum(upperField);
369 					lower = lf & 0x7F;
370 					upper = (lf>>7)<<8;
371 				}
372 			} else {
373 				if (upperField.length) {
374 					rNote = parseRegister(upperField);
375 					if (rNote != -1) emitWithRegVal |= 0x04;
376 					else {
377 						static if (note) {
378 							upper = cast(uint)parseNote(upperField)<<8;
379 						} 
380 					}
381 				}
382 				if (lowerField.length) {
383 					rAux = parseRegister(lowerField);
384 					if (rAux != -1) emitWithRegVal |= 0x01;
385 					else lower = cast(uint)parsenum(lowerField);
386 				}
387 			}
388 			rCh = parseRegister(chField);
389 			if (rCh != -1) emitWithRegVal |= 0x02;
390 			else channel = cast(uint)parsenum(chField);
391 			if (emitWithRegVal) {
392 				flushEmitStr();
393 				insertCmd([0x42_00_00_00 | currDevNum | (rValue<<16), (rNote<<24) | (rCh<<16) | (rAux<<8) | emitWithRegVal,
394 						cmdCode | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | upper | lower, value]);
395 			} else {
396 				currEmitStr ~= [cmdCode | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | upper | lower, value];
397 			}
398 		}
399 		for (sizediff_t lineNum = key.lineNum ; lineNum < key.lineNum + key.lineLen ; lineNum++) {
400 			string[] words = removeComment(lines[lineNum]).split!isWhite();
401 			if (!words.length) continue;//If line is used as padding or just for comments, don't try to parse it
402 			if (words[0][0] == '$') {	//parse MIDI emit commands
403 				const sizediff_t f = countUntil(words[0], '['), t =countUntil(words[0], ']');
404 				const uint deviceNum = cast(uint)parsenum(words[0][f + 1..t]);
405 				enforce(deviceNum <= 65_535, "Device number too large");
406 				if (currEmitStr.length > 251 || currDevNum != deviceNum) flushEmitStr();	//flush emit string if it's not guaranteed that a 4 word long data won't fit, or device isn't equal
407 				currDevNum = deviceNum;
408 				switch (words[1]) {
409 					case "note":		//note macro, inserts a note on, then a note off command after a set time
410 						const uint channel = cast(uint)parsenum(words[2]);
411 						const uint vel = cast(uint)parsenum(words[3]);
412 						enforce(channel <= 255, "Channel number too high");
413 						enforce(vel <= 65_535, "Velocity number too high");
414 						if (words[4][0] == '~') {	//use the same duration to all the notes
415 							const ulong noteDur = parseRhythm(words[4][1..$], bpm, result.songdata.timebase);
416 							for (int j = 5 ; i < words.length ; i++) {
417 								const uint note = parseNote(words[j]);
418 								currEmitStr ~= [0x40_90_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8), vel<<16];
419 								noteMacroHandler.put(NoteData(deviceNum, cast(ubyte)channel, cast(ubyte)note, cast(ushort)vel, timepos, 
420 										timepos + noteDur));
421 							}
422 						} else {	//each note should have their own duration
423 							for (int j = 4 ; j < words.length ; i++) {
424 								string[] notebase = words[j].split(":");
425 								const ulong noteDur = parseRhythm(notebase[0], bpm, result.songdata.timebase);
426 								const uint note = parseNote(notebase[i]);
427 								currEmitStr ~= [0x40_90_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8), vel<<16];
428 								noteMacroHandler.put(NoteData(deviceNum, cast(ubyte)channel, cast(ubyte)note, cast(ushort)vel, timepos, 
429 										timepos + noteDur));
430 							}
431 						}
432 						break;
433 					//MIDI 1.0 begin
434 					case "m1_nf":		//MIDI 1.0 note off
435 						const uint channel = cast(uint)parsenum(words[2]);
436 						const uint note = parseNote(words[3]);
437 						const uint vel = cast(uint)parsenum(words[4]);
438 						enforce(channel <= 255, "Channel number too high");
439 						enforce(vel <= 127, "Velocity number too high");
440 						currEmitStr ~= [0x20_80_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8) | vel];
441 						break;
442 					case "m1_nn":		//MIDI 1.0 note on
443 						const uint channel = cast(uint)parsenum(words[2]);
444 						const uint note = parseNote(words[3]);
445 						const uint vel = cast(uint)parsenum(words[4]);
446 						enforce(channel <= 255, "Channel number too high");
447 						enforce(vel <= 127, "Velocity number too high");
448 						currEmitStr ~= [0x20_90_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8) | vel];
449 						break;
450 					case "m1_ppres":		//MIDI 1.0 poly pressure
451 						const uint channel = cast(uint)parsenum(words[2]);
452 						const uint note = parseNote(words[3]);
453 						const uint vel = cast(uint)parsenum(words[4]);
454 						enforce(channel <= 255, "Channel number too high");
455 						enforce(vel <= 127, "Velocity number too high");
456 						currEmitStr ~= [0x20_A0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8) | vel];
457 						break;
458 					case "m1_cc":			//MIDI 1.0 control change
459 						const uint channel = cast(uint)parsenum(words[2]);
460 						const uint num = cast(uint)parsenum(words[3]);
461 						const uint vel = cast(uint)parsenum(words[4]);
462 						enforce(channel <= 255, "Channel number too high");
463 						enforce(vel <= 127, "Velocity number too high");
464 						enforce(num <= 127, "Control number too high");
465 						currEmitStr ~= [0x20_B0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (num<<8) | vel];
466 						break;
467 					case "m1_pc":			//MIDI 1.0 program change
468 						const uint channel = cast(uint)parsenum(words[2]);
469 						const uint num = cast(uint)parsenum(words[3]);
470 						//const uint vel = cast(uint)parsenum(words[4]);
471 						enforce(channel <= 255, "Channel number too high");
472 						//enforce(vel <= 127, "Velocity number too high");
473 						enforce(num <= 127, "Program number too high");
474 						currEmitStr ~= [0x20_C0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (num<<8)];
475 						break;
476 					case "m1_cpres":			//MIDI 1.0 channel pressure
477 						const uint channel = cast(uint)parsenum(words[2]);
478 						const uint vel = cast(uint)parsenum(words[3]);
479 						//const uint vel = cast(uint)parsenum(words[4]);
480 						enforce(channel <= 255, "Channel number too high");
481 						enforce(vel <= 127, "Velocity number too high");
482 						//enforce(num <= 127, "Program number too high");
483 						currEmitStr ~= [0x20_D0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (vel<<8)];
484 						break;
485 					case "m1_pb":		//MIDI 1.0 pitch bend
486 						const uint channel = cast(uint)parsenum(words[2]);
487 						const uint amount = cast(uint)parsenum(words[3]);
488 						enforce(channel <= 255, "Channel number too high");
489 						enforce(amount <= 16_383, "Velocity number too high");
490 						currEmitStr ~= [0x20_E0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | ((amount<<1) & 0x7f_00) | 
491 								(amount & 0x7F)];
492 						break;
493 					//MIDI 1.0 end
494 					//MIDI 2.0 begin
495 					case "nf":			//MIDI note off
496 						/* const uint channel = cast(uint)parsenum(words[2]);
497 						const uint note = parseNote(words[3]);
498 						const uint vel = cast(uint)parsenum(words[4]);
499 						enforce(channel <= 255, "Channel number too high");
500 						enforce(vel <= 65_535, "Velocity number too high");
501 						currEmitStr ~= [0x20_80_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8), vel<<16]; */
502 						string auxField;
503 						if (words.length == 6) auxField = words[5];
504 						insertMIDI2Cmd!(false, true)(0x20_80_00_00, words[2], words[3], null, words[4], auxField);
505 						break;
506 					case "nn":			//MIDI note on
507 						/* const uint channel = cast(uint)parsenum(words[2]);
508 						const uint note = parseNote(words[3]);
509 						const uint vel = cast(uint)parsenum(words[4]);
510 						enforce(channel <= 255, "Channel number too high");
511 						enforce(vel <= 65_535, "Velocity number too high");
512 						currEmitStr ~= [0x20_90_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8), vel<<16]; */
513 						string auxField;
514 						if (words.length == 6) auxField = words[5];
515 						insertMIDI2Cmd!(false, true)(0x20_90_00_00, words[2], words[3], null, words[4], auxField);
516 						break;
517 					case "ppres":		//Poly aftertouch
518 						/* const uint channel = cast(uint)parsenum(words[2]);
519 						const uint note = parseNote(words[3]);
520 						const uint vel = cast(uint)parsenum(words[4]);
521 						enforce(channel <= 255, "Channel number too high");
522 						currEmitStr ~= [0x20_A0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8), vel]; */
523 						insertMIDI2Cmd!(false, false)(0x20_A0_00_00, words[2], words[3], null, words[4], null);
524 						break;
525 					case "pccr":		//Poly registered per-note controller change
526 						/* const uint channel = cast(uint)parsenum(words[2]);
527 						const uint note = parseNote(words[3]);
528 						const uint index = cast(uint)parsenum(words[4]);
529 						const uint val = cast(uint)parsenum(words[5]);
530 						enforce(channel <= 255, "Channel number too high");
531 						enforce(index <= 255, "Index number too high");
532 						currEmitStr ~= [0x20_00_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8) | index, val]; */
533 						insertMIDI2Cmd!(false, false)(0x20_00_00_00, words[2], words[3], words[4], words[5], null);
534 						break;
535 					case "pcca":		//Poly assignable per-note controller change
536 						/* const uint channel = cast(uint)parsenum(words[2]);
537 						const uint note = parseNote(words[3]);
538 						const uint index = cast(uint)parsenum(words[4]);
539 						const uint val = cast(uint)parsenum(words[5]);
540 						enforce(channel <= 255, "Channel number too high");
541 						enforce(index <= 255, "Index number too high");
542 						currEmitStr ~= [0x20_10_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8) | index, val]; */
543 						insertMIDI2Cmd!(false, false)(0x20_10_00_00, words[2], words[3], words[4], words[5], null);
544 						break;
545 					case "pnoteman":	//Poly management message
546 						const uint channel = cast(uint)parsenum(words[2]);
547 						const uint note = parseNote(words[3]);
548 						uint option;
549 						if (words.length >= 5) {
550 							option |= countUntil(words[4], "S", "s") != -1 ? 0x01 : 0;
551 							option |= countUntil(words[4], "D", "d") != -1 ? 0x02 : 0;
552 						}
553 						enforce(channel <= 255, "Channel number too high");
554 						currEmitStr ~= [0x20_F0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8) | option, 0];
555 						break;
556 					case "ccl":			//Legacy controller change
557 						/* const uint channel = cast(uint)parsenum(words[2]);
558 						const uint index = cast(uint)parsenum(words[3]);
559 						const uint val = cast(uint)parsenum(words[4]);
560 						enforce(channel <= 255, "Channel number too high");
561 						enforce(index <= 127, "Index number too high");
562 						currEmitStr ~= [0x20_B0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (index<<8), val]; */
563 						insertMIDI2Cmd!(false, false)(0x20_B0_00_00, words[2], words[3], null, words[4], null);
564 						break;
565 					case "ccr":
566 						insertMIDI2Cmd!(true, false)(0x20_20_00_00, words[2], words[3], null, words[4], null);
567 						break;
568 					case "cc":
569 						insertMIDI2Cmd!(true, false)(0x20_30_00_00, words[2], words[3], null, words[4], null);
570 						break;
571 					case "rccr":
572 						insertMIDI2Cmd!(true, false)(0x20_40_00_00, words[2], words[3], null, words[4], null);
573 						break;
574 					case "rcc":
575 						insertMIDI2Cmd!(true, false)(0x20_50_00_00, words[2], words[3], null, words[4], null);
576 						break;
577 					/* case "cc", "ccr", "rcc", "rccr"://Controller change commands
578 						const uint channel = cast(uint)parsenum(words[2]);
579 						const uint index = cast(uint)parsenum(words[3]);
580 						const uint val = cast(uint)parsenum(words[4]);
581 						const uint cmdNum = words[1] == "ccr" ? 0x20_20_00_00 : 
582 								(words[1] == "cc" ? 0x20_30_00_00 : 
583 								(words[1] == "rccr" ? 0x20_40_00_00 : 0x20_50_00_00));
584 						enforce(channel <= 255, "Channel number too high");
585 						enforce(index <= 16_383, "Index number too high");
586 						currEmitStr ~= [cmdNum | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | 
587 								((index & 0x3F_80)<<1) | (index & 0x7F), val];
588 						break; */
589 					case "pc":			//Program change
590 						const uint channel = cast(uint)parsenum(words[2]);
591 						const uint prg = cast(uint)parsenum(words[3]);
592 						uint option, bank;
593 						if (words.length >= 5) {
594 							option = 1;
595 							const uint bank0 = cast(uint)parsenum(words[4]);
596 							enforce(bank0 <= 16_383, "Bank number too high");
597 							bank = ((bank0 & 0x3F_80)<<1) | (bank0 & 0x7F);
598 						}
599 						enforce(prg <= 127, "Program number too high");
600 						currEmitStr ~= [0x20_C0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (prg<<8) | option, 
601 								(prg<<24) | bank];
602 						break;
603 					case "cpres":		//Channel aftertouch
604 						/* const uint channel = cast(uint)parsenum(words[2]);
605 						const uint val = cast(uint)parsenum(words[3]);
606 						enforce(channel <= 255, "Channel number too high");
607 						currEmitStr ~= [0x20_D0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16), val]; */
608 						insertMIDI2Cmd!(false, false)(0x20_D0_00_00, words[2], null, null, words[3], null);
609 						break;
610 					case "pb":			//Pitch bend
611 						/* const uint channel = cast(uint)parsenum(words[2]);
612 						const uint val = cast(uint)parsenum(words[3]);
613 						enforce(channel <= 255, "Channel number too high");
614 						currEmitStr ~= [0x20_E0_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16), val]; */
615 						insertMIDI2Cmd!(false, false)(0x20_E0_00_00, words[2], null, null, words[3], null);
616 						break;
617 					case "ppb":			//Poly pitch bend
618 						/* const uint channel = cast(uint)parsenum(words[2]);
619 						const uint note = parseNote(words[3]);
620 						const uint val = cast(uint)parsenum(words[4]);
621 						enforce(channel <= 255, "Channel number too high");
622 						currEmitStr ~= [0x20_60_00_00 | ((channel & 0xF0)<<20) | ((channel & 0x0F)<<16) | (note<<8), val]; */
623 						insertMIDI2Cmd!(false, false)(0x20_60_00_00, words[2], words[3], null, words[4], null);
624 						break;
625 					//MIDI 2.0 end
626 					default:
627 						break;
628 				}
629 			} else {					//parse any other command
630 				//flush emit string to command list before moving onto parsing any other data.
631 				flushEmitStr();
632 				switch (words[0]) {
633 					case "wait":		//parse wait command
634 						ulong amount;
635 						try {	//Try to parse it as a number
636 							amount = parsenum(words[1]);
637 						} catch (Exception e) {	//It is not a number, try to parse it as a rhythm
638 							amount = parseRhythm(words[1], bpm, result.songdata.timebase);
639 						}
640 						//go through all the note macros if any of them have expired, and insert one or more wait commands if needed
641 						do {
642 							flushEmitStr();
643 							size_t num;
644 							ulong lowestAmount;
645 							for (size_t j ; j < noteMacroHandler.length ; j++) {	//search for few first elements with the same wait amounts
646 								if (noteMacroHandler[i].durTo <= timepos + amount) {
647 									if (!lowestAmount) {
648 										lowestAmount = (timepos + amount) - noteMacroHandler[j].durTo;
649 									} else if (lowestAmount != noteMacroHandler[j].durTo) {
650 										break;
651 									}
652 									num = j + 1;
653 								} else {
654 									break;
655 								}
656 							}
657 							for (size_t j ; j < num ; j++) {						//if there are expired note macros: put noteoff commands into the emit string and remove them from the list
658 								NoteData nd = noteMacroHandler.remove(0);
659 								currEmitStr ~= [0x40_A0_00_00 | ((nd.ch & 0xF0)<<20) | ((nd.ch & 0x0F)<<16) | (nd.note<<8), nd.velocity<<16];
660 							}
661 							if (num == 0) {											//if there's no (more) expired note macros, then just simply emit a wait command with the current amount
662 								insertWaitCmd(amount);
663 								timepos += amount;
664 								amount = 0;
665 							} else {
666 								insertWaitCmd(lowestAmount);
667 								timepos += lowestAmount;
668 								amount -= lowestAmount;
669 							}
670 						} while(amount);
671 						break;
672 					case "chain-par":
673 						sizediff_t refPtrnID = searchPatternByName(words[1]);
674 						enforce(refPtrnID != -1, "Pattern not found");
675 						insertCmd([0x05_00_00_00 | cast(uint)refPtrnID]);
676 						break;
677 					case "chain-ser":
678 						sizediff_t refPtrnID = searchPatternByName(words[1]);
679 						enforce(refPtrnID != -1, "Pattern not found");
680 						insertCmd([0x06_00_00_00 | cast(uint)refPtrnID]);
681 						break;
682 					case "chain":
683 						sizediff_t refPtrnID = searchPatternByName(words[1]);
684 						enforce(refPtrnID != -1, "Pattern not found");
685 						insertCmd([0x41_00_00_00 | cast(uint)refPtrnID]);
686 						break;
687 					case "jmpnc", "jmp":
688 						insertJmpCmd(lineNum, 0x04_00_00_00, words[1..$]);
689 						break;
690 					case "jmpeq":
691 						insertJmpCmd(lineNum, 0x04_01_00_00, words[1..$]);
692 						break;
693 					case "jmpne":
694 						insertJmpCmd(lineNum, 0x04_02_00_00, words[1..$]);
695 						break;
696 					case "jmpsh":
697 						insertJmpCmd(lineNum, 0x04_03_00_00, words[1..$]);
698 						break;
699 					case "jmpop":
700 						insertJmpCmd(lineNum, 0x04_04_00_00, words[1..$]);
701 						break;
702 					case "add": 
703 						insertMathCmd(0x07_00_00_00, words[1..$]);
704 						break;
705 					case "sub":
706 						insertMathCmd(0x08_00_00_00, words[1..$]);
707 						break;
708 					case "mul":
709 						insertMathCmd(0x09_00_00_00, words[1..$]);
710 						break;
711 					case "div":
712 						insertMathCmd(0x0A_00_00_00, words[1..$]);
713 						break;
714 					case "mod":
715 						insertMathCmd(0x0B_00_00_00, words[1..$]);
716 						break;
717 					case "and":
718 						insertMathCmd(0x0C_00_00_00, words[1..$]);
719 						break;
720 					case "or":
721 						insertMathCmd(0x0D_00_00_00, words[1..$]);
722 						break;
723 					case "xor":
724 						insertMathCmd(0x0E_00_00_00, words[1..$]);
725 						break;
726 					case "not":
727 						insertTwoOpCmd(0x0F_00_00_00, words[1..$]);
728 						break;
729 					case "lshi": 
730 						insertShImmCmd(0x10_00_00_00, words[1..$]);
731 						break;
732 					case "rshi": 
733 						insertShImmCmd(0x11_00_00_00, words[1..$]);
734 						break;
735 					case "rasi": 
736 						insertShImmCmd(0x12_00_00_00, words[1..$]);
737 						break;
738 					case "adds": 
739 						insertMathCmd(0x13_00_00_00, words[1..$]);
740 						break;
741 					case "subs": 
742 						insertMathCmd(0x14_00_00_00, words[1..$]);
743 						break;
744 					case "muls": 
745 						insertMathCmd(0x15_00_00_00, words[1..$]);
746 						break;
747 					case "divs": 
748 						insertMathCmd(0x16_00_00_00, words[1..$]);
749 						break;
750 					case "lsh": 
751 						insertMathCmd(0x17_00_00_00, words[1..$]);
752 						break;
753 					case "rsh": 
754 						insertMathCmd(0x18_00_00_00, words[1..$]);
755 						break;
756 					case "ras": 
757 						insertMathCmd(0x19_00_00_00, words[1..$]);
758 						break;
759 					case "mov":
760 						insertTwoOpCmd(0x1A_00_00_00, words[1..$]);
761 						break;
762 					case "cmpeq":
763 						insertCmpInstr(0x40_01_00_00, words[1..$]);
764 						break;
765 					case "cmpne":
766 						insertCmpInstr(0x40_02_00_00, words[1..$]);
767 						break;
768 					case "cmpgt":
769 						insertCmpInstr(0x40_03_00_00, words[1..$]);
770 						break;
771 					case "cmpge":
772 						insertCmpInstr(0x40_04_00_00, words[1..$]);
773 						break;
774 					case "cmplt":
775 						insertCmpInstr(0x40_05_00_00, words[1..$]);
776 						break;
777 					case "cmple":
778 						insertCmpInstr(0x40_06_00_00, words[1..$]);
779 						break;
780 					case "cmpze":
781 						insertCmpInstr(0x40_07_00_00, words[1..$]);
782 						break;
783 					case "cmpnz":
784 						insertCmpInstr(0x40_08_00_00, words[1..$]);
785 						break;
786 					case "cmpng":
787 						insertCmpInstr(0x40_09_00_00, words[1..$]);
788 						break;
789 					case "cmppo":
790 						insertCmpInstr(0x40_0a_00_00, words[1..$]);
791 						break;
792 					case "cmpsgt":
793 						insertCmpInstr(0x40_0b_00_00, words[1..$]);
794 						break;
795 					case "cmpsge":
796 						insertCmpInstr(0x40_0c_00_00, words[1..$]);
797 						break;
798 					case "cmpslt":
799 						insertCmpInstr(0x40_0d_00_00, words[1..$]);
800 						break;
801 					case "cmpsle":
802 						insertCmpInstr(0x40_0e_00_00, words[1..$]);
803 						break;
804 					case "ctrl":
805 						break;
806 					case "display":
807 						break;
808 					default:
809 						break;
810 				}
811 			}
812 			
813 		}
814 	}
815 	return result;
816 }