private static bool _TryReadChunk(Stream stream, out KeyValuePair <string, byte[]> chunk) { chunk = default(KeyValuePair <string, byte[]>); var buf = new byte[4]; if (4 != stream.Read(buf, 0, 4)) { return(false); } var name = Encoding.ASCII.GetString(buf); if (4 != stream.Read(buf, 0, 4)) { throw new EndOfStreamException(); } var len = BitConverter.ToInt32(buf, 0); if (BitConverter.IsLittleEndian) { len = MidiUtility.Swap(len); } buf = new byte[len]; if (len != stream.Read(buf, 0, len)) { throw new EndOfStreamException(); } chunk = new KeyValuePair <string, byte[]>(name, buf); return(true); }
/// <summary> /// Reads a MIDI file from the specified stream /// </summary> /// <param name="stream">The stream to read from</param> /// <returns>A MIDI file object representing the MIDI data in the stream</returns> public static MidiFile ReadFrom(Stream stream) { KeyValuePair <string, byte[]> chunk; if (!_TryReadChunk(stream, out chunk) || "MThd" != chunk.Key || 6 > chunk.Value.Length) { throw new InvalidDataException("The stream is not a MIDI file format."); } var type = BitConverter.ToInt16(chunk.Value, 0); var trackCount = BitConverter.ToInt16(chunk.Value, 2); var timeBase = BitConverter.ToInt16(chunk.Value, 4); if (BitConverter.IsLittleEndian) { type = MidiUtility.Swap(type); trackCount = MidiUtility.Swap(trackCount); timeBase = MidiUtility.Swap(timeBase); } var result = new MidiFile(type, timeBase); while (_TryReadChunk(stream, out chunk)) { if ("MTrk" == chunk.Key) { var tstm = new MemoryStream(chunk.Value, false); var trk = MidiSequence.ReadFrom(tstm); result.Tracks.Add(trk); } } return(result); }
/// <summary> /// Writes the MIDI file to the specified stream /// </summary> /// <param name="stream">The stream to write to</param> public void WriteTo(Stream stream) { var fileStream = stream as FileStream; if (null != fileStream) { FilePath = fileStream.Name; } try { stream.SetLength(0); } catch { } var buf = new byte[4]; buf[0] = (byte)'M'; buf[1] = (byte)'T'; buf[2] = (byte)'h'; buf[3] = (byte)'d'; stream.Write(buf, 0, 4); var len = 6; if (BitConverter.IsLittleEndian) { len = MidiUtility.Swap(len); } stream.Write(BitConverter.GetBytes(len), 0, 4); var type = Type; var trackCount = (short)Tracks.Count; var timeBase = TimeBase; if (BitConverter.IsLittleEndian) { type = MidiUtility.Swap(type); trackCount = MidiUtility.Swap(trackCount); timeBase = MidiUtility.Swap(timeBase); } stream.Write(BitConverter.GetBytes(type), 0, 2); stream.Write(BitConverter.GetBytes(trackCount), 0, 2); stream.Write(BitConverter.GetBytes(timeBase), 0, 2); foreach (var trk in Tracks) { buf[0] = (byte)'M'; buf[1] = (byte)'T'; buf[2] = (byte)'r'; buf[3] = (byte)'k'; stream.Write(buf, 0, 4); var tstm = new MemoryStream(); trk.WriteTo(tstm); tstm.Position = 0; len = (int)tstm.Length; if (BitConverter.IsLittleEndian) { len = MidiUtility.Swap(len); } stream.Write(BitConverter.GetBytes(len), 0, 4); tstm.CopyTo(stream); tstm.Close(); } }
/// <summary> /// Sends a message immediately to the device /// </summary> /// <param name="message">The message to send</param> /// <remarks>The message is not queued. Tempo change messages are not honored.</remarks> public void Send(MidiMessage message) { if (IntPtr.Zero == _handle) { throw new InvalidOperationException("The device is closed."); } if (null == message) { throw new ArgumentNullException("message"); } if (0xF0 == (message.Status & 0xF0)) { if (0xF != message.Channel) { var data = MidiUtility.ToMessageBytes(message); if (null == data) { return; } if (254 < data.Length) { var len = 254; for (var i = 0; i < data.Length; i += len) { if (data.Length <= i + len) { len = data.Length - i; } _SendRaw(data, i, len); } } else { _SendRaw(data, 0, data.Length); } } } else { _CheckOutResult(midiOutShortMsg(_handle, MidiUtility.PackMessage(message))); } }
void _MidiInProc(IntPtr handle, int msg, int instance, int lparam, int wparam) { switch (msg) { case MIM_OPEN: Opened?.Invoke(this, EventArgs.Empty); break; case MIM_CLOSE: Closed?.Invoke(this, EventArgs.Empty); break; case MIM_DATA: MidiMessage m; if (0 != _tempoSynchEnabled && 0xF8 == (0xFF & lparam)) { if (0 != _timingTimestamp) { var dif = (_PreciseUtcNowTicks - _timingTimestamp) * 24; var tpm = TimeSpan.TicksPerMillisecond * 60000; var newTempo = (tpm / (double)dif); if (newTempo < _tempoSynchMininumTempo) { Interlocked.Exchange(ref _timingTimestamp, 0); } else { var timeNow = _PreciseUtcNowTicks; if (0L == _tempoSyncTimestamp || 0L == _tempoSyncFrequency || (timeNow - _tempoSyncTimestamp > _tempoSyncFrequency)) { var tmp = Tempo; var ta = (tmp + newTempo) / 2; Tempo = ta; } Interlocked.Exchange(ref _timingTimestamp, timeNow); } } else { var timeNow = _PreciseUtcNowTicks; Interlocked.Exchange(ref _timingTimestamp, timeNow); } } else { m = MidiUtility.UnpackMessage(lparam); _ProcessRecording(m); Input?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), m)); } break; case MIM_ERROR: Error?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), MidiUtility.UnpackMessage(lparam))); break; case MIM_LONGDATA: case MIM_LONGERROR: // TODO: Semi tested var hdr = (MIDIHDR)Marshal.PtrToStructure(new IntPtr(lparam), typeof(MIDIHDR)); if (0 == hdr.dwBytesRecorded) { return; // no message } // this code assumes it's a sysex message but I should probably check it. var status = Marshal.ReadByte(hdr.lpData, 0); var payload = new byte[hdr.dwBytesRecorded - 1]; Marshal.Copy(new IntPtr((int)hdr.lpData + 1), payload, 0, payload.Length); m = new MidiMessageSysex(payload); var sz = Marshal.SizeOf(typeof(MIDIHDR)); _inHeader.dwBufferLength = _inHeader.dwBytesRecorded = 65536u; _inHeader.lpData = _buffer; _CheckInResult(midiInPrepareHeader(_handle, ref _inHeader, sz)); _CheckInResult(midiInAddBuffer(_handle, ref _inHeader, sz)); _ProcessRecording(m); if (MIM_LONGDATA == msg) { Input?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), m)); } else { Error?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), m)); } break; case MIM_MOREDATA: break; default: break; } }
/// <summary> /// Adjusts the tempo of a MIDI file /// </summary> /// <param name="tempo">The new tempo</param> /// <returns>A new file with an adjusted tempo. All other tempo messages are adjusted relatively to the first one</returns> public MidiFile AdjustTempo(double tempo) => AdjustTempo(MidiUtility.TempoToMicroTempo(tempo));
/// <summary> /// Sends events directly to the event queue without buffering /// </summary> /// <param name="events">The events to send</param> /// <remarks>The total size of the events must be less than 64kb</remarks> public void SendDirect(IEnumerable <MidiEvent> events) { if (null == events) { throw new ArgumentNullException("events"); } if (IntPtr.Zero == _handle) { throw new InvalidOperationException("The stream is closed."); } if (IntPtr.Zero != _sendHeader.lpData) { throw new InvalidOperationException("The stream is busy playing."); } int baseEventSize = Marshal.SizeOf(typeof(MIDIEVENT)); int blockSize = 0; IntPtr eventPointer = _sendEventBuffer; var ofs = 0; var ptrOfs = 0; var hasEvents = false; foreach (var @event in events) { hasEvents = true; if (0xF0 != (@event.Message.Status & 0xF0)) { blockSize += baseEventSize; if (_SendBufferSize <= blockSize) { throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events"); } var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = MidiUtility.PackMessage(@event.Message); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += baseEventSize; ofs = 0; } else if (0xFF == @event.Message.Status) { var mm = @event.Message as MidiMessageMeta; if (0x51 == mm.Data1) // tempo { blockSize += baseEventSize; if (_SendBufferSize <= blockSize) { throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events"); } var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = (mm.Data[0] << 16) | (mm.Data[1] << 8) | mm.Data[2] | (MEVT_TEMPO << 24); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += baseEventSize; ofs = 0; // TODO: This signal is sent too early. It should really wait until after the // MEVT_TEMPO message is processed by the driver, but i have no easy way to // do that. All we can do is hope, here Interlocked.Exchange(ref _tempoSyncMessagesSentCount, 0); } else if (0x2f == mm.Data1) // end track { blockSize += baseEventSize; if (_SendBufferSize <= blockSize) { throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events"); } // add a NOP message to it just to pad our output in case we're looping var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = (MEVT_NOP << 24); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += baseEventSize; ofs = 0; } else { ofs = @event.Position; } } else // sysex { var msx = @event.Message as MidiMessageSysex; var dl = msx.Data.Length + 1; if (0 != (dl % 4)) { dl += 4 - (dl % 4); } blockSize += baseEventSize + dl; if (_SendBufferSize <= blockSize) { throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events"); } var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = MEVT_F_LONG | (msx.Data.Length + 1); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += baseEventSize; Marshal.WriteByte(new IntPtr(ptrOfs + eventPointer.ToInt64()), msx.Status); Marshal.Copy(msx.Data, 0, new IntPtr(ptrOfs + eventPointer.ToInt64() + 1), msx.Data.Length); ptrOfs += dl; ofs = 0; } } if (hasEvents) { _sendHeader = default(MIDIHDR); Interlocked.Exchange(ref _sendHeader.lpData, eventPointer); _sendHeader.dwBufferLength = _sendHeader.dwBytesRecorded = unchecked ((uint)blockSize); _sendEventBuffer = eventPointer; int headerSize = Marshal.SizeOf(typeof(MIDIHDR)); _CheckOutResult(midiOutPrepareHeader(_handle, ref _sendHeader, headerSize)); _CheckOutResult(midiStreamOut(_handle, ref _sendHeader, headerSize)); } }
/// <summary> /// Creates a new MIDI channel pitch message /// </summary> /// <param name="pitch">The MIDI pressure (0-16383)</param> /// <param name="channel">The MIDI channel (0-15)</param> public MidiMessageChannelPitch(short pitch, byte channel) : base(unchecked ((byte)(0xE0 | channel)), BitConverter.IsLittleEndian?MidiUtility.Swap(pitch):pitch) { }
void _SendBlock() { if (null == _sendQueue) { return; } if (IntPtr.Zero == _handle) { throw new InvalidOperationException("The stream is closed."); } if (IntPtr.Zero != Interlocked.CompareExchange(ref _sendHeader.lpData, _sendEventBuffer, IntPtr.Zero)) { throw new InvalidOperationException("The stream is busy playing."); } int baseEventSize = Marshal.SizeOf(typeof(MIDIEVENT)); int blockSize = 0; IntPtr eventPointer = _sendEventBuffer; var ofs = 0; var ptrOfs = 0; for (; _sendQueuePosition < _sendQueue.Count; Interlocked.Exchange(ref _sendQueuePosition, _sendQueuePosition + 1)) { var @event = _sendQueue[_sendQueuePosition]; if (0x00 != @event.Message.Status && 0xF0 != (@event.Message.Status & 0xF0)) { if (_SendBufferSize < blockSize + baseEventSize) { break; } blockSize += baseEventSize; var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = MidiUtility.PackMessage(@event.Message); var gch = GCHandle.Alloc(se, GCHandleType.Pinned); CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT))); gch.Free(); ptrOfs += baseEventSize; ofs = 0; } else if (0xFF == @event.Message.Status) { var mm = @event.Message as MidiMessageMeta; if (0x51 == mm.Data1) // tempo { if (_SendBufferSize < blockSize + baseEventSize) { break; } blockSize += baseEventSize; var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = (mm.Data[0] << 16) | (mm.Data[1] << 8) | mm.Data[2] | (MEVT_TEMPO << 24); var gch = GCHandle.Alloc(se, GCHandleType.Pinned); CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT))); gch.Free(); ptrOfs += baseEventSize; ofs = 0; } else if (0x2f == mm.Data1) // end track { if (_SendBufferSize < blockSize + baseEventSize) { break; } blockSize += baseEventSize; // add a NOP message to it just to pad our output in case we're looping var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = (MEVT_NOP << 24); var gch = GCHandle.Alloc(se, GCHandleType.Pinned); CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT))); gch.Free(); ptrOfs += baseEventSize; ofs = 0; } else { ofs = @event.Position; } } else // sysex or sysex part { byte[] data; if (0 == @event.Message.Status) { data = (@event.Message as MidiMessageSysexPart).Data; } else { data = MidiUtility.ToMessageBytes(@event.Message); } var dl = data.Length; if (0 != (dl % 4)) { dl += 4 - (dl % 4); } if (_SendBufferSize < blockSize + baseEventSize + dl) { break; } blockSize += baseEventSize + dl; var se = new MIDIEVENT(); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = MEVT_F_LONG | data.Length; var gch = GCHandle.Alloc(se, GCHandleType.Pinned); CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT))); gch.Free(); ptrOfs += baseEventSize; Marshal.Copy(data, 0, new IntPtr(ptrOfs + eventPointer.ToInt64()), data.Length); ptrOfs += dl; ofs = 0; } } _sendHeader = default(MIDIHDR); _sendHeader.dwBufferLength = _sendHeader.dwBytesRecorded = unchecked ((uint)blockSize); _sendHeader.lpData = _sendEventBuffer; int headerSize = Marshal.SizeOf(typeof(MIDIHDR)); _CheckOutResult(midiOutPrepareHeader(_handle, ref _sendHeader, headerSize)); _CheckOutResult(midiStreamOut(_handle, ref _sendHeader, headerSize)); }
/// <summary> /// Creates a new instance with the specified tempo /// </summary> /// <param name="tempo">The tempo</param> public MidiMessageMetaTempo(double tempo) : this(MidiUtility.TempoToMicroTempo(tempo)) { }
/// <summary> /// Gets a string representation of this message /// </summary> /// <returns></returns> public override string ToString() { return("Key Pressure: " + MidiUtility.NoteIdToNote(NoteId) + ", Pressure: " + Pressure.ToString() + ", Channel: " + Channel.ToString()); }
/// <summary> /// Gets a string representation of this message /// </summary> /// <returns></returns> public override string ToString() { return("Note Off: " + MidiUtility.NoteIdToNote(NoteId) + ", Velocity: " + Velocity.ToString() + ", Channel: " + Channel.ToString()); }
/// <summary> /// Plays the sequence to the specified MIDI device using the specified timebase /// </summary> /// <param name="timeBase">The timebase to use, in pulses/ticks per quarter note</param> /// <param name="deviceIndex">The MIDI device to output to</param> /// <param name="loop">Indicates whether to loop playback or not</param> public void Preview(short timeBase = 480, int deviceIndex = 0, bool loop = false) { var handle = MidiUtility.OpenOutputDevice(deviceIndex); var ppq = timeBase; var mt = MidiUtility.TempoToMicroTempo(120d); try { while (loop) { var ticksusec = mt / (double)timeBase; var tickspertick = ticksusec / (TimeSpan.TicksPerMillisecond / 1000) * 100; var tickStart = MidiUtility.PreciseUtcNowTicks; var tickCurrent = tickStart; var end = (long)(Length * tickspertick + tickStart); var tpm = TimeSpan.TicksPerMillisecond; using (var e = AbsoluteEvents.GetEnumerator()) { if (!e.MoveNext()) { return; } var done = false; while (!done && tickCurrent <= end) { tickCurrent = MidiUtility.PreciseUtcNowTicks; var ce = (long)((tickCurrent - tickStart) / tickspertick); while (!done && e.Current.Position <= ce) { if (0xFF == e.Current.Message.Status) { var mbs = e.Current.Message as MidiMessageMeta; if (0x51 == mbs.Data1) { if (BitConverter.IsLittleEndian) { mt = (mbs.Data[0] << 16) | (mbs.Data[1] << 8) | mbs.Data[2]; } else { mt = (mbs.Data[2] << 16) | (mbs.Data[1] << 8) | mbs.Data[0]; } ticksusec = mt / (double)ppq; tickspertick = ticksusec / (tpm / 1000) * 100; end = (long)(Length * tickspertick + tickStart); } else if (0x2F == mbs.Data1) { done = true; } } MidiUtility.Send(handle, e.Current.Message); if (!e.MoveNext()) { done = true; } } } } } } finally { MidiUtility.CloseOutputDevice(handle); } }
void _SendBlock() { if (null == _sendQueue) { return; } if (IntPtr.Zero == Handle) { throw new InvalidOperationException("The stream is closed."); } int blockSize = 0; IntPtr headerPointer = Marshal.AllocHGlobal(MIDIHDR_SIZE + MAX_EVENTBLOCK_SIZE); try { IntPtr eventPointer = new IntPtr(headerPointer.ToInt64() + MIDIHDR_SIZE); var ofs = 0; var ptrOfs = 0; for (; _sendQueuePosition < _sendQueue.Count; Interlocked.Exchange(ref _sendQueuePosition, _sendQueuePosition + 1)) { var @event = _sendQueue[_sendQueuePosition]; if (0x00 != @event.Message.Status && 0xF0 != (@event.Message.Status & 0xF0)) { if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE) { break; } blockSize += MIDIEVENT_SIZE; var se = default(MIDIEVENT); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = MidiUtility.PackMessage(@event.Message); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += MIDIEVENT_SIZE; ofs = 0; } else if (0xFF == @event.Message.Status) { var mm = @event.Message as MidiMessageMeta; if (0x51 == mm.Data1) // tempo { if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE) { break; } blockSize += MIDIEVENT_SIZE; var se = default(MIDIEVENT); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = (mm.Data[0] << 16) | (mm.Data[1] << 8) | mm.Data[2] | (MEVT_TEMPO << 24); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += MIDIEVENT_SIZE; ofs = 0; } else if (0x2f == mm.Data1) // end track { if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE) { break; } blockSize += MIDIEVENT_SIZE; // add a NOP message to it just to pad our output in case we're looping var se = default(MIDIEVENT); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = (MEVT_NOP << 24); Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += MIDIEVENT_SIZE; ofs = 0; } else { ofs = @event.Position; } } else // sysex or sysex part { byte[] data; if (0 == @event.Message.Status) { data = (@event.Message as MidiMessageSysexPart).Data; } else { data = MidiUtility.ToMessageBytes(@event.Message); } var dl = data.Length; if (0 != (dl % 4)) { dl += 4 - (dl % 4); } if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE + dl) { break; } blockSize += MIDIEVENT_SIZE + dl; var se = default(MIDIEVENT); se.dwDeltaTime = @event.Position + ofs; se.dwStreamId = 0; se.dwEvent = MEVT_F_LONG | data.Length; Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false); ptrOfs += MIDIEVENT_SIZE; Marshal.Copy(data, 0, new IntPtr(ptrOfs + eventPointer.ToInt64()), data.Length); ptrOfs += dl; ofs = 0; } } var header = default(MIDIHDR); header.dwBufferLength = header.dwBytesRecorded = unchecked ((uint)blockSize); header.lpData = eventPointer; Marshal.StructureToPtr(header, headerPointer, false); _CheckOutResult(midiOutPrepareHeader(Handle, headerPointer, MIDIHDR_SIZE)); _CheckOutResult(midiStreamOut(Handle, headerPointer, MIDIHDR_SIZE)); headerPointer = IntPtr.Zero; } finally { if (IntPtr.Zero != headerPointer) { Marshal.FreeHGlobal(headerPointer); } } }