|
@@ -0,0 +1,582 @@
|
|
|
+unit MIDI;
|
|
|
+
|
|
|
+{$ifdef fpc}
|
|
|
+ {$mode objfpc}{$H+}
|
|
|
+{$endif}
|
|
|
+
|
|
|
+
|
|
|
+{ *** You can choose if You want MIDI.PAS to use exceptions *** }
|
|
|
+{ *** in case of Errors. If not, MIDI.PAS uses POSTMESSAGE() *** }
|
|
|
+{ *** and You have to handle these messages in Your MainForm *** }
|
|
|
+
|
|
|
+//{$define MIDI_UseExceptions}
|
|
|
+
|
|
|
+//{$define DebugSysEx}
|
|
|
+
|
|
|
+
|
|
|
+{*******************************************************************************
|
|
|
+* *
|
|
|
+* MIDI.PAS *
|
|
|
+* *
|
|
|
+* This file is based on the MIDI device classes by Adrian Meyer *
|
|
|
+* This file was taken from the ZIP archive 'demo_MidiDevices_D6.zip' *
|
|
|
+* and partly changed by me, some changes take over from 'DAV_MidiIO.pas' *
|
|
|
+* *
|
|
|
+* latest changes 2015-04-13 *
|
|
|
+********************************************************************************
|
|
|
+* V1.0 First release with simple MIDI Input/Output *
|
|
|
+* V1.1 SysEx Input Event added, refactured error handling *
|
|
|
+* V1.2 SysEx Output procedure added, changes sysex input for multiple ports *
|
|
|
+* V1.3 Changes by BREAKOUTBOX 2009-07 (www.breakoutbox.de) *
|
|
|
+* V1.4 Changes adapted from DAV_MidiIO.pas - see http://www.ohloh.net/p/DAV *
|
|
|
+* V1.5 removed an Exception on sending SysEx Data to a CLOSED Output *
|
|
|
+* V1.6 added a Switch to choose between Exceptions and Other behaviore ... *
|
|
|
+* V1.7 replaced pChar => pAnsiChar Char => AnsiChar string => AnsiString *
|
|
|
+* to gain compatibility to DelphiXE and higher versions .. *
|
|
|
+* - *
|
|
|
+*******************************************************************************}
|
|
|
+
|
|
|
+
|
|
|
+interface
|
|
|
+
|
|
|
+uses
|
|
|
+ {$ifdef FPC}
|
|
|
+ Interfaces,
|
|
|
+ JWAwindows,
|
|
|
+ {$else}
|
|
|
+ Windows,
|
|
|
+ {$endif}
|
|
|
+ Forms, Classes, Messages, SysUtils, Math, Contnrs, MMSystem, Dialogs;
|
|
|
+
|
|
|
+
|
|
|
+// -------------------------- WARNING --------------------------
|
|
|
+// If Your Application uses User-defined Messages YOU MUST CHECK
|
|
|
+// if these values collide with one of Your own messages !!!
|
|
|
+const
|
|
|
+ // define window messages for MainForm :
|
|
|
+ WM_MIDISYSTEM_MESSAGE = WM_USER +1;
|
|
|
+ WM_MIDIDATA_ARRIVED = WM_USER +2;
|
|
|
+ WM_MIM_ERROR = WM_USER +3;//1001;
|
|
|
+ WM_MIM_LONGERROR = WM_USER +4;//1002;
|
|
|
+
|
|
|
+
|
|
|
+const
|
|
|
+ // define Your size of System Exclusive buffer :
|
|
|
+ cMySysExBufferSize = 2048;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+type
|
|
|
+ TMIDIChannel = 1..16;
|
|
|
+ TMIDIDataByte = 0..$7F; // 7 bits
|
|
|
+ TMIDIDataWord = 0..$3FFF; // 14 bits
|
|
|
+ TMIDIStatusByte = $80..$FF;
|
|
|
+ TMIDIVelocity = TMIDIDataByte;
|
|
|
+ TMIDIKey = TMIDIDataByte;
|
|
|
+ TMIDINote = TMIDIKey;
|
|
|
+
|
|
|
+type
|
|
|
+ // event if data is received
|
|
|
+ TOnMidiInData = procedure (const aDeviceIndex: integer; const aStatus, aData1, aData2: byte) of object;
|
|
|
+ // event if system exclusive data is received
|
|
|
+ TOnSysExData = procedure (const aDeviceIndex: integer; const aStream: TMemoryStream) of object;
|
|
|
+
|
|
|
+ EMidiDevices = Exception;
|
|
|
+
|
|
|
+ // base class for MIDI devices
|
|
|
+ TMidiDevices = class
|
|
|
+ private
|
|
|
+ fDevices : TStringList;
|
|
|
+ fMidiResult : MMResult;
|
|
|
+ procedure SetMidiResult(const Value: MMResult);
|
|
|
+ protected
|
|
|
+ property MidiResult: MMResult read fMidiResult write SetMidiResult;
|
|
|
+ function GetHandle(const aDeviceIndex: integer): THandle;
|
|
|
+ public
|
|
|
+ // create the MIDI devices
|
|
|
+ constructor Create; virtual;
|
|
|
+ // whack the devices
|
|
|
+ destructor Destroy; override;
|
|
|
+ // open a specific device
|
|
|
+ procedure Open(const aDeviceIndex: integer); virtual; abstract;
|
|
|
+ // close a specific device
|
|
|
+ procedure Close(const aDeviceIndex: integer); virtual; abstract;
|
|
|
+ // close all devices
|
|
|
+ procedure CloseAll;
|
|
|
+ // THE devices
|
|
|
+ function IsOpen(ADeviceIndex: Integer): Boolean; // check if open
|
|
|
+ property Devices: TStringList read fDevices;
|
|
|
+ end;
|
|
|
+
|
|
|
+ // MIDI input devices
|
|
|
+ TMidiInput = class(TMidiDevices)
|
|
|
+ private
|
|
|
+ fOnMidiData : TOnMidiInData;
|
|
|
+ fOnSysExData : TOnSysExData;
|
|
|
+ fSysExData : TObjectList;
|
|
|
+ protected
|
|
|
+ procedure DoSysExData( const aDeviceIndex: integer);
|
|
|
+ public
|
|
|
+ // create an input device
|
|
|
+ constructor Create; override;
|
|
|
+ // what the input devices
|
|
|
+ destructor Destroy; override;
|
|
|
+ // open a specific input device
|
|
|
+ procedure Open( const aDeviceIndex: integer); override;
|
|
|
+ // close a specific device
|
|
|
+ procedure Close( const aDeviceIndex: integer); override;
|
|
|
+ // midi data event
|
|
|
+ property OnMidiData: TOnMidiInData read fOnMidiData write fOnMidiData;
|
|
|
+ // midi system exclusive is received
|
|
|
+ property OnSysExData: TOnSysExData read fOnSysExData write fOnSysExData;
|
|
|
+ end;
|
|
|
+
|
|
|
+ // MIDI output devices
|
|
|
+ TMidiOutput = class(TMidiDevices)
|
|
|
+ constructor Create; override;
|
|
|
+ // open a specific input device
|
|
|
+ procedure Open(const aDeviceIndex: integer); override;
|
|
|
+ // close a specific device
|
|
|
+ procedure Close(const aDeviceIndex: integer); override;
|
|
|
+ // send some midi data to the indexed device
|
|
|
+ procedure Send(const aDeviceIndex: integer; const aStatus, aData1, aData2: byte);
|
|
|
+ procedure SendSystemReset( const aDeviceIndex: integer);
|
|
|
+ procedure SendAllSoundOff( const aDeviceIndex: integer; const channel: byte);
|
|
|
+ // send system exclusive data to a device
|
|
|
+ procedure SendSysEx( const aDeviceIndex: integer; const aStream: TMemoryStream); overload;
|
|
|
+ procedure SendSysEx( const aDeviceIndex: integer; const aString: AnsiString); overload;
|
|
|
+ end;
|
|
|
+
|
|
|
+ // convert the stream into xx xx xx xx AnsiString
|
|
|
+ function SysExStreamToStr( const aStream: TMemoryStream): AnsiString;
|
|
|
+ // fill the AnsiString in a xx xx xx xx into the stream
|
|
|
+ procedure StrToSysExStream( const aString: AnsiString; const aStream: TMemoryStream);
|
|
|
+
|
|
|
+ // MIDI input devices
|
|
|
+ function MidiInput: TMidiInput;
|
|
|
+ // MIDI output Devices
|
|
|
+ function MidiOutput: TMidiOutput;
|
|
|
+
|
|
|
+
|
|
|
+ type
|
|
|
+ TSysExBuffer = array[0..cMySysExBufferSize -1] of AnsiChar;
|
|
|
+
|
|
|
+ TSysExData = class
|
|
|
+ private
|
|
|
+ fSysExStream: TMemoryStream;
|
|
|
+ public
|
|
|
+ SysExHeader: {$ifdef FPC} _midihdr {$else} TMidiHdr {$endif};
|
|
|
+ SysExData: TSysExBuffer;
|
|
|
+ constructor Create;
|
|
|
+ destructor Destroy; override;
|
|
|
+ property SysExStream: TMemoryStream read fSysExStream;
|
|
|
+ end;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+implementation
|
|
|
+
|
|
|
+
|
|
|
+{ ***** TMidiBase ************************************************************ }
|
|
|
+constructor TMidiDevices.Create;
|
|
|
+begin
|
|
|
+ FDevices:= TStringList.Create;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TMidiDevices.Destroy;
|
|
|
+begin
|
|
|
+ FreeAndNil( FDevices);
|
|
|
+ inherited;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+var
|
|
|
+ gMidiInput: TMidiInput;
|
|
|
+ gMidiOutput: TMidiOutput;
|
|
|
+
|
|
|
+
|
|
|
+function MidiInput: TMidiInput;
|
|
|
+begin
|
|
|
+ if not Assigned( gMidiInput)
|
|
|
+ then gMidiInput := TMidiInput.Create;
|
|
|
+ Result := gMidiInput;
|
|
|
+end;
|
|
|
+
|
|
|
+function MidiOutput: TMidiOutput;
|
|
|
+begin
|
|
|
+ if not Assigned( gMidiOutput)
|
|
|
+ then gMidiOutput := TMidiOutput.Create;
|
|
|
+ Result := gMidiOutput;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{$IFDEF FPC}
|
|
|
+//type
|
|
|
+// PHMIDIIN = ^HMIDIIN;
|
|
|
+// TMidiOutCaps = TMidiOutCapsA;
|
|
|
+{$ENDIF}
|
|
|
+
|
|
|
+{ I don't know whatfor this RECORD is used in DAV_MidiIO.pas
|
|
|
+ but I think this maybe allows renumeration / resorting of MIDI devices
|
|
|
+ for use in Your application - I didn't use this until now
|
|
|
+ so I didn't take over the implementation in this MIDI.PAS ... !
|
|
|
+
|
|
|
+TMidiInputDeviceRecord = record
|
|
|
+ MidiInput : TMidiInput;
|
|
|
+ DeviceNumber : Integer;
|
|
|
+ end;
|
|
|
+ PMidiInputDeviceRecord = ^TMidiInputDeviceRecord;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+{ The Callback-Procedure receives MIDI data on interrupt : }
|
|
|
+procedure MidiInCallback( aMidiInHandle: PHMIDIIN; aMsg: Integer; aInstance,
|
|
|
+ aMidiData, aTimeStamp: integer); stdcall;
|
|
|
+begin
|
|
|
+ case aMsg of
|
|
|
+ MIM_DATA:
|
|
|
+ begin
|
|
|
+ if Assigned(MidiInput.OnMidiData) then
|
|
|
+ begin
|
|
|
+ MidiInput.OnMidiData( aInstance,
|
|
|
+ aMidiData and $000000FF,
|
|
|
+ ( aMidiData and $0000FF00) shr 8,
|
|
|
+ ( aMidiData and $00FF0000) shr 16);
|
|
|
+ PostMessage( Application.MainForm.Handle, WM_MIDIDATA_ARRIVED, aInstance, aMidiData);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+ MIM_LONGDATA:
|
|
|
+ MidiInput.DoSysExData( aInstance);
|
|
|
+
|
|
|
+ MIM_ERROR: PostMessage( Application.MainForm.Handle, WM_MIM_ERROR, aInstance, aMidiData);
|
|
|
+
|
|
|
+ MIM_LONGERROR:
|
|
|
+ {$ifdef MIDI_UseExceptions}
|
|
|
+ raise Exception.Create( 'Midi In Error!');
|
|
|
+ {$else} // in a Callback Function you CANNOT use a MessageBox() !!!
|
|
|
+ PostMessage( Application.MainForm.Handle, WM_MIM_LONGERROR, aInstance, aMidiData);
|
|
|
+ {$endif}
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ ***** TMidiInput *********************************************************** }
|
|
|
+constructor TMidiInput.Create;
|
|
|
+var
|
|
|
+ AvailableMIDIinputs : integer;
|
|
|
+ lInCaps : TMidiInCaps;
|
|
|
+ i : integer;
|
|
|
+begin
|
|
|
+ inherited;
|
|
|
+ fSysExData := TObjectList.Create(true);
|
|
|
+ TRY // TRY..EXCEPT was adapted from file "DAV_MidiIO.pas"
|
|
|
+ AvailableMIDIinputs:= MidiInGetNumDevs;
|
|
|
+ EXCEPT
|
|
|
+ AvailableMIDIinputs:= 0;
|
|
|
+ end;
|
|
|
+
|
|
|
+ if AvailableMIDIinputs > 0 then
|
|
|
+ for i:= 0 to AvailableMIDIinputs - 1 do
|
|
|
+ begin
|
|
|
+ MidiResult := midiInGetDevCaps(i, @lInCaps, SizeOf(TMidiInCaps));
|
|
|
+ if MidiResult = MMSYSERR_NOERROR then
|
|
|
+ begin
|
|
|
+ fDevices.Add(StrPas(lInCaps.szPname));
|
|
|
+ fSysExData.Add(TSysExData.Create);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TMidiInput.Destroy;
|
|
|
+begin
|
|
|
+ FreeAndNil( fSysExData);
|
|
|
+ inherited;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiInput.Close( const aDeviceIndex: integer);
|
|
|
+begin
|
|
|
+ if GetHandle( aDeviceIndex) <> 0 then
|
|
|
+ begin
|
|
|
+ MidiResult := midiInStop(GetHandle(aDeviceIndex));
|
|
|
+ MidiResult := midiInReset(GetHandle(aDeviceIndex));
|
|
|
+ MidiResult := midiInUnprepareHeader(GetHandle(aDeviceIndex),
|
|
|
+ @TSysExData(fSysExData[aDeviceIndex]).SysExHeader, SizeOf(TMidiHdr));
|
|
|
+ MidiResult := midiInClose(GetHandle(aDeviceIndex));
|
|
|
+ FDevices.Objects[aDeviceIndex] := nil;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiDevices.CloseAll;
|
|
|
+var
|
|
|
+ i : integer;
|
|
|
+begin
|
|
|
+ for i:= 0 to FDevices.Count - 1 do Close(i);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiInput.Open( const aDeviceIndex: integer);
|
|
|
+var
|
|
|
+ lSysExData : TSysExData;
|
|
|
+ lHandle : THandle;
|
|
|
+begin
|
|
|
+ if GetHandle(aDeviceIndex) <> 0 then Exit;
|
|
|
+
|
|
|
+ MidiResult := midiInOpen( @lHandle, aDeviceIndex, cardinal(@midiInCallback),
|
|
|
+ aDeviceIndex, CALLBACK_FUNCTION);
|
|
|
+
|
|
|
+ fDevices.Objects[ aDeviceIndex ] := TObject(lHandle);
|
|
|
+ lSysExData := TSysExData(fSysExData[aDeviceIndex]);
|
|
|
+
|
|
|
+ lSysExData.SysExHeader.dwFlags := 0;
|
|
|
+
|
|
|
+ // DRAGONS: why are the function returns not checked on errors here ?
|
|
|
+ MidiResult := midiInPrepareHeader(lHandle, @lSysExData.SysExHeader, SizeOf(TMidiHdr));
|
|
|
+ MidiResult := midiInAddBuffer( lHandle, @lSysExData.SysExHeader, SizeOf(TMidiHdr));
|
|
|
+ MidiResult := midiInStart( lHandle);
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ ***** TMidiInput - SysEx *************************************************** }
|
|
|
+procedure TMidiInput.DoSysExData( const aDeviceIndex: integer);
|
|
|
+var
|
|
|
+ lSysExData : TSysExData;
|
|
|
+begin
|
|
|
+ lSysExData := TSysExData(fSysExData[aDeviceIndex]);
|
|
|
+ if lSysExData.SysExHeader.dwBytesRecorded = 0 then Exit;
|
|
|
+
|
|
|
+ lSysExData.SysExStream.Write( lSysExData.SysExData,
|
|
|
+ lSysExData.SysExHeader.dwBytesRecorded);
|
|
|
+ if lSysExData.SysExHeader.dwFlags and MHDR_DONE = MHDR_DONE then
|
|
|
+ begin
|
|
|
+ lSysExData.SysExStream.Position := 0;
|
|
|
+ if assigned(fOnSysExData)
|
|
|
+ then fOnSysExData(aDeviceIndex, lSysExData.SysExStream);
|
|
|
+ lSysExData.SysExStream.Clear;
|
|
|
+ end;
|
|
|
+
|
|
|
+ lSysExData.SysExHeader.dwBytesRecorded := 0;
|
|
|
+
|
|
|
+ // DRAGONS: why not check function returns on errors here ?
|
|
|
+ MidiResult := MidiInPrepareHeader( GetHandle(aDeviceIndex),
|
|
|
+ @lSysExData.SysExHeader, SizeOf(TMidiHdr));
|
|
|
+ MidiResult := MidiInAddBuffer( GetHandle(aDeviceIndex), @lSysExData.SysExHeader,
|
|
|
+ SizeOf( TMidiHdr));
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ ***** TMidiOutput ********************************************************** }
|
|
|
+constructor TMidiOutput.Create;
|
|
|
+var
|
|
|
+ AvailableMIDIoutputs : integer;
|
|
|
+ lOutCaps : TMidiOutCaps;
|
|
|
+ i : integer;
|
|
|
+begin
|
|
|
+ inherited;
|
|
|
+
|
|
|
+ TRY
|
|
|
+ AvailableMIDIoutputs := MidiOutGetNumDevs;
|
|
|
+ EXCEPT
|
|
|
+ AvailableMIDIoutputs := 0;
|
|
|
+ end;
|
|
|
+
|
|
|
+ //ShowMessage( 'DEBUG - AvailableMIDIoutputs = ' +IntToStr( AvailableMIDIoutputs));
|
|
|
+ for i:= 0 to AvailableMIDIoutputs - 1 do
|
|
|
+ begin
|
|
|
+ MidiResult := MidiOutGetDevCaps( i, @lOutCaps, SizeOf(TMidiOutCaps));
|
|
|
+ fDevices.Add( lOutCaps.szPname);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiOutput.Open( const aDeviceIndex: integer);
|
|
|
+var
|
|
|
+ lHandle: THandle;
|
|
|
+begin
|
|
|
+ {$ifndef FPC}
|
|
|
+ inherited; // Lazarus doesn't like this, so for Delphi only ..
|
|
|
+ {$endif}
|
|
|
+ // device already open;
|
|
|
+ if GetHandle(aDeviceIndex) <> 0 then exit;
|
|
|
+
|
|
|
+ MidiResult := midiOutOpen( @lHandle, aDeviceIndex, 0, 0, CALLBACK_NULL);
|
|
|
+ fDevices.Objects[ aDeviceIndex ]:= TObject( lHandle);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiOutput.Close( const aDeviceIndex: integer);
|
|
|
+begin
|
|
|
+ {$ifndef FPC}
|
|
|
+ inherited; // Lazarus doesn't like this, so for Delphi only ..
|
|
|
+ {$endif}
|
|
|
+ if GetHandle(aDeviceIndex) <> 0 then // 'if .. then' added by BREAKOUTBOX 2009-07-15
|
|
|
+ begin
|
|
|
+ MidiResult := midiOutClose(GetHandle(aDeviceIndex));
|
|
|
+ fDevices.Objects[ aDeviceIndex ] := nil;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiOutput.Send( const aDeviceIndex: integer; const aStatus,
|
|
|
+ aData1, aData2: byte);
|
|
|
+var
|
|
|
+ lMsg: cardinal;
|
|
|
+begin
|
|
|
+ // open if the device is not open // NOW: do NOT open .. !
|
|
|
+ if not Assigned( fDevices.Objects[ aDeviceIndex ])
|
|
|
+ then exit; // Open( aDeviceIndex); // Breakoutbox changed 2008-07-01
|
|
|
+
|
|
|
+ //lMsg := aStatus + (aData1 * $100) + (aData2 * $10000);
|
|
|
+ lMsg:= aStatus or (aData1 shl 8) or (aData2 shl 16); // better ?
|
|
|
+ MidiResult := MidiOutShortMsg( GetHandle( aDeviceIndex), lMSG);
|
|
|
+end;
|
|
|
+
|
|
|
+{ --- common MIDI Out messages ----------------------------------------------- }
|
|
|
+{ System Reset = Status Byte FFh }
|
|
|
+procedure TMidiOutput.SendSystemReset( const aDeviceIndex: integer);
|
|
|
+begin
|
|
|
+ Self.Send( aDeviceIndex, $FF, $0, $0);
|
|
|
+end;
|
|
|
+
|
|
|
+{ All Sound Off = Status + Channel Byte Bnh, n = Channel number }
|
|
|
+{ Controller-ID = Byte 78h, 2nd Data-Byte = 00h }
|
|
|
+procedure TMidiOutput.SendAllSoundOff(const aDeviceIndex: integer; const channel: byte);
|
|
|
+begin
|
|
|
+ Self.Send( aDeviceIndex, $b0 +channel, $78, $0);
|
|
|
+end;
|
|
|
+
|
|
|
+// HINT: in a Thread MidiInGetErrorText() makes no sense ..
|
|
|
+// read out the error text in Your main thread / MainForm !
|
|
|
+procedure TMidiDevices.SetMidiResult( const Value: MMResult);
|
|
|
+var
|
|
|
+ lError: array[0..MAXERRORLENGTH] of AnsiChar;
|
|
|
+begin
|
|
|
+ fMidiResult := Value;
|
|
|
+ if fMidiResult <> MMSYSERR_NOERROR then
|
|
|
+ if MidiInGetErrorText( fMidiResult, @lError, MAXERRORLENGTH) = MMSYSERR_NOERROR
|
|
|
+ {$ifdef MIDI_UseExceptions}
|
|
|
+ then raise EMidiDevices.Create(StrPas(lError));
|
|
|
+ {$else}
|
|
|
+ // in a Thread or a Callback Function you CANNOT use a MessageBox() !!!
|
|
|
+ //then ShowMessage( 'ERROR in TMidiDevices.SetMidiResult() Line 409' +#13#13
|
|
|
+ // +StrPas( lError) );
|
|
|
+ then PostMessage( Application.MainForm.Handle, WM_MIDISYSTEM_MESSAGE, fMidiResult, 0);
|
|
|
+ {$endif}
|
|
|
+end;
|
|
|
+
|
|
|
+function TMidiDevices.GetHandle( const aDeviceIndex: integer): THandle;
|
|
|
+begin
|
|
|
+ try
|
|
|
+ if not InRange(aDeviceIndex, 0, fDevices.Count - 1) then
|
|
|
+ raise EMidiDevices.CreateFmt( '%s: Device index out of bounds! (%d)',
|
|
|
+ [ClassName,aDeviceIndex]);
|
|
|
+
|
|
|
+ Result:= THandle(fDevices.Objects[ aDeviceIndex ]);
|
|
|
+ except
|
|
|
+ Result:= 0;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TMidiDevices.IsOpen(ADeviceIndex: Integer): boolean;
|
|
|
+begin
|
|
|
+ Result := GetHandle(ADeviceIndex) <> 0;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ ***** TMidiOutput - SysEx ************************************************** }
|
|
|
+procedure TMidiOutput.SendSysEx( const aDeviceIndex: integer;
|
|
|
+ const aString: AnsiString);
|
|
|
+var
|
|
|
+ lStream: TMemoryStream;
|
|
|
+begin
|
|
|
+ lStream := TMemoryStream.Create;
|
|
|
+ try
|
|
|
+ StrToSysExStream( aString, lStream);
|
|
|
+ SendSysEx( aDeviceIndex, lStream);
|
|
|
+ finally
|
|
|
+ FreeAndNil( lStream);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TMidiOutput.SendSysEx( const aDeviceIndex: integer;
|
|
|
+ const aStream: TMemoryStream);
|
|
|
+var
|
|
|
+ lSysExHeader: TMidiHdr;
|
|
|
+begin
|
|
|
+ // exit here if DeviceIndex is not open !
|
|
|
+ if not assigned(fDevices.Objects[ aDeviceIndex ])
|
|
|
+ then exit; // Breakoutbox added this 2013-06-15
|
|
|
+
|
|
|
+ aStream.Position := 0;
|
|
|
+ lSysExHeader.dwBufferLength := aStream.Size;
|
|
|
+ lSysExHeader.lpData := aStream.Memory;
|
|
|
+ lSysExHeader.dwFlags := 0;
|
|
|
+
|
|
|
+ //ShowMessage( 'HEX: ' +SysExStreamToStr( aStream));
|
|
|
+ {$ifdef DebugSysEx} ShowMessage( '0 - ' +IntToStr(MidiResult)); {$endif}
|
|
|
+ MidiResult := midiOutPrepareHeader(GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
|
|
|
+ {$ifdef DebugSysEx} ShowMessage( '1 - ' +IntToStr(MidiResult)); {$endif}
|
|
|
+ MidiResult := midiOutLongMsg( GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
|
|
|
+ {$ifdef DebugSysEx} ShowMessage( '2 - ' +IntToStr(MidiResult)); {$endif}
|
|
|
+ MidiResult := midiOutUnprepareHeader(GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
|
|
|
+ {$ifdef DebugSysEx} ShowMessage( '3 - ' +IntToStr(MidiResult)); {$endif}
|
|
|
+ {$ifdef DebugSysEx} ShowMessage( '4 - ' +aStream.ReadAnsiString); {$endif}
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ ***** TSysExData *********************************************************** }
|
|
|
+constructor TSysExData.Create;
|
|
|
+begin
|
|
|
+ SysExHeader.dwBufferLength := cMySysExBufferSize;
|
|
|
+ SysExHeader.lpData := SysExData;
|
|
|
+ fSysExStream := TMemoryStream.Create;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TSysExData.Destroy;
|
|
|
+begin
|
|
|
+ FreeAndNil( fSysExStream);
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ ***** Helper Funktions ***************************************************** }
|
|
|
+function SysExStreamToStr(const aStream: TMemoryStream): AnsiString;
|
|
|
+var
|
|
|
+ i : integer;
|
|
|
+begin
|
|
|
+ Result := '';
|
|
|
+ aStream.Position:= 0;
|
|
|
+ for i:= 0 to aStream.Size - 1
|
|
|
+ do Result := Result + Format( '%.2x ', [ byte(pAnsiChar(aStream.Memory)[i]) ]);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure StrToSysExStream(const aString: AnsiString; const aStream: TMemoryStream);
|
|
|
+const
|
|
|
+ cHex : AnsiString = '123456789ABCDEF';
|
|
|
+var
|
|
|
+ lStr : AnsiString;
|
|
|
+ i : integer;
|
|
|
+ L : integer;
|
|
|
+begin
|
|
|
+ // check on errors - added by BREAKOUTBOX 2009-07-30
|
|
|
+ L := length( aString);
|
|
|
+ if not (L mod 2 = 0) // as HEX every byte must be two AnsiChars long, for example '0F'
|
|
|
+ then raise EMidiDevices.Create( 'SysEx string corrupted')
|
|
|
+ else if l < 10 // shortest System Exclusive Message = 5 bytes = 10 hex AnsiChars
|
|
|
+ then raise EMidiDevices.Create( 'SysEx string too short');
|
|
|
+
|
|
|
+ lStr := StringReplace( AnsiUpperCase( aString), ' ', '', [rfReplaceAll]);
|
|
|
+ aStream.Size := Length( lStr) div 2; // ' - 1' removed by BREAKOUTBOX 2009-07-15
|
|
|
+ aStream.Position := 0;
|
|
|
+
|
|
|
+ for i:= 1 to aStream.Size do
|
|
|
+ pAnsiChar( aStream.Memory)[i-1] :=
|
|
|
+ AnsiChar( AnsiPos( lStr[ i*2 - 1], cHex) shl 4 + AnsiPos( lStr[i*2], cHex));
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+initialization
|
|
|
+ gMidiInput := nil;
|
|
|
+ gMidiOutput := nil;
|
|
|
+
|
|
|
+finalization
|
|
|
+ FreeAndNil( gMidiInput);
|
|
|
+ FreeAndNil( gMidiOutput);
|
|
|
+
|
|
|
+
|
|
|
+end.
|