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 }