midi.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. unit MIDI;
  2. {$ifdef fpc}
  3. {$mode objfpc}{$H+}
  4. {$endif}
  5. { *** You can choose if You want MIDI.PAS to use exceptions *** }
  6. { *** in case of Errors. If not, MIDI.PAS uses POSTMESSAGE() *** }
  7. { *** and You have to handle these messages in Your MainForm *** }
  8. //{$define MIDI_UseExceptions}
  9. //{$define DebugSysEx}
  10. {*******************************************************************************
  11. * *
  12. * MIDI.PAS *
  13. * *
  14. * This file is based on the MIDI device classes by Adrian Meyer *
  15. * This file was taken from the ZIP archive 'demo_MidiDevices_D6.zip' *
  16. * and partly changed by me, some changes take over from 'DAV_MidiIO.pas' *
  17. * *
  18. * latest changes 2015-04-13 *
  19. ********************************************************************************
  20. * V1.0 First release with simple MIDI Input/Output *
  21. * V1.1 SysEx Input Event added, refactured error handling *
  22. * V1.2 SysEx Output procedure added, changes sysex input for multiple ports *
  23. * V1.3 Changes by BREAKOUTBOX 2009-07 (www.breakoutbox.de) *
  24. * V1.4 Changes adapted from DAV_MidiIO.pas - see http://www.ohloh.net/p/DAV *
  25. * V1.5 removed an Exception on sending SysEx Data to a CLOSED Output *
  26. * V1.6 added a Switch to choose between Exceptions and Other behaviore ... *
  27. * V1.7 replaced pChar => pAnsiChar Char => AnsiChar string => AnsiString *
  28. * to gain compatibility to DelphiXE and higher versions .. *
  29. * - *
  30. *******************************************************************************}
  31. interface
  32. uses
  33. {$ifdef FPC}
  34. Interfaces,
  35. JWAwindows,
  36. {$else}
  37. Windows,
  38. {$endif}
  39. Forms, Classes, Messages, SysUtils, Math, Contnrs, MMSystem, Dialogs;
  40. // -------------------------- WARNING --------------------------
  41. // If Your Application uses User-defined Messages YOU MUST CHECK
  42. // if these values collide with one of Your own messages !!!
  43. const
  44. // define window messages for MainForm :
  45. WM_MIDISYSTEM_MESSAGE = WM_USER +1;
  46. WM_MIDIDATA_ARRIVED = WM_USER +2;
  47. WM_MIM_ERROR = WM_USER +3;//1001;
  48. WM_MIM_LONGERROR = WM_USER +4;//1002;
  49. const
  50. // define Your size of System Exclusive buffer :
  51. cMySysExBufferSize = 2048;
  52. type
  53. TMIDIChannel = 1..16;
  54. TMIDIDataByte = 0..$7F; // 7 bits
  55. TMIDIDataWord = 0..$3FFF; // 14 bits
  56. TMIDIStatusByte = $80..$FF;
  57. TMIDIVelocity = TMIDIDataByte;
  58. TMIDIKey = TMIDIDataByte;
  59. TMIDINote = TMIDIKey;
  60. type
  61. // event if data is received
  62. TOnMidiInData = procedure (const aDeviceIndex: integer; const aStatus, aData1, aData2: byte) of object;
  63. // event if system exclusive data is received
  64. TOnSysExData = procedure (const aDeviceIndex: integer; const aStream: TMemoryStream) of object;
  65. EMidiDevices = Exception;
  66. // base class for MIDI devices
  67. TMidiDevices = class
  68. private
  69. fDevices : TStringList;
  70. fMidiResult : MMResult;
  71. procedure SetMidiResult(const Value: MMResult);
  72. protected
  73. property MidiResult: MMResult read fMidiResult write SetMidiResult;
  74. function GetHandle(const aDeviceIndex: integer): THandle;
  75. public
  76. // create the MIDI devices
  77. constructor Create; virtual;
  78. // whack the devices
  79. destructor Destroy; override;
  80. // open a specific device
  81. procedure Open(const aDeviceIndex: integer); virtual; abstract;
  82. // close a specific device
  83. procedure Close(const aDeviceIndex: integer); virtual; abstract;
  84. // close all devices
  85. procedure CloseAll;
  86. // THE devices
  87. function IsOpen(ADeviceIndex: Integer): Boolean; // check if open
  88. property Devices: TStringList read fDevices;
  89. end;
  90. // MIDI input devices
  91. TMidiInput = class(TMidiDevices)
  92. private
  93. fOnMidiData : TOnMidiInData;
  94. fOnSysExData : TOnSysExData;
  95. fSysExData : TObjectList;
  96. protected
  97. procedure DoSysExData( const aDeviceIndex: integer);
  98. public
  99. // create an input device
  100. constructor Create; override;
  101. // what the input devices
  102. destructor Destroy; override;
  103. // open a specific input device
  104. procedure Open( const aDeviceIndex: integer); override;
  105. // close a specific device
  106. procedure Close( const aDeviceIndex: integer); override;
  107. // midi data event
  108. property OnMidiData: TOnMidiInData read fOnMidiData write fOnMidiData;
  109. // midi system exclusive is received
  110. property OnSysExData: TOnSysExData read fOnSysExData write fOnSysExData;
  111. end;
  112. // MIDI output devices
  113. TMidiOutput = class(TMidiDevices)
  114. constructor Create; override;
  115. // open a specific input device
  116. procedure Open(const aDeviceIndex: integer); override;
  117. // close a specific device
  118. procedure Close(const aDeviceIndex: integer); override;
  119. // send some midi data to the indexed device
  120. procedure Send(const aDeviceIndex: integer; const aStatus, aData1, aData2: byte);
  121. procedure SendSystemReset( const aDeviceIndex: integer);
  122. procedure SendAllSoundOff( const aDeviceIndex: integer; const channel: byte);
  123. // send system exclusive data to a device
  124. procedure SendSysEx( const aDeviceIndex: integer; const aStream: TMemoryStream); overload;
  125. procedure SendSysEx( const aDeviceIndex: integer; const aString: AnsiString); overload;
  126. end;
  127. // convert the stream into xx xx xx xx AnsiString
  128. function SysExStreamToStr( const aStream: TMemoryStream): AnsiString;
  129. // fill the AnsiString in a xx xx xx xx into the stream
  130. procedure StrToSysExStream( const aString: AnsiString; const aStream: TMemoryStream);
  131. // MIDI input devices
  132. function MidiInput: TMidiInput;
  133. // MIDI output Devices
  134. function MidiOutput: TMidiOutput;
  135. type
  136. TSysExBuffer = array[0..cMySysExBufferSize -1] of AnsiChar;
  137. TSysExData = class
  138. private
  139. fSysExStream: TMemoryStream;
  140. public
  141. SysExHeader: {$ifdef FPC} _midihdr {$else} TMidiHdr {$endif};
  142. SysExData: TSysExBuffer;
  143. constructor Create;
  144. destructor Destroy; override;
  145. property SysExStream: TMemoryStream read fSysExStream;
  146. end;
  147. implementation
  148. { ***** TMidiBase ************************************************************ }
  149. constructor TMidiDevices.Create;
  150. begin
  151. FDevices:= TStringList.Create;
  152. end;
  153. destructor TMidiDevices.Destroy;
  154. begin
  155. FreeAndNil( FDevices);
  156. inherited;
  157. end;
  158. var
  159. gMidiInput: TMidiInput;
  160. gMidiOutput: TMidiOutput;
  161. function MidiInput: TMidiInput;
  162. begin
  163. if not Assigned( gMidiInput)
  164. then gMidiInput := TMidiInput.Create;
  165. Result := gMidiInput;
  166. end;
  167. function MidiOutput: TMidiOutput;
  168. begin
  169. if not Assigned( gMidiOutput)
  170. then gMidiOutput := TMidiOutput.Create;
  171. Result := gMidiOutput;
  172. end;
  173. {$IFDEF FPC}
  174. //type
  175. // PHMIDIIN = ^HMIDIIN;
  176. // TMidiOutCaps = TMidiOutCapsA;
  177. {$ENDIF}
  178. { I don't know whatfor this RECORD is used in DAV_MidiIO.pas
  179. but I think this maybe allows renumeration / resorting of MIDI devices
  180. for use in Your application - I didn't use this until now
  181. so I didn't take over the implementation in this MIDI.PAS ... !
  182. TMidiInputDeviceRecord = record
  183. MidiInput : TMidiInput;
  184. DeviceNumber : Integer;
  185. end;
  186. PMidiInputDeviceRecord = ^TMidiInputDeviceRecord;
  187. }
  188. { The Callback-Procedure receives MIDI data on interrupt : }
  189. procedure MidiInCallback( aMidiInHandle: PHMIDIIN; aMsg: Integer; aInstance,
  190. aMidiData, aTimeStamp: integer); stdcall;
  191. begin
  192. case aMsg of
  193. MIM_DATA:
  194. begin
  195. if Assigned(MidiInput.OnMidiData) then
  196. begin
  197. MidiInput.OnMidiData( aInstance,
  198. aMidiData and $000000FF,
  199. ( aMidiData and $0000FF00) shr 8,
  200. ( aMidiData and $00FF0000) shr 16);
  201. PostMessage( Application.MainForm.Handle, WM_MIDIDATA_ARRIVED, aInstance, aMidiData);
  202. end;
  203. end;
  204. MIM_LONGDATA:
  205. MidiInput.DoSysExData( aInstance);
  206. MIM_ERROR: PostMessage( Application.MainForm.Handle, WM_MIM_ERROR, aInstance, aMidiData);
  207. MIM_LONGERROR:
  208. {$ifdef MIDI_UseExceptions}
  209. raise Exception.Create( 'Midi In Error!');
  210. {$else} // in a Callback Function you CANNOT use a MessageBox() !!!
  211. PostMessage( Application.MainForm.Handle, WM_MIM_LONGERROR, aInstance, aMidiData);
  212. {$endif}
  213. end;
  214. end;
  215. { ***** TMidiInput *********************************************************** }
  216. constructor TMidiInput.Create;
  217. var
  218. AvailableMIDIinputs : integer;
  219. lInCaps : TMidiInCaps;
  220. i : integer;
  221. begin
  222. inherited;
  223. fSysExData := TObjectList.Create(true);
  224. TRY // TRY..EXCEPT was adapted from file "DAV_MidiIO.pas"
  225. AvailableMIDIinputs:= MidiInGetNumDevs;
  226. EXCEPT
  227. AvailableMIDIinputs:= 0;
  228. end;
  229. if AvailableMIDIinputs > 0 then
  230. for i:= 0 to AvailableMIDIinputs - 1 do
  231. begin
  232. MidiResult := midiInGetDevCaps(i, @lInCaps, SizeOf(TMidiInCaps));
  233. if MidiResult = MMSYSERR_NOERROR then
  234. begin
  235. fDevices.Add(StrPas(lInCaps.szPname));
  236. fSysExData.Add(TSysExData.Create);
  237. end;
  238. end;
  239. end;
  240. destructor TMidiInput.Destroy;
  241. begin
  242. FreeAndNil( fSysExData);
  243. inherited;
  244. end;
  245. procedure TMidiInput.Close( const aDeviceIndex: integer);
  246. begin
  247. if GetHandle( aDeviceIndex) <> 0 then
  248. begin
  249. MidiResult := midiInStop(GetHandle(aDeviceIndex));
  250. MidiResult := midiInReset(GetHandle(aDeviceIndex));
  251. MidiResult := midiInUnprepareHeader(GetHandle(aDeviceIndex),
  252. @TSysExData(fSysExData[aDeviceIndex]).SysExHeader, SizeOf(TMidiHdr));
  253. MidiResult := midiInClose(GetHandle(aDeviceIndex));
  254. FDevices.Objects[aDeviceIndex] := nil;
  255. end;
  256. end;
  257. procedure TMidiDevices.CloseAll;
  258. var
  259. i : integer;
  260. begin
  261. for i:= 0 to FDevices.Count - 1 do Close(i);
  262. end;
  263. procedure TMidiInput.Open( const aDeviceIndex: integer);
  264. var
  265. lSysExData : TSysExData;
  266. lHandle : THandle;
  267. begin
  268. if GetHandle(aDeviceIndex) <> 0 then Exit;
  269. MidiResult := midiInOpen( @lHandle, aDeviceIndex, cardinal(@midiInCallback),
  270. aDeviceIndex, CALLBACK_FUNCTION);
  271. fDevices.Objects[ aDeviceIndex ] := TObject(lHandle);
  272. lSysExData := TSysExData(fSysExData[aDeviceIndex]);
  273. lSysExData.SysExHeader.dwFlags := 0;
  274. // DRAGONS: why are the function returns not checked on errors here ?
  275. MidiResult := midiInPrepareHeader(lHandle, @lSysExData.SysExHeader, SizeOf(TMidiHdr));
  276. MidiResult := midiInAddBuffer( lHandle, @lSysExData.SysExHeader, SizeOf(TMidiHdr));
  277. MidiResult := midiInStart( lHandle);
  278. end;
  279. { ***** TMidiInput - SysEx *************************************************** }
  280. procedure TMidiInput.DoSysExData( const aDeviceIndex: integer);
  281. var
  282. lSysExData : TSysExData;
  283. begin
  284. lSysExData := TSysExData(fSysExData[aDeviceIndex]);
  285. if lSysExData.SysExHeader.dwBytesRecorded = 0 then Exit;
  286. lSysExData.SysExStream.Write( lSysExData.SysExData,
  287. lSysExData.SysExHeader.dwBytesRecorded);
  288. if lSysExData.SysExHeader.dwFlags and MHDR_DONE = MHDR_DONE then
  289. begin
  290. lSysExData.SysExStream.Position := 0;
  291. if assigned(fOnSysExData)
  292. then fOnSysExData(aDeviceIndex, lSysExData.SysExStream);
  293. lSysExData.SysExStream.Clear;
  294. end;
  295. lSysExData.SysExHeader.dwBytesRecorded := 0;
  296. // DRAGONS: why not check function returns on errors here ?
  297. MidiResult := MidiInPrepareHeader( GetHandle(aDeviceIndex),
  298. @lSysExData.SysExHeader, SizeOf(TMidiHdr));
  299. MidiResult := MidiInAddBuffer( GetHandle(aDeviceIndex), @lSysExData.SysExHeader,
  300. SizeOf( TMidiHdr));
  301. end;
  302. { ***** TMidiOutput ********************************************************** }
  303. constructor TMidiOutput.Create;
  304. var
  305. AvailableMIDIoutputs : integer;
  306. lOutCaps : TMidiOutCaps;
  307. i : integer;
  308. begin
  309. inherited;
  310. TRY
  311. AvailableMIDIoutputs := MidiOutGetNumDevs;
  312. EXCEPT
  313. AvailableMIDIoutputs := 0;
  314. end;
  315. //ShowMessage( 'DEBUG - AvailableMIDIoutputs = ' +IntToStr( AvailableMIDIoutputs));
  316. for i:= 0 to AvailableMIDIoutputs - 1 do
  317. begin
  318. MidiResult := MidiOutGetDevCaps( i, @lOutCaps, SizeOf(TMidiOutCaps));
  319. fDevices.Add( lOutCaps.szPname);
  320. end;
  321. end;
  322. procedure TMidiOutput.Open( const aDeviceIndex: integer);
  323. var
  324. lHandle: THandle;
  325. begin
  326. {$ifndef FPC}
  327. inherited; // Lazarus doesn't like this, so for Delphi only ..
  328. {$endif}
  329. // device already open;
  330. if GetHandle(aDeviceIndex) <> 0 then exit;
  331. MidiResult := midiOutOpen( @lHandle, aDeviceIndex, 0, 0, CALLBACK_NULL);
  332. fDevices.Objects[ aDeviceIndex ]:= TObject( lHandle);
  333. end;
  334. procedure TMidiOutput.Close( const aDeviceIndex: integer);
  335. begin
  336. {$ifndef FPC}
  337. inherited; // Lazarus doesn't like this, so for Delphi only ..
  338. {$endif}
  339. if GetHandle(aDeviceIndex) <> 0 then // 'if .. then' added by BREAKOUTBOX 2009-07-15
  340. begin
  341. MidiResult := midiOutClose(GetHandle(aDeviceIndex));
  342. fDevices.Objects[ aDeviceIndex ] := nil;
  343. end;
  344. end;
  345. procedure TMidiOutput.Send( const aDeviceIndex: integer; const aStatus,
  346. aData1, aData2: byte);
  347. var
  348. lMsg: cardinal;
  349. begin
  350. // open if the device is not open // NOW: do NOT open .. !
  351. if not Assigned( fDevices.Objects[ aDeviceIndex ])
  352. then exit; // Open( aDeviceIndex); // Breakoutbox changed 2008-07-01
  353. //lMsg := aStatus + (aData1 * $100) + (aData2 * $10000);
  354. lMsg:= aStatus or (aData1 shl 8) or (aData2 shl 16); // better ?
  355. MidiResult := MidiOutShortMsg( GetHandle( aDeviceIndex), lMSG);
  356. end;
  357. { --- common MIDI Out messages ----------------------------------------------- }
  358. { System Reset = Status Byte FFh }
  359. procedure TMidiOutput.SendSystemReset( const aDeviceIndex: integer);
  360. begin
  361. Self.Send( aDeviceIndex, $FF, $0, $0);
  362. end;
  363. { All Sound Off = Status + Channel Byte Bnh, n = Channel number }
  364. { Controller-ID = Byte 78h, 2nd Data-Byte = 00h }
  365. procedure TMidiOutput.SendAllSoundOff(const aDeviceIndex: integer; const channel: byte);
  366. begin
  367. Self.Send( aDeviceIndex, $b0 +channel, $78, $0);
  368. end;
  369. // HINT: in a Thread MidiInGetErrorText() makes no sense ..
  370. // read out the error text in Your main thread / MainForm !
  371. procedure TMidiDevices.SetMidiResult( const Value: MMResult);
  372. var
  373. lError: array[0..MAXERRORLENGTH] of AnsiChar;
  374. begin
  375. fMidiResult := Value;
  376. if fMidiResult <> MMSYSERR_NOERROR then
  377. if MidiInGetErrorText( fMidiResult, @lError, MAXERRORLENGTH) = MMSYSERR_NOERROR
  378. {$ifdef MIDI_UseExceptions}
  379. then raise EMidiDevices.Create(StrPas(lError));
  380. {$else}
  381. // in a Thread or a Callback Function you CANNOT use a MessageBox() !!!
  382. //then ShowMessage( 'ERROR in TMidiDevices.SetMidiResult() Line 409' +#13#13
  383. // +StrPas( lError) );
  384. then PostMessage( Application.MainForm.Handle, WM_MIDISYSTEM_MESSAGE, fMidiResult, 0);
  385. {$endif}
  386. end;
  387. function TMidiDevices.GetHandle( const aDeviceIndex: integer): THandle;
  388. begin
  389. try
  390. if not InRange(aDeviceIndex, 0, fDevices.Count - 1) then
  391. raise EMidiDevices.CreateFmt( '%s: Device index out of bounds! (%d)',
  392. [ClassName,aDeviceIndex]);
  393. Result:= THandle(fDevices.Objects[ aDeviceIndex ]);
  394. except
  395. Result:= 0;
  396. end;
  397. end;
  398. function TMidiDevices.IsOpen(ADeviceIndex: Integer): boolean;
  399. begin
  400. Result := GetHandle(ADeviceIndex) <> 0;
  401. end;
  402. { ***** TMidiOutput - SysEx ************************************************** }
  403. procedure TMidiOutput.SendSysEx( const aDeviceIndex: integer;
  404. const aString: AnsiString);
  405. var
  406. lStream: TMemoryStream;
  407. begin
  408. lStream := TMemoryStream.Create;
  409. try
  410. StrToSysExStream( aString, lStream);
  411. SendSysEx( aDeviceIndex, lStream);
  412. finally
  413. FreeAndNil( lStream);
  414. end;
  415. end;
  416. procedure TMidiOutput.SendSysEx( const aDeviceIndex: integer;
  417. const aStream: TMemoryStream);
  418. var
  419. lSysExHeader: TMidiHdr;
  420. begin
  421. // exit here if DeviceIndex is not open !
  422. if not assigned(fDevices.Objects[ aDeviceIndex ])
  423. then exit; // Breakoutbox added this 2013-06-15
  424. aStream.Position := 0;
  425. lSysExHeader.dwBufferLength := aStream.Size;
  426. lSysExHeader.lpData := aStream.Memory;
  427. lSysExHeader.dwFlags := 0;
  428. //ShowMessage( 'HEX: ' +SysExStreamToStr( aStream));
  429. {$ifdef DebugSysEx} ShowMessage( '0 - ' +IntToStr(MidiResult)); {$endif}
  430. MidiResult := midiOutPrepareHeader(GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
  431. {$ifdef DebugSysEx} ShowMessage( '1 - ' +IntToStr(MidiResult)); {$endif}
  432. MidiResult := midiOutLongMsg( GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
  433. {$ifdef DebugSysEx} ShowMessage( '2 - ' +IntToStr(MidiResult)); {$endif}
  434. MidiResult := midiOutUnprepareHeader(GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
  435. {$ifdef DebugSysEx} ShowMessage( '3 - ' +IntToStr(MidiResult)); {$endif}
  436. {$ifdef DebugSysEx} ShowMessage( '4 - ' +aStream.ReadAnsiString); {$endif}
  437. end;
  438. { ***** TSysExData *********************************************************** }
  439. constructor TSysExData.Create;
  440. begin
  441. SysExHeader.dwBufferLength := cMySysExBufferSize;
  442. SysExHeader.lpData := SysExData;
  443. fSysExStream := TMemoryStream.Create;
  444. end;
  445. destructor TSysExData.Destroy;
  446. begin
  447. FreeAndNil( fSysExStream);
  448. end;
  449. { ***** Helper Funktions ***************************************************** }
  450. function SysExStreamToStr(const aStream: TMemoryStream): AnsiString;
  451. var
  452. i : integer;
  453. begin
  454. Result := '';
  455. aStream.Position:= 0;
  456. for i:= 0 to aStream.Size - 1
  457. do Result := Result + Format( '%.2x ', [ byte(pAnsiChar(aStream.Memory)[i]) ]);
  458. end;
  459. procedure StrToSysExStream(const aString: AnsiString; const aStream: TMemoryStream);
  460. const
  461. cHex : AnsiString = '123456789ABCDEF';
  462. var
  463. lStr : AnsiString;
  464. i : integer;
  465. L : integer;
  466. begin
  467. // check on errors - added by BREAKOUTBOX 2009-07-30
  468. L := length( aString);
  469. if not (L mod 2 = 0) // as HEX every byte must be two AnsiChars long, for example '0F'
  470. then raise EMidiDevices.Create( 'SysEx string corrupted')
  471. else if l < 10 // shortest System Exclusive Message = 5 bytes = 10 hex AnsiChars
  472. then raise EMidiDevices.Create( 'SysEx string too short');
  473. lStr := StringReplace( AnsiUpperCase( aString), ' ', '', [rfReplaceAll]);
  474. aStream.Size := Length( lStr) div 2; // ' - 1' removed by BREAKOUTBOX 2009-07-15
  475. aStream.Position := 0;
  476. for i:= 1 to aStream.Size do
  477. pAnsiChar( aStream.Memory)[i-1] :=
  478. AnsiChar( AnsiPos( lStr[ i*2 - 1], cHex) shl 4 + AnsiPos( lStr[i*2], cHex));
  479. end;
  480. initialization
  481. gMidiInput := nil;
  482. gMidiOutput := nil;
  483. finalization
  484. FreeAndNil( gMidiInput);
  485. FreeAndNil( gMidiOutput);
  486. end.