MCI play function can only play a MIDI file once

239 Views Asked by At

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;
1

There are 1 best solutions below

0
Remy Lebeau On

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.