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/>.
10 * @brief MIDI music player for MacOS X using CoreAudio.
16 #include "../stdafx.h"
17 #include "../os/macosx/macos.h"
19 #include "midifile.hpp"
21 #include "../base_media_base.h"
23 #include <CoreServices/CoreServices.h>
24 #include <AudioUnit/AudioUnit.h>
25 #include <AudioToolbox/AudioToolbox.h>
27 #include "../safeguards.h"
29 #if !defined(HAVE_OSX_1011_SDK)
30 #define kMusicSequenceFile_AnyType 0
33 static FMusicDriver_Cocoa iFMusicDriver_Cocoa
;
36 static MusicPlayer _player
= nullptr;
37 static MusicSequence _sequence
= nullptr;
38 static MusicTimeStamp _seq_length
= 0;
39 static bool _playing
= false;
40 static uint8_t _volume
= 127;
43 /** Set the volume of the current sequence. */
44 static void DoSetVolume()
46 if (_sequence
== nullptr) return;
49 MusicSequenceGetAUGraph(_sequence
, &graph
);
51 AudioUnit output_unit
= nullptr;
53 /* Get output audio unit */
54 UInt32 node_count
= 0;
55 AUGraphGetNodeCount(graph
, &node_count
);
56 for (UInt32 i
= 0; i
< node_count
; i
++) {
58 AUGraphGetIndNode(graph
, i
, &node
);
61 AudioComponentDescription desc
;
62 AUGraphNodeInfo(graph
, node
, &desc
, &unit
);
64 if (desc
.componentType
== kAudioUnitType_Output
) {
69 if (output_unit
== nullptr) {
70 Debug(driver
, 1, "cocoa_m: Failed to get output node to set volume");
74 Float32 vol
= _volume
/ 127.0f
; // 0 - +127 -> 0.0 - 1.0
75 AudioUnitSetParameter(output_unit
, kHALOutputParam_Volume
, kAudioUnitScope_Global
, 0, vol
, 0);
80 * Initialized the MIDI player, including QuickTime initialization.
82 std::optional
<std::string_view
> MusicDriver_Cocoa::Start(const StringList
&)
84 if (NewMusicPlayer(&_player
) != noErr
) return "failed to create music player";
91 * Checks whether the player is active.
93 bool MusicDriver_Cocoa::IsSongPlaying()
95 if (!_playing
) return false;
97 MusicTimeStamp time
= 0;
98 MusicPlayerGetTime(_player
, &time
);
99 return time
< _seq_length
;
104 * Stops the MIDI player.
106 void MusicDriver_Cocoa::Stop()
108 if (_player
!= nullptr) DisposeMusicPlayer(_player
);
109 if (_sequence
!= nullptr) DisposeMusicSequence(_sequence
);
114 * Starts playing a new song.
116 * @param song Description of music to load and play
118 void MusicDriver_Cocoa::PlaySong(const MusicSongInfo
&song
)
120 std::string filename
= MidiFile::GetSMFFile(song
);
122 Debug(driver
, 2, "cocoa_m: trying to play '{}'", filename
);
125 if (_sequence
!= nullptr) {
126 DisposeMusicSequence(_sequence
);
130 if (filename
.empty()) return;
132 if (NewMusicSequence(&_sequence
) != noErr
) {
133 Debug(driver
, 0, "cocoa_m: Failed to create music sequence");
137 std::string os_file
= OTTD2FS(filename
);
138 CFAutoRelease
<CFURLRef
> url(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
, (const UInt8
*)os_file
.c_str(), os_file
.length(), false));
140 if (MusicSequenceFileLoad(_sequence
, url
.get(), kMusicSequenceFile_AnyType
, 0) != noErr
) {
141 Debug(driver
, 0, "cocoa_m: Failed to load MIDI file");
145 /* Construct audio graph */
146 AUGraph graph
= nullptr;
148 MusicSequenceGetAUGraph(_sequence
, &graph
);
150 if (AUGraphInitialize(graph
) != noErr
) {
151 Debug(driver
, 0, "cocoa_m: Failed to initialize AU graph");
155 /* Figure out sequence length */
157 MusicSequenceGetTrackCount(_sequence
, &num_tracks
);
159 for (UInt32 i
= 0; i
< num_tracks
; i
++) {
160 MusicTrack track
= nullptr;
161 MusicTimeStamp track_length
= 0;
162 UInt32 prop_size
= sizeof(MusicTimeStamp
);
163 MusicSequenceGetIndTrack(_sequence
, i
, &track
);
164 MusicTrackGetProperty(track
, kSequenceTrackProperty_TrackLength
, &track_length
, &prop_size
);
165 if (track_length
> _seq_length
) _seq_length
= track_length
;
167 /* Add 8 beats for reverb/long note release */
171 MusicPlayerSetSequence(_player
, _sequence
);
172 MusicPlayerPreroll(_player
);
173 if (MusicPlayerStart(_player
) != noErr
) return;
176 Debug(driver
, 3, "cocoa_m: playing '{}'", filename
);
181 * Stops playing the current song, if the player is active.
183 void MusicDriver_Cocoa::StopSong()
185 MusicPlayerStop(_player
);
186 MusicPlayerSetSequence(_player
, nullptr);
192 * Changes the playing volume of the MIDI player.
194 * @param vol The desired volume, range of the value is @c 0-127
196 void MusicDriver_Cocoa::SetVolume(uint8_t vol
)
202 #endif /* WITH_COCOA */