Sysex Viewer

16 posts / 0 new
Last post
Royce
Royce's picture
Sysex Viewer

Hi Mark,

welcome back.

I was wondering if you would consider a few things on the Sysex tool?

Firstly allowing all sysex (even the large ones) to be displayed as a single line.

Second can you display the byte position  within the sysex - above the data

. . . . 5 . . . . 10 . . etc or change to
. . . 4 . . . 8 . . . C . . etc
F0 41 10 00 00 25 1F 00 00 10 23 12 F7

Finally allow the data, with the byte position to scroll across and stay there.

That is, I would like a viewer that I can position the data so I am only looking at the data from, say, byte 6 onward.

. 8 . . . C . . . 10 . .
1F 20 00 10 23 12 F7
1F 22 00 10 43 12 F7
1F 24 00 10 21 12 F7

Now I would like to scroll up/down through the messages and the start point is always byte 6 for all messages.

It would also great if I could colour code sections of the line, say byte 6 to 9 (Roland address).in red

Or if you are happy to release the souce I'd be happy to do it.

By the way what Midi library do you use?

Thanks

Royce

Royce
Royce's picture

Sorry could of sworn I formatted that display.

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

This test build tries to incorporate most of your suggestions:
[Obsolete link removed]
(This only contains an exe file; you can put this in any folder where it can create its own configuration (ini) file. So you should probably avoid C:\Program Files[ (x86)], because of the UAC problems that may give.)

The grid in the "MIDI System Exclusive messages" window now puts the bytes in individual columns, with the headers indicating the index.

You can remove the horizontal and/or vertical grid lines from the View pull-down menu.
You can toggle the highlighting (currently hard-coded yellow) of any individual column by right-clicking anywhere in the column.
(Note: as yet, these settings are lost when you exit MIDI Tools.)

Selection by left mouse clicking no longer automatically selects the whole row, but only a single cell.
By holding the left mouse button down and dragging the mouse you can select a block of cells.

It shouldn't be too difficult to allow editing "in situ" of individual bytes, but I haven't come round to that yet.

Hopefully all this is more or less what you wanted...

   Mark.

P.S. I'm not sure what you mean by "Midi library".
For MIDI communication I (of course) use Windows' MMSystem routines, but I suppose that's not what you meant. For the visual elements (e.g. the grids) I simply use the standard Delphi VCL library.

Royce
Royce's picture

Hi Mark,

I thought I had sent a message, but I'm not sure it saved it. I hope I am not repeating myself.

Thanks for the additions. This looks perfect.

I can even use my mousewheeel without loosing the horizontal offset if I  select a cell first.

It will make looking though Roland data very easy.

I was wondering about a Midi library as there are a couple of Delphi Midi components about.

I also wrote my own back in the dim dark ages but swapped to a library when Windows went from 16 to 32bit. I figured Microsoft would change change the API again at some stage.

I have been using a page of Delphi code from the internet for a while and I thought that it was fine, but the more I use it I find strange memory overwrites/corruption.

I can write simple Delphi but I am really at home with C++ so I use C++ Builder for Windows apps.

I was able find and fix a few bugs but I can't seem to see where the problem is let alone fix it.

 

Thanks again

Royce

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

Here's the next version, 1.7.0 alpha 2:
[Obsolete link removed]

Changes compared to the 1.7.0 alpha 1 version:

  • Fixed a bug by which cells that should be empty showed garbage data after you had edited/inserted a message.
  • The message numbers and lengths are now right-aligned in their columns.
  • The message length column is now a "fixed", gray column.
  • Message lengths can be displayed in hexadecimal notation (in fact this is now the default).
  • The horizontal grid lines in the message number and length columns and the vertical grid lines in the header row can be turned on and off (separate from the horizontal/vertical lines in the data area).
  • The color for highlighting can be customized.
  • You can choose between cell selection and row selection. (Row selection is the "old" way.)
  • All display options (except the specific highlighted columns) are retained between runs of the program.
  • You can "unhighlight" all highlighted columns in one command.

See the View pull-down menu for all the new options.

Concerning your problem with the Delphi MIDI library: do you have the source code? If so, you/I/we might be able to figure out what is wrong.

Mark

Royce
Royce's picture

Hi Mark

I shall check out the new version.

Thanks for offer of checking the Delphi code.

The Midi library seems to work perfectly well for simple programs, but there seems to be point when it starts corrupting other unrelated elements especially graphic components.

I have a number of editors with graphic displays of envelopes for instantce.

The code works for a while then after a burst of Midi traffic the graphic component stops working or goes black. I suspect that the pointer to the BMP work area is corrupted. The Midi still works fine.

Codeguard thinks everything is fine.

I found a group of coders that have taken Adrian's code and worked on it, but it still causes the same error.

I have tried Midi I/O from http://bitbucket.org/h4ndy/midiio-dev and re-wrote my code to fit and it seems like this fixes the problem, but I would love to fix this library as there are quite a few of my programs that have used it.

A lot of them developed very strange problems that I could not solve (I didn't suspect the Midi component) and so if this is an easy fix that would be great to just recompile.

If not, I will change to Midi I/O and recode.

Thanks for the help

Royce

Here is the original with some tidying up by me.

//****************************************************************************/
//* MIDI device classes by Adrian Meyer
//****************************************************************************/
//* V1.1 Delphi 6 Windows 2000
//****************************************************************************/
//* 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
//****************************************************************************/
//* Homepage: http://www.midimountain.com
//****************************************************************************/
//* If you get a hold of this source you may use it upon your own risk. Please
//* let me know if you have any questions: adrian.meyer@rocketmail.com.
//****************************************************************************/
unit Midi;

interface

uses
  classes, SysUtils, mmsystem, Math, Windows, Contnrs, StrUtils;

const
  // size of system exclusive buffer
//  cSysExBufferSize = 32768;
  cSysExBufferSize = 65536;        // 2 times the size
 

type
  // event if data is received
  TOnMidiInData = procedure (const aDeviceIndex: integer; const aStatus, aData1, aData2: byte) of object;
  // event of 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
    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);
    // send system exclusive data to a device
    procedure SendSysEx(const aDeviceIndex: integer; const aStream: TMemoryStream); overload;
    procedure SendSysEx(const aDeviceIndex: integer; const aString: string); overload;
  end;
// ------------------------------------------------------------------

  // convert the stream into xx xx xx xx string
  function SysExStreamToStr(const aStream: TMemoryStream): string;
  // fill the string in a xx xx xx xx into the stream
  procedure StrToSysExStream(const aString: string; const aStream: TMemoryStream);
// ------------------------------------------------------------------
// ------------------------------------------------------------------

  // MIDI input devices
  function MidiInput: TMidiInput;
  // MIDI output Devices
  function MidiOutput: TMidiOutput;
// ------------------------------------------------------------------
// ------------------------------------------------------------------
implementation

{ TMidiBase }
type
  TSysExBuffer = array[0..cSysExBufferSize] of char;

  TSysExData = class
  private
    fSysExStream: TMemoryStream;
  public
    SysExHeader: TMidiHdr;
    SysExData: TSysExBuffer;
    constructor Create;
    destructor Destroy; override;
    property SysExStream: TMemoryStream read fSysExStream;
  end;
// ------------------------------------------------------------------
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;
// ------------------------------------------------------------------
{ TMidiInput }

procedure midiInCallback(aMidiInHandle: PHMIDIIN; aMsg: UInt; aData, aMidiData, aTimeStamp: integer); stdcall;
begin
  case aMsg of
    MIM_DATA:
      begin
        if assigned(MidiInput.OnMidiData) then
           MidiInput.OnMidiData(aData, aMidiData and $000000FF,
           (aMidiData and $0000FF00) shr 8, (aMidiData and $00FF0000) shr 16);
      end;

    MIM_LONGDATA:
      MidiInput.DoSysExData(aData);
  end;
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;
// ------------------------------------------------------------------
constructor TMidiInput.Create;
var
  i: integer;
  lInCaps: TMidiInCaps;
begin
  inherited;
  fSysExData := TObjectList.Create(true);
  for i:=0 to midiInGetNumDevs - 1 do
  begin
    MidiResult := midiInGetDevCaps(i, @lInCaps, SizeOf(TMidiInCaps));
    fDevices.Add(StrPas(lInCaps.szPname));
    fSysExData.Add(TSysExData.Create);
  end;
end;
// ------------------------------------------------------------------
procedure TMidiInput.Open(const aDeviceIndex: integer);
var
  lHandle: THandle;
  lSysExData: TSysExData;
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;

  MidiResult := midiInPrepareHeader(lHandle, @lSysExData.SysExHeader, SizeOf(TMidiHdr));
  MidiResult := midiInAddBuffer(lHandle, @lSysExData.SysExHeader, SizeOf(TMidiHdr));
  MidiResult := midiInStart(lHandle);
end;
// ------------------------------------------------------------------
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;
  MidiResult := midiInPrepareHeader(GetHandle(aDeviceIndex), @lSysExData.SysExHeader, SizeOf(TMidiHdr));
  MidiResult := midiInAddBuffer(GetHandle(aDeviceIndex), @lSysExData.SysExHeader, SizeOf(TMidiHdr));
end;
// ------------------------------------------------------------------
destructor TMidiInput.Destroy;
begin
  FreeAndNil(fSysExData);
  inherited;
end;
// ------------------------------------------------------------------
// ------------------------------------------------------------------
{ TMidiOutput }

procedure TMidiOutput.Close(const aDeviceIndex: integer);
begin
  inherited;
  if GetHandle(aDeviceIndex) <> 0 then
  begin
   MidiResult := midiOutClose(GetHandle(aDeviceIndex));
   fDevices.Objects[ aDeviceIndex ] := nil;
  end;
end;
// ------------------------------------------------------------------
constructor TMidiOutput.Create;
var
  i: integer;
  lOutCaps: TMidiOutCaps;
begin
  inherited;
  for i:=0 to midiOutGetNumDevs - 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
  inherited;
  // 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.Send(const aDeviceINdex: integer; const aStatus,
  aData1, aData2: byte);
var
  lMsg: cardinal;
begin
  // open the device is not open
  if not assigned(fDevices.Objects[ aDeviceIndex ]) then
    Open(aDeviceIndex);

  lMsg := aStatus + (aData1 * $100) + (aData2 * $10000);
  MidiResult := midiOutShortMsg(GetHandle(aDeviceIndex), lMSG);
end;
// ------------------------------------------------------------------
procedure TMidiDevices.SetMidiResult(const Value: MMResult);
var
  lError: array[0..MAXERRORLENGTH] of char;
begin
  fMidiResult := Value;
  if fMidiResult <> MMSYSERR_NOERROR then
    if midiInGetErrorText(fMidiResult, @lError, MAXERRORLENGTH) = MMSYSERR_NOERROR then
      raise EMidiDevices.Create(StrPas(lError));
end;
// ------------------------------------------------------------------
function TMidiDevices.GetHandle(const aDeviceIndex: integer): THandle;
begin
  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 ]);
end;
// ------------------------------------------------------------------
procedure TMidiOutput.SendSysEx(const aDeviceIndex: integer; const aString: string);
var
  lStream: TMemoryStream;
  tStr: string;
  i: integer;
const
  cHex = '0123456789ABCDEF ';
begin
// Hex only -
  tStr := UpperCase(aString);
  for i:=1 to Length(tStr)  do
      if AnsiPos(MidStr(tStr,i,1) , cHex) = 0 then exit;
  if tStr = '' then exit;
 
  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
  aStream.Position := 0;
  lSysExHeader.dwBufferLength := aStream.Size;
  lSysExHeader.lpData := aStream.Memory;
  lSysExHeader.dwFlags := 0;

  MidiResult := midiOutPrepareHeader(GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
  MidiResult := midiOutLongMsg( GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
  MidiResult := midiOutUnprepareHeader(GetHandle(aDeviceIndex), @lSysExHeader, SizeOf(TMidiHdr));
end;
// ------------------------------------------------------------------
// ------------------------------------------------------------------
{ TSysExData }

constructor TSysExData.Create;
begin
  SysExHeader.dwBufferLength := cSysExBufferSize;
  SysExHeader.lpData := SysExData;
  fSysExStream := TMemoryStream.Create;
end;
// ------------------------------------------------------------------
destructor TSysExData.Destroy;
begin
  FreeAndNil(fSysExStream);
end;
// ------------------------------------------------------------------
function SysExStreamToStr(const aStream: TMemoryStream): string;
var
  i: integer;
begin
  Result := '';
  aStream.Position := 0;
  for i:=0 to aStream.Size - 1 do
    Result := Result + Format('%.2x ', [ byte(pchar(aStream.Memory)[i]) ]);
end;
// ------------------------------------------------------------------
procedure StrToSysExStream(const aString: string; const aStream: TMemoryStream);
const
  cHex = '123456789ABCDEF';
var
  i: integer;
  lStr: string;
begin
  lStr := StringReplace(AnsiUpperCase(aString), ' ', '', [rfReplaceAll]);
//RPC original   aStream.Size := Length(lStr) div 2 - 1;
  aStream.Size := Length(lStr) div 2 ;
  aStream.Position := 0;

  for i:=1 to aStream.Size do
    pchar(aStream.Memory)[i-1] :=
      char(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.

 

 

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

I've looked at the MIDI library in quite some detail.
I've encountered several potential causes of your problem, but nothing definite yet.
So to zoom in on the problem, let me first ask a few questions:

1. Which Delphi version are you using?
I'm asking this because in Delphi 2009 and later the Char type is an alias of the 2-byte WideChar instead of the 1-byte AnsiChar (as was the case in Delphi 2007 and earlier).
Consequently, I couldn't even compile the MIDI library in Delphi 2009 without tweaking!

2. Do you experience problems when there has been MIDI (SysEx?) input or when there has been MIDI (SysEx?) output?
This would be a strong indicator of the cause of the problem.

Mark.

Royce
Royce's picture

Hi Mark

All my Borland compilers rae very old.

I am runnning Delphi7 which I only use occasionally. I used this when initiallly testing the library.

My 'goto' compiler is C++ Builder and that happily compiles Delphi as build from Delphi and its library is VLC.

The BCB version I use is version 6 and the VCL is 6.0  so I would assume it is 1 byte wide.

In my editor, currently under development, the error occurs consistantly after a ranom number of patch generations which involves transmitting a lot of sysex.

That said, it can also occur during a dump of the current patch sysex from the synth.

Not very helpfull I'm afraid.

The error is intermitent and seemingly random and I have found it impossible to create failure scenarios that are repeatable.

 

Appreciate your time

Royce

 

 

 

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

I think there is a problem with the overloaded version of TMidiOutput.SendSysEx that has TMemoryStream as a parameter:

In accordance with Microsoft's MMSystem documentation, lSysExHeader's dwBufferLength, lpData and dwFlags fields are set explicitly before lSysExHeader is passed to the three midiOut routines.

However, what Microsoft has not documented is that midiOutLongMsg also uses the dwBytesRecorded field to determine the actual number of bytes it will send.
This seems to work as follows:

  • if dwBytesRecorded=0 or dwBytesRecorded>=dwBufferLength, then dwBufferLength bytes are sent
  • if 0<dwBytesRecorded<dwBufferLength, then dwBytesRecorded bytes are sent

Obviously this behavior is currently problematic for TMidiOutput.SendSysEx, since lSysExHeader is a local variable: its dwBytesRecorded field can have any value on entrance.
Hence, the wrong number of bytes get sent if lSysExHeader.dwBytesRecorded happens to be between 0 and dwBufferLength. (I've confirmed this with MIDI Yoke.)

So I suggest you insert "lSysExHeader.dwBytesRecorded:=0;" somewhere before the call of midiOutPrepareHeader.

I'm not sure if this is related to your problem(s), but it won't harm to fix this...

   Mark.

Royce
Royce's picture

Thanks Mark.

Good point.

I have never heard of this despite my trawling of mmsystem doc in MSDN and Messick's book.

I always thought that dwBytesRecorded  was for MidiIn only.

Where did you find out about dwBytesRecorded  in midiOutLongMsg?

Unfortunately it doesn't fix the problem.

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

Last weekend, when I tested Adrian Meyer's library, certain SysEx messages sent through MIDI Yoke came back in truncated form; this turned out to be caused by dwBytesRecorded.
Then I found that my own MIDI SysEx output routine (written about 20 years ago...) sets dwBytesRecorded to zero, to circumvent the same problem. So this behavior has been around for decades, although it may not always come to light, either because TMidiHdr is fully initalized to zero (e.g. if it's part of an object), or simply because an uninitialized dwBytesRecorded only rarely causes truncation - in fact: the shorter the SysEx message, the rarer!

But since initializing dwBytesRecorded doesn't fix your problem, here are some more things you might try:

1. Initialize the whole of lSysExHeader, e.g. by inserting the following line at the start of TMidiOutput.SendSysEx:
FillChar(lSysExHeader, SizeOf(TMidiHdr), 0);
I doubt this will help, but who knows.

2. Temporarily remove (comment out) the midiOutLongMsg statement.

3. Similarly: temporarily remove all three midiOut... statements.

By the way, which overloaded version(s) of TMidiOutput.SendSysEx are you calling?

A. If you are calling the TMemoryStream version, are you sure the caller is handling (creating/destroying etc.) its TMemoryStream variable correctly?

B. On the other hand, if you are calling the string version, there might be something wrong with the library's StrToSysExStream routine, or there might be some other string-related bug:
There are many different string types around: if I remember correctly, in Turbo/Borland Pascal "string" amounted to "string [255]", in Delphi <=2007 to "AnsiString" and in Delphi >=2009 to "UnicodeString"; and then of course there are the C-like PChar, PAnsiChar and PWideChar. And different compilers have different compiler settings affecting all this. So there's plenty of scope for horrible string-related errors like buffer overruns.

Mark.

Royce
Royce's picture

Hi Mark

No luck for these suggestions.

I'm using the AnsiString version that calls the TMemoryStream version.

The memory dump in the debugger looks correct.

As my editors are coupled to the BCR, all midiOutLongMsg are preceded by a received short message (CC) from the BCR.

So it looks like there could be a problem with the MidiIn. I haven't been able to duplicate the fault using mouse clicks or key strokes even when the change causes MidiOut Short message to the BCR.

I do get the fault (randomly) when I select the 'page' via the BCR (no sysex is sent )

I am using multiple MidiIn's (a thread for each, if I understand the Delphi correctly) so I have a global TCriticalSection with an Aquire() and Release() surrounding the copying of the passed values.

Thanks

 

Royce

Royce
Royce's picture

Hi Mark

further investigation reveals that the Delphi code should also be made thread safe (unless it already is???)

Using Codeguard  (an extra thread) causes the problem to happen faster.

There is some comment on the web that there should be a 'bulking' out of the TCriticalSection class so my additions are a new unit to create the global TCriticalSection ...

 

//****************************************************************************/
//* Needed for MIDI device classes by Adrian Meyer
//****************************************************************************/

//****************************************************************************/
// RPC added Threadsafe code         30 May 2014
//****************************************************************************/
unit MidiThread;

interface

uses
  SyncObjs;

type
// ------------------------------------------------------------------
//RPC
   TFixedCriticalSection = class(TCriticalSection)
      private
         FDummy : array [0..95] of Byte;
   end ;
// ------------------------------------------------------------------
//RPC Global
var
    fCriticalSec : TFixedCriticalSection ;
implementation

end.

I add this unit to Midi.pas.

Is this correct?

In my code I use a pointer to the global fCriticalSec to lock the code to copy the variables then release  the CriticalSection.

Unfortunately all of that doesn't seem to help too much.

Royce

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

I'm not an expert on threads (I try to avoid them as much as I can!), so I can't give you any concrete advice on CriticalSection etc.

All I can say is that it does seem likely to me that your problem is related to the MIDI input callback routine.

Historically the idea was that this routine is called "at interrupt time" (causing all kinds of restrictions), although on modern machines it's probably more correct to say it is called from a different thread.

In any case, the official documentation on MIDI input callback routines ("MidiInProc") states:
Applications should not call any system-defined functions from inside a callback function, except for EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, and timeSetEvent.
However, I've heard conflicting opinions on how strictly we should take this.

In any case another warning concerning threads (not specifically related to MIDI input) should be taken very seriously indeed: as the Delphi documentation warns, all VCL calls should be made from the main thread. I know from experience that calls from other threads indeed lead to strange GUI effects (very possibly the effects you're seeing).

In the context of MIDI input, this probably means that you shouldn't make VCL calls (e.g. to update your GUI controls) from midiInCallback (or - obviously - any event handlers called from midiInCallback).
Instead, you could post a user-defined message (via "PostMessage") from midiInCallback to the message queue of any "window" (i.e. anything with a window handle) in your application, and perform any VCL-related actions from the corresponding user-defined window message handler. Since the message handler runs from the main thread, this prevents any thread/VCL conflicts. In fact, this is how my own system works.

One difference between Meyer's library and my system is that my system uses multiple buffers (which are replenished from the window message handler), whereas Meyer has only one SysEx buffer - so I can understand why he restores it as soon as possible, i.e. by calling midiInPrepareHeader and midiInAddBuffer from midiInCallback=>TMidiInput.DoSysExData - but I'm not sure how safe this is, in view of the official warning about MIDI input callback routines: I think that formally midiInPrepareHeader and midiInAddBuffer are not allowed, but I doubt whether this is the actual cause of your problem; in any case it can't be the only cause if (as you've stated) you're facing problems after incoming non-SysEx messages.

Mark.

Royce
Royce's picture

Thanks for the info Mark.

I, in fact, only copy the data to local variables in the main thread then release the critical section then process the incomming Midi.

I think you may be right about the lack of multiple buffers.

Looking at the Delphi Midi In components in MidiIODev they use 16 4k buffers.

I think I will just convert all my programs to the MidiIODev library.

Thanks for the help.

Royce

Mark van den Berg
Mark van den Berg's picture

Hi Royce,

Yes, considering your problems with Adrian Meyer's library (and its limitations), it's probably a good idea to switch to a different library, even though that may have its own problems...

Good luck!
   Mark.