public Note(Data.Buffer data) { byte b1 = data.Pop <byte>(); byte b2 = data.Pop <byte>(); byte b3 = data.Pop <byte>(); byte b4 = data.Pop <byte>(); SampleNumber = (byte)((b1 & 0xf0) | (b3 >> 4)); Period = b2 | ((b1 & 0x0f) << 8); EffectCommand = new Effect((uint)(b4 | ((b3 & 0x0f) << 8))); }
uint ParseDeltaTime(Data.Buffer data) { uint deltaTime = 0; byte b = data.Pop <byte>(); while ((b & 0x80) != 0) { deltaTime = (deltaTime << 7) | (uint)(b & 0x7f); b = data.Pop <byte>(); } deltaTime = (deltaTime << 7) | (uint)(b & 0x7f); return(deltaTime); }
void ParseMetaEvent(Data.Buffer data) { // we ignore most of them // we only need the "set tempo" event byte type = data.Pop <byte>(); byte len = data.Pop <byte>(); if (type == 0x51) // set tempo { if (len != 3) { throw new ExceptionAudio("Invalid event length."); } // 24 bit value: microseconds per quarternote byte high = data.Pop <byte>(); byte mid = data.Pop <byte>(); byte low = data.Pop <byte>(); tempo = (uint)high << 16 | (uint)mid << 8 | low; events.Add(new SetTempoEvent(tempo, currentTime, currentTick)); Log.Verbose.Write(ErrorSystemType.Audio, $"XMI Tempo Changed: {tempo} microseconds per quarter note."); } else if (type == 0x58) { if (len != 4) { throw new ExceptionAudio("Invalid event length."); } int numerator = data.Pop <byte>(); int denominator = 1 << data.Pop <byte>(); int numMidiClocks = data.Pop <byte>(); int num32InQuarterNote = data.Pop <byte>(); Log.Verbose.Write(ErrorSystemType.Audio, $"XMI Time Signature: {numerator}/{denominator} ({numMidiClocks} MIDI Clocks, {num32InQuarterNote} 32nd-notes in quarter note."); } else { // skip event data data.Pop(len); } }
public static short[] ConvertToWav(Data.Buffer data, int level = 0, bool invert = false) { List <short> wavData = new List <short>(); while (data.Readable()) { int value = data.Pop <byte>(); value = value + level; if (invert) { value = 0xFF - value; } value *= 0xFF; wavData.Add((short)value); } return(wavData.ToArray()); }
public MOD(Data.Buffer data) { data.SetEndianess(Endian.Endianess.Big); Log.Info.Write(ErrorSystemType.Data, $"Loading MOD song: {System.Text.Encoding.ASCII.GetString(data.Pop(20).ReinterpretAsArray(20))}"); // songname // add dummy sample 0 samples.Add(new Sample()); for (int i = 1; i < 32; ++i) // samples 1-31 { Log.Info.Write(ErrorSystemType.Data, $"Sample {i} name: {System.Text.Encoding.ASCII.GetString(data.Pop(22).ReinterpretAsArray(22))}"); // samplename samples.Add(new Sample() { Length = data.Pop <UInt16>() * 2, FineTune = ConvertFineTune(data.Pop <byte>() & 0xf), Volume = data.Pop <byte>(), // 0-64, change in dB = 20*log10(Volume/64) RepeatPointOffset = data.Pop <UInt16>() * 2, RepeatLength = Math.Max(0, data.Pop <UInt16>() - 1) * 2 }); } int songLength = data.Pop <byte>(); if (songLength < 1 || songLength > 128) { throw new ExceptionAudio("Invalid MOD format."); } data.Skip(1); byte[] songPatterns = data.Pop(128).ReinterpretAsArray(128); var magic = data.Pop(4).ToString(); if (magic != "M!K!" && magic != "M.K.") { throw new ExceptionAudio("Invalid or unsupported MOD format."); } int numPatterns = songPatterns.Max() + 1; List <Pattern> patterns = new List <Pattern>(numPatterns); for (int i = 0; i < numPatterns; ++i) { var pattern = new Pattern(); for (int div = 0; div < 64; ++div) { for (int chan = 0; chan < 4; ++chan) { pattern.AddNote(chan, new Note(data)); } } patterns.Add(pattern); } for (int i = 0; i < samples.Count; ++i) { if (samples[i].Length > 0) { data.Skip(2); // ignore tracker word samples[i].Length -= 2; uint length = (uint)samples[i].Length; if (length > 0) { samples[i].SetData(data.Pop(length).ReinterpretAsArray(length)); } } } for (int i = 0; i < songLength; ++i) { song.Add(patterns[songPatterns[i]]); } }
public XMI(Data.Buffer data) { // Note: Chunk length and so on are encoded as big endian. // But as we don't use them we use little endian because // the XMI data is encoded in little endian. data.SetEndianess(Endian.Endianess.Little); // Form chunk if (data.ToString(4) != "FORM") { return; } data.Pop(4); // FORM data.Pop <uint>(); // FORM chunk length // format XDIR if (data.ToString(4) != "XDIR") { return; } data.Pop(4); // XDIR if (data.ToString(4) != "INFO") { return; } data.Pop(4); // INFO data.Pop <uint>(); // INFO chunk length int numTracks = data.Pop <ushort>(); if (numTracks != 1) { return; // we only support one track per file } if (data.ToString(4) != "CAT ") { return; } data.Pop(4); // CAT_ data.Pop <uint>(); // CAT chunk length // format XMID if (data.ToString(4) != "XMID") { return; } data.Pop(4); // XMID // load the one track // Form chunk if (data.ToString(4) != "FORM") { return; } data.Pop(4); // FORM data.Pop <uint>(); // FORM chunk length // format XMID if (data.ToString(4) != "XMID") { return; } data.Pop(4); // XMID // TIMB chunk if (data.ToString(4) != "TIMB") { return; } data.Pop(4); // TIMB data.Pop <uint>(); // TIMB chunk length int count = data.Pop <ushort>(); for (int j = 0; j < count; ++j) { // we don't need the TIMB information, just skip it data.Pop(2); } // EVNT chunk if (data.ToString(4) != "EVNT") { return; } data.Pop(4); // EVNT data.Pop <uint>(); // EVNT chunk length // read xmi/midi events while (data.Readable()) { ParseEvent(data); } var eventIndices = new Dictionary <Event, int>(); for (int i = 0; i < events.Count; ++i) { eventIndices.Add(events[i], i); } events.Sort((a, b) => { int result = a.StartTime.CompareTo(b.StartTime); if (result == 0) { return(eventIndices[a].CompareTo(eventIndices[b])); } return(result); }); }
void ParseEvent(Data.Buffer data) { byte status = data.PeekByte(); if (status == 0xff) // meta event { data.Pop(1); ParseMetaEvent(data); return; } int channel = status & 0xf; int eventType = status >> 4; if (eventType < 0x8) { uint ticks = data.Pop <byte>(); currentTime += ConvertTicksToTime(ticks); currentTick += ticks; return; } else if (eventType < 0xF) { data.Pop(1); } switch (eventType) { case 0x9: // Note on // Note: In XMI it has 3 parameters (last for duration). // But we create two events (note on and off). { byte note = data.Pop <byte>(); byte velocity = data.Pop <byte>(); uint length = ParseDeltaTime(data); if (velocity != 0) { var onEvent = new PlayNoteEvent((byte)channel, note, velocity, currentTime, currentTick); var offEvent = new StopNoteEvent((byte)channel, note, currentTime + ConvertTicksToTime(length), currentTick + length); events.Add(onEvent); events.Add(offEvent); } } break; case 0xB: // Control change { byte controller = (byte)(data.Pop <byte>() & 0x7f); byte value = (byte)(data.Pop <byte>() & 0x7f); if (controller < 120) // ignore reserved controller events >= 120 { events.Add(new SetControllerValueEvent((byte)channel, controller, value, currentTime, currentTick)); } } break; case 0x8: case 0xA: case 0xE: data.Pop(2); break; case 0xC: events.Add(new SetInstrumentEvent((byte)channel, data.Pop <byte>(), currentTime, currentTick)); break; case 0xD: data.Pop(1); break; default: throw new ExceptionAudio("Unsupported xmi/midi event type."); } }
void ParseEvent(Data.Buffer data) { byte status = data.PeekByte(); if (status == 0xff) // meta event { data.Pop(1); ParseMetaEvent(data); return; } int channel = status & 0xf; int eventType = status >> 4; if (eventType < 0x8) { currentTime += ConvertTicksToTime(data.Pop <byte>()); return; } else if (eventType < 0xF) { data.Pop(1); } switch (eventType) { case 0x9: // Note on // Note: In XMI it has 3 parameters (last for duration). // But we create two events (note on and off). { byte note = data.Pop <byte>(); byte velocity = data.Pop <byte>(); uint length = ParseDeltaTime(data); if (velocity != 0) { var onEvent = new PlayNoteEvent((byte)channel, note, currentTime); var offEvent = new StopNoteEvent((byte)channel, note, currentTime + ConvertTicksToTime(length)); events.Add(onEvent); events.Add(offEvent); } } break; case 0x8: case 0xA: case 0xB: case 0xE: data.Pop(2); break; case 0xC: { var patchEvent = new SetInstrumentEvent((byte)channel, data.Pop <byte>(), currentTime); events.Add(patchEvent); } break; case 0xD: data.Pop(1); break; default: throw new ExceptionAudio("Unsupported xmi/midi event type."); } }