improvements to MIDI data format

This commit is contained in:
Michael Becker 2019-12-18 04:31:22 -05:00
parent fd74e7a36e
commit f3df76ed40
No known key found for this signature in database
GPG Key ID: 389DFF5D73781A12
5 changed files with 252 additions and 63 deletions

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using UniversalEditor.Accessors;
using UniversalEditor.IO;
using UniversalEditor.ObjectModels.Multimedia.Audio.Synthesized;
namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI
@ -34,76 +35,61 @@ namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI
long position = br.Accessor.Position;
while (br.Accessor.Position - position < (long)trackLength)
{
try
int deltaTime = br.ReadVariableLengthInt32();
MIDIEventType command = (MIDIEventType) br.ReadByte();
if (command == MIDIEventType.Meta)
{
int deltaTime = br.ReadVariableLengthInt32();
byte command = br.ReadByte();
if (command == 255)
MIDIMetaEventType metaEventType = (MIDIMetaEventType) br.ReadByte();
byte length = br.ReadByte();
switch (metaEventType)
{
byte metaEventType = br.ReadByte();
byte b = metaEventType;
if (b <= 47)
case MIDIMetaEventType.Text:
{
switch (b)
{
case 1:
{
byte length = br.ReadByte();
string text = br.ReadFixedLengthString(length);
track.Commands.Add(new SynthesizedAudioCommandText(text));
break;
}
case 2:
{
break;
}
case 3:
{
byte length = br.ReadByte();
string text = br.ReadFixedLengthString(length);
track.Name = text;
break;
}
default:
{
if (b == 47)
{
byte endOfTrackMarker = br.ReadByte();
syn.Tracks.Add(track);
}
break;
}
}
string text = br.ReadFixedLengthString(length);
track.Commands.Add(new SynthesizedAudioCommandText(text));
break;
}
else
case MIDIMetaEventType.CopyrightNotice:
{
if (b != 81)
{
if (b == 88)
{
byte zero4 = br.ReadByte();
byte numerator = br.ReadByte();
byte denominator = (byte)Math.Pow(2.0, (double)br.ReadByte());
byte ticksPerMetronomeClick = br.ReadByte();
byte numberOf32ndNotesPerQuarterNote = br.ReadByte();
track.Commands.Add(new SynthesizedAudioCommandTimeSignature(numerator, denominator, ticksPerMetronomeClick, numberOf32ndNotesPerQuarterNote));
}
}
else
{
byte zero5 = br.ReadByte();
int tempo = (int)br.ReadInt16();
int tempo2 = (int)br.ReadByte();
int tempo3 = tempo + tempo2;
track.Commands.Add(new SynthesizedAudioCommandTempo((double)tempo3));
}
break;
}
case MIDIMetaEventType.SequenceName:
{
string text = br.ReadFixedLengthString(length);
track.Name = text;
break;
}
case MIDIMetaEventType.EndOfTrack:
{
syn.Tracks.Add(track);
break;
}
case MIDIMetaEventType.TimeSignature:
{
byte zero4 = br.ReadByte();
byte numerator = br.ReadByte();
byte denominator = (byte)Math.Pow(2.0, (double)br.ReadByte());
byte ticksPerMetronomeClick = br.ReadByte();
byte numberOf32ndNotesPerQuarterNote = br.ReadByte();
track.Commands.Add(new SynthesizedAudioCommandTimeSignature(numerator, denominator, ticksPerMetronomeClick, numberOf32ndNotesPerQuarterNote));
break;
}
case MIDIMetaEventType.SetTempo:
{
byte zero5 = br.ReadByte();
int tempo = (int)br.ReadInt16();
int tempo2 = (int)br.ReadByte();
int tempo3 = tempo + tempo2;
track.Commands.Add(new SynthesizedAudioCommandTempo((double)tempo3));
break;
}
default:
{
Console.WriteLine("ue: MIDI: warning: meta event type {0} ({1}) [{2} bytes] unhandled", metaEventType, (byte)metaEventType, length);
break;
}
}
}
catch (System.IO.EndOfStreamException ex)
{
continue;
}
}
}
}
@ -154,7 +140,91 @@ namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI
protected override void SaveInternal(ObjectModel objectModel)
{
throw new NotImplementedException();
Writer bw = Accessor.Writer;
bw.Endianness = Endianness.BigEndian;
SynthesizedAudioObjectModel syn = objectModel as SynthesizedAudioObjectModel;
bw.WriteFixedLengthString("MThd");
int headerSize = 6;
bw.WriteInt32(headerSize);
MIDIFileFormatType fileFormat = MIDIFileFormatType.SimultaneousMultitrack;
bw.WriteInt16((short)fileFormat);
bw.WriteInt16((short)syn.Tracks.Count);
short ticksPerQuarterNote = 0; // if MSB is 1, bits 0-7 are ticks / frame and bits 8-14 are frames / second
bw.WriteInt16(ticksPerQuarterNote);
for (short i = 0; i < syn.Tracks.Count; i += 1)
{
bw.WriteFixedLengthString("MTrk");
int trackLength = 0;
bw.WriteInt32(trackLength);
SynthesizedAudioTrack track = syn.Tracks[i];
// track name
WriteMIDIEvent(bw, 0, MIDIMetaEventType.SequenceName, track.Name);
for (int j = 0; j < syn.Tracks[i].Commands.Count; j++)
{
int deltaTime = 0;
if (syn.Tracks[i].Commands[j] is SynthesizedAudioCommandText)
{
SynthesizedAudioCommandText cmd = (syn.Tracks[i].Commands[j] as SynthesizedAudioCommandText);
WriteMIDIEvent(bw, deltaTime, MIDIMetaEventType.Text, cmd.Text);
}
else if (syn.Tracks[i].Commands[j] is SynthesizedAudioCommandTimeSignature)
{
SynthesizedAudioCommandTimeSignature cmd = (syn.Tracks[i].Commands[j] as SynthesizedAudioCommandTimeSignature);
byte numerator = 4;
byte denom = 4;
byte denominator = (byte)(Math.Sqrt(denom) / Math.Sqrt(2));
byte ticksPerMetronomeClick = 0;
byte numberOf32ndNotesPerQuarterNote = 0;
WriteMIDIEvent(bw, deltaTime, MIDIMetaEventType.TimeSignature, new byte[] { numerator, denominator, ticksPerMetronomeClick, numberOf32ndNotesPerQuarterNote });
}
else if (syn.Tracks[i].Commands[j] is SynthesizedAudioCommandTempo)
{
SynthesizedAudioCommandTempo cmd = (syn.Tracks[i].Commands[j] as SynthesizedAudioCommandTempo);
MemoryAccessor ma = new MemoryAccessor();
ma.Writer.WriteInt24((int)cmd.Tempo);
WriteMIDIEvent(bw, deltaTime, MIDIMetaEventType.SetTempo, ma.ToArray());
}
}
WriteMIDIEvent(bw, 0, MIDIMetaEventType.EndOfTrack, new byte[0]);
}
}
private void WriteMIDIEvent(Writer bw, int deltaTime, MIDIMetaEventType midiEventType, string data)
{
WriteMIDIEvent(bw, deltaTime, midiEventType, System.Text.Encoding.UTF8.GetBytes(data));
}
private void WriteMIDIEvent(Writer bw, int deltaTime, MIDIMetaEventType midiEventType, byte[] data)
{
bw.WriteVariableLengthInt32(deltaTime);
bw.WriteByte((byte)MIDIEventType.Meta);
bw.WriteByte((byte)midiEventType);
bw.WriteVariableLengthInt32(data.Length);
bw.WriteBytes(data);
}
private void WriteMIDIEvent(Writer bw, int deltaTime, MIDIEventType midiEventType, string data)
{
WriteMIDIEvent(bw, deltaTime, midiEventType, System.Text.Encoding.UTF8.GetBytes(data));
}
private void WriteMIDIEvent(Writer bw, int deltaTime, MIDIEventType midiEventType, byte[] data)
{
bw.WriteVariableLengthInt32(deltaTime);
bw.WriteByte((byte)midiEventType);
bw.WriteVariableLengthInt32(data.Length);
bw.WriteBytes(data);
}
}
}

View File

@ -0,0 +1,30 @@
//
// MIDIEventType.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// Copyright (c) 2019 Mike Becker
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI
{
public enum MIDIEventType : byte
{
SysEx = 0xF0,
Escape = 0xF7,
Meta = 0xFF
}
}

View File

@ -0,0 +1,44 @@
//
// MIDIFileFormatType.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// Copyright (c) 2019 Mike Becker
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI
{
public enum MIDIFileFormatType : short
{
/// <summary>
/// Single track (format 0). Consists of a header-chunk and a single track-chunk. The single track chunk will contain all the
/// note and tempo information.
/// </summary>
SingleTrack = 0,
/// <summary>
/// Simultaneous multi-track (format 1). Consists of a header-chunk and one or more track-chunks, with all tracks being played
/// simultaneously. The first track of a Format 1 file is special, and is also known as the 'Tempo Map'. It should contain all
/// meta-events of the types Time Signature, and Set Tempo. The meta-events Sequence/Track Name, Sequence Number, Marker, and
/// SMTPE Offset. should also be on the first track of a Format 1 file.
/// </summary>
SimultaneousMultitrack = 1,
/// <summary>
/// Independent multi-track (format 2). Consists of a header-chunk and one or more track-chunks, where each track represents an
/// independent sequence.
/// </summary>
IndependentMultitrack = 2
}
}

View File

@ -0,0 +1,42 @@
//
// MIDIMetaEventType.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// Copyright (c) 2019 Mike Becker
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI
{
public enum MIDIMetaEventType : byte
{
SequenceNumber = 0x00,
Text = 0x01,
CopyrightNotice = 0x02,
SequenceName = 0x03,
InstrumentName = 0x04,
Lyric = 0x05,
Marker = 0x06,
CuePoint = 0x07,
ChannelPrefix = 0x20,
EndOfTrack = 0x2F,
SetTempo = 0x51,
SMPTEOffset = 0x54,
TimeSignature = 0x58,
KeySignature = 0x59,
SequencerSpecific = 0x7F
}
}

View File

@ -324,6 +324,9 @@
<Compile Include="DataFormats\Multimedia\Audio\Synthesized\EdLib\D00DataFormat.cs" />
<Compile Include="DataFormats\Multimedia\Audio\Synthesized\EdLib\AdlibSoundcard.cs" />
<Compile Include="DataFormats\Multimedia\Audio\Synthesized\EdLib\AdlibBlockType.cs" />
<Compile Include="DataFormats\Multimedia\Audio\Synthesized\MIDI\MIDIFileFormatType.cs" />
<Compile Include="DataFormats\Multimedia\Audio\Synthesized\MIDI\MIDIEventType.cs" />
<Compile Include="DataFormats\Multimedia\Audio\Synthesized\MIDI\MIDIMetaEventType.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Compression\UniversalEditor.Compression.csproj">