mirror of
https://github.com/OpenTTD/OpenTTD.git
synced 2025-03-06 14:27:16 +00:00
(svn r3022) -feature: [OSX] OSX now uses quicktime to play midi files
this eliminates the long pauses between songs and the "leaving process hehind" bug (moebius_)
This commit is contained in:
parent
9666e753fb
commit
37af5bc475
1
Makefile
1
Makefile
@ -711,6 +711,7 @@ endif
|
||||
|
||||
ifdef OSX
|
||||
OBJC_SOURCES += os/macosx/macos.m
|
||||
C_SOURCES += music/qtmidi.c
|
||||
endif
|
||||
|
||||
OBJS = $(C_SOURCES:%.c=%.o) $(CXX_SOURCES:%.cpp=%.o) $(OBJC_SOURCES:%.m=%.o)
|
||||
|
4
driver.c
4
driver.c
@ -14,6 +14,7 @@
|
||||
#include "music/null_m.h"
|
||||
#include "music/os2_m.h"
|
||||
#include "music/win32_m.h"
|
||||
#include "music/qtmidi.h"
|
||||
|
||||
#include "sound/null_s.h"
|
||||
#include "sound/sdl_s.h"
|
||||
@ -51,6 +52,9 @@ static const DriverDesc _music_driver_descs[] = {
|
||||
#ifdef WIN32
|
||||
M("win32", "Win32 MIDI Driver", &_win32_music_driver),
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
M("qt", "QuickTime MIDI Driver", &_qtime_music_driver),
|
||||
#endif
|
||||
#ifdef UNIX
|
||||
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
|
||||
M("extmidi", "External MIDI Driver", &_extmidi_music_driver),
|
||||
|
379
music/qtmidi.c
Normal file
379
music/qtmidi.c
Normal file
@ -0,0 +1,379 @@
|
||||
/*
|
||||
* qtmidi.c - MIDI Player for OpenTTD using QuickTime (MacOS X).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
#include "../stdafx.h"
|
||||
#include "../openttd.h"
|
||||
#include "../debug.h"
|
||||
#include "qtmidi.h"
|
||||
|
||||
/*
|
||||
* System includes. We need to workaround with some defines because there's
|
||||
* stuff already defined in QuickTime headers.
|
||||
*/
|
||||
#define bool OSX_bool
|
||||
#define Rect OSX_Rect
|
||||
#define Point OSX_Point
|
||||
#define SL_ERROR OSX_SL_ERROR
|
||||
#define WindowClass OSX_WindowClass
|
||||
#define OTTD_Random OSX_OTTD_Random
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <QuickTime/QuickTime.h>
|
||||
#undef OTTD_Random
|
||||
#undef WindowClass
|
||||
#undef SL_ERROR
|
||||
#undef Point
|
||||
#undef Rect
|
||||
#undef bool
|
||||
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.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);
|
||||
assert(path);
|
||||
|
||||
if (noErr != FSPathMakeRef((UInt8*) path, &ref, NULL))
|
||||
return false;
|
||||
|
||||
return (noErr ==
|
||||
FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, spec, NULL));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
assert(moov);
|
||||
|
||||
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).
|
||||
*/
|
||||
if ((fd = open(path, O_RDONLY, 0)) == -1)
|
||||
return false;
|
||||
ret = read(fd, magic, 4);
|
||||
close(fd);
|
||||
if (ret < 4) return false;
|
||||
|
||||
DEBUG(driver, 3) ("qtmidi: header is '%c%c%c%c'",
|
||||
magic[0], magic[1], magic[2], magic[3]);
|
||||
if (magic[0] != 'M' || magic[1] != 'T' || magic[2] != 'h' || magic[3] != 'd')
|
||||
return false;
|
||||
|
||||
if (!PathToFSSpec(path, &fsspec))
|
||||
return false;
|
||||
SetMIDITypeIfNeeded(&fsspec);
|
||||
|
||||
if (noErr != OpenMovieFile(&fsspec, &refnum, fsRdPerm))
|
||||
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,
|
||||
};
|
||||
|
||||
|
15
music/qtmidi.h
Normal file
15
music/qtmidi.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* qtmidi.h
|
||||
*
|
||||
* $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 */
|
||||
|
Loading…
Reference in New Issue
Block a user