public byte[] GetBytes() { byte[] buffer = null; byte oplChnl = MainForm.channelMap[midiChannel]; if (oplChnl < 18) { byte baseReg = (oplChnl < 9) ? (byte)0x5e : (byte)0x5f; switch (type) { case METype.progchange: // read the patch file byte[] gmData = GeneralMidi.GetInstrument(program); MainForm.ChannelKSL[oplChnl] = gmData[8]; if ((gmData[0] & 4) != 0) { buffer = new byte[66]; // double voice instrument // operator 1 byte addr1 = 0x20; byte addr2 = 0x28; Array.Copy(new byte[3] { baseReg, (byte)(addr1 + channelToOperatorOffset[oplChnl % 9]), gmData[4] }, 0, buffer, 0, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0x40 + channelToOperatorOffset[oplChnl % 9]), gmData[5] }, 0, buffer, 3, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0x60 + channelToOperatorOffset[oplChnl % 9]), gmData[6] }, 0, buffer, 6, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0xC0 + channelToOperatorOffset[oplChnl % 9]), gmData[7] }, 0, buffer, 9, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0x20 + channelToOperatorOffset[oplChnl % 9]), (byte)(gmData[8] + gmData[9]) }, 0, buffer, 12, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0xA0 + oplChnl % 9), (byte)(gmData[10] | 0x30) }, 0, buffer, 15, 3); // operator 2 Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[11] }, 0, buffer, 18, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0x40 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[12] }, 0, buffer, 21, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0x60 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[13] }, 0, buffer, 24, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0xC0 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[14] }, 0, buffer, 27, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr1 + 0x20 + 3 + channelToOperatorOffset[oplChnl % 9]), (byte)(gmData[15] | gmData[16]) }, 0, buffer, 30, 3); // operator 3 Array.Copy(new byte[3] { baseReg, (byte)(addr2 + channelToOperatorOffset[oplChnl % 9]), gmData[20] }, 0, buffer, 33, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0x40 + channelToOperatorOffset[oplChnl % 9]), gmData[21] }, 0, buffer, 36, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0x60 + channelToOperatorOffset[oplChnl % 9]), gmData[22] }, 0, buffer, 39, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0xC0 + channelToOperatorOffset[oplChnl % 9]), gmData[23] }, 0, buffer, 42, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0x20 + channelToOperatorOffset[oplChnl % 9]), (byte)(gmData[24] + gmData[25]) }, 0, buffer, 45, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0xA0 + oplChnl % 9), (byte)(gmData[26] | 0x30) }, 0, buffer, 48, 3); // operator 4 Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[27] }, 0, buffer, 51, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0x40 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[28] }, 0, buffer, 54, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0x60 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[29] }, 0, buffer, 57, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0xC0 + 3 + channelToOperatorOffset[oplChnl % 9]), gmData[30] }, 0, buffer, 60, 3); Array.Copy(new byte[3] { baseReg, (byte)(addr2 + 0x20 + 3 + channelToOperatorOffset[oplChnl % 9]), (byte)(gmData[31] | gmData[32]) }, 0, buffer, 63, 3); } else { buffer = new byte[33]; // Single voice channel // operator 1 Array.Copy(new byte[3] { baseReg, (byte)(0x20 + channelToOperatorOffset[oplChnl % 9]), gmData[4] }, 0, buffer, 0, 3); // tremolo/vibrato/sustain/KSR/Freq Mult Array.Copy(new byte[3] { baseReg, (byte)(0x60 + channelToOperatorOffset[oplChnl % 9]), gmData[5] }, 0, buffer, 3, 3); // attack/decay Array.Copy(new byte[3] { baseReg, (byte)(0x80 + channelToOperatorOffset[oplChnl % 9]), gmData[6] }, 0, buffer, 6, 3); // sustain/release Array.Copy(new byte[3] { baseReg, (byte)(0xE0 + channelToOperatorOffset[oplChnl % 9]), gmData[7] }, 0, buffer, 9, 3); // Waveform select Array.Copy(new byte[3] { baseReg, (byte)(0x40 + channelToOperatorOffset[oplChnl % 9]), (byte)(gmData[8] + gmData[9]) }, 0, buffer, 12, 3); // KSL/Output Level Array.Copy(new byte[3] { baseReg, (byte)(0xC0 + oplChnl % 9), (byte)(gmData[10] | 0xF0) }, 0, buffer, 15, 3); // Speaker/Feedback/Syn Type // operator 2 Array.Copy(new byte[3] { baseReg, (byte)(0x23 + channelToOperatorOffset[oplChnl % 9]), gmData[11] }, 0, buffer, 18, 3); // tremolo/vibrato/sustain/KSR/Freq Mult Array.Copy(new byte[3] { baseReg, (byte)(0x63 + channelToOperatorOffset[oplChnl % 9]), gmData[12] }, 0, buffer, 21, 3); // attack/decay Array.Copy(new byte[3] { baseReg, (byte)(0x83 + channelToOperatorOffset[oplChnl % 9]), gmData[13] }, 0, buffer, 24, 3); // sustain/release Array.Copy(new byte[3] { baseReg, (byte)(0xE3 + channelToOperatorOffset[oplChnl % 9]), gmData[14] }, 0, buffer, 27, 3); // Waveform select Array.Copy(new byte[3] { baseReg, (byte)(0x43 + channelToOperatorOffset[oplChnl % 9]), (byte)(gmData[15] + gmData[16]) }, 0, buffer, 30, 3); // KSL/Output Level } break; case METype.noteon: if (midiChannel == 9 && MainForm.PercussionSet != 0) { buffer = new byte[6]; bool BD = (note == 35) | (note == 36); bool SN = (note == 38) | (note == 40); bool TT = (note == 41) | (note == 45); bool CY = (note == 49) | (note == 55) | (note == 57); bool HH = (note == 46) | (note == 42); buffer[0] = 0x5e; buffer[1] = 0xBD; buffer[3] = 0x5e; if (BD) { MainForm.PercussionSet = velocity == 0 ? (byte)(MainForm.PercussionSet & ~0x10) : (byte)(MainForm.PercussionSet | 0x10); buffer[4] = (byte)(0x40 + 0x10); } if (SN) { MainForm.PercussionSet = velocity == 0 ? (byte)(MainForm.PercussionSet & ~0x8) : (byte)(MainForm.PercussionSet | 0x8); buffer[4] = (byte)(0x40 + 0x14); } if (TT) { MainForm.PercussionSet = velocity == 0 ? (byte)(MainForm.PercussionSet & ~0x4) : (byte)(MainForm.PercussionSet | 0x4); buffer[4] = (byte)(0x40 + 0x12); } if (CY) { MainForm.PercussionSet = velocity == 0 ? (byte)(MainForm.PercussionSet & ~0x2) : (byte)(MainForm.PercussionSet | 0x2); buffer[4] = (byte)(0x40 + 0x15); } if (HH) { MainForm.PercussionSet = velocity == 0 ? (byte)(MainForm.PercussionSet & ~0x1) : (byte)(MainForm.PercussionSet | 0x1); buffer[4] = (byte)(0x40 + 0x11); } buffer[2] = MainForm.PercussionSet; buffer[5] = (byte)(0x3F - (velocity >> 1)); // attenuation } else { buffer = new byte[9]; byte[] onFreq = GetFreq(note); buffer[0] = baseReg; buffer[1] = (byte)(0xA0 + oplChnl % 9); buffer[2] = onFreq[0]; buffer[3] = baseReg; buffer[4] = (byte)(0xB0 + oplChnl % 9); buffer[5] = (byte)(onFreq[1] | (velocity != 0 ? 0x20 : 0)); // set KEY on buffer[6] = baseReg; buffer[7] = (byte)(0x40 + channelToOperatorOffset[oplChnl % 9]); buffer[8] = (byte)(MainForm.ChannelKSL[oplChnl] | (0x3F - (velocity >> 1))); // attenuation } break; case METype.noteoff: buffer = new byte[3]; _ = GetFreq(note); if (midiChannel == 9 && MainForm.PercussionSet != 0) { bool BD = (note == 35) | (note == 36); bool SN = (note == 38) | (note == 40); bool TT = (note == 41) | (note == 45); bool CY = (note == 49) | (note == 57); bool HH = (note == 46) | (note == 42); buffer[0] = 0x5e; buffer[1] = 0xBD; if (BD) { MainForm.PercussionSet = (byte)(MainForm.PercussionSet & ~0x10); buffer[2] = (byte)(0x40 + 0x10); } if (SN) { MainForm.PercussionSet = (byte)(MainForm.PercussionSet & ~0x8); buffer[2] = (byte)(0x40 + 0x14); } if (TT) { MainForm.PercussionSet = (byte)(MainForm.PercussionSet & ~0x4); buffer[2] = (byte)(0x40 + 0x12); } if (CY) { MainForm.PercussionSet = (byte)(MainForm.PercussionSet & ~0x2); buffer[2] = (byte)(0x40 + 0x15); } if (HH) { MainForm.PercussionSet = (byte)(MainForm.PercussionSet & ~0x1); buffer[2] = (byte)(0x40 + 0x11); } } else { buffer[0] = baseReg; buffer[1] = (byte)(0xB0 + oplChnl % 9); buffer[2] = 0; } break; } } else { // program the drum channels } return buffer; }
private void GenerateVGMButton_Click(object sender, EventArgs e) { // Print all events events.Sort(new MidiEventComparer()); StringBuilder sb = new StringBuilder(); // First pass - map midi channels to OPL3 channels byte fourOps = 0; byte twoOps = 0; List <byte> availableChannels = new List <byte>(); availableChannels.Add(0); availableChannels.Add(1); availableChannels.Add(2); availableChannels.Add(3); availableChannels.Add(4); availableChannels.Add(5); if (PercussionSet == 0) { availableChannels.Add(6); availableChannels.Add(7); availableChannels.Add(8); } availableChannels.Add(9); availableChannels.Add(10); availableChannels.Add(11); availableChannels.Add(12); availableChannels.Add(13); availableChannels.Add(14); availableChannels.Add(15); availableChannels.Add(16); availableChannels.Add(17); byte[] twoOpChannels = new byte[15]; for (int i = 0; i < channelMap.Length; i++) { channelMap[i] = 0xF0; } if (PercussionSet != 0) { channelMap[9] = 6; } foreach (MidiEvent ev in events) { if (ev.type != METype.progchange) { break; } if (ev.midiChannel != 9 || PercussionSet != 0) { byte[] prog = GeneralMidi.GetInstrument(ev.program); if (prog[0] == 4) { // allocate 4 ops until you can't if (fourOps < 7) { // check if the channel has already been assigned if (channelMap[ev.midiChannel] == 0xF0) { byte opChnl = (byte)(fourOps < 3 ? fourOps : fourOps + 5); channelMap[ev.midiChannel] = opChnl; availableChannels.Remove(opChnl); availableChannels.Remove((byte)(opChnl + 3)); fourOps++; } } else { // disable this channel channelMap[ev.midiChannel] = 0xFF; } } else { twoOpChannels[twoOps] = ev.midiChannel; twoOps++; } } } if (fourOps * 4 + twoOps * 2 + 6 > 36) { throw new Exception("Insufficient number of operators!"); } else { sb.AppendFormat("Two Op Channels: {0}, Four Op Channels: {1}", twoOps, fourOps).AppendLine(); } // given a number of 4 operator channels, return the starting offset for (int i = 0; i < twoOps; i++) { byte top = availableChannels[0]; channelMap[twoOpChannels[i]] = top; availableChannels.Remove(top); } byte OPL3Mode = (byte)(fourOps != 0 ? 1 : 0); byte connectionSel = (byte)(Math.Pow(2, fourOps) - 1); int totalWaits = 0; int totalBytes = 0; MemoryStream ms = new MemoryStream(); // set the machine in OPL3 mode - no timers byte[] initialRegister = { 0x5f, 5, OPL3Mode, // OPL3 mode 0x5e, 1, 0x20, // Waveform Select - Test Registers 0x5e, 2, 0, // timer 1 0x5e, 3, 0, // timer 2 0x5e, 4, 0x60, // RST, Timer Masks, Timer Starts 0x5e, 8, 0x40, // Keyboard Split 0x5e, 0xBD, PercussionSet, // Drum Mode // address 1 0x5f, 1, 0x0, // Waveform Select - Test Registers 0x5f, 4, 0, // connection sel 0x5e, 0xB6, 0xc, // clearing KEY ON for percussion 0x5e, 0xB7, 0xc, // clearing KEY ON for percussion 0x5e, 0xB8, 0xc, // clearing KEY ON for percussion 0x5e, 0xA6, 0xf0, // freq bd 0x5e, 0xA7, 0xf0, // freq sn 0x5e, 0xA8, 0xf0, // freq tt //0x5e, 0xC0, 0x0, // enabling output //0x5e, 0xC1, 0x0, // enabling output //0x5e, 0xC2, 0x0, // enabling output //0x5e, 0xC3, 0x0, // enabling output //0x5e, 0xC4, 0x0, // enabling output //0x5e, 0xC5, 0x0, // enabling output 0x5e, 0xC6, 0x0, // enabling output 0x5e, 0xC7, 0x0, // enabling output 0x5e, 0xC8, 0x0, // enabling output // default snare sound //01 f6 08 05 0 0c 08 20 f6 04 01 0 0 0 0x5e, 0x20 + 0x14, 01, 0x5e, 0x60 + 0x14, 0xf6, 0x5e, 0x80 + 0x14, 8, 0x5e, 0xE0 + 0x14, 5, 0x5e, 0x40 + 0x14, 0 + 0xC }; ms.Write(initialRegister, 0, initialRegister.Length); totalBytes += initialRegister.Length; int idx = 0; foreach (MidiEvent ev in events) { if (ev.index - idx > 0) { int wait = (ev.index - idx) * samplesPerTick; totalWaits += wait; while (wait > 65535) { sb.Append("Adding Wait: ").Append(65535).AppendLine(); ms.Write(new byte[3] { 0x61, 0xFF, 0xFF }, 0, 3); totalBytes += 3; wait -= 65535; } if (wait > 0) { sb.Append("Adding Wait: ").Append(wait).AppendLine(); ms.Write(new byte[3] { 0x61, (byte)(wait & 0xFF), (byte)(wait >> 8) }, 0, 3); totalBytes += 3; } } if (SingleChannel.SelectedIndex == 0 || (SingleChannel.SelectedIndex - 1 == ev.midiChannel)) { sb.Append(ev.index).Append("\t").Append(ev).AppendLine(); byte[] partial = ev.GetBytes(); if (partial != null) { ms.Write(partial, 0, partial.Length); totalBytes += partial.Length; } } idx = ev.index; } ms.Write(new byte[1] { 0x66 }, 0, 1); // end of song totalBytes += 1; MIDIOutputText.Text = sb.ToString(); byte[] gd3 = CreateGD3(); // Write the file string vgmFileName = Path.ChangeExtension(FileLabel.Text, ".vgm"); if (File.Exists(vgmFileName)) { File.Delete(vgmFileName); } FileStream stream = new FileStream(vgmFileName, FileMode.CreateNew); byte[] header = GetVGMHeader(totalWaits, totalBytes + 0x80, gd3.Length); stream.Write(header, 0, header.Length); // write data byte[] data = ms.GetBuffer(); stream.Write(data, 0, (int)ms.Length); // write GD3 stuff stream.Write(gd3, 0, gd3.Length); stream.Flush(); stream.Close(); }