// ObjectWindows - (C) Copyright 1992 by Borland International


/* This example demonstrates the use of MCI APIs in Windows 3.1 in an OWL
 *  application:
 *
 * You must have a sound board/Speaker and its device driver installed under
 * Windows 3.1 and be certain that it works -- Use the sound applet in Windows
 * Control Pannel to generate a sound.  You may copy one of the .WAV files from
 * the WINDOWS subdirectory in your system to this example's subdirectory.
 *
 * Run the .EXE.  Choose File:Open and select a .WAV file.  Choose Control:Play
 * to play the waveform.  The Control menu lets you stop/play/pause and resume.
 * The scrollbar allows random access through the waveform while it is playing.
 * This example demonstrates the use of the MCI API and a callback via
 * WM_MCINOTIFY.
 */

#include <string.h>
#include <stdio.h>
#include <owl.h>
#include <filedial.h>
#include <button.h>
#include <scrollba.h>
#include <math.h>
#include <mmsystem.h>
#include "soundply.h"

#define ID_SCROLL 	150			// Scroll bar
#define TIMER_ID 	264			// Unique timer ID.

// The waveform device opened.
WORD	wDeviceID = 0;
BOOL	flushNotify = FALSE;

class TSoundBar : public TScrollBar
{
private:
	int	waveRatio;
	LONG	waveLength;
	char	elementName[255];

	void RePosAndPlay (LONG newPos);
public:
	TSoundBar(PTWindowsObject AParent, int AnId, int X, int Y, int W,
             int H, BOOL IsHScrollBar, PTModule AModule = NULL) :
        TScrollBar(AParent, AnId, X, Y, W, H, IsHScrollBar, AModule) {};

	void ScrollSetInfo (int wRatio, LONG wLength);
   void ScrollSetName (char *eName);

	virtual void SBLineUp(RTMessage Msg) = [NF_FIRST + SB_LINEUP];
	virtual void SBLineDown(RTMessage Msg) = [NF_FIRST + SB_LINEDOWN];
	virtual void SBPageUp(RTMessage Msg) = [NF_FIRST + SB_PAGEUP];
	virtual void SBPageDown(RTMessage Msg) = [NF_FIRST + SB_PAGEDOWN];
	virtual void SBThumbPosition(RTMessage Msg) = [NF_FIRST + SB_THUMBPOSITION];
	virtual void SBTop(RTMessage Msg) = [NF_FIRST + SB_TOP];
	virtual void SBBottom(RTMessage Msg) = [NF_FIRST + SB_BOTTOM];
};


// Define a class derived from TApplication
class TSoundApp :public TApplication
{
public:
	TSoundApp(LPSTR AName, HINSTANCE hInstance, HINSTANCE hPrevInstance,
             LPSTR lpCmdLine, int nCmdShow)
		: TApplication(AName, hInstance, hPrevInstance, lpCmdLine, nCmdShow) {};
	virtual void InitMainWindow();
};

_CLASSDEF(TSoundWindow)
class TSoundWindow : public TWindow
{
private:
	char		elementName[255];
   int		running;
   int		pause;
   UINT		timeGoing;
	int		waveRatio;
   LONG		waveLength;
   TSoundBar	*tScroll;
	MCI_GENERIC_PARMS	MciGenParm;
	MCI_OPEN_PARMS		MciOpenParm;
	MCI_PLAY_PARMS		MciPlayParm;
   MCI_STATUS_PARMS	MciStatusParm;
   MCI_SET_PARMS		MciSetParm;

	void GetDeviceInfo ();
   void StopWave ();
	void StopMCI ();
public:
	TSoundWindow (PTWindowsObject Parent, LPSTR Title);
   ~TSoundWindow ();

	void UpdateSoundWindow ();

	virtual void GetWindowClass (WNDCLASS &WndClass);
   virtual void SetupWindow();
	virtual LPSTR GetClassName ();
	virtual void MciNotify (RTMessage Msg) = [WM_FIRST + MM_MCINOTIFY];
	virtual void Paint (HDC PaintDC, PAINTSTRUCT _FAR &PaintInfo);
   virtual void FileOpen (RTMessage Msg) = [CM_FIRST + SM_OPEN];
   virtual void FileExit (RTMessage Msg) = [CM_FIRST + SM_EXIT];
   virtual void PlayWave (RTMessage Msg) = [CM_FIRST + SM_PLAY];
   virtual void PauseWave (RTMessage Msg) = [CM_FIRST + SM_PAUSE];
   virtual void HelpAbout (RTMessage Msg) = [CM_FIRST + SM_ABOUT];
   virtual void IdleStuff (RTMessage Msg) = [WM_FIRST + WM_TIMER];
};

void TSoundBar::RePosAndPlay (LONG newPos)
{
MCI_PLAY_PARMS MciPlayParm;
MCI_SEEK_PARMS MciSeekParm;
MCI_SET_PARMS MciSetParm;
MCI_OPEN_PARMS MciOpenParm;
MCI_GENERIC_PARMS MciGenParm;

   // Only allow SEEK if playing.
   if (!wDeviceID)
		return;

	// Close the currently playing wave.
   flushNotify = TRUE;
	MciGenParm.dwCallback = 0L;
	mciSendCommand (wDeviceID, MCI_STOP, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&MciGenParm);
	mciSendCommand (wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&MciGenParm);

   // Open the wave again and seek to new position.
	MciOpenParm.dwCallback = 0L;
	MciOpenParm.wDeviceID = wDeviceID;
	MciOpenParm.wReserved0 = 0;
	MciOpenParm.lpstrDeviceType = NULL;
	MciOpenParm.lpstrElementName = elementName;
	MciOpenParm.lpstrAlias = NULL;

	if (mciSendCommand (wDeviceID, MCI_OPEN, MCI_WAIT| MCI_OPEN_ELEMENT,
      (DWORD)(LPMCI_OPEN_PARMS)&MciOpenParm)) {
		   MessageBox (HWindow, "Open Error", "Sound Play", MB_OK);
		   return;
      }
   wDeviceID = MciOpenParm.wDeviceID;

   // Our time scale is in SAMPLES.
	MciSetParm.dwTimeFormat = MCI_FORMAT_SAMPLES;
   if (mciSendCommand (wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT,
      (DWORD)(LPMCI_SET_PARMS)&MciSetParm)) {
	   MessageBox (HWindow, "Set Time Error", "Sound Play", MB_OK);
	   return;
   }

   // Compute new position, remember the scrollbar range has been scaled based on waveRatio.
   MciSeekParm.dwCallback = NULL;
   MciSeekParm.dwTo = ((newPos * waveRatio) > waveLength) ? waveLength : (newPos * waveRatio);
   if (mciSendCommand (wDeviceID, MCI_SEEK, MCI_TO,
      (DWORD)(LPMCI_SEEK_PARMS)&MciSeekParm)) {
	   MessageBox (HWindow, "Seek Error", "Sound Play", MB_OK);
	   return;
   }

	MciPlayParm.dwCallback = (unsigned long)HWindow;
	MciPlayParm.dwFrom = 0;
	MciPlayParm.dwTo = 0;
	if (mciSendCommand (wDeviceID, MCI_PLAY, MCI_NOTIFY,
      (DWORD) (LPMCI_PLAY_PARMS)&MciPlayParm)) {
		MessageBox (HWindow, "Play Error", "Sound Play", MB_OK);
		return;
   }
}

void TSoundBar::ScrollSetInfo (int wRatio, LONG wLength)
{
	waveRatio = wRatio;
   waveLength = wLength;
}

void TSoundBar::ScrollSetName (char *eName)
{
	strcpy (elementName, eName);
}

void TSoundBar::SBLineUp(RTMessage msg)
{
	TScrollBar::SBLineUp (msg);
	RePosAndPlay (GetPosition ());
}

void TSoundBar::SBLineDown(RTMessage msg)
{
	TScrollBar::SBLineDown (msg);
	RePosAndPlay (GetPosition ());
}

void TSoundBar::SBPageUp(RTMessage msg)
{
	TScrollBar::SBPageUp (msg);
	RePosAndPlay (GetPosition ());
}

void TSoundBar::SBPageDown(RTMessage msg)
{
	TScrollBar::SBPageDown (msg);
	RePosAndPlay (GetPosition ());
}

void TSoundBar::SBThumbPosition(RTMessage msg)
{
	TScrollBar::SBThumbPosition (msg);
	RePosAndPlay (GetPosition ());
}

void TSoundBar::SBTop(RTMessage msg)
{
	TScrollBar::SBTop (msg);
	RePosAndPlay (GetPosition ());
}

void TSoundBar::SBBottom(RTMessage msg)
{
 	TScrollBar::SBBottom (msg);
	RePosAndPlay (GetPosition ());
}


// Construct the THelloApp's MainWindow data member
void TSoundApp::InitMainWindow()
{
	MainWindow = new TSoundWindow(NULL, "OWL SoundPlay Demo ... using MCI");
}


int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nCmdShow)
{
	TSoundApp SoundApp ("SoundApp", hInstance, hPrevInstance, lpCmdLine,
                        nCmdShow);
	SoundApp.HAccTable = LoadAccelerators (hInstance, "ACCELERATORS_1");
	SoundApp.Run();
	return SoundApp.Status;
}

// Construct the TSoundWindow
TSoundWindow::TSoundWindow (PTWindowsObject Parent, LPSTR Title) :
    TWindow (Parent, Title)
{
   AssignMenu (ID_MENU);
	Attr.X = 50;
	Attr.Y = 100;
	Attr.W = 400;
	Attr.H = 150;

   running = 0;
   pause = 0;
   waveLength = waveRatio = 0;
   elementName[0] = '\0';

   tScroll = new TSoundBar (this, ID_SCROLL, 50, 67, 300, 0, TRUE);
}

void TSoundWindow::SetupWindow()
{
   TWindow::SetupWindow();
   tScroll->SetRange (0, 0);
}

TSoundWindow::~TSoundWindow ()
{
	StopMCI ();
}

// Paint member function
void TSoundWindow::Paint (HDC PaintDC, PAINTSTRUCT _FAR &)
{
	char buffer[100];

   // File name.
   if (lstrlen (elementName))
		TextOut (PaintDC, 5, 5, elementName, lstrlen(elementName));
   else
     	TextOut (PaintDC, 5, 5, "<No WAVEFORM file loaded>", 25);

   TextOut (PaintDC, 160, 35, "Samples:", 8);

   TextOut (PaintDC, 50, 48, "0", 1);		// Beginning value.

   // Ending number of samples.
	if (waveLength)
		sprintf (buffer, "%d", waveLength * waveRatio);
   else
      strcpy (buffer, "Unknown");
	TextOut (PaintDC, 315, 48, buffer, lstrlen (buffer));
}

// GetClassWindow member
void TSoundWindow::GetWindowClass (WNDCLASS &WndClass)
{
	TWindow::GetWindowClass(WndClass);
}

// GetClassName member
LPSTR TSoundWindow::GetClassName ()
{
	return "SoundPlay";
}

void TSoundWindow::GetDeviceInfo ()
{
WAVEOUTCAPS	wOutCaps;

	if (!waveOutGetDevCaps (wDeviceID, (LPWAVEOUTCAPS)&wOutCaps,
      sizeof (wOutCaps))) {
		MessageBox (HWindow, "GetDevCaps Error", "Sound Play", MB_OK);
		return;
	}
}

// Play the wave...
void TSoundWindow::PlayWave (RTMessage)
{
	if (!running) {
		// MCI APIs to open a device and play a .WAV file, using
      // notification to close
		MciOpenParm.dwCallback = 0L;
		MciOpenParm.wDeviceID = 0;
		MciOpenParm.wReserved0 = 0;
		MciOpenParm.lpstrDeviceType = NULL;
		MciOpenParm.lpstrElementName = elementName;
		MciOpenParm.lpstrAlias = NULL;

		if (mciSendCommand (0, MCI_OPEN, (DWORD) (MCI_WAIT | MCI_OPEN_ELEMENT),
         (DWORD)(LPMCI_OPEN_PARMS)&MciOpenParm)) {
			MessageBox (HWindow,
          "Open Error - a waveForm output device is necessary to use this demo.",
          "Sound Play", MB_OK);
			return;
		}
   	wDeviceID = MciOpenParm.wDeviceID;

      // The time format in this demo is in Samples.
      MciSetParm.dwCallback = 0L;
		MciSetParm.dwTimeFormat = MCI_FORMAT_SAMPLES;
      if (mciSendCommand (wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT,
         (DWORD)(LPMCI_SET_PARMS)&MciSetParm)) {
			MessageBox (HWindow, "SetTime Error", "Sound Play", MB_OK);
			return;
      }

		MciPlayParm.dwCallback = (unsigned long)HWindow;
		MciPlayParm.dwFrom = 0;
		MciPlayParm.dwTo = 0;
		mciSendCommand (wDeviceID, MCI_PLAY, MCI_NOTIFY,
         (DWORD) (LPMCI_PLAY_PARMS)&MciPlayParm);

      // Modify the menu to toggle PLAY to STOP, and enable PAUSE.
		HMENU hMenu = GetMenu (HWindow);
		ModifyMenu (hMenu, SM_PLAY, MF_STRING, SM_PLAY, "&Stop");
		EnableMenuItem (hMenu, SM_PAUSE, MF_ENABLED);
      // Make sure the Play/Stop toggle menu knows we're running.
		running = TRUE;

      // Start a timer to show our progress through the waveform file.
      timeGoing = SetTimer (HWindow, TIMER_ID, 500, NULL);

      // Give enough information to the scrollbar to monitor the
      // progress and issue a re-MCI_OPEN.
		tScroll->ScrollSetName (elementName);
   } else
       StopWave ();
}

void TSoundWindow::PauseWave (RTMessage)
{
	if (!pause) {
      // Pause the playing.
		MciGenParm.dwCallback = 0L;
		mciSendCommand (wDeviceID, MCI_PAUSE, MCI_WAIT,
          (DWORD)(LPMCI_GENERIC_PARMS)&MciGenParm);

      // Toggle Pause menu to Resume.
		HMENU hMenu = GetMenu (HWindow);
		ModifyMenu (hMenu, SM_PAUSE, MF_STRING, SM_PAUSE, "&Resume\tCtrl+R");
      pause = TRUE;
	} else {
      // Resume the playing.
		MciGenParm.dwCallback = 0L;
		mciSendCommand (wDeviceID, MCI_RESUME, MCI_WAIT,
         (DWORD)(LPMCI_GENERIC_PARMS)&MciGenParm);

      // Toggle Resume menu to Pause.
		HMENU hMenu = GetMenu (HWindow);
		ModifyMenu (hMenu, SM_PAUSE, MF_STRING, SM_PAUSE, "P&ause\tCtrl+P");
      pause = FALSE;
   }
}

void TSoundWindow::StopMCI ()
{
	if (timeGoing)			// Timer running, then kill it now.
		timeGoing = !KillTimer (HWindow, TIMER_ID);

   // Stop playing the waveform file and close the waveform device.
	MciGenParm.dwCallback = 0L;
	mciSendCommand (wDeviceID, MCI_STOP, MCI_WAIT,
      (DWORD)(LPMCI_GENERIC_PARMS)&MciGenParm);
	mciSendCommand (wDeviceID, MCI_CLOSE, MCI_WAIT,
      (DWORD)(LPMCI_GENERIC_PARMS)&MciGenParm);

	running = FALSE;
   wDeviceID = 0;
}

void TSoundWindow::StopWave ()
{
   if (wDeviceID) {
		StopMCI ();

      // Reset the menus to Play menu and gray the Pause menu.
	   HMENU hMenu = GetMenu (HWindow);
	   ModifyMenu (hMenu, SM_PLAY, MF_STRING, SM_PLAY, "&Play\tCtrl+P");
	   ModifyMenu (hMenu, SM_PAUSE, MF_STRING | MF_GRAYED, SM_PAUSE, "P&ause\tCtrl+A");
   }
}

void TSoundWindow::FileOpen (RTMessage)
{
char	FileName[MAXPATH];

	if ( GetApplication()->ExecDialog(new TFileDialog(this, SD_FILEOPEN, strcpy(FileName, "*.WAV"))) == IDOK )
        	if (CanClose()) {
            // Remember the waveform file to open.
	        	strcpy (elementName, FileName);

            // Turn the Play menu on.
            HMENU hMenu = GetMenu (HWindow);
            EnableMenuItem (hMenu, SM_PLAY, MF_ENABLED);

            waveLength = 0;
            waveRatio = 0;
        		tScroll->SetPosition (0);
            UpdateSoundWindow ();
         }
}

void TSoundWindow::FileExit (RTMessage)
{
	CloseWindow ();
}

// Response function MM_MCINOTIFY message when MCI_PLAY is complete.
void TSoundWindow::MciNotify (RTMessage)
{
int	loVal, hiVal;

   if (!flushNotify) { 			// Internal STOP/CLOSE, from thumb re-pos?
		StopWave ();

		// Make sure the thumb is at the end. There could be some WM_TIMER messages on the queue when
		// we kill it, thereby flushing WM_TIMER's from the message queue.
		tScroll->GetRange (loVal, hiVal);
		tScroll->SetPosition (hiVal);
	}
   flushNotify = FALSE; 			// Yes, so ignore the close.
}


void TSoundWindow::UpdateSoundWindow ()
{
RECT	r;

	// First time it's different update the scroll bar numbers.
	GetClientRect (HWindow, &r);
	InvalidateRect (HWindow, &r, TRUE);
}


void TSoundWindow::HelpAbout (RTMessage)
{
	MessageBox (HWindow, "SoundPly loads and plays waveform sound \
files (*.WAV).  The features of this demo are Play/Stop, Pause/Resume, \
and random seeks through the file via the scollbar (while the sound is \
playing).  NOTE: SoundPly will only play sounds if the machine contains \
a waveForm Multimedia device, e.g. SoundBlaster, etc.).",
   "About Sound Play", MB_OK);
}

void TSoundWindow::IdleStuff (RTMessage)
{
	if (!flushNotify) {			// Internal STOP/CLOSE, from thumb re-pos?
		MciStatusParm.dwCallback = 0L;	// No, normal close.
		MciStatusParm.dwItem = MCI_STATUS_LENGTH;
		mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)(LPMCI_STATUS_PARMS)&MciStatusParm);
		if (waveLength != MciStatusParm.dwReturn) {
                        // First time it's different update the scroll bar numbers.
                        UpdateSoundWindow ();
                	waveLength = MciStatusParm.dwReturn;
		}

        	// Compute the length and ratio and update tScroll info.
		waveRatio = (int)ceill((waveLength / 32000) + .5);
		tScroll->ScrollSetInfo (waveRatio, waveLength);
		tScroll->SetRange (0, (int)(waveLength / waveRatio));

        	// Update the current position.
 		MciStatusParm.dwCallback = 0L;
     	MciStatusParm.dwItem = MCI_STATUS_POSITION;
     	mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)(LPMCI_STATUS_PARMS)&MciStatusParm);

     	tScroll->SetPosition ((int)(MciStatusParm.dwReturn / waveRatio));
   }

   flushNotify = FALSE; 			// Yes, ignore this close.
}

