1

I am using Delphi 10.2.

I want a small music program of mine to be able to play MIDI files. I want to use MCI (Media Control Interface), as it should be simpler to code than the more low-level MIDI API. I found the following code, which functions OK as long as I choose a given MIDI file only once.

I have a TFileListBox on my Form, and I can choose and play as many MIDI files as I want as long as they are not repeated. As soon as I chose one of the MIDI files for a second time, it will stop playing.

It might be a problem that I don't call mciSendCommand() with MCI_CLOSE, but if I try to do this then I get an error like:

Could not convert variant of type (NULL) into type (Int64)

Function TForm1.PlayMidiFile(FileName: string): Word;
// http://www.delphigroups.info/2/3e/176031.html
// You need to add the MMSYSTEM unit in your USES clause.
var
  wDeviceID: Integer;
  dwReturn : Word;
  mciOpen  : TMCI_Open_Parms;
  mciPlay  : TMCI_Play_Parms;
  mciStat  : TMCI_Status_Parms;
  mciSeq   : TMCI_Seq_Set_Parms;
begin
  // Open the device by specifying the device and filename.
  // MCI will attempt to choose the MIDI mapper as the output port.
  mciOpen.lpstrDeviceType := 'sequencer';
  mciOpen.lpstrElementName := PChar(FileName);
  dwReturn := mciSendCommand($0, MCI_OPEN, MCI_OPEN_TYPE or MCI_OPEN_ELEMENT, LongInt(@mciOpen));
  if dwReturn <> 0 then
    Result := dwReturn
  else begin
    // The device opened successfully; get the device ID.
    wDeviceID := mciOpen.wDeviceID;
    // Check if the output port is the MIDI mapper.
    mciStat.dwItem := MCI_SEQ_STATUS_PORT;
    dwReturn := mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, LongInt(@mciStat));
    if dwReturn <> 0 then begin
      { close if not succeeding }
      mciSendCommand(wDeviceID, MCI_CLOSE, 0, NULL);
      Result := dwReturn;
    end else begin
      // Begin playback. The window procedure function for the parent
      // window will be notified with an MM_MCINOTIFY message when
      // playback is complete. At this time, the window procedure closes
      // the device.
      mciPlay.dwCallback := Application.Handle;
      dwReturn := mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, LongInt(@mciPlay));
      if dwReturn <> 0 then begin
        mciSendCommand(wDeviceID, MCI_CLOSE, 0, NULL);
        Result := dwReturn;
      end;
    end;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
larshgf
  • 43
  • 4
  • 1
    1) the last param of `mciSendCommand()` is a `DWORD_PTR` not `LongInt`. Your code won't work correctly in 64bit. 2) Delphi's `NULL` is not equivalent to C/C++'s `NULL`. In C/C++, `NULL` is an alias for `0`. In Delphi, `NULL` is a `Variant` holding a null value. Don't use `NULL` in this code, use `0` instead. 3) Where does the code fail exactly? When opening the device, or playing it? Is MCI reporting an error code? Your `Result` is undefined if everything works OK. 4) When a file is played twice, is it just that file that fails, and other files still work? Or do all files fail at that point? – Remy Lebeau Jul 15 '22 at 22:09
  • 2) Replacing NULL with 0 solves the errormessage-problem. 3) the code fails when opening the device. I have tried to use then function MciGetErrorString(dwReturn, pszText, 128), but I dont know how to read pszText. I have tried with pszText^ but this gives me an errormessage ("Access violation..."). But the dwReturn value is 265. 4) Yes, when a file is played twice it is only that file that fails. The other files still works. I have tried to place a MCI_CLOSE command in the end of my code, like mciSendCommand(wDeviceID, MCI_CLOSE, 0, 0). The result is thet the midifile is not played. – larshgf Jul 16 '22 at 07:43
  • (cont) As if the device is closed before the program reach to play the file. On the other hand, if I place mciSendCommand(wDeviceID, MCI_CLOSE, 0, 0) on a seperate button (and make the wDeviceID a global variant) I am able to play the same midi-file as often I want. I then tried to initiate my wDeviceID in Form.Create to a dummy value (999) and start my code like this: if wDeviceID <> 999 then mciSendCommand(wDeviceID, MCI_CLOSE, 0, 0). Now the code function OK and the same midi-file kan be played over and over again. But I think it is odd that my MCI_CLOSE in the end.?? – larshgf Jul 16 '22 at 07:44

1 Answers1

2

the code fails when opening the device... the dwReturn value is 265

MCI error 265 is MCIERR_DEVICE_OPEN ("The device name is already used as an alias by this application. Use a unique alias.") Always close an MCI device when you are done using it.

I have tried to place a MCI_CLOSE command in the end of my code, like mciSendCommand(wDeviceID, MCI_CLOSE, 0, 0). The result is thet the midifile is not played. As if the device is closed before the program reach to play the file.

Yes, that is exactly what is happening. You are not using the MCI_WAIT flag on the MCI_PLAY command, so the playback is asynchronous. Calling MCI_CLOSE at the end of PlayMidiFile() will kill the playback.

Since you are using MCI_NOTIFY, you can close the device in your MM_MCINOTIFY handler instead when playback ends.

On the other hand, if I place mciSendCommand(wDeviceID, MCI_CLOSE, 0, 0) on a seperate button (and make the wDeviceID a global variant) I am able to play the same midi-file as often I want. I then tried to initiate my wDeviceID in Form.Create to a dummy value (999) and start my code like this: if wDeviceID <> 999 then mciSendCommand(wDeviceID, MCI_CLOSE, 0, 0). Now the code function OK and the same midi-file can be played over and over again.

Yes, you should stop and close the device at the beginning of PlayMidiFile() if the device is open.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770