2

I want to be able to begin playing a song at a particular time (for example, 30 seconds into the song) in my program which replicates a radio station, where when you change the channel and the song is already playing or almost finished.

Currently I use PlaySound and have my 26 songs saved as wav files and as resources within the program. I have seen mciSendString as an option however I do not understand how i could manage to get it to work when coding with Windows API and C++.

Here is my current PlayWavFile function (where the resource integer is a randomly generated number from 0 to 25 cast to this function to play a random song):

void PlayWavFile(int resource) {
    PlaySound(MAKEINTRESOURCE(resource), hInst, SND_RESOURCE | SND_ASYNC);
}

I would expect to be able to play the song a certain way through, using an integer value i have predetermined (like a duration).

drescherjm
  • 10,365
  • 5
  • 44
  • 64
  • 2
    I'm afraid you'll have to use the waveOut API, which is more involved, and deal with the audio buffers yourself. There's no API as simple as PlaySound that lets you pick a position. – Jeffrey Jun 30 '19 at 03:14
  • What problem are you having with using the MCI commands? There is a `seek` command available for audio playback – Remy Lebeau Jun 30 '19 at 03:57
  • With MCI I just don't understand how to use them at all. Especially to do what I want to do. I'm relatively new to C++ which is probably a factor of my confusion. – Ethan Sigler Jun 30 '19 at 04:13
  • Then you'll need to study the documentation until you do ;) – Lightness Races in Orbit Jul 01 '19 at 09:57

3 Answers3

2

I have seen mciSendString as an option however I do not understand how i could manage to get it to work when coding with Windows API and C++.

You can read MSDN docs like Multimedia Command Strings for the syntax of commands

A basic sample reading a wav file from 1 second =>

int rc = mciSendString(L"open E:\\test.wav alias wav1", NULL, 0, 0);
if (rc == 0)
{
    rc = mciSendString(L"set wav1 time format ms", NULL, 0, 0);
    rc = mciSendString(L"seek wav1 to 1000", NULL, 0, 0);
    if (rc == 0)
    {
        rc = mciSendString(L"play wav1", NULL, 0, 0);
    }
    else
    {
        // handle error (like MCIERR_OUTOFRANGE for example)
    }
}
else
{
    // handle error
}
Castorix
  • 1,465
  • 1
  • 9
  • 11
  • I would use `mciSendCommand()` instead of `mciSendString()`, but yes, this is the basic approach to take – Remy Lebeau Jun 30 '19 at 23:47
  • I did this, I got no errors but I still didn't get any sound and instead my program just continued on to the next line. What's going on there? – Ethan Sigler Jul 01 '19 at 06:06
  • @EthanSigler Try `mciSendString(L"play wav1 wait", NULL, 0, 0);` – Drake Wu Jul 01 '19 at 09:50
  • @DrakeWu-MSFT Ok this made it play, however I need it to work alongside other functions otherwise the window will not respond (e.g. I need to make it asynchronous). Is there a way of doing that with mciSendString? – Ethan Sigler Jul 03 '19 at 02:21
  • 1
    @EthanSigler First, you can't hear the sound because you have little running time after start to play it. It will initiate the play and close it immediately(It can be verified by adding Sleep() after play starts). See the [related issue](https://stackoverflow.com/questions/16962645/does-mcisendstring-must-have-wait-in-order-to-hear-sound). If you want play no wait, you need to handle the `MCI_NOTIFY`, set the callback window handle, and handle the MM_MCINOTIFY when the play has finish. See the sample in my answer. – Drake Wu Jul 03 '19 at 03:37
2

To use MCI with mciSendCommand, you should

  • Open the Device and get the deviceID.
  • Set the seek.
  • Play.

Or

  • Open the Device and get the deviceID.
  • Play from the specified position directly.

Sample:

#include <windows.h>
#pragma comment(lib, "winmm.lib")
MCIDEVICEID MCIOpen(LPCTSTR strPath)
{
    MCI_OPEN_PARMS mciOP;
    DWORD opReturn;
    mciOP.lpstrDeviceType = NULL;
    mciOP.lpstrElementName = strPath;  //Set the .wav file name to open
    opReturn = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_ELEMENT, (DWORD)(LPVOID)&mciOP);
    if (!opReturn)
        return mciOP.wDeviceID;
    return -1;
}
DWORD MCISeek(MCIDEVICEID wDeviceID,int sec)
{
    MCI_SEEK_PARMS SeekParms;
    SeekParms.dwTo = (sec) * 1000;
    return mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO, (DWORD)(LPVOID)&SeekParms);
}
DWORD MCIPlay(MCIDEVICEID wDeviceID)
{
    MCI_PLAY_PARMS mciPP;
    return mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY| MCI_WAIT, (DWORD)&mciPP);
}
DWORD MCIPlayFrom(MCIDEVICEID wDeviceID,int sec)
{
    MCI_PLAY_PARMS play;
    play.dwFrom = sec*1000;//Play From sec*1000 ms
    return mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY | MCI_FROM| MCI_WAIT, (DWORD)&play);
}

int main()
{
    //open device
    MCIDEVICEID wDeviceID = MCIOpen("test.wav");  //Save DeviceID
    DWORD opReturn;
    if (wDeviceID != -1)
    {
        //MCI_SET_PARMS mciSet;
        //mciSet.dwTimeFormat = MCI_FORMAT_MILLISECONDS;//set time format to milliseconds
        //opReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID)&mciSet);

        ////set the position at 30s.
        //opReturn = MCISeek(wDeviceID, 30);

        ////play
        //opReturn = MCIPlay(wDeviceID);

        opReturn = MCIPlayFrom(wDeviceID,30);

    }
    return opReturn;
}

Note that if you want to get the sound, MCI_WAIT flag is required when using MCI_PLAY.

EDIT: You can't hear the sound because you have little running time after start to play it. It will initiate the play and close it immediately(It can be verified by adding Sleep() after play starts). See the related issue. If you want play no wait, you need to handle the MCI_NOTIFY, set the callback window handle, and handle the MM_MCINOTIFY when the play has finish.

#include <windows.h>
#pragma comment(lib, "winmm.lib")

HWND hwnd;
MCIDEVICEID MCIOpen(LPCTSTR strPath)
{
    MCI_OPEN_PARMS mciOP;
    DWORD opReturn;
    mciOP.lpstrDeviceType = NULL;
    mciOP.lpstrElementName = strPath;  //Set the .wav file name to open
    opReturn = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_ELEMENT, (DWORD)(LPVOID)&mciOP);
    if (!opReturn)
        return mciOP.wDeviceID;
    return -1;
}
DWORD MCISeek(MCIDEVICEID wDeviceID,int sec)
{
    MCI_SEEK_PARMS SeekParms;
    SeekParms.dwTo = (sec) * 1000;
    return mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO, (DWORD)(LPVOID)&SeekParms);
}
DWORD MCIPlay(MCIDEVICEID wDeviceID)
{
    MCI_PLAY_PARMS mciPP;
    mciPP.dwCallback = (DWORD_PTR)hwnd;
    return mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)&mciPP);
}
DWORD MCIPlayFrom(MCIDEVICEID wDeviceID,int sec)
{
    MCI_PLAY_PARMS play;
    play.dwFrom = sec*1000;//Play From sec*1000 ms
    return mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY | MCI_FROM, (DWORD)&play);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case MM_MCINOTIFY:
    {
        if (MCI_NOTIFY_SUCCESSFUL == wParam) //MCI_NOTIFY_SUCCESSFUL means that the song has been played successfully. 
        {
            //To Do

        }
    }
    break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
int main()
{
    static const char* class_name = "DUMMY_CLASS";
    WNDCLASSEX wx = {};
    wx.cbSize = sizeof(WNDCLASSEX);
    wx.lpfnWndProc = WndProc;        // function which will handle messages
    wx.hInstance = GetModuleHandleA(NULL);
    wx.lpszClassName = class_name;
    if (RegisterClassEx(&wx)) {
        hwnd = CreateWindowEx(0, class_name, "dummy_name", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
    }

    //open device
    MCIDEVICEID wDeviceID = MCIOpen("test.wav");  //Save DeviceID
    DWORD opReturn;
    if (wDeviceID != -1)
    {
        //MCI_SET_PARMS mciSet;
        //mciSet.dwTimeFormat = MCI_FORMAT_MILLISECONDS;//set time format to milliseconds
        //opReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID)&mciSet);

        ////set the position at 30s.
        //opReturn = MCISeek(wDeviceID, 30);

        ////play
        //opReturn = MCIPlay(wDeviceID);

        opReturn = MCIPlayFrom(wDeviceID,30);
    }
    HACCEL hAccelTable = LoadAccelerators(wx.hInstance, class_name);
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return 0;
}
Drake Wu
  • 6,927
  • 1
  • 7
  • 30
0

There are lots of libraries that can play sound, but if you want to continue using PlaySound(), here's an idea:

So while it may not be the easiest way, you could open your original sound, skip the first N seconds of it (easy enough with .wav files, since they are constant-bitrate and do not have keyframes), store that as a new resource, and finally play it.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436