- BORLAND/: Borland C++ 4.52 (chosen over 4.5 by byte-match: CODE/RP/CW32.LIB
is identical to 4.52's install lib). BCC32/TLINK32/TLIB/MAKE run natively on
Win11; CODE/BT/OPT.MAK is the shipped BTL4OPT.EXE's exact flag recipe
(extender = Borland PowerPack DPMI32, not Phar Lap TNT).
- restoration/source410/: the literal 1995-form reconstruction of the missing
BT game source (never mixed into CODE/). Round 1-3 state:
* 6 of 10 surviving original TUs COMPILE CLEAN under the period toolchain
(BTMSSN, BTCNSL, BTSCNRL, BTTEAM, BTL4MODE, BTL4ARND) - first builds
since 1996.
* BT_L4/BTL4APP.CPP pilot reconstruction: 12/12 functions, Fail() lands on
its binary-recorded line 400 exactly.
* BT/BTCNSL.HPP: console wire IDs recovered from the binary's ctors
(Killed=9, Damaged=10, ScoreUpdate=13, DeathWithoutHonor=15 [T1];
TeamScore=12 flagged [T4]).
* MUNGA/: 8 engine-header backfills back-dated from the BT412 WinTesla tree
(VDATA numbering decomp-verified; AUDREND's OpenAL-era virtual removed -
the period compiler is the drift detector).
* Tooling: backdate.py (WinTesla->1995 header transform), compile410.sh
(per-TU verification sweep under authentic OPT.MAK flags).
* README: corrected roadmap - MECH.HPP is the capstone grown with the mech
TU reconstructions; BTREG.CPP green = the header-family milestone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
561 lines
14 KiB
C++
561 lines
14 KiB
C++
//----------------------------------------------------------------------------
|
|
// ObjectWindows - (C) Copyright 1991, 1993 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 <owl\owlpch.h>
|
|
#include <owl\applicat.h>
|
|
#include <owl\opensave.h>
|
|
#include <owl\framewin.h>
|
|
#include <owl\dc.h>
|
|
#include <owl\menu.h>
|
|
#include <owl\button.h>
|
|
#include <owl\slider.h>
|
|
#include <owl\slider.rh>
|
|
#include <math.h>
|
|
#include <mmsystem.h>
|
|
#include "mcisound.h"
|
|
|
|
#define ID_SCROLL 150 // Scroll bar
|
|
#define TIMER_ID 264 // Unique timer ID.
|
|
#define MCI_PARM2(p) ((long)(void far*)&p)
|
|
|
|
UINT DeviceId = 0; // The global waveform device opened handle
|
|
BOOL FlushNotify = FALSE;
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
class TSoundBar : public THSlider {
|
|
public:
|
|
TSoundBar(TWindow* parent, int id, int x, int y, int w, int h,
|
|
TModule* module = 0)
|
|
: THSlider(parent, id, x, y, w, h, IDB_HSLIDERTHUMB, module) {}
|
|
|
|
void SetInfo(int ratio, long length);
|
|
void SetName(char* name);
|
|
|
|
//
|
|
// Override TScrollBars virtual functions
|
|
//
|
|
void SBLineUp();
|
|
void SBLineDown();
|
|
void SBPageUp();
|
|
void SBPageDown();
|
|
void SBThumbPosition(int thumbPos);
|
|
void SBTop();
|
|
void SBBottom();
|
|
|
|
private:
|
|
int WaveRatio;
|
|
long WaveLength;
|
|
char ElementName[255];
|
|
|
|
void ReposAndPlay(long newPos);
|
|
};
|
|
|
|
|
|
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 (!DeviceId)
|
|
return;
|
|
|
|
// Close the currently playing wave.
|
|
//
|
|
FlushNotify = TRUE;
|
|
MciGenParm.dwCallback = 0;
|
|
mciSendCommand(DeviceId, MCI_STOP, MCI_WAIT, MCI_PARM2(MciGenParm));
|
|
mciSendCommand(DeviceId, MCI_CLOSE, MCI_WAIT, MCI_PARM2(MciGenParm));
|
|
|
|
// Open the wave again and seek to new position.
|
|
//
|
|
MciOpenParm.dwCallback = 0;
|
|
MciOpenParm.wDeviceID = DeviceId;
|
|
#if !defined(__WIN32__)
|
|
MciOpenParm.wReserved0 = 0;
|
|
#endif
|
|
MciOpenParm.lpstrDeviceType = 0;
|
|
MciOpenParm.lpstrElementName = ElementName;
|
|
MciOpenParm.lpstrAlias = 0;
|
|
|
|
if (mciSendCommand(DeviceId, MCI_OPEN, MCI_WAIT| MCI_OPEN_ELEMENT, MCI_PARM2(MciOpenParm))) {
|
|
MessageBox("Open Error", "Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
DeviceId = MciOpenParm.wDeviceID;
|
|
|
|
// Our time scale is in SAMPLES.
|
|
//
|
|
MciSetParm.dwTimeFormat = MCI_FORMAT_SAMPLES;
|
|
if (mciSendCommand(DeviceId, MCI_SET, MCI_SET_TIME_FORMAT, MCI_PARM2(MciSetParm))) {
|
|
MessageBox("Set Time Error", "Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
|
|
// Compute new position, remember the scrollbar range has been scaled based
|
|
// on WaveRatio.
|
|
//
|
|
MciSeekParm.dwCallback = 0;
|
|
MciSeekParm.dwTo = (newPos*WaveRatio > WaveLength) ? WaveLength :
|
|
newPos*WaveRatio;
|
|
if (mciSendCommand(DeviceId, MCI_SEEK, MCI_TO, MCI_PARM2(MciSeekParm))) {
|
|
MessageBox("Seek Error", "Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
|
|
MciPlayParm.dwCallback = (long)HWindow;
|
|
MciPlayParm.dwFrom = 0;
|
|
MciPlayParm.dwTo = 0;
|
|
if (mciSendCommand(DeviceId, MCI_PLAY, MCI_NOTIFY, MCI_PARM2(MciPlayParm))) {
|
|
MessageBox("Play Error", "Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
TSoundBar::SetInfo(int ratio, long length)
|
|
{
|
|
WaveRatio = ratio;
|
|
WaveLength = length;
|
|
}
|
|
|
|
void
|
|
TSoundBar::SetName(char* name)
|
|
{
|
|
strcpy(ElementName, name);
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBLineUp()
|
|
{
|
|
THSlider::SBLineUp();
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBLineDown()
|
|
{
|
|
THSlider::SBLineDown();
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBPageUp()
|
|
{
|
|
THSlider::SBPageUp();
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBPageDown()
|
|
{
|
|
THSlider::SBPageDown();
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBThumbPosition(int thumbPos)
|
|
{
|
|
THSlider::SBThumbPosition(thumbPos);
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBTop()
|
|
{
|
|
THSlider::SBTop();
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
void
|
|
TSoundBar::SBBottom()
|
|
{
|
|
THSlider::SBBottom();
|
|
ReposAndPlay(GetPosition());
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
class TSoundWindow : public TFrameWindow {
|
|
public:
|
|
TSoundWindow(TWindow* parent, const char far* title);
|
|
~TSoundWindow();
|
|
|
|
void SetupWindow();
|
|
|
|
LRESULT MciNotify(WPARAM, LPARAM);
|
|
void EvPaint();
|
|
void CmFileOpen();
|
|
void CmFileExit();
|
|
void CmPlayWave();
|
|
void CmStopWave();
|
|
void CmHelpAbout();
|
|
void EvTimer(UINT id);
|
|
|
|
private:
|
|
char ElementName[255];
|
|
int Running;
|
|
int Pause;
|
|
UINT TimeGoing;
|
|
int WaveRatio;
|
|
long WaveLength;
|
|
TSoundBar* SoundBar;
|
|
|
|
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();
|
|
|
|
DECLARE_RESPONSE_TABLE(TSoundWindow);
|
|
};
|
|
|
|
DEFINE_RESPONSE_TABLE1(TSoundWindow, TFrameWindow)
|
|
EV_MESSAGE(MM_MCINOTIFY, MciNotify),
|
|
EV_WM_PAINT,
|
|
EV_COMMAND(SM_OPEN, CmFileOpen),
|
|
EV_COMMAND(SM_EXIT, CmFileExit),
|
|
EV_COMMAND(SM_PLAY, CmPlayWave),
|
|
EV_COMMAND(SM_STOP, CmStopWave),
|
|
EV_COMMAND(SM_ABOUT, CmHelpAbout),
|
|
EV_WM_TIMER,
|
|
END_RESPONSE_TABLE;
|
|
|
|
|
|
TSoundWindow::TSoundWindow(TWindow* parent, const char far* title)
|
|
: TFrameWindow(parent, title)
|
|
{
|
|
AssignMenu(ID_MENU);
|
|
Attr.AccelTable = IDA_SOUNDPLY;
|
|
Attr.X = 50;
|
|
Attr.Y = 100;
|
|
Attr.W = 400;
|
|
Attr.H = 220;
|
|
|
|
Running = 0;
|
|
Pause = 0;
|
|
WaveLength = WaveRatio = 0;
|
|
ElementName[0] = 0;
|
|
|
|
SoundBar = new TSoundBar(this, ID_SCROLL, 50, 67, 300, 40);
|
|
|
|
new TButton(this, SM_PLAY, "&Play", 50, 120, 50, 20, TRUE);
|
|
new TButton(this, SM_STOP, "&Stop", 120, 120, 50, 20, TRUE);
|
|
}
|
|
|
|
void
|
|
TSoundWindow::SetupWindow()
|
|
{
|
|
TFrameWindow::SetupWindow();
|
|
SoundBar->SetRange(0, 0);
|
|
SoundBar->SetRuler(0);
|
|
}
|
|
|
|
TSoundWindow::~TSoundWindow()
|
|
{
|
|
StopMCI();
|
|
}
|
|
|
|
//
|
|
// EvPaint member function responds to WM_PAINT messages
|
|
//
|
|
void
|
|
TSoundWindow::EvPaint()
|
|
{
|
|
TPaintDC paintDC(HWindow);
|
|
|
|
// File name.
|
|
//
|
|
if (strlen(ElementName))
|
|
paintDC.TextOut(5, 5, ElementName, strlen(ElementName));
|
|
else
|
|
paintDC.TextOut(5, 5, "<No WAVEFORM file loaded>", 25);
|
|
|
|
paintDC.TextOut(160, 35, "Samples:", 8);
|
|
paintDC.TextOut(50, 48, "0", 1); // Beginning value.
|
|
|
|
// Ending number of samples.
|
|
//
|
|
char buffer[10];
|
|
if (WaveLength)
|
|
wsprintf(buffer, "%ld", WaveLength);
|
|
else
|
|
strcpy(buffer, "Unknown");
|
|
paintDC.TextOut(315, 48, buffer, strlen(buffer));
|
|
}
|
|
|
|
void
|
|
TSoundWindow::GetDeviceInfo()
|
|
{
|
|
WAVEOUTCAPS waveOutCaps;
|
|
|
|
if (!waveOutGetDevCaps(DeviceId, &waveOutCaps, sizeof(waveOutCaps))) {
|
|
MessageBox("GetDevCaps Error", "Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Play the wave...
|
|
//
|
|
void
|
|
TSoundWindow::CmPlayWave()
|
|
{
|
|
if (!Running) {
|
|
//
|
|
// MCI APIs to open a device and play a .WAV file, using
|
|
// notification to close
|
|
//
|
|
memset(&MciOpenParm, 0, sizeof MciOpenParm);
|
|
|
|
MciOpenParm.lpstrElementName = ElementName;
|
|
|
|
if (mciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_ELEMENT,
|
|
MCI_PARM2(MciOpenParm))) {
|
|
MessageBox(
|
|
"Open Error - a waveForm output device is necessary to use this demo.",
|
|
"Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
DeviceId = MciOpenParm.wDeviceID;
|
|
|
|
// The time format in this demo is in Samples.
|
|
//
|
|
MciSetParm.dwCallback = 0;
|
|
MciSetParm.dwTimeFormat = MCI_FORMAT_SAMPLES;
|
|
if (mciSendCommand(DeviceId, MCI_SET, MCI_SET_TIME_FORMAT, MCI_PARM2(MciSetParm))) {
|
|
MessageBox("SetTime Error", "Sound Play", MB_OK);
|
|
return;
|
|
}
|
|
|
|
MciPlayParm.dwCallback = (long)HWindow;
|
|
MciPlayParm.dwFrom = 0;
|
|
MciPlayParm.dwTo = 0;
|
|
mciSendCommand(DeviceId, MCI_PLAY, MCI_NOTIFY, MCI_PARM2(MciPlayParm));
|
|
|
|
// Modify the menu to toggle PLAY to STOP, and enable PAUSE.
|
|
//
|
|
TMenu menu(HWindow);
|
|
menu.ModifyMenu(SM_PLAY, MF_STRING, SM_PLAY, "P&ause\tCtrl+A");
|
|
menu.EnableMenuItem(SM_STOP, 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(TIMER_ID, 200, 0);
|
|
|
|
// Give enough information to the scrollbar to monitor the
|
|
// progress and issue a re-MCI_OPEN.
|
|
//
|
|
SoundBar->SetName(ElementName);
|
|
|
|
} else {
|
|
if (!Pause) {
|
|
// Pause the playing.
|
|
//
|
|
MciGenParm.dwCallback = 0;
|
|
mciSendCommand(DeviceId, MCI_PAUSE, MCI_WAIT, MCI_PARM2(MciGenParm));
|
|
|
|
// Toggle Pause menu to Resume.
|
|
//
|
|
TMenu menu(HWindow);
|
|
menu.ModifyMenu(SM_PLAY, MF_STRING, SM_PLAY, "&Play\tCtrl+P");
|
|
Pause = TRUE;
|
|
|
|
} else {
|
|
// Resume the playing.
|
|
//
|
|
MciGenParm.dwCallback = 0;
|
|
mciSendCommand(DeviceId, MCI_RESUME, MCI_WAIT, MCI_PARM2(MciGenParm));
|
|
|
|
// Toggle Resume menu to Pause.
|
|
//
|
|
TMenu menu(HWindow);
|
|
menu.ModifyMenu(SM_PLAY, MF_STRING, SM_PLAY, "P&ause\tCtrl+A");
|
|
Pause = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TSoundWindow::CmStopWave()
|
|
{
|
|
StopWave();
|
|
}
|
|
|
|
void
|
|
TSoundWindow::StopMCI()
|
|
{
|
|
if (TimeGoing) // if Timer is Running, then kill it now.
|
|
TimeGoing = !KillTimer(TIMER_ID);
|
|
|
|
// Stop playing the waveform file and close the waveform device.
|
|
//
|
|
MciGenParm.dwCallback = 0;
|
|
mciSendCommand(DeviceId, MCI_STOP, MCI_WAIT, MCI_PARM2(MciGenParm));
|
|
mciSendCommand(DeviceId, MCI_CLOSE, MCI_WAIT,MCI_PARM2(MciGenParm));
|
|
|
|
Running = FALSE;
|
|
DeviceId = 0;
|
|
}
|
|
|
|
//
|
|
// Reset the menus to Play menu and gray the Pause menu.
|
|
//
|
|
void
|
|
TSoundWindow::StopWave()
|
|
{
|
|
if (DeviceId) {
|
|
StopMCI();
|
|
TMenu menu(HWindow);
|
|
menu.ModifyMenu(SM_PLAY, MF_STRING, SM_PLAY, "&Play\tCtrl+P");
|
|
}
|
|
}
|
|
|
|
void
|
|
TSoundWindow::CmFileOpen()
|
|
{
|
|
static TOpenSaveDialog::TData data (
|
|
OFN_HIDEREADONLY|OFN_FILEMUSTEXIST,
|
|
"Wave Files (*.WAV)|*.wav|",
|
|
0,
|
|
0,
|
|
"WAV"
|
|
);
|
|
if (TFileOpenDialog(this, data).Execute() == IDOK) {
|
|
if (CanClose()) {
|
|
strcpy(ElementName, data.FileName); // Remember the wave file to open.
|
|
|
|
// Turn the Play menu on.
|
|
TMenu(HWindow).EnableMenuItem(SM_PLAY, MF_ENABLED);
|
|
|
|
WaveLength = 0;
|
|
WaveRatio = 0;
|
|
SoundBar->SetPosition(0);
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TSoundWindow::CmFileExit()
|
|
{
|
|
CloseWindow();
|
|
}
|
|
|
|
//
|
|
// Response function MM_MCINOTIFY message when MCI_PLAY is complete.
|
|
//
|
|
LRESULT
|
|
TSoundWindow::MciNotify(WPARAM, LPARAM)
|
|
{
|
|
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.
|
|
//
|
|
int loVal, hiVal;
|
|
SoundBar->GetRange(loVal, hiVal);
|
|
SoundBar->SetPosition(hiVal);
|
|
|
|
} else
|
|
FlushNotify = FALSE; // Yes, so ignore the close.
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
TSoundWindow::CmHelpAbout()
|
|
{
|
|
MessageBox("SoundPly loads and plays waveform sound "
|
|
"files (*.WAV). The features of this demo are Play/Pause, Stop, "
|
|
"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 device, e.g. SoundBlaster, etc.).",
|
|
"About Sound Play", MB_OK);
|
|
}
|
|
|
|
void
|
|
TSoundWindow::EvTimer(UINT)
|
|
{
|
|
if (!FlushNotify) { // Internal STOP/CLOSE, from thumb re-pos?
|
|
MciStatusParm.dwCallback = 0; // No, normal close.
|
|
MciStatusParm.dwItem = MCI_STATUS_LENGTH;
|
|
mciSendCommand (DeviceId, MCI_STATUS, MCI_STATUS_ITEM, MCI_PARM2(MciStatusParm));
|
|
|
|
if (WaveLength != MciStatusParm.dwReturn) {
|
|
Invalidate(); // First time it's different update the scrollbar nums
|
|
WaveLength = MciStatusParm.dwReturn;
|
|
}
|
|
|
|
// Compute the length and ratio and update SoundBar info.
|
|
//
|
|
WaveRatio = int((WaveLength + 32000/2) / 32000);
|
|
if (!WaveRatio)
|
|
WaveRatio = 1;
|
|
SoundBar->SetInfo(WaveRatio, WaveLength);
|
|
SoundBar->SetRange(0, int(WaveLength / WaveRatio));
|
|
SoundBar->SetRuler(int((WaveLength / WaveRatio) / 10));
|
|
|
|
// Update the current position.
|
|
//
|
|
MciStatusParm.dwCallback = 0;
|
|
MciStatusParm.dwItem = MCI_STATUS_POSITION;
|
|
mciSendCommand(DeviceId, MCI_STATUS, MCI_STATUS_ITEM, MCI_PARM2(MciStatusParm));
|
|
|
|
SoundBar->SetPosition(int(MciStatusParm.dwReturn / WaveRatio));
|
|
}
|
|
|
|
FlushNotify = FALSE; // Yes, ignore this close.
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
class TSoundApp : public TApplication {
|
|
public:
|
|
TSoundApp() : TApplication() {}
|
|
void InitMainWindow() {
|
|
MainWindow = new TSoundWindow(0, "OWL MCI SoundPlay Demo");
|
|
EnableCtl3d();
|
|
}
|
|
};
|
|
|
|
int
|
|
OwlMain(int /*argc*/, char* /*argv*/ [])
|
|
{
|
|
return TSoundApp().Run();
|
|
}
|