(svn r27729) -Codechange: Do not count static NewGRF when checking for the maximum...
[openttd.git] / src / music / qtmidi.cpp
blobdfd6f16eb39a08ff92d80e79dc56216bc4ad8416
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
10 /**
11 * @file qtmidi.cpp
12 * @brief MIDI music player for MacOS X using QuickTime.
14 * This music player should work in all MacOS X releases starting from 10.0,
15 * as QuickTime is an integral part of the system since the old days of the
16 * Motorola 68k-based Macintoshes. The only extra dependency apart from
17 * QuickTime itself is Carbon, which is included since 10.0 as well.
19 * QuickTime gets fooled with the MIDI files from Transport Tycoon Deluxe
20 * because of the @c .gm suffix. To force QuickTime to load the MIDI files
21 * without the need of dealing with the individual QuickTime components
22 * needed to play music (data source, MIDI parser, note allocators,
23 * synthesizers and the like) some Carbon functions are used to set the file
24 * type as seen by QuickTime, using @c FSpSetFInfo() (which modifies the
25 * file's resource fork).
29 #ifndef NO_QUICKTIME
31 #include "../stdafx.h"
32 #include "qtmidi.h"
33 #include "../debug.h"
35 #define Rect OTTDRect
36 #define Point OTTDPoint
37 #include <QuickTime/QuickTime.h>
38 #undef Rect
39 #undef Point
41 #include "../safeguards.h"
43 static FMusicDriver_QtMidi iFMusicDriver_QtMidi;
46 static const uint MIDI_TYPE = 'Midi'; ///< OSType code for MIDI songs.
49 /**
50 * Sets the @c OSType of a given file to @c 'Midi', but only if it's not
51 * already set.
53 * @param *ref A @c FSSpec structure referencing a file.
55 static void SetMIDITypeIfNeeded(const FSRef *ref)
57 FSCatalogInfo catalogInfo;
59 assert(ref);
61 if (noErr != FSGetCatalogInfo(ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL)) return;
62 if (!(catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)) {
63 FileInfo * const info = (FileInfo *) catalogInfo.finderInfo;
64 if (info->fileType != MIDI_TYPE && !(info->finderFlags & kIsAlias)) {
65 OSErr e;
66 info->fileType = MIDI_TYPE;
67 e = FSSetCatalogInfo(ref, kFSCatInfoFinderInfo, &catalogInfo);
68 if (e == noErr) {
69 DEBUG(driver, 3, "qtmidi: changed filetype to 'Midi'");
70 } else {
71 DEBUG(driver, 0, "qtmidi: changing filetype to 'Midi' failed - error %d", e);
78 /**
79 * Loads a MIDI file and returns it as a QuickTime Movie structure.
81 * @param *path String with the path of an existing MIDI file.
82 * @param *moov Pointer to a @c Movie where the result will be stored.
83 * @return Whether the file was loaded and the @c Movie successfully created.
85 static bool LoadMovieForMIDIFile(const char *path, Movie *moov)
87 int fd;
88 int ret;
89 char magic[4];
90 FSRef fsref;
91 FSSpec fsspec;
92 short refnum = 0;
93 short resid = 0;
95 assert(path != NULL);
96 assert(moov != NULL);
98 DEBUG(driver, 2, "qtmidi: start loading '%s'...", path);
101 * XXX Manual check for MIDI header ('MThd'), as I don't know how to make
102 * QuickTime load MIDI files without a .mid suffix without knowing it's
103 * a MIDI file and setting the OSType of the file to the 'Midi' value.
104 * Perhaps ugly, but it seems that it does the Right Thing(tm).
106 fd = open(path, O_RDONLY, 0);
107 if (fd == -1) return false;
108 ret = read(fd, magic, 4);
109 close(fd);
110 if (ret < 4) return false;
112 DEBUG(driver, 3, "qtmidi: header is '%.4s'", magic);
113 if (magic[0] != 'M' || magic[1] != 'T' || magic[2] != 'h' || magic[3] != 'd') {
114 return false;
117 if (noErr != FSPathMakeRef((const UInt8 *) path, &fsref, NULL)) return false;
118 SetMIDITypeIfNeeded(&fsref);
120 if (noErr != FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsspec, NULL)) return false;
121 if (OpenMovieFile(&fsspec, &refnum, fsRdPerm) != noErr) return false;
122 DEBUG(driver, 3, "qtmidi: '%s' successfully opened", path);
124 if (noErr != NewMovieFromFile(moov, refnum, &resid, NULL,
125 newMovieActive | newMovieDontAskUnresolvedDataRefs, NULL)) {
126 CloseMovieFile(refnum);
127 return false;
129 DEBUG(driver, 3, "qtmidi: movie container created");
131 CloseMovieFile(refnum);
132 return true;
137 * Flag which has the @c true value when QuickTime is available and
138 * initialized.
140 static bool _quicktime_started = false;
144 * Initialize QuickTime if needed. This function sets the
145 * #_quicktime_started flag to @c true if QuickTime is present in the system
146 * and it was initialized properly.
148 static void InitQuickTimeIfNeeded()
150 OSStatus dummy;
152 if (_quicktime_started) return;
154 DEBUG(driver, 2, "qtmidi: initializing Quicktime");
155 /* Be polite: check wether QuickTime is available and initialize it. */
156 _quicktime_started =
157 (noErr == Gestalt(gestaltQuickTime, &dummy)) &&
158 (noErr == EnterMovies());
159 if (!_quicktime_started) DEBUG(driver, 0, "qtmidi: Quicktime initialization failed!");
163 /** Possible states of the QuickTime music driver. */
164 enum QTStates {
165 QT_STATE_IDLE, ///< No file loaded.
166 QT_STATE_PLAY, ///< File loaded, playing.
167 QT_STATE_STOP, ///< File loaded, stopped.
171 static Movie _quicktime_movie; ///< Current QuickTime @c Movie.
172 static byte _quicktime_volume = 127; ///< Current volume.
173 static int _quicktime_state = QT_STATE_IDLE; ///< Current player state.
177 * Maps OpenTTD volume to QuickTime notion of volume.
179 #define VOLUME ((short)((0x00FF & _quicktime_volume) << 1))
183 * Initialized the MIDI player, including QuickTime initialization.
185 * @todo Give better error messages by inspecting error codes returned by
186 * @c Gestalt() and @c EnterMovies(). Needs changes in
187 * #InitQuickTimeIfNeeded.
189 const char *MusicDriver_QtMidi::Start(const char * const *parm)
191 InitQuickTimeIfNeeded();
192 return (_quicktime_started) ? NULL : "can't initialize QuickTime";
197 * Checks whether the player is active.
199 * This function is called at regular intervals from OpenTTD's main loop, so
200 * we call @c MoviesTask() from here to let QuickTime do its work.
202 bool MusicDriver_QtMidi::IsSongPlaying()
204 if (!_quicktime_started) return true;
206 switch (_quicktime_state) {
207 case QT_STATE_IDLE:
208 case QT_STATE_STOP:
209 /* Do nothing. */
210 break;
212 case QT_STATE_PLAY:
213 MoviesTask(_quicktime_movie, 0);
214 /* Check wether movie ended. */
215 if (IsMovieDone(_quicktime_movie) ||
216 (GetMovieTime(_quicktime_movie, NULL) >=
217 GetMovieDuration(_quicktime_movie))) {
218 _quicktime_state = QT_STATE_STOP;
222 return _quicktime_state == QT_STATE_PLAY;
227 * Stops the MIDI player.
229 * Stops playing and frees any used resources before returning. As it
230 * deinitilizes QuickTime, the #_quicktime_started flag is set to @c false.
232 void MusicDriver_QtMidi::Stop()
234 if (!_quicktime_started) return;
236 DEBUG(driver, 2, "qtmidi: stopping driver...");
237 switch (_quicktime_state) {
238 case QT_STATE_IDLE:
239 DEBUG(driver, 3, "qtmidi: stopping not needed, already idle");
240 /* Do nothing. */
241 break;
243 case QT_STATE_PLAY:
244 StopSong();
245 /* FALL THROUGH */
247 case QT_STATE_STOP:
248 DisposeMovie(_quicktime_movie);
251 ExitMovies();
252 _quicktime_started = false;
257 * Starts playing a new song.
259 * @param filename Path to a MIDI file.
261 void MusicDriver_QtMidi::PlaySong(const char *filename)
263 if (!_quicktime_started) return;
265 DEBUG(driver, 2, "qtmidi: trying to play '%s'", filename);
266 switch (_quicktime_state) {
267 case QT_STATE_PLAY:
268 StopSong();
269 DEBUG(driver, 3, "qtmidi: previous tune stopped");
270 /* FALL THROUGH */
272 case QT_STATE_STOP:
273 DisposeMovie(_quicktime_movie);
274 DEBUG(driver, 3, "qtmidi: previous tune disposed");
275 _quicktime_state = QT_STATE_IDLE;
276 /* FALL THROUGH */
278 case QT_STATE_IDLE:
279 LoadMovieForMIDIFile(filename, &_quicktime_movie);
280 SetMovieVolume(_quicktime_movie, VOLUME);
281 StartMovie(_quicktime_movie);
282 _quicktime_state = QT_STATE_PLAY;
284 DEBUG(driver, 3, "qtmidi: playing '%s'", filename);
289 * Stops playing the current song, if the player is active.
291 void MusicDriver_QtMidi::StopSong()
293 if (!_quicktime_started) return;
295 switch (_quicktime_state) {
296 case QT_STATE_IDLE:
297 /* FALL THROUGH */
299 case QT_STATE_STOP:
300 DEBUG(driver, 3, "qtmidi: stop requested, but already idle");
301 /* Do nothing. */
302 break;
304 case QT_STATE_PLAY:
305 StopMovie(_quicktime_movie);
306 _quicktime_state = QT_STATE_STOP;
307 DEBUG(driver, 3, "qtmidi: player stopped");
313 * Changes the playing volume of the MIDI player.
315 * As QuickTime controls volume in a per-movie basis, the desired volume is
316 * stored in #_quicktime_volume, and the volume is set here using the
317 * #VOLUME macro, @b and when loading new song in #PlaySong.
319 * @param vol The desired volume, range of the value is @c 0-127
321 void MusicDriver_QtMidi::SetVolume(byte vol)
323 if (!_quicktime_started) return;
325 _quicktime_volume = vol;
327 DEBUG(driver, 2, "qtmidi: set volume to %u (%hi)", vol, VOLUME);
328 switch (_quicktime_state) {
329 case QT_STATE_IDLE:
330 /* Do nothing. */
331 break;
333 case QT_STATE_PLAY:
334 case QT_STATE_STOP:
335 SetMovieVolume(_quicktime_movie, VOLUME);
339 #endif /* NO_QUICKTIME */