private static void ProcessCommands(Command[] commands, ref int currentCommandIndex, int depth) { if (depth > Constants.MaxProcessingStack) { throw new Exception(Resources.ProcessingStackOverflow); } if (depth < 0) { throw new ArgumentOutOfRangeException(nameof(depth)); } while (currentCommandIndex < commands.Length) { Command currentCommand = commands[currentCommandIndex]; //Console.WriteLine(currentCommand.Name); if (currentCommand.Name.Equals(Constants.StartDefinition)) { currentCommandIndex++; ParseDefinition(commands, ref currentCommandIndex, false); } else if (currentCommand.Name.Equals(Constants.RedefineDefinition)) { currentCommandIndex++; ParseDefinition(commands, ref currentCommandIndex, true); } else if (currentCommand is IfCommand) { IfCommand ifCommand = currentCommand as IfCommand; FInteger intValue = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); if (intValue.Value == Constants.True) { int ifCommandIndex = 0; ProcessCommands(ifCommand.IfCommands.ToArray(), ref ifCommandIndex, depth + 1); } else { int elseCommandIndex = 0; ProcessCommands(ifCommand.ElseCommands.ToArray(), ref elseCommandIndex, depth + 1); } } else if (currentCommand.Name.Equals(Constants.Else)) { } else if (currentCommand.Name.Equals(Constants.EndIf)) { } else if (currentCommand is RepeatCommand) { RepeatCommand repeatCommand = currentCommand as RepeatCommand; FInteger fInteger; do { int repeatCommandIndex = 0; ProcessCommands(repeatCommand.RepeatCommands.ToArray(), ref repeatCommandIndex, depth + 1); fInteger = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); } while (fInteger.Value == Constants.False); } else if (currentCommand.Name.Equals(Constants.Until)) { } else if (currentCommand is LoopCommand) { LoopCommand loopCommand = currentCommand as LoopCommand; FInteger incrementor = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); FInteger currentValue = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); FInteger targetValue = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); while (currentValue.Value != targetValue.Value) { Stack.Push(new Cell(targetValue.GetBytes())); Stack.Push(new Cell(currentValue.GetBytes())); Stack.Push(new Cell(incrementor.GetBytes())); int loopCommandIndex = 0; ProcessCommands(loopCommand.LoopCommands.ToArray(), ref loopCommandIndex, depth + 1); incrementor = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); currentValue = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); targetValue = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); currentValue.Value += incrementor.Value; } } else if (currentCommand.Name.Equals(Constants.EndLoop)) { } else { if ((_ioMode == IoModeEnum.Hex) && (IsHexInteger(currentCommand.Name))) { int intValue; if (!int.TryParse(currentCommand.Name, System.Globalization.NumberStyles.HexNumber, null, out intValue)) { throw new Exception(string.Format(Resources.InvalidHexInteger, currentCommand)); } FInteger intConstant = new FInteger(intValue); Stack.Push(new Cell(intConstant.GetBytes())); } else if ((_ioMode == IoModeEnum.Decimal) && (IsInteger(currentCommand.Name))) { int intValue; if (!int.TryParse(currentCommand.Name, out intValue)) { throw new Exception(string.Format(Resources.InvalidInteger, currentCommand)); } FInteger intConstant = new FInteger(intValue); Stack.Push(new Cell(intConstant.GetBytes())); } else { float floatValue; if ((_ioMode == IoModeEnum.Fraction) && (IsFloat(currentCommand.Name))) { if (!float.TryParse(currentCommand.Name, out floatValue)) { throw new Exception(string.Format(Resources.InvalidFloat, currentCommand)); } FFloat intConstant = new FFloat(floatValue); Stack.Push(new Cell(intConstant.GetBytes())); } else if ((_ioMode == IoModeEnum.Char) && (IsChar(currentCommand.Name))) { string subString = currentCommand.Name.Substring(Constants.StartChar.Length, currentCommand.Name.Length - Constants.EndChar.Length - 1); char ch; if (!char.TryParse(subString, out ch)) { throw new Exception(string.Format(Resources.InvalidCharacter, subString)); } FInteger intConstant = new FInteger(ch); Stack.Push(new Cell(intConstant.GetBytes())); } else if (IsString(currentCommand.Name)) { string subString = currentCommand.Name.Substring(Constants.StartString.Length, currentCommand.Name.Length - Constants.EndString.Length - 2); Console.Write(subString); } else if (IsVariable(currentCommand.Name)) { Stack.Push(new Cell(new FInteger(Objects[currentCommand.Name].Item2).GetBytes())); } else if (IsValue(currentCommand.Name)) { byte[] bytes = new byte[Constants.CellSize]; Array.Copy(Memory, Objects[currentCommand.Name].Item2, bytes, 0, Constants.CellSize); Stack.Push(new Cell(bytes)); } else if (IsConstant(currentCommand.Name)) { byte[] bytes = new byte[Constants.CellSize]; Array.Copy(Memory, Objects[currentCommand.Name].Item2, bytes, 0, Constants.CellSize); Stack.Push(new Cell(bytes)); } else if (IsDefinition(currentCommand.Name)) { int definitionTokenIndex = 0; ProcessCommands(Definitions[currentCommand.Name].ToArray(), ref definitionTokenIndex, depth + 1); } else if (currentCommand.Name.Equals(Constants.ViewDefinitions)) { Console.WriteLine(); foreach (string key in Definitions.Keys) { Console.Write(Resources.Executor_ProcessCommands__StartDefinition, Constants.StartDefinition); Console.Write(string.Format(Resources.Executor_ProcessCommands__Key, key).PadRight(18, ' ')); foreach (Command command in Definitions[key]) { Console.Write(string.Format("{0} ", command.Name)); } Console.WriteLine(string.Format("{0}", Constants.EndDefinition)); } } else if (currentCommand.Name.Equals(Constants.ViewObjects)) { Console.WriteLine(); foreach (string key in Objects.Keys) { Console.Write(string.Format("{0}", key).PadRight(20, ' ')); Console.Write(string.Format("{0}\t", Objects[key].Item1)); Console.Write(string.Format("{0}\t", Objects[key].Item2)); byte[] bytes = FetchFromMemory(new FInteger(Objects[key].Item2)); Console.WriteLine(string.Format("{0}", GetOutputValue(bytes))); } } else if (currentCommand.Name.Equals(Constants.Help)) { Console.WriteLine(); foreach (string command in Constants.ValidCommands) { Console.WriteLine(command); } } else if (currentCommand.Name.Equals(Constants.Variable)) { if (currentCommandIndex >= commands.Length - 1) { throw new Exception(Resources.ExpectedAName); } currentCommandIndex++; string variableName = commands[currentCommandIndex].Name; if (IsAlreadyDefined(variableName)) { throw new Exception(string.Format(Resources.AlreadyDefined, variableName)); } AddObject(variableName, MemoryEntryEnum.Variable); } else if (currentCommand.Name.Equals(Constants.Value)) { if (currentCommandIndex >= commands.Length - 1) { throw new Exception(Resources.ExpectedAName); } currentCommandIndex++; string variableName = commands[currentCommandIndex].Name; if (IsAlreadyDefined(variableName)) { throw new Exception(string.Format(Resources.AlreadyDefined, variableName)); } AddObject(variableName, MemoryEntryEnum.Value, Stack.Pop().Bytes); } else if (currentCommand.Name.Equals(Constants.Constant)) { if (currentCommandIndex >= commands.Length - 1) { throw new Exception(Resources.ExpectedAName); } currentCommandIndex++; string variableName = commands[currentCommandIndex].Name; if (IsAlreadyDefined(variableName)) { throw new Exception(string.Format(Resources.AlreadyDefined, variableName)); } AddObject(variableName, MemoryEntryEnum.Constant, Stack.Pop().Bytes); } else if (currentCommand.Name.Equals(Constants.Store)) { FInteger memoryLocation = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); FInteger intValue = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); StoreToMemory(intValue, memoryLocation); } else if (currentCommand.Name.Equals(Constants.Fetch)) { FInteger memoryLocation = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); byte[] bytes = FetchFromMemory(memoryLocation); FInteger intValue = new FInteger(BitConverter.ToInt32(bytes, 0)); Stack.Push(new Cell(intValue.GetBytes())); } else if (currentCommand.Name.Equals(Constants.To)) { if (currentCommandIndex >= commands.Length - 1) { throw new Exception(Resources.ExpectedAName); } currentCommandIndex++; string valueName = commands[currentCommandIndex].Name; if (!IsValue(valueName)) { throw new Exception(string.Format(Resources.NameIsNotAValue, valueName)); } SetObject(Objects[valueName].Item2, Stack.Pop().Bytes); } else if (currentCommand.Name.Equals(Constants.Cell)) { Stack.Push(new Cell(new FInteger(Constants.CellSize).GetBytes())); } else if (currentCommand.Name.Equals(Constants.Here)) { Stack.Push(new Cell(new FInteger(_memoryPointer).GetBytes())); } else if (currentCommand.Name.Equals(Constants.Allot)) { FInteger memoryAllocBytes = new FInteger(BitConverter.ToInt32(Stack.Pop().Bytes, 0)); if (_memoryPointer + memoryAllocBytes.Value < 0) { throw new Exception(Resources.MemoryUnderflow); } else if (_memoryPointer + memoryAllocBytes.Value > Constants.MemorySize) { throw new Exception(Resources.MemoryOverflow); } _memoryPointer += memoryAllocBytes.Value; } else if (currentCommand.Name.Equals(Constants.Decimal)) { _ioMode = IoModeEnum.Decimal; } else if (currentCommand.Name.Equals(Constants.Hex)) { _ioMode = IoModeEnum.Hex; } else if (currentCommand.Name.Equals(Constants.Fractional)) { _ioMode = IoModeEnum.Fraction; } else if (currentCommand.Name.Equals(Constants.Char)) { _ioMode = IoModeEnum.Char; } else if (currentCommand.Name.Equals(Constants.Period)) { OutputValue(Stack.Pop().Bytes); } else if (currentCommand.Name.Equals(Constants.Dup)) { Stack.Dup(); } else if (currentCommand.Name.Equals(Constants.Swap)) { Stack.Swap(); } else if (currentCommand.Name.Equals(Constants.Drop)) { Stack.Drop(); } else if (currentCommand.Name.Equals(Constants.Rot)) { Stack.Rot(); } else if (currentCommand.Name.Equals(Constants.Over)) { Stack.Over(); } else if (currentCommand.Name.Equals(Constants.Tuck)) { Stack.Tuck(); } else if (currentCommand.Name.Equals(Constants.Roll)) { Stack.Roll(); } else if (currentCommand.Name.Equals(Constants.Pick)) { Stack.Pick(); } else if (currentCommand.Name.Equals(Constants.Cr)) { Console.WriteLine(); } else if ( currentCommand.Name.Equals(Constants.Add) || currentCommand.Name.Equals(Constants.Subtract) || currentCommand.Name.Equals(Constants.Multiply) || currentCommand.Name.Equals(Constants.Modulus) || currentCommand.Name.Equals(Constants.Divide) || currentCommand.Name.Equals(Constants.GreaterThan) || currentCommand.Name.Equals(Constants.LessThan) || currentCommand.Name.Equals(Constants.GreaterThanOrEqual) || currentCommand.Name.Equals(Constants.LessThanOrEqual) || currentCommand.Name.Equals(Constants.Equal) || currentCommand.Name.Equals(Constants.NotEqual) || currentCommand.Name.Equals(Constants.And) || currentCommand.Name.Equals(Constants.Or) || currentCommand.Name.Equals(Constants.Not) ) { if (_ioMode == IoModeEnum.Fraction) { Stack.FloatMaths(currentCommand.Name); } else { Stack.IntMaths(currentCommand.Name); } } else { throw new Exception(string.Format(Resources.UnknownItem, currentCommand.Name)); } } } // Increment to next token currentCommandIndex++; } }
/// <summary> /// Main Parser function. Takes string of tokens and parses into Command elements. /// Most Commands will just be plain strings, but some ('if', 'loop' etc. will be /// dedicated commands with properties specifying the sub-commands to be executed) /// </summary> /// <param name="tokens">Array of tokens to parse</param> /// <param name="tokenIndex">Initial token index</param> /// <param name="stopTokens">Array of stop-tokens. E.g. 'If' parsing should stop on 'else' or 'endif'</param> /// <returns>Array of Command objects</returns> public static Command[] Parse(string[] tokens, ref int tokenIndex, string[] stopTokens) { List <Command> parsedTokens = new List <Command>(); while (tokenIndex < tokens.Length) { string token = tokens[tokenIndex]; if (token.Equals(Constants.If)) { IfCommand ifCommand = new IfCommand(); tokenIndex++; ifCommand.IfCommands.AddRange(Parse(tokens, ref tokenIndex, new[] { Constants.Else, Constants.EndIf })); // Only parse the following tokens in our 'if' statement if the next token after the 'then' phase is *not* // 'endif'. This is to handle 'else-less' 'if's where we are only programmed to handle what happens when // the 'if' evaluation is true, and to do nothing when the 'if' evaluation is false. if (tokenIndex >= 1 && !tokens[tokenIndex - 1].Equals(Constants.EndIf)) { ifCommand.ElseCommands.AddRange(Parse(tokens, ref tokenIndex, new[] { Constants.EndIf })); } parsedTokens.Add(ifCommand); } else if (token.Equals(Constants.Loop)) { LoopCommand loopCommand = new LoopCommand(); tokenIndex++; loopCommand.LoopCommands.AddRange(Parse(tokens, ref tokenIndex, new[] { Constants.EndLoop })); parsedTokens.Add(loopCommand); } else if (token.Equals(Constants.Repeat)) { RepeatCommand repeatCommand = new RepeatCommand(); tokenIndex++; repeatCommand.RepeatCommands.AddRange(Parse(tokens, ref tokenIndex, new[] { Constants.Until })); parsedTokens.Add(repeatCommand); } else { // For anything that's not an 'if', or loop, just add the string as a command. // The executor can handle if it's not an actual command parsedTokens.Add(new Command(token)); if ((stopTokens != null) && (stopTokens.Contains(token))) { tokenIndex++; return(parsedTokens.ToArray()); } tokenIndex++; } } if (stopTokens != null) { // If we get here then 'stopTokens' was set to something that should terminate a sequence of commands, // but wasn't found. E.G. An 'if' without an 'endif'. throw new Exception(string.Format(Resources.Expected_, string.Join("/", stopTokens))); } return(parsedTokens.ToArray()); }
protected void Load(byte[] binary, EndianBinaryReader reader, int headerOffset) { _binary = binary; Header = reader.ReadObject <M4ASongHeader>(headerOffset); VoiceTable = VoiceTable.LoadTable <M4AVoiceTable>(Header.VoiceTable - ROM.Pak); Commands = new List <SongEvent> [Header.NumTracks]; for (int i = 0; i < Header.NumTracks; i++) { Commands[i] = new List <SongEvent>(); } if (Header.NumTracks > ROM.Instance.Game.Engine.TrackLimit) { throw new InvalidDataException($"Song has too many tracks ({Header.NumTracks})."); } for (int i = 0; i < NumTracks; i++) { reader.BaseStream.Position = Header.Tracks[i] - ROM.Pak; byte cmd = 0, runCmd = 0, prevNote = 0, prevVelocity = 0x7F; while (cmd != 0xB1 && cmd != 0xB6) { int off = (int)reader.BaseStream.Position; ICommand command = null; cmd = reader.ReadByte(); if (cmd >= 0xBD) // Commands that work within running status { runCmd = cmd; } #region TIE & Notes if (runCmd >= 0xCF && cmd < 0x80) // Within running status { var peek = reader.PeekBytes(2); if (peek[0] >= 0x80) { command = AddNoteEvent(cmd, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek[1] > 3 || peek[1] < 1) { command = AddNoteEvent(cmd, reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(cmd, reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } else if (cmd >= 0xCF) { var peek = reader.PeekBytes(3); if (peek[0] >= 0x80) { command = AddNoteEvent(prevNote, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek[1] >= 0x80) { command = AddNoteEvent(reader.ReadByte(), prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } // TIE cannot have an added duration so it needs to stop here else if (cmd == 0xCF || peek[2] > 3 || peek[2] < 1) { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } #endregion #region Rests else if (cmd >= 0x80 && cmd <= 0xB0) { command = new RestCommand { Rest = SongEvent.RestFromCMD(0x80, cmd) } } ; #endregion #region Commands else if (runCmd < 0xCF && cmd < 0x80) // Commands within running status { switch (runCmd) { case 0xBD: command = new VoiceCommand { Voice = cmd }; break; case 0xBE: command = new VolumeCommand { Volume = cmd }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(cmd - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(cmd - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = cmd }; break; case 0xC2: command = new LFOSpeedCommand { Speed = cmd }; break; case 0xC3: command = new LFODelayCommand { Delay = cmd }; break; case 0xC4: command = new ModDepthCommand { Depth = cmd }; break; case 0xC5: command = new ModTypeCommand { Type = cmd }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(cmd - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = cmd, Argument = reader.ReadByte() }; break; case 0xCE: command = new EndOfTieCommand { Note = (sbyte)cmd }; prevNote = cmd; break; } } else if (cmd > 0xB0 && cmd < 0xCF) { switch (cmd) { case 0xB1: // FINE & PREV case 0xB6: command = new M4AFinishCommand { Type = cmd }; break; case 0xB2: command = new GoToCommand { Offset = reader.ReadInt32() - ROM.Pak }; break; case 0xB3: command = new CallCommand { Offset = reader.ReadInt32() - ROM.Pak }; break; case 0xB4: command = new ReturnCommand(); break; case 0xB5: command = new RepeatCommand { Times = reader.ReadByte(), Offset = reader.ReadInt32() - ROM.Pak }; break; case 0xB9: command = new MemoryAccessCommand { Arg1 = reader.ReadByte(), Arg2 = reader.ReadByte(), Arg3 = reader.ReadByte() }; break; case 0xBA: command = new PriorityCommand { Priority = reader.ReadByte() }; break; case 0xBB: command = new TempoCommand { Tempo = (short)(reader.ReadByte() * 2) }; break; case 0xBC: command = new KeyShiftCommand { Shift = reader.ReadSByte() }; break; // Commands that work within running status: case 0xBD: command = new VoiceCommand { Voice = reader.ReadByte() }; break; case 0xBE: command = new VolumeCommand { Volume = reader.ReadByte() }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = reader.ReadByte() }; break; case 0xC2: command = new LFOSpeedCommand { Speed = reader.ReadByte() }; break; case 0xC3: command = new LFODelayCommand { Delay = reader.ReadByte() }; break; case 0xC4: command = new ModDepthCommand { Depth = reader.ReadByte() }; break; case 0xC5: command = new ModTypeCommand { Type = reader.ReadByte() }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = reader.ReadByte(), Argument = reader.ReadByte() }; break; case 0xCE: // EOT sbyte note; if (reader.PeekByte() < 0x80) { note = reader.ReadSByte(); prevNote = (byte)note; } else { note = -1; } command = new EndOfTieCommand { Note = note }; break; default: Console.WriteLine("Invalid command: 0x{0:X} = {1}", off, cmd); break; } } #endregion Commands[i].Add(new SongEvent(off, command)); } } ICommand AddNoteEvent(byte note, byte velocity, byte addedDuration, byte runCmd, out byte prevNote, out byte prevVelocity) { return(new M4ANoteCommand { Note = (sbyte)(prevNote = note), Velocity = prevVelocity = velocity, Duration = (short)(runCmd == 0xCF ? -1 : (SongEvent.RestFromCMD(0xCF, runCmd) + addedDuration)) }); } }
protected void Load(byte[] binary, M4ASongHeader head) { _binary = binary; Header = head; Array.Resize(ref Header.Tracks, Header.NumTracks); // Not really necessary yet Commands = new List <SongEvent> [Header.NumTracks]; for (int i = 0; i < Header.NumTracks; i++) { Commands[i] = new List <SongEvent>(); } VoiceTable = VoiceTable.LoadTable <M4AVoiceTable>(Header.VoiceTable); if (NumTracks == 0 || NumTracks > 16) { return; } var reader = new ROMReader(); reader.InitReader(_binary); for (int i = 0; i < NumTracks; i++) { reader.SetOffset(Header.Tracks[i]); byte cmd = 0, runCmd = 0, prevNote = 0, prevVelocity = 127; while (cmd != 0xB1 && cmd != 0xB6) { uint off = reader.Position; ICommand command = null; cmd = reader.ReadByte(); if (cmd >= 0xBD) // Commands that work within running status { runCmd = cmd; } #region TIE & Notes if (runCmd >= 0xCF && cmd < 0x80) // Within running status { var o = reader.Position; byte peek1 = reader.ReadByte(), peek2 = reader.ReadByte(); reader.SetOffset(o); if (peek1 >= 128) { command = AddNoteEvent(cmd, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek2 > 3 || peek2 < 1) { command = AddNoteEvent(cmd, reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(cmd, reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } else if (cmd >= 0xCF) { var o = reader.Position; byte peek1 = reader.ReadByte(), peek2 = reader.ReadByte(), peek3 = reader.ReadByte(); reader.SetOffset(o); if (peek1 >= 128) { command = AddNoteEvent(prevNote, prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } else if (peek2 >= 128) { command = AddNoteEvent(reader.ReadByte(), prevVelocity, 0, runCmd, out prevNote, out prevVelocity); } // TIE cannot have an added duration so it needs to stop here else if (cmd == 0xCF || peek3 > 3 || peek3 < 1) { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), 0, runCmd, out prevNote, out prevVelocity); } else { command = AddNoteEvent(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), runCmd, out prevNote, out prevVelocity); } } #endregion #region Rests else if (cmd >= 0x80 && cmd <= 0xB0) { command = new RestCommand { Rest = SongEvent.RestFromCMD(0x80, cmd) } } ; #endregion #region Commands else if (runCmd < 0xCF && cmd < 0x80) // Commands within running status { switch (runCmd) { case 0xBD: command = new VoiceCommand { Voice = cmd }; break; case 0xBE: command = new VolumeCommand { Volume = cmd }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(cmd - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(cmd - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = cmd }; break; case 0xC2: command = new LFOSpeedCommand { Speed = cmd }; break; case 0xC3: command = new LFODelayCommand { Delay = cmd }; break; case 0xC4: command = new ModDepthCommand { Depth = cmd }; break; case 0xC5: command = new ModTypeCommand { Type = cmd }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(cmd - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = cmd, Argument = reader.ReadByte() }; break; case 0xCE: command = new EndOfTieCommand { Note = (sbyte)cmd }; prevNote = cmd; break; } } else if (cmd > 0xB0 && cmd < 0xCF) { switch (cmd) { case 0xB1: // FINE & PREV case 0xB6: command = new M4AFinishCommand { Type = cmd }; break; case 0xB2: command = new GoToCommand { Offset = reader.ReadPointer() }; break; case 0xB3: command = new CallCommand { Offset = reader.ReadPointer() }; break; case 0xB4: command = new ReturnCommand(); break; case 0xB5: command = new RepeatCommand { Times = reader.ReadByte(), Offset = reader.ReadPointer() }; break; case 0xB9: command = new MemoryAccessCommand { Arg1 = reader.ReadByte(), Arg2 = reader.ReadByte(), Arg3 = reader.ReadByte() }; break; case 0xBA: command = new PriorityCommand { Priority = reader.ReadByte() }; break; case 0xBB: command = new TempoCommand { Tempo = (ushort)(reader.ReadByte() * 2) }; break; case 0xBC: command = new KeyShiftCommand { Shift = reader.ReadSByte() }; break; // Commands that work within running status: case 0xBD: command = new VoiceCommand { Voice = reader.ReadByte() }; break; case 0xBE: command = new VolumeCommand { Volume = reader.ReadByte() }; break; case 0xBF: command = new PanpotCommand { Panpot = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC0: command = new BendCommand { Bend = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xC1: command = new BendRangeCommand { Range = reader.ReadByte() }; break; case 0xC2: command = new LFOSpeedCommand { Speed = reader.ReadByte() }; break; case 0xC3: command = new LFODelayCommand { Delay = reader.ReadByte() }; break; case 0xC4: command = new ModDepthCommand { Depth = reader.ReadByte() }; break; case 0xC5: command = new ModTypeCommand { Type = reader.ReadByte() }; break; case 0xC8: command = new TuneCommand { Tune = (sbyte)(reader.ReadByte() - 0x40) }; break; case 0xCD: command = new LibraryCommand { Command = reader.ReadByte(), Argument = reader.ReadByte() }; break; case 0xCE: // EOT sbyte note; if (reader.PeekByte() < 128) { note = reader.ReadSByte(); prevNote = (byte)note; } else { note = -1; } command = new EndOfTieCommand { Note = note }; break; default: Console.WriteLine("Invalid command: 0x{0:X} = {1}", reader.Position, cmd); break; } } #endregion Commands[i].Add(new SongEvent(off, command)); } } ICommand AddNoteEvent(byte note, byte velocity, byte addedDuration, byte runCmd, out byte prevNote, out byte prevVelocity) { return(new M4ANoteCommand { Note = (sbyte)(prevNote = note), Velocity = prevVelocity = velocity, Duration = (short)(runCmd == 0xCF ? -1 : (SongEvent.RestFromCMD(0xCF, runCmd) + addedDuration)) }); } }