Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 51 additions & 8 deletions MidiFileReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,29 @@ namespace MidiCS
{
public class MidiFileReader
{
public static MidiFile FromBytes(byte[] bytes)
private static string DecodeText(byte[] bytes)
{
// Try strict UTF-8 first (throws on invalid sequences).
try
{
var utf8 = new System.Text.UTF8Encoding(false, true);
return utf8.GetString(bytes);
}
catch (System.ArgumentException)
{
// Invalid UTF-8 -> fall back to Windows-1252 (typical single-byte MIDI text)
try
{
return System.Text.Encoding.GetEncoding(1252).GetString(bytes);
}
catch
{
// Last resort: 28591 (Latin1) to preserve raw byte->codepoint mapping
return System.Text.Encoding.GetEncoding(28591).GetString(bytes);
}
}
}
public static MidiFile FromBytes(byte[] bytes)
{
using (var s = new MemoryStream(bytes))
return FromStream(s);
Expand Down Expand Up @@ -122,19 +144,40 @@ public static IMidiMessage readMessage(Stream s)
throw new InvalidDataException("Sequence number events must have 2 bytes of data; this one has " + length);
return new SequenceNumber(deltaTime, s.ReadUInt16BE());
case MetaEventType.TextEvent:
return new TextEvent(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new TextEvent(deltaTime, DecodeText(data));
}
case MetaEventType.CopyrightNotice:
return new CopyrightNotice(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new CopyrightNotice(deltaTime, DecodeText(data));
}
case MetaEventType.TrackName:
return new TrackName(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new TrackName(deltaTime, DecodeText(data));
}
case MetaEventType.InstrumentName:
return new InstrumentName(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new InstrumentName(deltaTime, DecodeText(data));
}
case MetaEventType.Lyric:
return new Lyric(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new Lyric(deltaTime, DecodeText(data));
}
case MetaEventType.Marker:
return new Marker(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new Marker(deltaTime, DecodeText(data));
}
case MetaEventType.CuePoint:
return new CuePoint(deltaTime, Encoding.ASCII.GetString(s.ReadBytes(length)));
{
var data = s.ReadBytes(length);
return new CuePoint(deltaTime, DecodeText(data));
}
case MetaEventType.ChannelPrefix:
if (length != 1)
throw new InvalidDataException("Channel prefix events must have 1 byte of data; this one has " + length);
Expand Down
269 changes: 145 additions & 124 deletions MidiFileWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,129 +30,150 @@ public static void WriteSMF(MidiFile f, Stream s)
}
}

static int tick = 0;
static byte running_status = 0;
static void writeMessage(IMidiMessage m, Stream s)
{
s.WriteMidiMultiByte(m.DeltaTime);
switch (m)
{
case IMidiEvent e:
var status = (byte)(e.Channel | (byte)e.Type);
if(status != running_status)
{
s.WriteByte(status);
running_status = status;
}
switch(e)
{
case NoteOffEvent x:
s.WriteByte(x.Key);
s.WriteByte(x.Velocity);
break;
case NoteOnEvent x:
s.WriteByte(x.Key);
s.WriteByte(x.Velocity);
break;
case NotePressureEvent x:
s.WriteByte(x.Key);
s.WriteByte(x.Pressure);
break;
case ControllerEvent x:
s.WriteByte(x.Controller);
s.WriteByte(x.Value);
break;
case ProgramChgEvent x:
s.WriteByte(x.Program);
break;
case ChannelPressureEvent x:
s.WriteByte(x.Pressure);
break;
case PitchBendEvent x:
s.WriteBE(x.Bend);
break;
}
break;
case MetaEvent e:
// cancel running status
running_status = 0;
s.WriteByte(0xFF);
s.WriteByte((byte)e.MetaType);
switch(e)
{
case SequenceNumber x:
s.WriteMidiMultiByte(2);
s.WriteBE(x.Number);
break;
case TextEvent x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case CopyrightNotice x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case TrackName x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case InstrumentName x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case Lyric x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case Marker x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case CuePoint x:
s.WriteMidiMultiByte((uint)x.Text.Length);
s.Write(System.Text.Encoding.ASCII.GetBytes(x.Text), 0, x.Text.Length);
break;
case ChannelPrefix x:
s.WriteMidiMultiByte(1);
s.WriteByte(x.Channel);
break;
case EndOfTrackEvent x:
s.WriteMidiMultiByte(0);
break;
case TempoEvent x:
s.WriteMidiMultiByte(3);
s.WriteUInt24BE(x.MicrosPerQn);
break;
case SmtpeOffset x:
s.WriteMidiMultiByte(5);
var sdata = new byte[5] { x.Hours, x.Minutes, x.Seconds, x.Frames, x.FrameHundredths };
s.Write(sdata, 0, 5);
break;
case TimeSignature x:
s.WriteMidiMultiByte(4);
var tdata = new byte[4] { x.Numerator, x.Denominator, x.ClocksPerTick, x.ThirtySecondNotesPer24Clocks };
s.Write(tdata, 0, 4);
break;
case KeySignature x:
s.WriteMidiMultiByte(2);
s.WriteByte(x.Sharps);
s.WriteByte(x.Tonality);
break;
case SequencerSpecificEvent x:
s.WriteMidiMultiByte((uint)x.Data.Length);
s.Write(x.Data, 0, x.Data.Length);
break;
}
break;
case SysexEvent e:
// cancel running status
running_status = 0;
s.WriteByte(0xF0);
s.WriteMidiMultiByte((uint)e.Data.Length);
s.Write(e.Data, 0, e.Data.Length);
break;
}
}
static int tick = 0;
static byte running_status = 0;
static void writeMessage(IMidiMessage m, Stream s)
{
s.WriteMidiMultiByte(m.DeltaTime);
switch (m)
{
case IMidiEvent e:
var status = (byte)(e.Channel | (byte)e.Type);
if (status != running_status)
{
s.WriteByte(status);
running_status = status;
}
switch (e)
{
case NoteOffEvent x:
s.WriteByte(x.Key);
s.WriteByte(x.Velocity);
break;
case NoteOnEvent x:
s.WriteByte(x.Key);
s.WriteByte(x.Velocity);
break;
case NotePressureEvent x:
s.WriteByte(x.Key);
s.WriteByte(x.Pressure);
break;
case ControllerEvent x:
s.WriteByte(x.Controller);
s.WriteByte(x.Value);
break;
case ProgramChgEvent x:
s.WriteByte(x.Program);
break;
case ChannelPressureEvent x:
s.WriteByte(x.Pressure);
break;
case PitchBendEvent x:
s.WriteBE(x.Bend);
break;
}
break;
case MetaEvent e:
// cancel running status
running_status = 0;
s.WriteByte(0xFF);
s.WriteByte((byte)e.MetaType);
switch (e)
{
case SequenceNumber x:
s.WriteMidiMultiByte(2);
s.WriteBE(x.Number);
break;
case TextEvent x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case CopyrightNotice x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case TrackName x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case InstrumentName x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case Lyric x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case Marker x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case CuePoint x:
{
var bytes = System.Text.Encoding.UTF8.GetBytes(x.Text ?? "");
s.WriteMidiMultiByte((uint)bytes.Length);
s.Write(bytes, 0, bytes.Length);
break;
}
case ChannelPrefix x:
s.WriteMidiMultiByte(1);
s.WriteByte(x.Channel);
break;
case EndOfTrackEvent x:
s.WriteMidiMultiByte(0);
break;
case TempoEvent x:
s.WriteMidiMultiByte(3);
s.WriteUInt24BE(x.MicrosPerQn);
break;
case SmtpeOffset x:
s.WriteMidiMultiByte(5);
var sdata = new byte[5] { x.Hours, x.Minutes, x.Seconds, x.Frames, x.FrameHundredths };
s.Write(sdata, 0, 5);
break;
case TimeSignature x:
s.WriteMidiMultiByte(4);
var tdata = new byte[4] { x.Numerator, x.Denominator, x.ClocksPerTick, x.ThirtySecondNotesPer24Clocks };
s.Write(tdata, 0, 4);
break;
case KeySignature x:
s.WriteMidiMultiByte(2);
s.WriteByte(x.Sharps);
s.WriteByte(x.Tonality);
break;
case SequencerSpecificEvent x:
s.WriteMidiMultiByte((uint)x.Data.Length);
s.Write(x.Data, 0, x.Data.Length);
break;
}
break;
case SysexEvent e:
// cancel running status
running_status = 0;
s.WriteByte(0xF0);
s.WriteMidiMultiByte((uint)e.Data.Length);
s.Write(e.Data, 0, e.Data.Length);
break;
}
}

}
}
}