2005-07-26 08:59:48 +02:00
|
|
|
/* $Id$ */
|
|
|
|
|
2009-08-21 22:21:05 +02:00
|
|
|
/*
|
|
|
|
* This file is part of OpenTTD.
|
|
|
|
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
|
|
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2008-05-06 17:11:33 +02:00
|
|
|
/** @file dmusic.cpp Playing music via DirectMusic. */
|
2007-04-17 22:23:13 +02:00
|
|
|
|
2011-12-21 13:28:02 +01:00
|
|
|
#ifdef WIN32_ENABLE_DIRECTMUSIC_SUPPORT
|
2005-07-26 08:59:48 +02:00
|
|
|
|
2010-06-27 02:19:28 +02:00
|
|
|
#define INITGUID
|
2007-02-23 13:56:10 +01:00
|
|
|
#include "../stdafx.h"
|
2008-05-17 00:29:27 +02:00
|
|
|
#ifdef WIN32_LEAN_AND_MEAN
|
|
|
|
#undef WIN32_LEAN_AND_MEAN // Don't exclude rarely-used stuff from Windows headers
|
|
|
|
#endif
|
2007-01-10 19:56:51 +01:00
|
|
|
#include "../debug.h"
|
2009-09-01 00:38:37 +02:00
|
|
|
#include "../os/windows/win32.h"
|
2013-05-20 15:35:41 +02:00
|
|
|
#include "../core/mem_func.hpp"
|
2007-01-10 19:56:51 +01:00
|
|
|
#include "dmusic.h"
|
2005-07-26 08:59:48 +02:00
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
#include <dmksctrl.h>
|
|
|
|
#include <dmusici.h>
|
|
|
|
#include <dmusicc.h>
|
|
|
|
#include <dmusicf.h>
|
|
|
|
|
2008-01-13 01:14:29 +01:00
|
|
|
static FMusicDriver_DMusic iFMusicDriver_DMusic;
|
2005-07-26 08:59:48 +02:00
|
|
|
|
2013-05-20 15:35:41 +02:00
|
|
|
/** the direct music object manages buffers and ports */
|
|
|
|
static IDirectMusic *music = NULL;
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/** the performance object controls manipulation of the segments */
|
2009-01-10 01:31:47 +01:00
|
|
|
static IDirectMusicPerformance *performance = NULL;
|
2005-07-26 08:59:48 +02:00
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/** the loader object can load many types of DMusic related files */
|
2009-01-10 01:31:47 +01:00
|
|
|
static IDirectMusicLoader *loader = NULL;
|
2005-07-26 08:59:48 +02:00
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/** the segment object is where the MIDI data is stored for playback */
|
2009-01-10 01:31:47 +01:00
|
|
|
static IDirectMusicSegment *segment = NULL;
|
2005-07-26 08:59:48 +02:00
|
|
|
|
|
|
|
static bool seeking = false;
|
|
|
|
|
|
|
|
|
|
|
|
#define M(x) x "\0"
|
|
|
|
static const char ole_files[] =
|
|
|
|
M("ole32.dll")
|
|
|
|
M("CoCreateInstance")
|
|
|
|
M("CoInitialize")
|
|
|
|
M("CoUninitialize")
|
|
|
|
M("")
|
|
|
|
;
|
|
|
|
#undef M
|
|
|
|
|
|
|
|
struct ProcPtrs {
|
2009-01-10 01:31:47 +01:00
|
|
|
unsigned long (WINAPI * CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
|
2005-07-26 08:59:48 +02:00
|
|
|
HRESULT (WINAPI * CoInitialize)(LPVOID pvReserved);
|
|
|
|
void (WINAPI * CoUninitialize)();
|
|
|
|
};
|
|
|
|
|
|
|
|
static ProcPtrs proc;
|
|
|
|
|
|
|
|
|
2007-07-05 14:23:54 +02:00
|
|
|
const char *MusicDriver_DMusic::Start(const char * const *parm)
|
2005-07-26 08:59:48 +02:00
|
|
|
{
|
|
|
|
if (performance != NULL) return NULL;
|
|
|
|
|
|
|
|
if (proc.CoCreateInstance == NULL) {
|
2009-04-10 13:03:48 +02:00
|
|
|
if (!LoadLibraryList((Function*)&proc, ole_files)) {
|
2005-07-26 08:59:48 +02:00
|
|
|
return "ole32.dll load failed";
|
2009-04-10 13:03:48 +02:00
|
|
|
}
|
2005-07-26 08:59:48 +02:00
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* Initialize COM */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(proc.CoInitialize(NULL))) {
|
|
|
|
return "COM initialization failed";
|
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* create the performance object */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(proc.CoCreateInstance(
|
|
|
|
CLSID_DirectMusicPerformance,
|
|
|
|
NULL,
|
|
|
|
CLSCTX_INPROC,
|
|
|
|
IID_IDirectMusicPerformance,
|
|
|
|
(LPVOID*)&performance
|
|
|
|
))) {
|
|
|
|
return "Failed to create the performance object";
|
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* initialize it */
|
2013-05-20 15:35:41 +02:00
|
|
|
if (FAILED(performance->Init(&music, NULL, NULL))) {
|
2005-07-26 08:59:48 +02:00
|
|
|
return "Failed to initialize performance object";
|
|
|
|
}
|
|
|
|
|
2013-05-20 15:35:41 +02:00
|
|
|
int port = GetDriverParamInt(parm, "port", -1);
|
|
|
|
|
|
|
|
#ifndef NO_DEBUG_MESSAGES
|
|
|
|
if (_debug_driver_level > 0) {
|
|
|
|
/* Print all valid output ports. */
|
|
|
|
char desc[DMUS_MAX_DESCRIPTION];
|
|
|
|
|
|
|
|
DMUS_PORTCAPS caps;
|
|
|
|
MemSetT(&caps, 0);
|
|
|
|
caps.dwSize = sizeof(DMUS_PORTCAPS);
|
|
|
|
|
|
|
|
DEBUG(driver, 1, "Detected DirectMusic ports:");
|
|
|
|
for (int i = 0; music->EnumPort(i, &caps) == S_OK; i++) {
|
|
|
|
if (caps.dwClass == DMUS_PC_OUTPUTCLASS) {
|
|
|
|
/* Description is UNICODE even for ANSI build. */
|
|
|
|
DEBUG(driver, 1, " %d: %s%s", i, convert_from_fs(caps.wszDescription, desc, lengthof(desc)), i == port ? " (selected)" : "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
IDirectMusicPort *music_port = NULL; // NULL means 'use default port'.
|
|
|
|
|
|
|
|
if (port >= 0) {
|
|
|
|
/* Check if the passed port is a valid port. */
|
|
|
|
DMUS_PORTCAPS caps;
|
|
|
|
MemSetT(&caps, 0);
|
|
|
|
caps.dwSize = sizeof(DMUS_PORTCAPS);
|
|
|
|
if (FAILED(music->EnumPort(port, &caps))) return "Supplied port parameter is not a valid port";
|
|
|
|
if (caps.dwClass != DMUS_PC_OUTPUTCLASS) return "Supplied port parameter is not an output port";
|
|
|
|
|
|
|
|
/* Create new port. */
|
|
|
|
DMUS_PORTPARAMS params;
|
|
|
|
MemSetT(¶ms, 0);
|
|
|
|
params.dwSize = sizeof(DMUS_PORTPARAMS);
|
|
|
|
params.dwValidParams = DMUS_PORTPARAMS_CHANNELGROUPS;
|
|
|
|
params.dwChannelGroups = 1;
|
|
|
|
|
|
|
|
if (FAILED(music->CreatePort(caps.guidPort, ¶ms, &music_port, NULL))) {
|
|
|
|
return "Failed to create port";
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Activate port. */
|
|
|
|
if (FAILED(music_port->Activate(TRUE))) {
|
|
|
|
music_port->Release();
|
|
|
|
return "Failed to activate port";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add port to performance. */
|
|
|
|
if (FAILED(performance->AddPort(music_port))) {
|
|
|
|
if (music_port != NULL) music_port->Release();
|
2005-07-26 08:59:48 +02:00
|
|
|
return "AddPort failed";
|
|
|
|
}
|
|
|
|
|
2013-05-20 15:35:41 +02:00
|
|
|
/* Assign a performance channel block to the performance if we added
|
|
|
|
* a custom port to the performance. */
|
|
|
|
if (music_port != NULL) {
|
|
|
|
if (FAILED(performance->AssignPChannelBlock(0, music_port, 1))) {
|
|
|
|
music_port->Release();
|
|
|
|
return "Failed to assign PChannel block";
|
|
|
|
}
|
|
|
|
/* We don't need the port anymore. */
|
|
|
|
music_port->Release();
|
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* create the loader object; this will be used to load the MIDI file */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(proc.CoCreateInstance(
|
|
|
|
CLSID_DirectMusicLoader,
|
|
|
|
NULL,
|
|
|
|
CLSCTX_INPROC,
|
|
|
|
IID_IDirectMusicLoader,
|
|
|
|
(LPVOID*)&loader
|
|
|
|
))) {
|
|
|
|
return "Failed to create loader object";
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-20 15:35:38 +02:00
|
|
|
MusicDriver_DMusic::~MusicDriver_DMusic()
|
|
|
|
{
|
|
|
|
this->Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-07-05 14:23:54 +02:00
|
|
|
void MusicDriver_DMusic::Stop()
|
2005-07-26 08:59:48 +02:00
|
|
|
{
|
|
|
|
seeking = false;
|
|
|
|
|
2005-09-08 14:22:47 +02:00
|
|
|
if (performance != NULL) performance->Stop(NULL, NULL, 0, 0);
|
2005-09-03 19:30:16 +02:00
|
|
|
|
2005-09-08 14:22:47 +02:00
|
|
|
if (segment != NULL) {
|
2006-01-26 23:09:25 +01:00
|
|
|
segment->SetParam(GUID_Unload, 0xFFFFFFFF, 0, 0, performance);
|
2005-09-08 14:22:47 +02:00
|
|
|
segment->Release();
|
|
|
|
segment = NULL;
|
|
|
|
}
|
2005-07-26 08:59:48 +02:00
|
|
|
|
2013-05-20 15:35:41 +02:00
|
|
|
if (music != NULL) {
|
|
|
|
music->Release();
|
|
|
|
music = NULL;
|
|
|
|
}
|
|
|
|
|
2005-09-08 14:22:47 +02:00
|
|
|
if (performance != NULL) {
|
|
|
|
performance->CloseDown();
|
|
|
|
performance->Release();
|
|
|
|
performance = NULL;
|
|
|
|
}
|
2005-07-26 08:59:48 +02:00
|
|
|
|
2005-09-08 14:22:47 +02:00
|
|
|
if (loader != NULL) {
|
|
|
|
loader->Release();
|
|
|
|
loader = NULL;
|
|
|
|
}
|
2005-07-26 08:59:48 +02:00
|
|
|
|
|
|
|
proc.CoUninitialize();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-01-10 01:31:47 +01:00
|
|
|
void MusicDriver_DMusic::PlaySong(const char *filename)
|
2005-07-26 08:59:48 +02:00
|
|
|
{
|
2007-04-17 22:23:13 +02:00
|
|
|
/* set up the loader object info */
|
2005-07-26 08:59:48 +02:00
|
|
|
DMUS_OBJECTDESC obj_desc;
|
|
|
|
ZeroMemory(&obj_desc, sizeof(obj_desc));
|
|
|
|
obj_desc.dwSize = sizeof(obj_desc);
|
|
|
|
obj_desc.guidClass = CLSID_DirectMusicSegment;
|
|
|
|
obj_desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
|
|
|
|
MultiByteToWideChar(
|
|
|
|
CP_ACP, MB_PRECOMPOSED,
|
|
|
|
filename, -1,
|
|
|
|
obj_desc.wszFileName, lengthof(obj_desc.wszFileName)
|
|
|
|
);
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* release the existing segment if we have any */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (segment != NULL) {
|
2010-07-10 00:25:25 +02:00
|
|
|
segment->Release();
|
|
|
|
segment = NULL;
|
2005-07-26 08:59:48 +02:00
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* make a new segment */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(loader->GetObject(
|
|
|
|
&obj_desc, IID_IDirectMusicSegment, (LPVOID*)&segment
|
|
|
|
))) {
|
2006-12-26 18:36:18 +01:00
|
|
|
DEBUG(driver, 0, "DirectMusic: GetObject failed");
|
2005-07-26 08:59:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* tell the segment what kind of data it contains */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(segment->SetParam(
|
|
|
|
GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, performance
|
|
|
|
))) {
|
2006-12-26 18:36:18 +01:00
|
|
|
DEBUG(driver, 0, "DirectMusic: SetParam (MIDI file) failed");
|
2005-07-26 08:59:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* tell the segment to 'download' the instruments */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(segment->SetParam(GUID_Download, 0xFFFFFFFF, 0, 0, performance))) {
|
2006-12-26 18:36:18 +01:00
|
|
|
DEBUG(driver, 0, "DirectMusic: failed to download instruments");
|
2005-07-26 08:59:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-04-17 22:23:13 +02:00
|
|
|
/* start playing the MIDI file */
|
2005-07-26 08:59:48 +02:00
|
|
|
if (FAILED(performance->PlaySegment(segment, 0, 0, NULL))) {
|
2006-12-26 18:36:18 +01:00
|
|
|
DEBUG(driver, 0, "DirectMusic: PlaySegment failed");
|
2005-07-26 08:59:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
seeking = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-07-05 14:23:54 +02:00
|
|
|
void MusicDriver_DMusic::StopSong()
|
2005-07-26 08:59:48 +02:00
|
|
|
{
|
|
|
|
if (FAILED(performance->Stop(segment, NULL, 0, 0))) {
|
2006-12-26 18:36:18 +01:00
|
|
|
DEBUG(driver, 0, "DirectMusic: StopSegment failed");
|
2005-07-26 08:59:48 +02:00
|
|
|
}
|
|
|
|
seeking = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-07-05 14:23:54 +02:00
|
|
|
bool MusicDriver_DMusic::IsSongPlaying()
|
2005-07-26 08:59:48 +02:00
|
|
|
{
|
|
|
|
/* Not the nicest code, but there is a short delay before playing actually
|
|
|
|
* starts. OpenTTD makes no provision for this. */
|
|
|
|
if (performance->IsPlaying(segment, NULL) == S_OK) {
|
|
|
|
seeking = false;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return seeking;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-07-05 14:23:54 +02:00
|
|
|
void MusicDriver_DMusic::SetVolume(byte vol)
|
2005-07-26 08:59:48 +02:00
|
|
|
{
|
2007-04-17 22:23:13 +02:00
|
|
|
long db = vol * 2000 / 127 - 2000; ///< 0 - 127 -> -2000 - 0
|
2005-07-26 08:59:48 +02:00
|
|
|
performance->SetGlobalParam(GUID_PerfMasterVolume, &db, sizeof(db));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-02-12 22:55:10 +01:00
|
|
|
#endif /* WIN32_ENABLE_DIRECTMUSIC_SUPPORT */
|