1
0
mirror of https://github.com/SineVector241/VoiceCraft-MCBE_Proximity_Chat.git synced 2024-11-20 10:27:45 +00:00
VoiceCraft-MCBE_Proximity_Chat/ATL/AudioData/IO/MIDI.cs
2024-07-13 11:16:08 +10:00

977 lines
47 KiB
C#

/****************************************************************************
Software: Midi Class
Version: 1.5
Date: 2005/04/25
Author: Valentin Schmidt
Contact: fluxus@freenet.de
License: Freeware
You may use and modify this software as you wish.
Translated to C# by Zeugma 440
Other formats : http://www.musicxml.com/for-developers/
****************************************************************************/
/*
==== SOME NOTES CONCERNING LENGTH CALCULATION - Z440 ====
- The calculated length is not "intelligent", i.e. the whole MIDI file is taken as musical material, even if there
is only one note followed by three minutes of TimeSig's and tempo changes. It may be a choice of policy, but
it appears that Winamp uses "intelligent" parsing, which ends the song whith the last _note_ event.
*/
using System;
using System.IO;
using ATL.Logging;
using System.Collections.Generic;
using System.Text;
using static ATL.AudioData.AudioDataManager;
using Commons;
using static ATL.ChannelsArrangements;
using static ATL.TagData;
namespace ATL.AudioData.IO
{
/// <summary>
/// Class for Musical Instruments Digital Interface files manipulation (extension : .MID, .MIDI)
/// </summary>
class Midi : MetaDataIO, IAudioDataIO
{
/// <summary>
/// Tracks of the song
/// </summary>
private IList<MidiTrack> tracks;
/// <summary>
/// Timebase = ticks per frame (quarter note) a.k.a. PPQN (Pulses Per Quarter Note)
/// </summary>
private int timebase;
/// <summary>
/// Tempo (0 for unknown)
/// NB : there is no such thing as an uniform "song tempo" since tempo can change over time within each track !
/// </summary>
private long tempo;
/// <summary>
/// MIDI structure type
/// 0 - single-track
/// 1 - multiple tracks, synchronous
/// 2 - multiple tracks, asynchronous
/// </summary>
private byte type;
private static class MidiEvents
{
// Standard events
public const int EVT_PROGRAM_CHANGE = 0x0C;
public const int EVT_NOTE_ON = 0x09;
public const int EVT_NOTE_OFF = 0x08;
public const int EVT_POLY_PRESSURE = 0x0A;
public const int EVT_CONTROLLER_CHANGE = 0x0B;
public const int EVT_CHANNEL_PRESSURE = 0x0D;
public const int EVT_PITCH_BEND = 0x0E;
public const int EVT_META = 0xFF;
public const int EVT_SYSEX = 0xF0;
// Meta events
public const int META_SEQUENCE_NUM = 0x00;
public const int META_TEXT = 0x01;
public const int META_COPYRIGHT = 0x02;
public const int META_TRACK_NAME = 0x03;
public const int META_INSTRUMENT_NAME = 0x04;
public const int META_LYRICS = 0x05;
public const int META_MARKER = 0x06;
public const int META_CUE = 0x07;
public const int META_CHANNEL_PREFIX = 0x20;
public const int META_CHANNEL_PREFIX_PORT = 0x21;
public const int META_TRACK_END = 0x2F;
public const int META_TEMPO = 0x51;
public const int META_SMPTE_OFFSET = 0x54;
public const int META_TIME_SIGNATURE = 0x58;
public const int META_KEY_SIGNATURE = 0x59;
public const int META_SEQUENCER_DATA = 0x7F; // Sequencer specific data
}
private sealed class MidiEvent
{
public long TickOffset = 0;
public int Type = 0;
public bool isMetaEvent = false;
public int Channel = 0;
public int Param0 = 0;
public int Param1 = 0;
public string Description;
public MidiEvent(long tickOffset, int type, int channel, int param0, int param1 = 0)
{
TickOffset = tickOffset;
Type = type;
Channel = channel;
Param0 = param0;
Param1 = param1;
}
}
private sealed class MidiTrack
{
public long Duration = 0;
public long Ticks = 0;
public long LastSignificantEventTicks = -1;
public MidiEvent LastEvent = null;
public IList<MidiEvent> events = new List<MidiEvent>();
public void Add(MidiEvent evt)
{
events.Add(evt);
LastEvent = evt;
if (MidiEvents.EVT_NOTE_ON == evt.Type
|| MidiEvents.EVT_NOTE_OFF == evt.Type
|| MidiEvents.EVT_CONTROLLER_CHANGE == evt.Type
|| MidiEvents.EVT_CHANNEL_PRESSURE == evt.Type
|| MidiEvents.EVT_POLY_PRESSURE == evt.Type
|| MidiEvents.EVT_PITCH_BEND == evt.Type
|| MidiEvents.META_TRACK_END == evt.Type
)
{
LastSignificantEventTicks = evt.TickOffset;
}
}
}
#region instruments
private static readonly string[] instrumentList = new string[] { "Piano",
"Bright Piano",
"Electric Grand",
"Honky Tonk Piano",
"Electric Piano 1",
"Electric Piano 2",
"Harpsichord",
"Clavinet",
"Celesta",
"Glockenspiel",
"Music Box",
"Vibraphone",
"Marimba",
"Xylophone",
"Tubular Bell",
"Dulcimer",
"Hammond Organ",
"Perc Organ",
"Rock Organ",
"Church Organ",
"Reed Organ",
"Accordion",
"Harmonica",
"Tango Accordion",
"Nylon Str Guitar",
"Steel String Guitar",
"Jazz Electric Gtr",
"Clean Guitar",
"Muted Guitar",
"Overdrive Guitar",
"Distortion Guitar",
"Guitar Harmonics",
"Acoustic Bass",
"Fingered Bass",
"Picked Bass",
"Fretless Bass",
"Slap Bass 1",
"Slap Bass 2",
"Syn Bass 1",
"Syn Bass 2",
"Violin",
"Viola",
"Cello",
"Contrabass",
"Tremolo Strings",
"Pizzicato Strings",
"Orchestral Harp",
"Timpani",
"Ensemble Strings",
"Slow Strings",
"Synth Strings 1",
"Synth Strings 2",
"Choir Aahs",
"Voice Oohs",
"Syn Choir",
"Orchestra Hit",
"Trumpet",
"Trombone",
"Tuba",
"Muted Trumpet",
"French Horn",
"Brass Ensemble",
"Syn Brass 1",
"Syn Brass 2",
"Soprano Sax",
"Alto Sax",
"Tenor Sax",
"Baritone Sax",
"Oboe",
"English Horn",
"Bassoon",
"Clarinet",
"Piccolo",
"Flute",
"Recorder",
"Pan Flute",
"Bottle Blow",
"Shakuhachi",
"Whistle",
"Ocarina",
"Syn Square Wave",
"Syn Saw Wave",
"Syn Calliope",
"Syn Chiff",
"Syn Charang",
"Syn Voice",
"Syn Fifths Saw",
"Syn Brass and Lead",
"Fantasia",
"Warm Pad",
"Polysynth",
"Space Vox",
"Bowed Glass",
"Metal Pad",
"Halo Pad",
"Sweep Pad",
"Ice Rain",
"Soundtrack",
"Crystal",
"Atmosphere",
"Brightness",
"Goblins",
"Echo Drops",
"Sci Fi",
"Sitar",
"Banjo",
"Shamisen",
"Koto",
"Kalimba",
"Bag Pipe",
"Fiddle",
"Shanai",
"Tinkle Bell",
"Agogo",
"Steel Drums",
"Woodblock",
"Taiko Drum",
"Melodic Tom",
"Syn Drum",
"Reverse Cymbal",
"Guitar Fret Noise",
"Breath Noise",
"Seashore",
"Bird",
"Telephone",
"Helicopter",
"Applause",
"Gunshot"};
#endregion
private static readonly byte[] MIDI_FILE_HEADER = Utils.Latin1Encoding.GetBytes("MThd");
private const string MIDI_TRACK_HEADER = "MTrk";
private const int DEFAULT_TEMPO = 500; // Default MIDI tempo is 120bpm, 500ms per beat
private StringBuilder comment;
private SizeInfo sizeInfo;
// ---------- INFORMATIVE INTERFACE IMPLEMENTATIONS & MANDATORY OVERRIDES
// From IAudioDataIO
/// <inheritdoc/>
public int SampleRate => 0;
/// <inheritdoc/>
public bool IsVBR => false;
/// <inheritdoc/>
public Format AudioFormat
{
get;
}
/// <inheritdoc/>
public int CodecFamily => AudioDataIOFactory.CF_SEQ;
/// <inheritdoc/>
public string FileName { get; }
/// <inheritdoc/>
public double BitRate { get; private set; }
/// <inheritdoc/>
public int BitDepth => -1; // Irrelevant for that format
/// <inheritdoc/>
public double Duration { get; private set; }
/// <inheritdoc/>
public ChannelsArrangement ChannelsArrangement => STEREO;
/// <inheritdoc/>
public List<MetaDataIOFactory.TagType> GetSupportedMetas()
{
return new List<MetaDataIOFactory.TagType> { MetaDataIOFactory.TagType.NATIVE }; // Only for comments
}
/// <inheritdoc/>
public long AudioDataOffset { get; set; }
/// <inheritdoc/>
public long AudioDataSize { get; set; }
// From IMetaDataIO
/// <inheritdoc/>
protected override int getDefaultTagOffset() => TO_BUILTIN;
/// <inheritdoc/>
protected override MetaDataIOFactory.TagType getImplementedTagType() => MetaDataIOFactory.TagType.NATIVE;
/// <inheritdoc/>
protected override Field getFrameMapping(string zone, string ID, byte tagVersion)
{
throw new NotImplementedException();
}
// ---------- CONSTRUCTORS & INITIALIZERS
/// <summary>
/// Reset all data
/// </summary>
protected void resetData()
{
Duration = 0;
BitRate = 0;
AudioDataOffset = -1;
AudioDataSize = 0;
ResetData();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="filePath">File path</param>
/// <param name="format">Audio format</param>
public Midi(string filePath, Format format)
{
this.FileName = filePath;
AudioFormat = format;
resetData();
}
// === PRIVATE STRUCTURES/SUBCLASSES ===
private double getDuration()
{
long maxTicks = 0;
double maxDuration = 0; // Longest duration among all tracks
if (tracks.Count > 0)
{
// Look for the position of the latest "significant event" of the entire song (including TrackEnd)
foreach (MidiTrack aTrack in tracks)
{
maxTicks = Math.Max(maxTicks, aTrack.LastSignificantEventTicks);
}
long currentDuration = 0;
long lastTempoTicks = 0;
int currentTempo = DEFAULT_TEMPO;
// Build "duration chunks" using the "master tempo map" provided in track 0
foreach (MidiEvent evt in tracks[0].events)
{
if (MidiEvents.META_TEMPO == evt.Type)
{
currentDuration += (evt.TickOffset - lastTempoTicks) * currentTempo;
currentTempo = evt.Param0;
lastTempoTicks = evt.TickOffset;
}
}
// Make sure the song lasts until the latest event
if (maxTicks > lastTempoTicks) currentDuration += (maxTicks - lastTempoTicks) * currentTempo;
maxDuration = currentDuration;
}
// For an obscure reason, this algorithm constantly calculates
// a duration equals to (actual duration calculated by BASSMIDI - 1 second), hence this ugly " + 1000"
return (maxDuration / timebase / 1000.00) + 1000;
}
/****************************************************************************
* *
* Public methods *
* *
****************************************************************************/
/// <inheritdoc/>
public bool Read(Stream source, SizeInfo sizeInfo, ReadTagParams readTagParams)
{
this.sizeInfo = sizeInfo;
return read(source, readTagParams);
}
public static bool IsValidHeader(byte[] data)
{
return StreamUtils.ArrBeginsWith(data, MIDI_FILE_HEADER);
}
public static bool FindValidHeader(Stream source)
{
return StreamUtils.FindSequence(source, MIDI_FILE_HEADER);
}
/// <inheritdoc/>
protected override bool read(Stream source, ReadTagParams readTagParams)
{
byte[] buffer = new byte[10];
string trigger;
resetData();
// Ignores everything (comments) written before the MIDI header
FindValidHeader(source);
// Ready to read header data...
source.Read(buffer, 0, buffer.Length);
if (buffer[0] != 0 ||
buffer[1] != 0 ||
buffer[2] != 0 ||
buffer[3] != 6)
{
LogDelegator.GetLogDelegate()(Log.LV_ERROR, "Wrong MIDI header");
return false;
}
type = buffer[5];
// MIDI STRUCTURE TYPE
// 0 - single-track
// 1 - multiple tracks, synchronous
// 2 - multiple tracks, asynchronous
if (type > 1)
{
LogDelegator.GetLogDelegate()(Log.LV_WARNING, "SMF type 2 MIDI files are partially supported; results may be approximate");
}
tagExists = true;
timebase = (buffer[8] << 8) + buffer[9];
tempo = 0; // maybe (hopefully!) overwritten by parseTrack
int nbTrack = 0;
comment = new StringBuilder("");
AudioDataOffset = source.Position;
AudioDataSize = sizeInfo.FileSize - AudioDataOffset;
IList<MidiTrack> m_tracks = new List<MidiTrack>();
// Ready to read track data...
while (source.Position < sizeInfo.FileSize - 4)
{
source.Read(buffer, 0, 4);
trigger = Utils.Latin1Encoding.GetString(buffer, 0, 4);
if (trigger != MIDI_TRACK_HEADER)
{
source.Seek(-3, SeekOrigin.Current);
if (!StreamUtils.FindSequence(source, Utils.Latin1Encoding.GetBytes(MIDI_TRACK_HEADER))) break;
}
// trackSize is stored in big endian -> needs inverting
source.Read(buffer, 0, 4);
var trackSize = StreamUtils.DecodeBEInt32(buffer);
byte[] trackData = new byte[trackSize];
source.Read(trackData, 0, trackSize);
m_tracks.Add(parseTrack(trackData, nbTrack));
nbTrack++;
}
tracks = m_tracks;
if (comment.Length > 0) comment.Remove(comment.Length - 1, 1);
tagData.IntegrateValue(TagData.Field.COMMENT, comment.ToString());
Duration = getDuration();
BitRate = sizeInfo.FileSize / Duration;
return true;
}
#region Legacy Utilities
//***************************************************************
// PUBLIC UTILITIES
//***************************************************************
//---------------------------------------------------------------
// returns list of drumset instrument names
//---------------------------------------------------------------
/* ## TO DO
function getDrumset(){
return array(
35=>'Acoustic Bass Drum',
36=>'Bass Drum 1',
37=>'Side Stick',
38=>'Acoustic Snare',
39=>'Hand Clap',
40=>'Electric Snare',
41=>'Low Floor Tom',
42=>'Closed Hi-Hat',
43=>'High Floor Tom',
44=>'Pedal Hi-Hat',
45=>'Low Tom',
46=>'Open Hi-Hat',
47=>'Low Mid Tom',
48=>'High Mid Tom',
49=>'Crash Cymbal 1',
50=>'High Tom',
51=>'Ride Cymbal 1',
52=>'Chinese Cymbal',
53=>'Ride Bell',
54=>'Tambourine',
55=>'Splash Cymbal',
56=>'Cowbell',
57=>'Crash Cymbal 2',
58=>'Vibraslap',
59=>'Ride Cymbal 2',
60=>'High Bongo',
61=>'Low Bongo',
62=>'Mute High Conga',
63=>'Open High Conga',
64=>'Low Conga',
65=>'High Timbale',
66=>'Low Timbale',
//35..66
67=>'High Agogo',
68=>'Low Agogo',
69=>'Cabase',
70=>'Maracas',
71=>'Short Whistle',
72=>'Long Whistle',
73=>'Short Guiro',
74=>'Long Guiro',
75=>'Claves',
76=>'High Wood Block',
77=>'Low Wood Block',
78=>'Mute Cuica',
79=>'Open Cuica',
80=>'Mute Triangle',
81=>'Open Triangle');
}
*/
//---------------------------------------------------------------
// returns list of standard drum kit names
//---------------------------------------------------------------
/* ## TO DO
function getDrumkitList(){
return array(
1 => 'Dry',
9 => 'Room',
19 => 'Power',
25 => 'Electronic',
33 => 'Jazz',
41 => 'Brush',
57 => 'SFX',
128 => 'Default'
);
}
*/
//---------------------------------------------------------------
// returns list of note names
//---------------------------------------------------------------
/*
function getNoteList(){
//note 69 (A6) = A440
//note 60 (C6) = Middle C
return array(
//Do Re Mi Fa So La Ti
'C0', 'Cs0', 'D0', 'Ds0', 'E0', 'F0', 'Fs0', 'G0', 'Gs0', 'A0', 'As0', 'B0',
'C1', 'Cs1', 'D1', 'Ds1', 'E1', 'F1', 'Fs1', 'G1', 'Gs1', 'A1', 'As1', 'B1',
'C2', 'Cs2', 'D2', 'Ds2', 'E2', 'F2', 'Fs2', 'G2', 'Gs2', 'A2', 'As2', 'B2',
'C3', 'Cs3', 'D3', 'Ds3', 'E3', 'F3', 'Fs3', 'G3', 'Gs3', 'A3', 'As3', 'B3',
'C4', 'Cs4', 'D4', 'Ds4', 'E4', 'F4', 'Fs4', 'G4', 'Gs4', 'A4', 'As4', 'B4',
'C5', 'Cs5', 'D5', 'Ds5', 'E5', 'F5', 'Fs5', 'G5', 'Gs5', 'A5', 'As5', 'B5',
'C6', 'Cs6', 'D6', 'Ds6', 'E6', 'F6', 'Fs6', 'G6', 'Gs6', 'A6', 'As6', 'B6',
'C7', 'Cs7', 'D7', 'Ds7', 'E7', 'F7', 'Fs7', 'G7', 'Gs7', 'A7', 'As7', 'B7',
'C8', 'Cs8', 'D8', 'Ds8', 'E8', 'F8', 'Fs8', 'G8', 'Gs8', 'A8', 'As8', 'B8',
'C9', 'Cs9', 'D9', 'Ds9', 'E9', 'F9', 'Fs9', 'G9', 'Gs9', 'A9', 'As9', 'B9',
'C10','Cs10','D10','Ds10','E10','F10','Fs10','G10');
}
*/
#endregion
private MidiTrack parseTrack(byte[] data, int trackNumber)
{
MidiTrack track = new MidiTrack();
int trackLen = data.Length;
int position = 0;
long currentTicks = 0;
int currentDelta;
byte eventType;
int eventTypeHigh;
int eventTypeLow;
byte meta;
int num;
int len;
byte tmp;
byte c;
string txt;
MidiEvent evt;
int currentTempo = 0;
long lastTempoTicks = -1;
while (position < trackLen)
{
// timedelta
currentDelta = readVarLen(ref data, ref position);
currentTicks += currentDelta;
eventType = data[position];
eventTypeHigh = (eventType >> 4);
eventTypeLow = (eventType - eventTypeHigh * 16);
switch (eventTypeHigh)
{
case MidiEvents.EVT_PROGRAM_CHANGE: //PrCh = ProgramChange
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, data[position + 1]);
evt.Description = " PrCh ch=" + evt.Channel + " p=" + evt.Param0;
track.Add(evt);
position += 2;
break;
case MidiEvents.EVT_NOTE_ON: //On
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, data[position + 1], data[position + 2]);
evt.Description = " On ch=" + evt.Channel + " n=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 3;
break;
case MidiEvents.EVT_NOTE_OFF: //Off
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, data[position + 1], data[position + 2]);
evt.Description = " Off ch=" + evt.Channel + " n=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 3;
break;
case MidiEvents.EVT_POLY_PRESSURE: //PoPr = PolyPressure
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, data[position + 1], data[position + 2]);
evt.Description = " PoPr ch=" + evt.Channel + " n=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 3;
break;
case MidiEvents.EVT_CONTROLLER_CHANGE: //Par = ControllerChange
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, data[position + 1], data[position + 2]);
evt.Description = " Par ch=" + evt.Channel + " c=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 3;
break;
case MidiEvents.EVT_CHANNEL_PRESSURE: //ChPr = ChannelPressure
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, data[position + 1]);
evt.Description = " ChPr ch=" + evt.Channel + " v=" + evt.Param0;
track.Add(evt);
position += 2;
break;
case MidiEvents.EVT_PITCH_BEND: //Pb = PitchBend
evt = new MidiEvent(currentTicks, eventTypeHigh, eventTypeLow + 1, (data[position + 1] & 0x7F) | ((data[position + 2] & 0x7F) << 7));
evt.Description = " Pb ch=" + evt.Channel + " v=" + evt.Param0;
track.Add(evt);
position += 3;
break;
default:
switch (eventType)
{
case 0xFF: // Meta
meta = data[position + 1];
switch (meta)
{
case MidiEvents.META_SEQUENCE_NUM: // sequence_number
tmp = data[position + 2];
if (tmp == 0x00) { num = trackNumber; position += 3; }
else { num = 1; position += 5; }
evt = new MidiEvent(currentTicks, meta, -1, num);
evt.isMetaEvent = true;
evt.Description = " Seqnr " + evt.Param0;
track.Add(evt);
break;
case MidiEvents.META_TEXT: // Meta Text
case MidiEvents.META_COPYRIGHT: // Meta Copyright
case MidiEvents.META_TRACK_NAME: // Meta TrackName ???sequence_name???
case MidiEvents.META_INSTRUMENT_NAME: // Meta InstrumentName
case MidiEvents.META_LYRICS: // Meta Lyrics
case MidiEvents.META_MARKER: // Meta Marker
case MidiEvents.META_CUE: // Meta Cue
string[] texttypes = new string[7] { "Text", "Copyright", "TrkName", "InstrName", "Lyric", "Marker", "Cue" };
string textType = texttypes[meta - 1];
position += 2;
len = readVarLen(ref data, ref position);
if ((len + position) > trackLen) throw new InvalidDataException("Meta " + textType + " has corrupt variable length field (" + len + ") [track: " + trackNumber + " dt: " + currentDelta + "]");
txt = Encoding.ASCII.GetString(data, position, len);
if (MidiEvents.META_TEXT == meta || MidiEvents.META_TRACK_NAME == meta || MidiEvents.META_MARKER == meta) comment.Append(txt).Append(Settings.InternalValueSeparator);
else if (MidiEvents.META_COPYRIGHT == meta) tagData.IntegrateValue(TagData.Field.COPYRIGHT, txt);
evt = new MidiEvent(currentTicks, meta, -1, meta - 1);
evt.isMetaEvent = true;
evt.Description = " Meta " + textType + " \"" + txt + "\"";
track.Add(evt);
position += len;
break;
case MidiEvents.META_CHANNEL_PREFIX: // ChannelPrefix
evt = new MidiEvent(currentTicks, meta, -1, data[position + 3]);
evt.isMetaEvent = true;
evt.Description = " Meta ChannelPrefix " + evt.Param0;
track.Add(evt);
position += 4;
break;
case MidiEvents.META_CHANNEL_PREFIX_PORT: // ChannelPrefixOrPort
evt = new MidiEvent(currentTicks, meta, -1, data[position + 3]);
evt.isMetaEvent = true;
evt.Description = " Meta ChannelPrefixOrPort " + evt.Param0;
track.Add(evt);
position += 4;
break;
case MidiEvents.META_TRACK_END: // Meta TrkEnd
evt = new MidiEvent(currentTicks, meta, -1, -1);
evt.isMetaEvent = true;
evt.Description = " Meta TrkEnd";
track.Add(evt);
track.Ticks = currentTicks;
if (lastTempoTicks > -1) // there has been at least one tempo change in the track
{
track.Duration += (currentTicks - lastTempoTicks) * currentTempo;
}
else
{
track.Duration = currentTicks * this.tempo;
}
return track;//ignore rest
case MidiEvents.META_TEMPO: // Tempo
// Adds (ticks since last tempo event)*current tempo to track duration
if (lastTempoTicks > -1)
{
track.Duration += (currentTicks - lastTempoTicks) * currentTempo;
}
lastTempoTicks = currentTicks;
currentTempo = data[position + 3] * 0x010000 + data[position + 4] * 0x0100 + data[position + 5];
if (0 == currentTempo) currentTempo = DEFAULT_TEMPO;
evt = new MidiEvent(currentTicks, meta, -1, currentTempo);
evt.isMetaEvent = true;
evt.Description = " Meta Tempo " + evt.Param0 + " (duration :" + track.Duration + ")";
track.Add(evt);
// Sets song tempo as last tempo event of 1st track
// according to some MIDI files convention
if (0 == trackNumber/* && 0 == this.tempo*/)
{
this.tempo = currentTempo;
}
position += 6;
break;
case MidiEvents.META_SMPTE_OFFSET: // SMPTE offset
byte h = data[position + 3];
byte m = data[position + 4];
byte s = data[position + 5];
byte f = data[position + 6];
byte fh = data[position + 7];
// TODO : store the arguments in a solid structure within MidiEvent
evt = new MidiEvent(currentTicks, meta, -1, -1);
evt.isMetaEvent = true;
evt.Description = " Meta SMPTE " + h + " " + m + " " + s + " " + f + " " + fh;
track.Add(evt);
position += 8;
break;
case MidiEvents.META_TIME_SIGNATURE: // TimeSig
byte z = data[position + 3];
int t = 2 ^ data[position + 4];
byte mc = data[position + 5];
c = data[position + 6];
// TODO : store the arguments in a solid structure within MidiEvent
evt = new MidiEvent(currentTicks, meta, -1, -1);
evt.isMetaEvent = true;
evt.Description = " Meta TimeSig " + z + "/" + t + " " + mc + " " + c;
track.Add(evt);
position += 7;
break;
case MidiEvents.META_KEY_SIGNATURE: // KeySig
evt = new MidiEvent(currentTicks, meta, -1, data[position + 3], data[position + 4]);
evt.isMetaEvent = true;
evt.Description = " Meta KeySig vz=" + evt.Param0 + " " + (evt.Param1 == 0 ? "major" : "minor");
track.Add(evt);
position += 5;
break;
case MidiEvents.META_SEQUENCER_DATA: // Sequencer specific data
position += 2;
len = readVarLen(ref data, ref position);
if ((len + position) > trackLen) throw new InvalidDataException("SeqSpec has corrupt variable length field (" + len + ") [track: " + trackNumber + " dt: " + currentDelta + "]");
position -= 3;
{
evt = new MidiEvent(currentTicks, meta, -1, currentTempo);
evt.isMetaEvent = true;
evt.Description = " Meta SeqSpec";
track.Add(evt);
}
position += len + 3;
break;
default:
// "unknown" Meta-Events
byte metacode = data[position + 1];
position += 2;
len = readVarLen(ref data, ref position);
if ((len + position) > trackLen) throw new InvalidDataException("Meta " + metacode + " has corrupt variable length field (" + len + ") [track: " + trackNumber + " dt: " + currentDelta + "]");
position -= 3;
{
String str = Encoding.ASCII.GetString(data, position + 3, len);
evt = new MidiEvent(currentTicks, meta, -1, currentTempo);
evt.isMetaEvent = true;
evt.Description = " Meta 0x" + metacode + " " + str;
track.Add(evt);
}
position += len + 3;
break;
} // switch meta
break; // End Meta
case MidiEvents.EVT_SYSEX: // SysEx
position += 1;
len = readVarLen(ref data, ref position);
if ((len + position) > trackLen) throw new InvalidDataException("SysEx has corrupt variable length field (" + len + ") [track: " + trackNumber + " dt: " + currentDelta + " p: " + position + "]");
{
evt = new MidiEvent(currentTicks, eventTypeHigh, -1, currentTempo);
evt.isMetaEvent = true;
evt.Description = " SysEx";
track.Add(evt);
}
position += len;
break;
default: // Repetition of last event?
if ((track.LastEvent.Type == MidiEvents.EVT_NOTE_ON) || (track.LastEvent.Type == MidiEvents.EVT_NOTE_OFF))
{
evt = new MidiEvent(currentTicks, track.LastEvent.Type, track.LastEvent.Channel, data[position], data[position + 1]);
evt.Description = " " + (track.LastEvent.Type == MidiEvents.EVT_NOTE_ON ? "On" : "Off") + " ch=" + evt.Channel + " n=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 2;
}
else if (track.LastEvent.Type == MidiEvents.EVT_PROGRAM_CHANGE)
{
evt = new MidiEvent(currentTicks, track.LastEvent.Type, track.LastEvent.Channel, data[position]);
evt.Description = " PrCh ch=" + evt.Channel + " p=" + evt.Param0;
track.Add(evt);
position += 1;
}
else if (track.LastEvent.Type == MidiEvents.EVT_POLY_PRESSURE)
{
evt = new MidiEvent(currentTicks, track.LastEvent.Type, track.LastEvent.Channel, data[position + 1], data[position + 2]);
evt.Description = " PoPr ch=" + evt.Channel + " n=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 2;
}
else if (track.LastEvent.Type == MidiEvents.EVT_CHANNEL_PRESSURE)
{
evt = new MidiEvent(currentTicks, track.LastEvent.Type, track.LastEvent.Channel, data[position]);
evt.Description = " ChPr ch=" + evt.Channel + " v=" + evt.Param0;
track.Add(evt);
position += 1;
}
else if (track.LastEvent.Type == MidiEvents.EVT_CONTROLLER_CHANGE)
{
evt = new MidiEvent(currentTicks, track.LastEvent.Type, track.LastEvent.Channel, data[position], data[position + 1]);
evt.Description = " Par ch=" + evt.Channel + " c=" + evt.Param0 + " v=" + evt.Param1;
track.Add(evt);
position += 2;
}
else if (track.LastEvent.Type == MidiEvents.EVT_PITCH_BEND)
{
evt = new MidiEvent(currentTicks, track.LastEvent.Type, track.LastEvent.Channel, (data[position] & 0x7F) | ((data[position + 1] & 0x7F) << 7));
evt.Description = " Pb ch=" + evt.Channel + " v=" + evt.Param0;
track.Add(evt);
position += 2;
}
//default:
// MM: ToDo: Repetition of SysEx and META-events? with <last>?? \n";
// _err("unknown repetition: $last");
break;
} // eventType
break;
} // $high
} // while p < trackLen
track.Ticks = currentTicks;
if (lastTempoTicks > -1)
{
track.Duration += (currentTicks - lastTempoTicks) * currentTempo;
}
else
{
track.Duration = currentTicks * this.tempo;
}
return track;
}
//---------------------------------------------------------------
// variable length string to int (+repositioning)
//---------------------------------------------------------------
//# TO LOOK AFTER CAREFULLY <.<
private static int readVarLen(ref byte[] data, ref int pos)
{
int value = data[pos++];
if ((value & 0x80) != 0)
{
value &= 0x7F;
int c;
do
{
c = data[pos++];
value = (value << 7) + (c & 0x7F);
} while ((c & 0x80) != 0);
}
return (value);
}
/// <inheritdoc/>
protected override int write(TagData tag, Stream s, string zone)
{
throw new NotImplementedException();
}
}
}