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