diff options
author | rubidium <rubidium@openttd.org> | 2007-01-02 19:19:48 +0000 |
---|---|---|
committer | rubidium <rubidium@openttd.org> | 2007-01-02 19:19:48 +0000 |
commit | 66bbf336c6af7353ef0aeed58002c46543b30635 (patch) | |
tree | ad4a63860df2626b22f77e7dac712e958bea54cb /src/music | |
parent | ccc0a3f4dbf58c005b22341ac8874252924690cd (diff) | |
download | openttd-66bbf336c6af7353ef0aeed58002c46543b30635.tar.xz |
(svn r7759) -Merge: makefile rewrite. This merge features:
- A proper ./configure, so everything needs to be configured only once, not for every make.
- Usage of makedepend when available. This greatly reduces the time needed for generating the dependencies.
- A generator for all project files. There is a single file with sources, which is used to generate Makefiles and the project files for MSVC.
- Proper support for OSX universal binaries.
- Object files for non-MSVC compiles are also placed in separate directories, making is faster to switch between debug and release compiles and it does not touch the directory with the source files.
- Functionality to make a bundle of all needed files for for example a nightly or distribution of a binary with all needed GRFs and language files.
Note: as this merge moves almost all files, it is recommended to make a backup of your working copy before updating your working copy.
Diffstat (limited to 'src/music')
-rw-r--r-- | src/music/bemidi.cpp | 53 | ||||
-rw-r--r-- | src/music/bemidi.h | 10 | ||||
-rw-r--r-- | src/music/dmusic.cpp | 232 | ||||
-rw-r--r-- | src/music/dmusic.h | 10 | ||||
-rw-r--r-- | src/music/extmidi.c | 108 | ||||
-rw-r--r-- | src/music/extmidi.h | 10 | ||||
-rw-r--r-- | src/music/null_m.c | 21 | ||||
-rw-r--r-- | src/music/null_m.h | 10 | ||||
-rw-r--r-- | src/music/os2_m.c | 77 | ||||
-rw-r--r-- | src/music/os2_m.h | 10 | ||||
-rw-r--r-- | src/music/qtmidi.c | 371 | ||||
-rw-r--r-- | src/music/qtmidi.h | 10 | ||||
-rw-r--r-- | src/music/win32_m.c | 170 | ||||
-rw-r--r-- | src/music/win32_m.h | 10 |
14 files changed, 1102 insertions, 0 deletions
diff --git a/src/music/bemidi.cpp b/src/music/bemidi.cpp new file mode 100644 index 000000000..d87fae63b --- /dev/null +++ b/src/music/bemidi.cpp @@ -0,0 +1,53 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "bemidi.h" + +// BeOS System Includes +#include <MidiSynthFile.h> + +static BMidiSynthFile midiSynthFile; + +static const char *bemidi_start(const char * const *parm) +{ + return NULL; +} + +static void bemidi_stop(void) +{ + midiSynthFile.UnloadFile(); +} + +static void bemidi_play_song(const char *filename) +{ + bemidi_stop(); + entry_ref midiRef; + get_ref_for_path(filename, &midiRef); + midiSynthFile.LoadFile(&midiRef); + midiSynthFile.Start(); +} + +static void bemidi_stop_song(void) +{ + midiSynthFile.UnloadFile(); +} + +static bool bemidi_is_playing(void) +{ + return !midiSynthFile.IsFinished(); +} + +static void bemidi_set_volume(byte vol) +{ + fprintf(stderr, "BeMidi: Set volume not implemented\n"); +} + +const HalMusicDriver _bemidi_music_driver = { + bemidi_start, + bemidi_stop, + bemidi_play_song, + bemidi_stop_song, + bemidi_is_playing, + bemidi_set_volume, +}; diff --git a/src/music/bemidi.h b/src/music/bemidi.h new file mode 100644 index 000000000..10ead59ff --- /dev/null +++ b/src/music/bemidi.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_BEMIDI_H +#define MUSIC_BEMIDI_H + +#include "../hal.h" + +extern const HalMusicDriver _bemidi_music_driver; + +#endif diff --git a/src/music/dmusic.cpp b/src/music/dmusic.cpp new file mode 100644 index 000000000..e20842d2e --- /dev/null +++ b/src/music/dmusic.cpp @@ -0,0 +1,232 @@ +/* $Id$ */ + +#include "../stdafx.h" + +#ifdef WIN32_ENABLE_DIRECTMUSIC_SUPPORT + +extern "C" { + #include "../openttd.h" + #include "../debug.h" + #include "../win32.h" + #include "dmusic.h" +} + +#include <windows.h> +#include <dmksctrl.h> +#include <dmusici.h> +#include <dmusicc.h> +#include <dmusicf.h> + + +// the performance object controls manipulation of the segments +static IDirectMusicPerformance* performance = NULL; + +// the loader object can load many types of DMusic related files +static IDirectMusicLoader* loader = NULL; + +// the segment object is where the MIDI data is stored for playback +static IDirectMusicSegment* segment = NULL; + +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 { + unsigned long (WINAPI * CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv); + HRESULT (WINAPI * CoInitialize)(LPVOID pvReserved); + void (WINAPI * CoUninitialize)(); +}; + +static ProcPtrs proc; + + +static const char* DMusicMidiStart(const char* const* parm) +{ + if (performance != NULL) return NULL; + + if (proc.CoCreateInstance == NULL) { + if (!LoadLibraryList((Function*)&proc, ole_files)) + return "ole32.dll load failed"; + } + + // Initialize COM + if (FAILED(proc.CoInitialize(NULL))) { + return "COM initialization failed"; + } + + // create the performance object + if (FAILED(proc.CoCreateInstance( + CLSID_DirectMusicPerformance, + NULL, + CLSCTX_INPROC, + IID_IDirectMusicPerformance, + (LPVOID*)&performance + ))) { + proc.CoUninitialize(); + return "Failed to create the performance object"; + } + + // initialize it + if (FAILED(performance->Init(NULL, NULL, NULL))) { + performance->Release(); + performance = NULL; + proc.CoUninitialize(); + return "Failed to initialize performance object"; + } + + // choose default Windows synth + if (FAILED(performance->AddPort(NULL))) { + performance->CloseDown(); + performance->Release(); + performance = NULL; + proc.CoUninitialize(); + return "AddPort failed"; + } + + // create the loader object; this will be used to load the MIDI file + if (FAILED(proc.CoCreateInstance( + CLSID_DirectMusicLoader, + NULL, + CLSCTX_INPROC, + IID_IDirectMusicLoader, + (LPVOID*)&loader + ))) { + performance->CloseDown(); + performance->Release(); + performance = NULL; + proc.CoUninitialize(); + return "Failed to create loader object"; + } + + return NULL; +} + + +static void DMusicMidiStop(void) +{ + seeking = false; + + if (performance != NULL) performance->Stop(NULL, NULL, 0, 0); + + if (segment != NULL) { + segment->SetParam(GUID_Unload, 0xFFFFFFFF, 0, 0, performance); + segment->Release(); + segment = NULL; + } + + if (performance != NULL) { + performance->CloseDown(); + performance->Release(); + performance = NULL; + } + + if (loader != NULL) { + loader->Release(); + loader = NULL; + } + + proc.CoUninitialize(); +} + + +static void DMusicMidiPlaySong(const char* filename) +{ + // set up the loader object info + 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) + ); + + // release the existing segment if we have any + if (segment != NULL) { + segment->Release(); + segment = NULL; + } + + // make a new segment + if (FAILED(loader->GetObject( + &obj_desc, IID_IDirectMusicSegment, (LPVOID*)&segment + ))) { + DEBUG(driver, 0, "DirectMusic: GetObject failed"); + return; + } + + // tell the segment what kind of data it contains + if (FAILED(segment->SetParam( + GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, performance + ))) { + DEBUG(driver, 0, "DirectMusic: SetParam (MIDI file) failed"); + return; + } + + // tell the segment to 'download' the instruments + if (FAILED(segment->SetParam(GUID_Download, 0xFFFFFFFF, 0, 0, performance))) { + DEBUG(driver, 0, "DirectMusic: failed to download instruments"); + return; + } + + // start playing the MIDI file + if (FAILED(performance->PlaySegment(segment, 0, 0, NULL))) { + DEBUG(driver, 0, "DirectMusic: PlaySegment failed"); + return; + } + + seeking = true; +} + + +static void DMusicMidiStopSong(void) +{ + if (FAILED(performance->Stop(segment, NULL, 0, 0))) { + DEBUG(driver, 0, "DirectMusic: StopSegment failed"); + } + seeking = false; +} + + +static bool DMusicMidiIsSongPlaying(void) +{ + /* 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; + } +} + + +static void DMusicMidiSetVolume(byte vol) +{ + // 0 - 127 -> -2000 - 0 + long db = vol * 2000 / 127 - 2000; + performance->SetGlobalParam(GUID_PerfMasterVolume, &db, sizeof(db)); +} + + +extern "C" const HalMusicDriver _dmusic_midi_driver = { + DMusicMidiStart, + DMusicMidiStop, + DMusicMidiPlaySong, + DMusicMidiStopSong, + DMusicMidiIsSongPlaying, + DMusicMidiSetVolume, +}; + +#endif diff --git a/src/music/dmusic.h b/src/music/dmusic.h new file mode 100644 index 000000000..5d0990137 --- /dev/null +++ b/src/music/dmusic.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_DMUSIC_H +#define MUSIC_DMUSIC_H + +#include "../hal.h" + +extern const HalMusicDriver _dmusic_midi_driver; + +#endif diff --git a/src/music/extmidi.c b/src/music/extmidi.c new file mode 100644 index 000000000..901fa4fd7 --- /dev/null +++ b/src/music/extmidi.c @@ -0,0 +1,108 @@ +/* $Id$ */ + +#ifndef __MORPHOS__ +#include "../stdafx.h" +#include "../openttd.h" +#include "../sound.h" +#include "../string.h" +#include "../variables.h" +#include "../debug.h" +#include "extmidi.h" +#include <fcntl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <signal.h> +#include <sys/stat.h> +#include <errno.h> + +static struct { + char song[MAX_PATH]; + pid_t pid; +} _midi; + +static void DoPlay(void); +static void DoStop(void); + +static const char* ExtMidiStart(const char* const * parm) +{ + _midi.song[0] = '\0'; + _midi.pid = -1; + return NULL; +} + +static void ExtMidiStop(void) +{ + _midi.song[0] = '\0'; + DoStop(); +} + +static void ExtMidiPlaySong(const char* filename) +{ + ttd_strlcpy(_midi.song, filename, lengthof(_midi.song)); + DoStop(); +} + +static void ExtMidiStopSong(void) +{ + _midi.song[0] = '\0'; + DoStop(); +} + +static bool ExtMidiIsPlaying(void) +{ + if (_midi.pid != -1 && waitpid(_midi.pid, NULL, WNOHANG) == _midi.pid) + _midi.pid = -1; + if (_midi.pid == -1 && _midi.song[0] != '\0') DoPlay(); + return _midi.pid != -1; +} + +static void ExtMidiSetVolume(byte vol) +{ + DEBUG(driver, 1, "extmidi: set volume not implemented"); +} + +static void DoPlay(void) +{ + _midi.pid = fork(); + switch (_midi.pid) { + case 0: { + int d; + + close(0); + d = open("/dev/null", O_RDONLY); + if (d != -1 && dup2(d, 1) != -1 && dup2(d, 2) != -1) { + #if defined(MIDI_ARG) + execlp(msf.extmidi, "extmidi", MIDI_ARG, _midi.song, (char*)0); + #else + execlp(msf.extmidi, "extmidi", _midi.song, (char*)0); + #endif + } + _exit(1); + } + + case -1: + DEBUG(driver, 0, "extmidi: couldn't fork: %s", strerror(errno)); + /* FALLTHROUGH */ + + default: + _midi.song[0] = '\0'; + break; + } +} + +static void DoStop(void) +{ + if (_midi.pid != -1) kill(_midi.pid, SIGTERM); +} + +const HalMusicDriver _extmidi_music_driver = { + ExtMidiStart, + ExtMidiStop, + ExtMidiPlaySong, + ExtMidiStopSong, + ExtMidiIsPlaying, + ExtMidiSetVolume, +}; + +#endif /* __MORPHOS__ */ diff --git a/src/music/extmidi.h b/src/music/extmidi.h new file mode 100644 index 000000000..681c34bd6 --- /dev/null +++ b/src/music/extmidi.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_EXTERNAL_H +#define MUSIC_EXTERNAL_H + +#include "../hal.h" + +extern const HalMusicDriver _extmidi_music_driver; + +#endif diff --git a/src/music/null_m.c b/src/music/null_m.c new file mode 100644 index 000000000..c76a60211 --- /dev/null +++ b/src/music/null_m.c @@ -0,0 +1,21 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "null_m.h" + +static const char* NullMidiStart(const char* const* parm) { return NULL; } +static void NullMidiStop(void) {} +static void NullMidiPlaySong(const char *filename) {} +static void NullMidiStopSong(void) {} +static bool NullMidiIsSongPlaying(void) { return true; } +static void NullMidiSetVolume(byte vol) {} + +const HalMusicDriver _null_music_driver = { + NullMidiStart, + NullMidiStop, + NullMidiPlaySong, + NullMidiStopSong, + NullMidiIsSongPlaying, + NullMidiSetVolume, +}; diff --git a/src/music/null_m.h b/src/music/null_m.h new file mode 100644 index 000000000..0f3e2cfa5 --- /dev/null +++ b/src/music/null_m.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_NULL_H +#define MUSIC_NULL_H + +#include "../hal.h" + +extern const HalMusicDriver _null_music_driver; + +#endif diff --git a/src/music/os2_m.c b/src/music/os2_m.c new file mode 100644 index 000000000..75df60c8d --- /dev/null +++ b/src/music/os2_m.c @@ -0,0 +1,77 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "os2_m.h" + +#define INCL_DOS +#define INCL_OS2MM +#define INCL_WIN + +#include <stdarg.h> +#include <os2.h> +#include <os2me.h> + +/********************** + * OS/2 MIDI PLAYER + **********************/ + +/* Interesting how similar the MCI API in OS/2 is to the Win32 MCI API, + * eh? Anyone would think they both came from the same place originally! ;) + */ + +static long CDECL MidiSendCommand(const char *cmd, ...) +{ + va_list va; + char buf[512]; + va_start(va, cmd); + vsprintf(buf, cmd, va); + va_end(va); + return mciSendString(buf, NULL, 0, NULL, 0); +} + +static void OS2MidiPlaySong(const char *filename) +{ + MidiSendCommand("close all"); + + if (MidiSendCommand("open %s type sequencer alias song", filename) != 0) + return; + + MidiSendCommand("play song from 0"); +} + +static void OS2MidiStopSong(void) +{ + MidiSendCommand("close all"); +} + +static void OS2MidiSetVolume(byte vol) +{ + MidiSendCommand("set song audio volume %d", ((vol/127)*100)); +} + +static bool OS2MidiIsSongPlaying(void) +{ + char buf[16]; + mciSendString("status song mode", buf, sizeof(buf), NULL, 0); + return strcmp(buf, "playing") == 0 || strcmp(buf, "seeking") == 0; +} + +static const char *OS2MidiStart(const char * const *parm) +{ + return 0; +} + +static void OS2MidiStop(void) +{ + MidiSendCommand("close all"); +} + +const HalMusicDriver _os2_music_driver = { + OS2MidiStart, + OS2MidiStop, + OS2MidiPlaySong, + OS2MidiStopSong, + OS2MidiIsSongPlaying, + OS2MidiSetVolume, +}; diff --git a/src/music/os2_m.h b/src/music/os2_m.h new file mode 100644 index 000000000..173f096a5 --- /dev/null +++ b/src/music/os2_m.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_OS2_H +#define MUSIC_OS2_H + +#include "../hal.h" + +extern const HalMusicDriver _os2_music_driver; + +#endif diff --git a/src/music/qtmidi.c b/src/music/qtmidi.c new file mode 100644 index 000000000..6f0a215d0 --- /dev/null +++ b/src/music/qtmidi.c @@ -0,0 +1,371 @@ +/* $Id$ */ + +/** + * @file qtmidi.c + * @brief MIDI music player for MacOS X using QuickTime. + * + * This music player should work in all MacOS X releases starting from 10.0, + * as QuickTime is an integral part of the system since the old days of the + * Motorola 68k-based Macintoshes. The only extra dependency apart from + * QuickTime itself is Carbon, which is included since 10.0 as well. + * + * QuickTime gets fooled with the MIDI files from Transport Tycoon Deluxe + * because of the @c .gm suffix. To force QuickTime to load the MIDI files + * without the need of dealing with the individual QuickTime components + * needed to play music (data source, MIDI parser, note allocators, + * synthesizers and the like) some Carbon functions are used to set the file + * type as seen by QuickTime, using @c FSpSetFInfo() (which modifies the + * file's resource fork). + */ + + +/* + * OpenTTD includes. + */ +#define WindowClass OSX_WindowClass +#include <QuickTime/QuickTime.h> +#undef WindowClass + +#include "../stdafx.h" +#include "../openttd.h" +#include "qtmidi.h" + +/* + * System includes. We need to workaround with some defines because there's + * stuff already defined in QuickTime headers. + */ +#define OTTD_Random OSX_OTTD_Random +#undef OTTD_Random +#undef WindowClass +#undef SL_ERROR +#undef bool + +#include <assert.h> +#include <unistd.h> +#include <fcntl.h> + +// we need to include debug.h after CoreServices because defining DEBUG will break CoreServices in OSX 10.2 +#include "../debug.h" + + +enum { + midiType = 'Midi' /**< OSType code for MIDI songs. */ +}; + + +/** + * Converts a Unix-like pathname to a @c FSSpec structure which may be + * used with functions from several MacOS X frameworks (Carbon, QuickTime, + * etc). The pointed file or directory must exist. + * + * @param *path A string containing a Unix-like path. + * @param *spec Pointer to a @c FSSpec structure where the result will be + * stored. + * @return Wether the conversion was successful. + */ +static bool PathToFSSpec(const char *path, FSSpec *spec) +{ + FSRef ref; + assert(spec != NULL); + assert(path != NULL); + + return + FSPathMakeRef((UInt8*)path, &ref, NULL) == noErr && + FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, spec, NULL) == noErr; +} + + +/** + * Sets the @c OSType of a given file to @c 'Midi', but only if it's not + * already set. + * + * @param *spec A @c FSSpec structure referencing a file. + */ +static void SetMIDITypeIfNeeded(const FSSpec *spec) +{ + FSRef ref; + FSCatalogInfo catalogInfo; + + assert(spec); + + if (noErr != FSpMakeFSRef(spec, &ref)) return; + if (noErr != FSGetCatalogInfo(&ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL)) return; + if (!(catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)) { + FileInfo * const info = (FileInfo *) catalogInfo.finderInfo; + if (info->fileType != midiType && !(info->finderFlags & kIsAlias)) { + OSErr e; + info->fileType = midiType; + e = FSSetCatalogInfo(&ref, kFSCatInfoFinderInfo, &catalogInfo); + if (e == noErr) { + DEBUG(driver, 3, "qtmidi: changed filetype to 'Midi'"); + } else { + DEBUG(driver, 0, "qtmidi: changing filetype to 'Midi' failed - error %d", e); + } + } + } +} + + +/** + * Loads a MIDI file and returns it as a QuickTime Movie structure. + * + * @param *path String with the path of an existing MIDI file. + * @param *moov Pointer to a @c Movie where the result will be stored. + * @return Wether the file was loaded and the @c Movie successfully created. + */ +static bool LoadMovieForMIDIFile(const char *path, Movie *moov) +{ + int fd; + int ret; + char magic[4]; + FSSpec fsspec; + short refnum = 0; + short resid = 0; + + assert(path != NULL); + assert(moov != NULL); + + DEBUG(driver, 2, "qtmidi: start loading '%s'...", path); + + /* + * XXX Manual check for MIDI header ('MThd'), as I don't know how to make + * QuickTime load MIDI files without a .mid suffix without knowing it's + * a MIDI file and setting the OSType of the file to the 'Midi' value. + * Perhahaps ugly, but it seems that it does the Right Thing(tm). + */ + fd = open(path, O_RDONLY, 0); + if (fd == -1) return false; + ret = read(fd, magic, 4); + close(fd); + if (ret < 4) return false; + + DEBUG(driver, 3, "qtmidi: header is '%.4s'", magic); + if (magic[0] != 'M' || magic[1] != 'T' || magic[2] != 'h' || magic[3] != 'd') + return false; + + if (!PathToFSSpec(path, &fsspec)) return false; + SetMIDITypeIfNeeded(&fsspec); + + if (OpenMovieFile(&fsspec, &refnum, fsRdPerm) != noErr) return false; + DEBUG(driver, 3, "qtmidi: '%s' successfully opened", path); + + if (noErr != NewMovieFromFile(moov, refnum, &resid, NULL, + newMovieActive | newMovieDontAskUnresolvedDataRefs, NULL)) { + CloseMovieFile(refnum); + return false; + } + DEBUG(driver, 3, "qtmidi: movie container created"); + + CloseMovieFile(refnum); + return true; +} + + +/** + * Flag which has the @c true value when QuickTime is available and + * initialized. + */ +static bool _quicktime_started = false; + + +/** + * Initialize QuickTime if needed. This function sets the + * #_quicktime_started flag to @c true if QuickTime is present in the system + * and it was initialized properly. + */ +static void InitQuickTimeIfNeeded(void) +{ + OSStatus dummy; + + if (_quicktime_started) return; + + DEBUG(driver, 2, "qtmidi: initializing Quicktime"); + /* Be polite: check wether QuickTime is available and initialize it. */ + _quicktime_started = + (noErr == Gestalt(gestaltQuickTime, &dummy)) && + (noErr == EnterMovies()); + if (!_quicktime_started) DEBUG(driver, 0, "qtmidi: Quicktime initialization failed!"); +} + + +/** Possible states of the QuickTime music driver. */ +enum { + QT_STATE_IDLE, /**< No file loaded. */ + QT_STATE_PLAY, /**< File loaded, playing. */ + QT_STATE_STOP, /**< File loaded, stopped. */ +}; + + +static Movie _quicktime_movie; /**< Current QuickTime @c Movie. */ +static byte _quicktime_volume = 127; /**< Current volume. */ +static int _quicktime_state = QT_STATE_IDLE; /**< Current player state. */ + + +/** + * Maps OpenTTD volume to QuickTime notion of volume. + */ +#define VOLUME ((short)((0x00FF & _quicktime_volume) << 1)) + + +static void StopSong(void); + + +/** + * Initialized the MIDI player, including QuickTime initialization. + * + * @todo Give better error messages by inspecting error codes returned by + * @c Gestalt() and @c EnterMovies(). Needs changes in + * #InitQuickTimeIfNeeded. + */ +static const char* StartDriver(const char * const *parm) +{ + InitQuickTimeIfNeeded(); + return (_quicktime_started) ? NULL : "can't initialize QuickTime"; +} + + +/** + * Checks wether the player is active. + * + * This function is called at regular intervals from OpenTTD's main loop, so + * we call @c MoviesTask() from here to let QuickTime do its work. + */ +static bool SongIsPlaying(void) +{ + if (!_quicktime_started) return true; + + switch (_quicktime_state) { + case QT_STATE_IDLE: + case QT_STATE_STOP: + /* Do nothing. */ + break; + case QT_STATE_PLAY: + MoviesTask(_quicktime_movie, 0); + /* Check wether movie ended. */ + if (IsMovieDone(_quicktime_movie) || + (GetMovieTime(_quicktime_movie, NULL) >= + GetMovieDuration(_quicktime_movie))) + _quicktime_state = QT_STATE_STOP; + } + + return _quicktime_state == QT_STATE_PLAY; +} + + +/** + * Stops the MIDI player. + * + * Stops playing and frees any used resources before returning. As it + * deinitilizes QuickTime, the #_quicktime_started flag is set to @c false. + */ +static void StopDriver(void) +{ + if (!_quicktime_started) return; + + DEBUG(driver, 2, "qtmidi: stopping driver..."); + switch (_quicktime_state) { + case QT_STATE_IDLE: + DEBUG(driver, 3, "qtmidi: stopping not needed, already idle"); + /* Do nothing. */ + break; + case QT_STATE_PLAY: + StopSong(); + case QT_STATE_STOP: + DisposeMovie(_quicktime_movie); + } + + ExitMovies(); + _quicktime_started = false; +} + + +/** + * Starts playing a new song. + * + * @param filename Path to a MIDI file. + */ +static void PlaySong(const char *filename) +{ + if (!_quicktime_started) return; + + DEBUG(driver, 2, "qtmidi: trying to play '%s'", filename); + switch (_quicktime_state) { + case QT_STATE_PLAY: + StopSong(); + DEBUG(driver, 3, "qtmidi: previous tune stopped"); + /* XXX Fall-through -- no break needed. */ + case QT_STATE_STOP: + DisposeMovie(_quicktime_movie); + DEBUG(driver, 3, "qtmidi: previous tune disposed"); + _quicktime_state = QT_STATE_IDLE; + /* XXX Fall-through -- no break needed. */ + case QT_STATE_IDLE: + LoadMovieForMIDIFile(filename, &_quicktime_movie); + SetMovieVolume(_quicktime_movie, VOLUME); + StartMovie(_quicktime_movie); + _quicktime_state = QT_STATE_PLAY; + } + DEBUG(driver, 3, "qtmidi: playing '%s'", filename); +} + + +/** + * Stops playing the current song, if the player is active. + */ +static void StopSong(void) +{ + if (!_quicktime_started) return; + + switch (_quicktime_state) { + case QT_STATE_IDLE: + /* XXX Fall-through -- no break needed. */ + case QT_STATE_STOP: + DEBUG(driver, 3, "qtmidi: stop requested, but already idle"); + /* Do nothing. */ + break; + case QT_STATE_PLAY: + StopMovie(_quicktime_movie); + _quicktime_state = QT_STATE_STOP; + DEBUG(driver, 3, "qtmidi: player stopped"); + } +} + + +/** + * Changes the playing volume of the MIDI player. + * + * As QuickTime controls volume in a per-movie basis, the desired volume is + * stored in #_quicktime_volume, and the volume is set here using the + * #VOLUME macro, @b and when loading new song in #PlaySong. + * + * @param vol The desired volume, range of the value is @c 0-127 + */ +static void SetVolume(byte vol) +{ + if (!_quicktime_started) return; + + _quicktime_volume = vol; + + DEBUG(driver, 2, "qtmidi: set volume to %u (%hi)", vol, VOLUME); + switch (_quicktime_state) { + case QT_STATE_IDLE: + /* Do nothing. */ + break; + case QT_STATE_PLAY: + case QT_STATE_STOP: + SetMovieVolume(_quicktime_movie, VOLUME); + } +} + + +/** + * Table of callbacks that implement the QuickTime MIDI player. + */ +const HalMusicDriver _qtime_music_driver = { + StartDriver, + StopDriver, + PlaySong, + StopSong, + SongIsPlaying, + SetVolume, +}; diff --git a/src/music/qtmidi.h b/src/music/qtmidi.h new file mode 100644 index 000000000..727ce25e1 --- /dev/null +++ b/src/music/qtmidi.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_MACOSX_QUICKTIME_H +#define MUSIC_MACOSX_QUICKTIME_H + +#include "../hal.h" + +extern const HalMusicDriver _qtime_music_driver; + +#endif /* !MUSIC_MACOSX_QUICKTIME_H */ diff --git a/src/music/win32_m.c b/src/music/win32_m.c new file mode 100644 index 000000000..36b79b30b --- /dev/null +++ b/src/music/win32_m.c @@ -0,0 +1,170 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "win32_m.h" +#include <windows.h> +#include <mmsystem.h> + +static struct { + bool stop_song; + bool terminate; + bool playing; + int new_vol; + HANDLE wait_obj; + UINT_PTR devid; + char start_song[260]; +} _midi; + +static void Win32MidiPlaySong(const char *filename) +{ + strcpy(_midi.start_song, filename); + _midi.playing = true; + _midi.stop_song = false; + SetEvent(_midi.wait_obj); +} + +static void Win32MidiStopSong(void) +{ + if (_midi.playing) { + _midi.stop_song = true; + _midi.start_song[0] = '\0'; + SetEvent(_midi.wait_obj); + } +} + +static bool Win32MidiIsSongPlaying(void) +{ + return _midi.playing; +} + +static void Win32MidiSetVolume(byte vol) +{ + _midi.new_vol = vol; + SetEvent(_midi.wait_obj); +} + +static MCIERROR CDECL MidiSendCommand(const char* cmd, ...) +{ + va_list va; + char buf[512]; + + va_start(va, cmd); + vsprintf(buf, cmd, va); + va_end(va); + return mciSendStringA(buf, NULL, 0, 0); +} + +static bool MidiIntPlaySong(const char *filename) +{ + MidiSendCommand("close all"); + if (MidiSendCommand("open \"%s\" type sequencer alias song", filename) != 0) + return false; + + if (MidiSendCommand("play song from 0") != 0) + return false; + return true; +} + +static void MidiIntStopSong(void) +{ + MidiSendCommand("close all"); +} + +static void MidiIntSetVolume(int vol) +{ + DWORD v = (vol * 65535 / 127); + midiOutSetVolume((HMIDIOUT)_midi.devid, v + (v << 16)); +} + +static bool MidiIntIsSongPlaying(void) +{ + char buf[16]; + mciSendStringA("status song mode", buf, sizeof(buf), 0); + return strcmp(buf, "playing") == 0 || strcmp(buf, "seeking") == 0; +} + +static DWORD WINAPI MidiThread(LPVOID arg) +{ + _midi.wait_obj = CreateEvent(NULL, FALSE, FALSE, NULL); + + do { + char *s; + int vol; + + vol = _midi.new_vol; + if (vol != -1) { + _midi.new_vol = -1; + MidiIntSetVolume(vol); + } + + s = _midi.start_song; + if (s[0] != '\0') { + _midi.playing = MidiIntPlaySong(s); + s[0] = '\0'; + + // Delay somewhat in case we don't manage to play. + if (!_midi.playing) { + Sleep(5000); + } + } + + if (_midi.stop_song && _midi.playing) { + _midi.stop_song = false; + _midi.playing = false; + MidiIntStopSong(); + } + + if (_midi.playing && !MidiIntIsSongPlaying()) + _midi.playing = false; + + WaitForMultipleObjects(1, &_midi.wait_obj, FALSE, 1000); + } while (!_midi.terminate); + + DeleteObject(_midi.wait_obj); + return 0; +} + +static const char *Win32MidiStart(const char * const *parm) +{ + MIDIOUTCAPS midicaps; + DWORD threadId; + UINT nbdev; + UINT_PTR dev; + char buf[16]; + + mciSendStringA("capability sequencer has audio", buf, lengthof(buf), 0); + if (strcmp(buf, "true") != 0) return "MCI sequencer can't play audio"; + + memset(&_midi, 0, sizeof(_midi)); + _midi.new_vol = -1; + + /* Get midi device */ + _midi.devid = MIDI_MAPPER; + for (dev = 0, nbdev = midiOutGetNumDevs(); dev < nbdev; dev++) { + if (midiOutGetDevCaps(dev, &midicaps, sizeof(midicaps)) == 0 && (midicaps.dwSupport & MIDICAPS_VOLUME)) { + _midi.devid = dev; + break; + } + } + + if (CreateThread(NULL, 8192, MidiThread, 0, 0, &threadId) == NULL) + return "Failed to create thread"; + + return NULL; +} + +static void Win32MidiStop(void) +{ + _midi.terminate = true; + SetEvent(_midi.wait_obj); +} + +const HalMusicDriver _win32_music_driver = { + Win32MidiStart, + Win32MidiStop, + Win32MidiPlaySong, + Win32MidiStopSong, + Win32MidiIsSongPlaying, + Win32MidiSetVolume, +}; diff --git a/src/music/win32_m.h b/src/music/win32_m.h new file mode 100644 index 000000000..83893ed1e --- /dev/null +++ b/src/music/win32_m.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef MUSIC_WIN32_H +#define MUSIC_WIN32_H + +#include "../hal.h" + +extern const HalMusicDriver _win32_music_driver; + +#endif |