2006-06-05 18:00:37 +00:00
|
|
|
/* $Id$ */
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2009-08-21 20:21:05 +00: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/>.
|
|
|
|
*/
|
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
/**
|
2007-02-23 01:48:53 +00:00
|
|
|
* @file qtmidi.cpp
|
2005-10-06 17:57:18 +00:00
|
|
|
* @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).
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2009-05-03 15:46:36 +00:00
|
|
|
#ifndef NO_QUICKTIME
|
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
#include "../stdafx.h"
|
|
|
|
#include "qtmidi.h"
|
2005-12-10 23:03:59 +00:00
|
|
|
#include "../debug.h"
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2009-07-14 16:43:45 +00:00
|
|
|
#define Rect OTTDRect
|
|
|
|
#define Point OTTDPoint
|
|
|
|
#include <QuickTime/QuickTime.h>
|
|
|
|
#undef Rect
|
|
|
|
#undef Point
|
|
|
|
|
2014-04-23 20:13:33 +00:00
|
|
|
#include "../safeguards.h"
|
|
|
|
|
2007-07-05 12:23:54 +00:00
|
|
|
static FMusicDriver_QtMidi iFMusicDriver_QtMidi;
|
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2010-05-14 20:29:26 +00:00
|
|
|
static const uint MIDI_TYPE = 'Midi'; ///< OSType code for MIDI songs.
|
2005-10-06 17:57:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the @c OSType of a given file to @c 'Midi', but only if it's not
|
|
|
|
* already set.
|
|
|
|
*
|
2009-09-19 09:51:14 +00:00
|
|
|
* @param *ref A @c FSSpec structure referencing a file.
|
2005-10-06 17:57:18 +00:00
|
|
|
*/
|
2007-11-07 21:35:33 +00:00
|
|
|
static void SetMIDITypeIfNeeded(const FSRef *ref)
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
2006-12-30 11:28:26 +00:00
|
|
|
FSCatalogInfo catalogInfo;
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2007-11-07 21:35:33 +00:00
|
|
|
assert(ref);
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2007-11-07 21:35:33 +00:00
|
|
|
if (noErr != FSGetCatalogInfo(ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL)) return;
|
2006-12-30 11:28:26 +00:00
|
|
|
if (!(catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)) {
|
|
|
|
FileInfo * const info = (FileInfo *) catalogInfo.finderInfo;
|
2010-05-13 10:14:29 +00:00
|
|
|
if (info->fileType != MIDI_TYPE && !(info->finderFlags & kIsAlias)) {
|
2006-12-30 11:28:26 +00:00
|
|
|
OSErr e;
|
2010-05-13 10:14:29 +00:00
|
|
|
info->fileType = MIDI_TYPE;
|
2007-11-07 21:35:33 +00:00
|
|
|
e = FSSetCatalogInfo(ref, kFSCatInfoFinderInfo, &catalogInfo);
|
2006-12-30 11:28:26 +00:00
|
|
|
if (e == noErr) {
|
|
|
|
DEBUG(driver, 3, "qtmidi: changed filetype to 'Midi'");
|
|
|
|
} else {
|
|
|
|
DEBUG(driver, 0, "qtmidi: changing filetype to 'Midi' failed - error %d", e);
|
|
|
|
}
|
|
|
|
}
|
2005-10-06 17:57:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2013-01-08 22:46:42 +00:00
|
|
|
* @return Whether the file was loaded and the @c Movie successfully created.
|
2005-10-06 17:57:18 +00:00
|
|
|
*/
|
|
|
|
static bool LoadMovieForMIDIFile(const char *path, Movie *moov)
|
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
int ret;
|
|
|
|
char magic[4];
|
2007-11-07 21:35:33 +00:00
|
|
|
FSRef fsref;
|
2005-10-06 17:57:18 +00:00
|
|
|
FSSpec fsspec;
|
|
|
|
short refnum = 0;
|
|
|
|
short resid = 0;
|
|
|
|
|
2006-06-10 08:37:41 +00:00
|
|
|
assert(path != NULL);
|
|
|
|
assert(moov != NULL);
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 2, "qtmidi: start loading '%s'...", path);
|
2005-10-06 17:57:18 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
2013-01-08 22:46:42 +00:00
|
|
|
* Perhaps ugly, but it seems that it does the Right Thing(tm).
|
2005-10-06 17:57:18 +00:00
|
|
|
*/
|
2006-06-10 08:37:41 +00:00
|
|
|
fd = open(path, O_RDONLY, 0);
|
|
|
|
if (fd == -1) return false;
|
2005-10-06 17:57:18 +00:00
|
|
|
ret = read(fd, magic, 4);
|
|
|
|
close(fd);
|
|
|
|
if (ret < 4) return false;
|
|
|
|
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: header is '%.4s'", magic);
|
2009-04-10 11:03:48 +00:00
|
|
|
if (magic[0] != 'M' || magic[1] != 'T' || magic[2] != 'h' || magic[3] != 'd') {
|
2005-10-06 17:57:18 +00:00
|
|
|
return false;
|
2009-04-10 11:03:48 +00:00
|
|
|
}
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2007-11-07 21:35:33 +00:00
|
|
|
if (noErr != FSPathMakeRef((const UInt8 *) path, &fsref, NULL)) return false;
|
|
|
|
SetMIDITypeIfNeeded(&fsref);
|
2005-10-06 17:57:18 +00:00
|
|
|
|
2007-11-07 21:35:33 +00:00
|
|
|
if (noErr != FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsspec, NULL)) return false;
|
2006-06-10 08:37:41 +00:00
|
|
|
if (OpenMovieFile(&fsspec, &refnum, fsRdPerm) != noErr) return false;
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: '%s' successfully opened", path);
|
2005-10-06 17:57:18 +00:00
|
|
|
|
|
|
|
if (noErr != NewMovieFromFile(moov, refnum, &resid, NULL,
|
2006-12-27 14:27:30 +00:00
|
|
|
newMovieActive | newMovieDontAskUnresolvedDataRefs, NULL)) {
|
2005-10-06 17:57:18 +00:00
|
|
|
CloseMovieFile(refnum);
|
|
|
|
return false;
|
|
|
|
}
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: movie container created");
|
2005-10-06 17:57:18 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2007-03-07 11:47:46 +00:00
|
|
|
static void InitQuickTimeIfNeeded()
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
OSStatus dummy;
|
|
|
|
|
|
|
|
if (_quicktime_started) return;
|
|
|
|
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 2, "qtmidi: initializing Quicktime");
|
2005-10-06 17:57:18 +00:00
|
|
|
/* Be polite: check wether QuickTime is available and initialize it. */
|
|
|
|
_quicktime_started =
|
|
|
|
(noErr == Gestalt(gestaltQuickTime, &dummy)) &&
|
|
|
|
(noErr == EnterMovies());
|
2006-12-27 14:34:09 +00:00
|
|
|
if (!_quicktime_started) DEBUG(driver, 0, "qtmidi: Quicktime initialization failed!");
|
2005-10-06 17:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Possible states of the QuickTime music driver. */
|
2010-05-13 10:14:29 +00:00
|
|
|
enum QTStates {
|
2009-03-15 00:32:18 +00:00
|
|
|
QT_STATE_IDLE, ///< No file loaded.
|
|
|
|
QT_STATE_PLAY, ///< File loaded, playing.
|
|
|
|
QT_STATE_STOP, ///< File loaded, stopped.
|
2005-10-06 17:57:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2009-03-15 00:32:18 +00:00
|
|
|
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.
|
2005-10-06 17:57:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps OpenTTD volume to QuickTime notion of volume.
|
|
|
|
*/
|
|
|
|
#define VOLUME ((short)((0x00FF & _quicktime_volume) << 1))
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-07-05 12:23:54 +00:00
|
|
|
const char *MusicDriver_QtMidi::Start(const char * const *parm)
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
InitQuickTimeIfNeeded();
|
|
|
|
return (_quicktime_started) ? NULL : "can't initialize QuickTime";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-01-08 22:46:42 +00:00
|
|
|
* Checks whether the player is active.
|
2005-10-06 17:57:18 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-07-05 12:23:54 +00:00
|
|
|
bool MusicDriver_QtMidi::IsSongPlaying()
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
if (!_quicktime_started) return true;
|
|
|
|
|
|
|
|
switch (_quicktime_state) {
|
|
|
|
case QT_STATE_IDLE:
|
|
|
|
case QT_STATE_STOP:
|
|
|
|
/* Do nothing. */
|
|
|
|
break;
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_PLAY:
|
|
|
|
MoviesTask(_quicktime_movie, 0);
|
|
|
|
/* Check wether movie ended. */
|
|
|
|
if (IsMovieDone(_quicktime_movie) ||
|
|
|
|
(GetMovieTime(_quicktime_movie, NULL) >=
|
2009-04-10 11:03:48 +00:00
|
|
|
GetMovieDuration(_quicktime_movie))) {
|
2005-10-06 17:57:18 +00:00
|
|
|
_quicktime_state = QT_STATE_STOP;
|
2009-04-10 11:03:48 +00:00
|
|
|
}
|
2005-10-06 17:57:18 +00:00
|
|
|
}
|
|
|
|
|
2006-06-10 08:37:41 +00:00
|
|
|
return _quicktime_state == QT_STATE_PLAY;
|
2005-10-06 17:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-07-05 12:23:54 +00:00
|
|
|
void MusicDriver_QtMidi::Stop()
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
if (!_quicktime_started) return;
|
|
|
|
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 2, "qtmidi: stopping driver...");
|
2005-10-06 17:57:18 +00:00
|
|
|
switch (_quicktime_state) {
|
|
|
|
case QT_STATE_IDLE:
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: stopping not needed, already idle");
|
2005-10-06 17:57:18 +00:00
|
|
|
/* Do nothing. */
|
|
|
|
break;
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_PLAY:
|
|
|
|
StopSong();
|
2010-07-29 14:26:28 +00:00
|
|
|
/* FALL THROUGH */
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_STOP:
|
|
|
|
DisposeMovie(_quicktime_movie);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExitMovies();
|
|
|
|
_quicktime_started = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts playing a new song.
|
|
|
|
*
|
|
|
|
* @param filename Path to a MIDI file.
|
|
|
|
*/
|
2007-07-05 12:23:54 +00:00
|
|
|
void MusicDriver_QtMidi::PlaySong(const char *filename)
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
if (!_quicktime_started) return;
|
|
|
|
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 2, "qtmidi: trying to play '%s'", filename);
|
2005-10-06 17:57:18 +00:00
|
|
|
switch (_quicktime_state) {
|
|
|
|
case QT_STATE_PLAY:
|
|
|
|
StopSong();
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: previous tune stopped");
|
2010-07-29 14:26:28 +00:00
|
|
|
/* FALL THROUGH */
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_STOP:
|
|
|
|
DisposeMovie(_quicktime_movie);
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: previous tune disposed");
|
2005-10-06 17:57:18 +00:00
|
|
|
_quicktime_state = QT_STATE_IDLE;
|
2010-07-29 14:26:28 +00:00
|
|
|
/* FALL THROUGH */
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_IDLE:
|
|
|
|
LoadMovieForMIDIFile(filename, &_quicktime_movie);
|
|
|
|
SetMovieVolume(_quicktime_movie, VOLUME);
|
|
|
|
StartMovie(_quicktime_movie);
|
|
|
|
_quicktime_state = QT_STATE_PLAY;
|
|
|
|
}
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: playing '%s'", filename);
|
2005-10-06 17:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops playing the current song, if the player is active.
|
|
|
|
*/
|
2007-07-05 12:23:54 +00:00
|
|
|
void MusicDriver_QtMidi::StopSong()
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
if (!_quicktime_started) return;
|
|
|
|
|
|
|
|
switch (_quicktime_state) {
|
|
|
|
case QT_STATE_IDLE:
|
2010-07-29 14:26:28 +00:00
|
|
|
/* FALL THROUGH */
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_STOP:
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: stop requested, but already idle");
|
2005-10-06 17:57:18 +00:00
|
|
|
/* Do nothing. */
|
|
|
|
break;
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_PLAY:
|
|
|
|
StopMovie(_quicktime_movie);
|
|
|
|
_quicktime_state = QT_STATE_STOP;
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 3, "qtmidi: player stopped");
|
2005-10-06 17:57:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2007-07-05 12:23:54 +00:00
|
|
|
void MusicDriver_QtMidi::SetVolume(byte vol)
|
2005-10-06 17:57:18 +00:00
|
|
|
{
|
|
|
|
if (!_quicktime_started) return;
|
|
|
|
|
|
|
|
_quicktime_volume = vol;
|
|
|
|
|
2006-12-27 14:27:30 +00:00
|
|
|
DEBUG(driver, 2, "qtmidi: set volume to %u (%hi)", vol, VOLUME);
|
2005-10-06 17:57:18 +00:00
|
|
|
switch (_quicktime_state) {
|
|
|
|
case QT_STATE_IDLE:
|
|
|
|
/* Do nothing. */
|
|
|
|
break;
|
2009-04-10 11:03:48 +00:00
|
|
|
|
2005-10-06 17:57:18 +00:00
|
|
|
case QT_STATE_PLAY:
|
|
|
|
case QT_STATE_STOP:
|
|
|
|
SetMovieVolume(_quicktime_movie, VOLUME);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-03 15:46:36 +00:00
|
|
|
#endif /* NO_QUICKTIME */
|