Files
@ r4381:c965d1f3016a
Branch filter:
Location: cpp/openttd-patchpack/source/music/qtmidi.c
r4381:c965d1f3016a
9.4 KiB
text/x-c
(svn r6131) -Codechange : Complete all missing _ttdpatch_flags entries
-Feature : both unifiedmaglevmode are now set.
Maglev and monorail are not allowed to run on each other tracks and will not be.
Setting those flags will allow grfsets as the Norvegian one to be loaded
-Codechange : link the TTDPatch's irregularstations with OTTD's nonuniform_stations
-Codechange : Reformat the whole array (thanks Rubidium, it sure looks better now)
-Feature : both unifiedmaglevmode are now set.
Maglev and monorail are not allowed to run on each other tracks and will not be.
Setting those flags will allow grfsets as the Norvegian one to be loaded
-Codechange : link the TTDPatch's irregularstations with OTTD's nonuniform_stations
-Codechange : Reformat the whole array (thanks Rubidium, it sure looks better now)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | /* $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)
{
FInfo info;
assert(spec);
if (noErr != FSpGetFInfo(spec, &info)) return;
/* Set file type to 'Midi' if the file is _not_ an alias. */
if (info.fdType != midiType && !(info.fdFlags & kIsAlias)) {
info.fdType = midiType;
FSpSetFInfo(spec, &info);
DEBUG(driver, 3) ("qtmidi: changed filetype to 'Midi'");
}
}
/**
* 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: begin 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, 1) ("qtmidi: '%s' successfully opened", path);
if (noErr != NewMovieFromFile(moov, refnum, &resid, NULL,
newMovieActive | newMovieDontAskUnresolvedDataRefs, NULL))
{
CloseMovieFile(refnum);
return false;
}
DEBUG(driver, 2) ("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: trying to initialize Quicktime");
/* Be polite: check wether QuickTime is available and initialize it. */
_quicktime_started =
(noErr == Gestalt(gestaltQuickTime, &dummy)) &&
(noErr == EnterMovies());
DEBUG(driver, 1) ("qtmidi: Quicktime was %s initialized",
_quicktime_started ? "successfully" : "NOT"
);
}
/** 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: trying to stop driver...");
switch (_quicktime_state) {
case QT_STATE_IDLE:
DEBUG(driver, 3) ("qtmidi: nothing to do (already idle)");
/* Do nothing. */
break;
case QT_STATE_PLAY:
StopSong();
case QT_STATE_STOP:
DisposeMovie(_quicktime_movie);
}
ExitMovies();
_quicktime_started = false;
DEBUG(driver, 1) ("qtmidi: driver successfully stopped");
}
/**
* Starts playing a new song.
*
* @param filename Path to a MIDI file.
*/
static void PlaySong(const char *filename)
{
if (!_quicktime_started) return;
DEBUG(driver, 3) ("qtmidi: request playing of '%s'n", filename);
switch (_quicktime_state) {
case QT_STATE_PLAY:
StopSong();
DEBUG(driver, 2) ("qtmidi: previous tune stopped");
/* XXX Fall-through -- no break needed. */
case QT_STATE_STOP:
DisposeMovie(_quicktime_movie);
DEBUG(driver, 2) ("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, 1) ("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, 2) ("qtmidi: stop requested, but already idle");
/* Do nothing. */
break;
case QT_STATE_PLAY:
StopMovie(_quicktime_movie);
_quicktime_state = QT_STATE_STOP;
DEBUG(driver, 1) ("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, 3) ("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,
};
|