/* Create a new MIDI file (represented by this instance), including the header chunk and appropriate * number of track chunks (with End of Track events). This does not set delta-time division. */ private MidiHeader Create(int format, int numberOfTracks) { /* Make sure we start with a clean slate. */ this.Clear(); int n = MidiChunkInfo.SizeItem(); this.Bytes = new byte[n + MidiHeader.SizeItem()]; /* Start the header chunk. */ MidiChunkInfo chunkInfo = this.CreateChunkInfo(0, MidiChunkInfo.HeaderType); this.Items.Add(chunkInfo); /* Finish the header chunk. (Don't set NumberOfTracks here; AddTrack will set it.) */ MidiHeader header = new MidiHeader(this, n); header.Format = format; this.Items.Add(header); /* Add each track chunk (with an End of Track meta-event). */ for (int i = 0; i < numberOfTracks; ++i) { this.AddTrack(); this.AddMetaEvent(0, MidiMetaEvent.EndOfTrackType, null); } /* Delta-time division still needs to be set. */ return(header); }
/// <summary>Removes from this file the MidiItem object at the specified index.</summary> /// <param name="index">The index in this file's list of the MidiItem object to remove.</param> public void RemoveItem(int index) { /* Determine how many bytes to remove from the array. If the item is chunk info, the entire * chunk will be removed. If it is a track (MTrk) chunk, decrement the number of tracks. */ MidiItem item = this.Items[index]; int i, n = item.Size; MidiChunkInfo chunkInfo = item as MidiChunkInfo; if (chunkInfo != null) { n += chunkInfo.Length; i = this.Bytes.Length - item.Offset; if (n > i) { n = i; } if (chunkInfo.Type == MidiChunkInfo.TrackType) { --this.Header.NumberOfTracks; } } /* If the item is an MTrk event with a nonzero delta-time, the total time of each subsequent * event in the track/chunk will need to be adjusted accordingly (after this item is removed). */ MidiEvent mtrkEvent = item as MidiEvent; int deltaTime = (mtrkEvent == null) ? 0 : mtrkEvent.DeltaTime; /* Remove from the list all items whose offsets are within the range of data being removed. */ for (i = item.Offset + n; index < this.ItemCount && this.Items[index].Offset < i; this.Items.RemoveAt(index)) { item = this.Items[index]; /* If the item being removed is a channel message/event that does not use running status, there * should be a corresponding entry in the map of running statuses that needs to be removed. */ MidiChannelEvent channelEvent = item as MidiChannelEvent; if (channelEvent != null && !channelEvent.RunningStatus) { this.RunningStatusMap.Remove(item.Offset); } /* If the item being removed is a meta-event representing a key signature, there should * be a corresponding entry in the key signature map that also needs to be removed. */ MidiMetaEvent metaEvent = MidiFile.ItemToKeySignatureEvent(item); if (metaEvent != null) { this.KeySignatureMap.Remove(metaEvent.TotalTime); } } /* If applicable, cascade total time changes through all subsequent events in the track. */ this.AdjustTotalTimes(index, -deltaTime); /* Remove the appropriate number of bytes from the array. */ this.Resize(-n, i, (chunkInfo == null) ? index : -1); }
/// <summary>Inserts a new track (MTrk) chunk into this file at the specified index.</summary> /// <param name="index">The index in this file's list at which the track chunk should be inserted.</param> /// <returns>The new MidiChunkInfo object that is inserted.</returns> public MidiChunkInfo InsertTrack(int index) { int n = MidiChunkInfo.SizeItem(), offset = this.Items[index].Offset; this.Resize(n, offset, -1); MidiChunkInfo chunkInfo = this.CreateChunkInfo(offset, MidiChunkInfo.TrackType); this.Items.Insert(index, chunkInfo); return(chunkInfo); }
/// <summary>Adds a new track (MTrk) chunk to the end of this file.</summary> /// <returns>The new MidiChunkInfo object that is added.</returns> public MidiChunkInfo AddTrack() { int n = MidiChunkInfo.SizeItem(), offset = this.Bytes.Length; this.Resize(n, offset, -1); MidiChunkInfo chunkInfo = this.CreateChunkInfo(offset, MidiChunkInfo.TrackType); this.Items.Add(chunkInfo); return(chunkInfo); }
private MidiChunkInfo CreateChunkInfo(int offset, string type) { MidiChunkInfo chunkInfo = new MidiChunkInfo(this, offset); chunkInfo.Type = type; chunkInfo.Length = (type == MidiChunkInfo.HeaderType) ? MidiHeader.SizeItem() : 0; if (type == MidiChunkInfo.TrackType) { ++this.Header.NumberOfTracks; } return(chunkInfo); }
/// <summary>Loads an existing MIDI file from disk (into this instance).</summary> /// <param name="path">The path of the MIDI file to load.</param> public void Load(string path) { int n, i, j = 0; MidiChunkInfo chunkInfo; MidiHeader header = null; /* Make sure we start with a clean slate. */ this.Clear(); this.Bytes = File.ReadAllBytes(path); /* Process each MidiItem object from the byte array. */ for (i = 0; i < this.Bytes.Length; i += chunkInfo.Length) { /* A chunk should begin here. */ chunkInfo = new MidiChunkInfo(this, i); this.Items.Add(chunkInfo); i += MidiChunkInfo.SizeItem(); /* What comes next depends on the chunk type. */ switch (chunkInfo.Type) { case MidiChunkInfo.HeaderType: if (header == null) { header = new MidiHeader(this, i); this.Items.Add(header); } else { this.AddErrorText(Properties.Resources.MultipleHeaders, 0); } break; case MidiChunkInfo.TrackType: this.ParseEvents(i, chunkInfo.Length, ++j); break; } } /* Check for track number mismatch. */ n = (header == null) ? 0 : header.NumberOfTracks; if (n != j) { string s = UI.ParseLabel(Properties.Resources.Track).ToLower(); s = string.Format(Properties.Resources.MismatchFormat, s, n, j); this.AddErrorText(s, 0); } }
/// <summary>Resizes this file's byte array, preserving the data from a given offset to the end.</summary> /// <param name="delta">The number of bytes by which to resize the array.</param> /// <param name="offset">Offset into the byte array at which to begin preservation.</param> /// <param name="index"> /// Optional (non-negative) index of the affected MidiItem object in this file's list (or zero to /// determine the index dynamatically), so that the length of the containing chunk can be adjusted. /// </param> public void Resize(int delta, int offset, int index) { if (delta == 0) { return; } int i, j, m, n; if (delta < 0) { m = offset; n = this.Bytes.Length; } else { m = this.Bytes.Length - 1; n = offset - 1; Array.Resize(ref this.Bytes, this.Bytes.Length + delta); } for (j = delta / Math.Abs(delta), i = m; i != n; i -= j) { this.Bytes[i + delta] = this.Bytes[i]; this.Bytes[i] = 0; } if (delta < 0) { Array.Resize(ref this.Bytes, this.Bytes.Length + delta); } /* If applicable, cascade offset changes through all subsequent items. */ j = offset + delta; if (j < this.Bytes.Length) { for (i = this.GetItemIndex(offset); i < this.ItemCount; ++i) { this.Items[i].Offset += delta; } /* Since our map of running statuses is keyed by offset, it must also be adjusted correspondingly. */ List <int> keys = new List <int>(); /* Collect a list of keys (offsets) affected by this change. */ foreach (int key in this.RunningStatusMap.Keys) { if (key >= offset) { keys.Add(key); } } if (delta > 0) { keys.Reverse(); } /* Replace each affected entry in the map with a new one having the updated key (offset). */ foreach (int key in keys) { int status = this.RunningStatusMap[key]; this.RunningStatusMap.Remove(key); if (delta < 0 && key < j) { continue; } this.RunningStatusMap[key + delta] = status; } } /* If specified, adjust the length of the containing chunk. */ if (index < 0) { return; } MidiChunkInfo chunkInfo = null; for (i = (index > 0) ? index : this.GetItemIndex((delta < 0) ? j : offset); chunkInfo == null; chunkInfo = this.Items[--i] as MidiChunkInfo) { ; } chunkInfo.Length += delta; }