OpenTTD
cocoa_m.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 
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  */
9 
16 #ifdef WITH_COCOA
17 
18 #include "../stdafx.h"
19 #include "../os/macosx/macos.h"
20 #include "cocoa_m.h"
21 #include "midifile.hpp"
22 #include "../debug.h"
23 #include "../base_media_base.h"
24 
25 #include <CoreServices/CoreServices.h>
26 #include <AudioUnit/AudioUnit.h>
27 #include <AudioToolbox/AudioToolbox.h>
28 
29 #include "../safeguards.h"
30 
31 #if !defined(HAVE_OSX_1011_SDK)
32 #define kMusicSequenceFile_AnyType 0
33 #endif
34 
35 static FMusicDriver_Cocoa iFMusicDriver_Cocoa;
36 
37 
38 static MusicPlayer _player = nullptr;
39 static MusicSequence _sequence = nullptr;
40 static MusicTimeStamp _seq_length = 0;
41 static bool _playing = false;
42 static byte _volume = 127;
43 
44 
46 static void DoSetVolume()
47 {
48  if (_sequence == nullptr) return;
49 
50  AUGraph graph;
51  MusicSequenceGetAUGraph(_sequence, &graph);
52 
53  AudioUnit output_unit = nullptr;
54 
55  /* Get output audio unit */
56  UInt32 node_count = 0;
57  AUGraphGetNodeCount(graph, &node_count);
58  for (UInt32 i = 0; i < node_count; i++) {
59  AUNode node;
60  AUGraphGetIndNode(graph, i, &node);
61 
62  AudioUnit unit;
63  OSType comp_type = 0;
64 
65 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
66  if (MacOSVersionIsAtLeast(10, 5, 0)) {
67  /* The 10.6 SDK has changed the function prototype of
68  * AUGraphNodeInfo. This is a binary compatible change,
69  * but we need to get the type declaration right or
70  * risk compilation errors. The header AudioComponent.h
71  * was introduced in 10.6 so use it to decide which
72  * type definition to use. */
73 #if defined(__AUDIOCOMPONENT_H__) || defined(HAVE_OSX_107_SDK)
74  AudioComponentDescription desc;
75 #else
76  ComponentDescription desc;
77 #endif
78  AUGraphNodeInfo(graph, node, &desc, &unit);
79  comp_type = desc.componentType;
80  } else
81 #endif
82  {
83 #if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
84  ComponentDescription desc;
85  AUGraphGetNodeInfo(graph, node, &desc, nullptr, nullptr, &unit);
86  comp_type = desc.componentType;
87 #endif
88  }
89 
90  if (comp_type == kAudioUnitType_Output) {
91  output_unit = unit;
92  break;
93  }
94  }
95  if (output_unit == nullptr) {
96  DEBUG(driver, 1, "cocoa_m: Failed to get output node to set volume");
97  return;
98  }
99 
100  Float32 vol = _volume / 127.0f; // 0 - +127 -> 0.0 - 1.0
101  AudioUnitSetParameter(output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, vol, 0);
102 }
103 
104 
108 const char *MusicDriver_Cocoa::Start(const char * const *parm)
109 {
110  if (NewMusicPlayer(&_player) != noErr) return "failed to create music player";
111 
112  return nullptr;
113 }
114 
115 
120 {
121  if (!_playing) return false;
122 
123  MusicTimeStamp time = 0;
124  MusicPlayerGetTime(_player, &time);
125  return time < _seq_length;
126 }
127 
128 
133 {
134  if (_player != nullptr) DisposeMusicPlayer(_player);
135  if (_sequence != nullptr) DisposeMusicSequence(_sequence);
136 }
137 
138 
145 {
146  std::string filename = MidiFile::GetSMFFile(song);
147 
148  DEBUG(driver, 2, "cocoa_m: trying to play '%s'", filename.c_str());
149 
150  this->StopSong();
151  if (_sequence != nullptr) {
152  DisposeMusicSequence(_sequence);
153  _sequence = nullptr;
154  }
155 
156  if (filename.empty()) return;
157 
158  if (NewMusicSequence(&_sequence) != noErr) {
159  DEBUG(driver, 0, "cocoa_m: Failed to create music sequence");
160  return;
161  }
162 
163  const char *os_file = OTTD2FS(filename.c_str());
164  CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)os_file, strlen(os_file), false);
165 
166 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
167  if (MacOSVersionIsAtLeast(10, 5, 0)) {
168  if (MusicSequenceFileLoad(_sequence, url, kMusicSequenceFile_AnyType, 0) != noErr) {
169  DEBUG(driver, 0, "cocoa_m: Failed to load MIDI file");
170  CFRelease(url);
171  return;
172  }
173  } else
174 #endif
175  {
176 #if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
177  FSRef ref_file;
178  if (!CFURLGetFSRef(url, &ref_file)) {
179  DEBUG(driver, 0, "cocoa_m: Failed to make FSRef");
180  CFRelease(url);
181  return;
182  }
183  if (MusicSequenceLoadSMFWithFlags(_sequence, &ref_file, 0) != noErr) {
184  DEBUG(driver, 0, "cocoa_m: Failed to load MIDI file old style");
185  CFRelease(url);
186  return;
187  }
188 #endif
189  }
190  CFRelease(url);
191 
192  /* Construct audio graph */
193  AUGraph graph = nullptr;
194 
195  MusicSequenceGetAUGraph(_sequence, &graph);
196  AUGraphOpen(graph);
197  if (AUGraphInitialize(graph) != noErr) {
198  DEBUG(driver, 0, "cocoa_m: Failed to initialize AU graph");
199  return;
200  }
201 
202  /* Figure out sequence length */
203  UInt32 num_tracks;
204  MusicSequenceGetTrackCount(_sequence, &num_tracks);
205  _seq_length = 0;
206  for (UInt32 i = 0; i < num_tracks; i++) {
207  MusicTrack track = nullptr;
208  MusicTimeStamp track_length = 0;
209  UInt32 prop_size = sizeof(MusicTimeStamp);
210  MusicSequenceGetIndTrack(_sequence, i, &track);
211  MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &track_length, &prop_size);
212  if (track_length > _seq_length) _seq_length = track_length;
213  }
214  /* Add 8 beats for reverb/long note release */
215  _seq_length += 8;
216 
217  DoSetVolume();
218  MusicPlayerSetSequence(_player, _sequence);
219  MusicPlayerPreroll(_player);
220  if (MusicPlayerStart(_player) != noErr) return;
221  _playing = true;
222 
223  DEBUG(driver, 3, "cocoa_m: playing '%s'", filename.c_str());
224 }
225 
226 
231 {
232  MusicPlayerStop(_player);
233  MusicPlayerSetSequence(_player, nullptr);
234  _playing = false;
235 }
236 
237 
243 void MusicDriver_Cocoa::SetVolume(byte vol)
244 {
245  _volume = vol;
246  DoSetVolume();
247 }
248 
249 #endif /* WITH_COCOA */
Metadata about a music track.
void SetVolume(byte vol) override
Set the volume, if possible.
static bool MacOSVersionIsAtLeast(long major, long minor, long bugfix)
Check if we are at least running on the specified version of Mac OS.
Definition: macos.h:27
void Stop() override
Stop this driver.
const char * Start(const char *const *param) override
Start this driver.
bool IsSongPlaying() override
Are we currently playing a song?
Base of music playback via CoreAudio.
const TCHAR * OTTD2FS(const char *name, bool console_cp)
Convert from OpenTTD&#39;s encoding to that of the local environment.
Definition: win32.cpp:578
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:37
static std::string GetSMFFile(const MusicSongInfo &song)
Get the name of a Standard MIDI File for a given song.
Definition: midifile.cpp:1050
void StopSong() override
Stop playing the current song.
void PlaySong(const MusicSongInfo &song) override
Play a particular song.