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/>.
12 * @brief MIDI music player for MacOS X using CoreAudio.
18 #include "../stdafx.h"
19 #include "../os/macosx/macos.h"
24 #define Point OTTDPoint
25 #include <CoreServices/CoreServices.h>
26 #include <AudioUnit/AudioUnit.h>
27 #include <AudioToolbox/AudioToolbox.h>
31 #include "../safeguards.h"
33 #if !defined(HAVE_OSX_1011_SDK)
34 #define kMusicSequenceFile_AnyType 0
37 static FMusicDriver_Cocoa iFMusicDriver_Cocoa
;
40 static MusicPlayer _player
= NULL
;
41 static MusicSequence _sequence
= NULL
;
42 static MusicTimeStamp _seq_length
= 0;
43 static bool _playing
= false;
44 static byte _volume
= 127;
47 /** Set the volume of the current sequence. */
48 static void DoSetVolume()
50 if (_sequence
== NULL
) return;
53 MusicSequenceGetAUGraph(_sequence
, &graph
);
55 AudioUnit output_unit
= NULL
;
57 /* Get output audio unit */
58 UInt32 node_count
= 0;
59 AUGraphGetNodeCount(graph
, &node_count
);
60 for (UInt32 i
= 0; i
< node_count
; i
++) {
62 AUGraphGetIndNode(graph
, i
, &node
);
67 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
68 if (MacOSVersionIsAtLeast(10, 5, 0)) {
69 /* The 10.6 SDK has changed the function prototype of
70 * AUGraphNodeInfo. This is a binary compatible change,
71 * but we need to get the type declaration right or
72 * risk compilation errors. The header AudioComponent.h
73 * was introduced in 10.6 so use it to decide which
74 * type definition to use. */
75 #if defined(__AUDIOCOMPONENT_H__) || defined(HAVE_OSX_107_SDK)
76 AudioComponentDescription desc
;
78 ComponentDescription desc
;
80 AUGraphNodeInfo(graph
, node
, &desc
, &unit
);
81 comp_type
= desc
.componentType
;
85 #if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
86 ComponentDescription desc
;
87 AUGraphGetNodeInfo(graph
, node
, &desc
, NULL
, NULL
, &unit
);
88 comp_type
= desc
.componentType
;
92 if (comp_type
== kAudioUnitType_Output
) {
97 if (output_unit
== NULL
) {
98 DEBUG(driver
, 1, "cocoa_m: Failed to get output node to set volume");
102 Float32 vol
= _volume
/ 127.0f
; // 0 - +127 -> 0.0 - 1.0
103 AudioUnitSetParameter(output_unit
, kHALOutputParam_Volume
, kAudioUnitScope_Global
, 0, vol
, 0);
108 * Initialized the MIDI player, including QuickTime initialization.
110 const char *MusicDriver_Cocoa::Start(const char * const *parm
)
112 if (NewMusicPlayer(&_player
) != noErr
) return "failed to create music player";
119 * Checks wether the player is active.
121 bool MusicDriver_Cocoa::IsSongPlaying()
123 if (!_playing
) return false;
125 MusicTimeStamp time
= 0;
126 MusicPlayerGetTime(_player
, &time
);
127 return time
< _seq_length
;
132 * Stops the MIDI player.
134 void MusicDriver_Cocoa::Stop()
136 if (_player
!= NULL
) DisposeMusicPlayer(_player
);
137 if (_sequence
!= NULL
) DisposeMusicSequence(_sequence
);
142 * Starts playing a new song.
144 * @param filename Path to a MIDI file.
146 void MusicDriver_Cocoa::PlaySong(const char *filename
)
148 DEBUG(driver
, 2, "cocoa_m: trying to play '%s'", filename
);
151 if (_sequence
!= NULL
) {
152 DisposeMusicSequence(_sequence
);
156 if (NewMusicSequence(&_sequence
) != noErr
) {
157 DEBUG(driver
, 0, "cocoa_m: Failed to create music sequence");
161 const char *os_file
= OTTD2FS(filename
);
162 CFURLRef url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
, (const UInt8
*)os_file
, strlen(os_file
), false);
164 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
165 if (MacOSVersionIsAtLeast(10, 5, 0)) {
166 if (MusicSequenceFileLoad(_sequence
, url
, kMusicSequenceFile_AnyType
, 0) != noErr
) {
167 DEBUG(driver
, 0, "cocoa_m: Failed to load MIDI file");
174 #if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
176 if (!CFURLGetFSRef(url
, &ref_file
)) {
177 DEBUG(driver
, 0, "cocoa_m: Failed to make FSRef");
181 if (MusicSequenceLoadSMFWithFlags(_sequence
, &ref_file
, 0) != noErr
) {
182 DEBUG(driver
, 0, "cocoa_m: Failed to load MIDI file old style");
190 /* Construct audio graph */
191 AUGraph graph
= NULL
;
193 MusicSequenceGetAUGraph(_sequence
, &graph
);
195 if (AUGraphInitialize(graph
) != noErr
) {
196 DEBUG(driver
, 0, "cocoa_m: Failed to initialize AU graph");
200 /* Figure out sequence length */
202 MusicSequenceGetTrackCount(_sequence
, &num_tracks
);
204 for (UInt32 i
= 0; i
< num_tracks
; i
++) {
205 MusicTrack track
= NULL
;
206 MusicTimeStamp track_length
= 0;
207 UInt32 prop_size
= sizeof(MusicTimeStamp
);
208 MusicSequenceGetIndTrack(_sequence
, i
, &track
);
209 MusicTrackGetProperty(track
, kSequenceTrackProperty_TrackLength
, &track_length
, &prop_size
);
210 if (track_length
> _seq_length
) _seq_length
= track_length
;
212 /* Add 8 beats for reverb/long note release */
216 MusicPlayerSetSequence(_player
, _sequence
);
217 MusicPlayerPreroll(_player
);
218 if (MusicPlayerStart(_player
) != noErr
) return;
221 DEBUG(driver
, 3, "cocoa_m: playing '%s'", filename
);
226 * Stops playing the current song, if the player is active.
228 void MusicDriver_Cocoa::StopSong()
230 MusicPlayerStop(_player
);
231 MusicPlayerSetSequence(_player
, NULL
);
237 * Changes the playing volume of the MIDI player.
239 * @param vol The desired volume, range of the value is @c 0-127
241 void MusicDriver_Cocoa::SetVolume(byte vol
)
247 #endif /* WITH_COCOA */