From f3df76ed405a76a6cd567e0071fd9413ce263d71 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Wed, 18 Dec 2019 04:31:22 -0500 Subject: [PATCH] improvements to MIDI data format --- .../Audio/Synthesized/MIDI/MIDIDataFormat.cs | 196 ++++++++++++------ .../Audio/Synthesized/MIDI/MIDIEventType.cs | 30 +++ .../Synthesized/MIDI/MIDIFileFormatType.cs | 44 ++++ .../Synthesized/MIDI/MIDIMetaEventType.cs | 42 ++++ .../UniversalEditor.Plugins.Multimedia.csproj | 3 + 5 files changed, 252 insertions(+), 63 deletions(-) create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIEventType.cs create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIFileFormatType.cs create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIMetaEventType.cs diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIDataFormat.cs b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIDataFormat.cs index c14cf1cd..af902929 100644 --- a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIDataFormat.cs +++ b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIDataFormat.cs @@ -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); } } } diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIEventType.cs b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIEventType.cs new file mode 100644 index 00000000..e1cfee5e --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIEventType.cs @@ -0,0 +1,30 @@ +// +// MIDIEventType.cs +// +// Author: +// Mike Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI +{ + public enum MIDIEventType : byte + { + SysEx = 0xF0, + Escape = 0xF7, + Meta = 0xFF + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIFileFormatType.cs b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIFileFormatType.cs new file mode 100644 index 00000000..88f044e7 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIFileFormatType.cs @@ -0,0 +1,44 @@ +// +// MIDIFileFormatType.cs +// +// Author: +// Mike Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.Multimedia.Audio.Synthesized.MIDI +{ + public enum MIDIFileFormatType : short + { + /// + /// 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. + /// + SingleTrack = 0, + /// + /// 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. + /// + SimultaneousMultitrack = 1, + /// + /// Independent multi-track (format 2). Consists of a header-chunk and one or more track-chunks, where each track represents an + /// independent sequence. + /// + IndependentMultitrack = 2 + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIMetaEventType.cs b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIMetaEventType.cs new file mode 100644 index 00000000..24f1992b --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/DataFormats/Multimedia/Audio/Synthesized/MIDI/MIDIMetaEventType.cs @@ -0,0 +1,42 @@ +// +// MIDIMetaEventType.cs +// +// Author: +// Mike Becker +// +// 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 . +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 + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/UniversalEditor.Plugins.Multimedia.csproj b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/UniversalEditor.Plugins.Multimedia.csproj index 1694e200..c9c4db24 100644 --- a/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/UniversalEditor.Plugins.Multimedia.csproj +++ b/CSharp/Plugins/UniversalEditor.Plugins.Multimedia/UniversalEditor.Plugins.Multimedia.csproj @@ -324,6 +324,9 @@ + + +