1 /*****************************************************************************
2 * audiotoolbox_midi.c: Software MIDI synthesizer using AudioToolbox
3 *****************************************************************************
4 * Copyright (C) 2017 VLC authors and VideoLAN
6 * Authors: Marvin Scholz <epirat07 at gmail dot com>
8 * Based on the fluidsynth module by RĂ©mi Denis-Courmont
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_codec.h>
32 #include <vlc_dialog.h>
34 #include <CoreFoundation/CoreFoundation.h>
35 #include <AudioUnit/AudioUnit.h>
36 #include <AudioToolbox/AudioToolbox.h>
38 #include <TargetConditionals.h>
41 #define on_err_goto(errorCode, exceptionLabel) \
42 do { if ((errorCode) != noErr) goto exceptionLabel; \
46 #define SOUNDFONT_TEXT N_("SoundFont file")
47 #define SOUNDFONT_LONGTEXT N_( \
48 "SoundFont file to use for software synthesis." )
50 static int Open (vlc_object_t
*);
51 static void Close (vlc_object_t
*);
53 #define CFG_PREFIX "aumidi-"
56 set_description(N_("AudioToolbox MIDI synthesizer"))
57 set_capability("audio decoder", 100)
58 set_shortname(N_("AUMIDI"))
59 set_category(CAT_INPUT
)
60 set_subcategory(SUBCAT_INPUT_ACODEC
)
61 set_callbacks(Open
, Close
)
62 add_loadfile(CFG_PREFIX
"soundfont", "",
63 SOUNDFONT_TEXT
, SOUNDFONT_LONGTEXT
)
75 static int DecodeBlock (decoder_t
*p_dec
, block_t
*p_block
);
76 static void Flush (decoder_t
*);
81 kMidiMessage_NoteOff
= 0x80,
82 kMidiMessage_NoteOn
= 0x90,
83 kMidiMessage_PolyPressure
= 0xA0,
84 kMidiMessage_ControlChange
= 0xB0,
85 kMidiMessage_ProgramChange
= 0xC0,
86 kMidiMessage_ChannelPressure
= 0xD0,
87 kMidiMessage_PitchWheel
= 0xE0,
88 kMidiMessage_SysEx
= 0xF0,
90 kMidiMessage_BankMSBControl
= 0,
91 kMidiMessage_BankLSBControl
= 32,
93 /* Values for kMidiMessage_ControlChange */
94 kMidiController_AllSoundOff
= 0x78,
95 kMidiController_ResetAllControllers
= 0x79,
96 kMidiController_AllNotesOff
= 0x7B
99 /* Helper functions */
100 static OSStatus
AddAppleAUNode(AUGraph graph
, OSType type
, OSType subtype
, AUNode
*node
)
102 AudioComponentDescription cDesc
= {};
103 cDesc
.componentType
= type
;
104 cDesc
.componentSubType
= subtype
;
105 cDesc
.componentManufacturer
= kAudioUnitManufacturer_Apple
;
107 return AUGraphAddNode(graph
, &cDesc
, node
);
110 static OSStatus
CreateAUGraph(AUGraph
*outGraph
, AudioUnit
*outSynth
, AudioUnit
*outOut
)
115 AUNode synthNode
, limiterNode
, outNode
;
117 // Create the Graph to which we will add our nodes
118 on_err_goto(res
= NewAUGraph(outGraph
), bailout
);
120 // Create/add the MIDI synthesizer node (DLS Synth)
122 // On iOS/tvOS use MIDISynth, DLSSynth does not exist there
123 on_err_goto(res
= AddAppleAUNode(*outGraph
,
124 kAudioUnitType_MusicDevice
,
125 kAudioUnitSubType_MIDISynth
,
126 &synthNode
), bailout
);
128 // Prefer DLSSynth on macOS, as it has a better default behavior
129 on_err_goto(res
= AddAppleAUNode(*outGraph
,
130 kAudioUnitType_MusicDevice
,
131 kAudioUnitSubType_DLSSynth
,
132 &synthNode
), bailout
);
135 // Create/add the peak limiter node
136 on_err_goto(res
= AddAppleAUNode(*outGraph
,
137 kAudioUnitType_Effect
,
138 kAudioUnitSubType_PeakLimiter
,
139 &limiterNode
), bailout
);
141 // Create/add the output node (GenericOutput)
142 on_err_goto(res
= AddAppleAUNode(*outGraph
,
143 kAudioUnitType_Output
,
144 kAudioUnitSubType_GenericOutput
,
147 // Open the Graph, this opens the units that belong to the graph
148 // so that we can connect them
149 on_err_goto(res
= AUGraphOpen(*outGraph
), bailout
);
151 // Connect the synthesizer node to the limiter
152 on_err_goto(res
= AUGraphConnectNodeInput(*outGraph
, synthNode
, 0, limiterNode
, 0), bailout
);
153 // Connect the limiter node to the output
154 on_err_goto(res
= AUGraphConnectNodeInput(*outGraph
, limiterNode
, 0, outNode
, 0), bailout
);
156 // Get reference to the synthesizer node
157 on_err_goto(res
= AUGraphNodeInfo(*outGraph
, synthNode
, 0, outSynth
), bailout
);
158 // Get reference to the output node
159 on_err_goto(res
= AUGraphNodeInfo(*outGraph
, outNode
, 0, outOut
), bailout
);
165 static int SetSoundfont(decoder_t
*p_dec
, AudioUnit synthUnit
, const char *sfPath
) {
167 msg_Dbg(p_dec
, "using default soundfont");
171 msg_Dbg(p_dec
, "using custom soundfont: '%s'", sfPath
);
172 CFURLRef url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
173 (const UInt8
*)sfPath
,
174 strlen(sfPath
), false);
175 if (unlikely(url
== NULL
))
178 OSStatus status
= AudioUnitSetProperty(synthUnit
,
179 kMusicDeviceProperty_SoundBankURL
,
180 kAudioUnitScope_Global
, 0,
184 if (status
!= noErr
) {
185 msg_Err(p_dec
, "failed setting custom SoundFont for MIDI synthesis (%i)", status
);
191 static int Open(vlc_object_t
*p_this
)
193 decoder_t
*p_dec
= (decoder_t
*)p_this
;
194 OSStatus status
= noErr
;
195 int ret
= VLC_SUCCESS
;
197 if (p_dec
->fmt_in
.i_codec
!= VLC_CODEC_MIDI
)
200 decoder_sys_t
*p_sys
= malloc(sizeof (*p_sys
));
201 if (unlikely(p_sys
== NULL
))
205 status
= CreateAUGraph(&p_sys
->graph
, &p_sys
->synthUnit
, &p_sys
->outputUnit
);
206 if (unlikely(status
!= noErr
)) {
207 msg_Err(p_dec
, "failed to create audiograph (%i)", status
);
212 // Set custom soundfont
213 char *sfPath
= var_InheritString(p_dec
, CFG_PREFIX
"soundfont");
214 ret
= SetSoundfont(p_dec
, p_sys
->synthUnit
, sfPath
);
216 if (unlikely(ret
!= VLC_SUCCESS
))
219 // Set VLC output audio format info
220 p_dec
->fmt_out
.i_codec
= VLC_CODEC_FL32
;
221 p_dec
->fmt_out
.audio
.i_bitspersample
= 32;
222 p_dec
->fmt_out
.audio
.i_rate
= 44100;
223 p_dec
->fmt_out
.audio
.i_channels
= 2;
224 p_dec
->fmt_out
.audio
.i_physical_channels
= AOUT_CHAN_LEFT
| AOUT_CHAN_RIGHT
;
226 if (decoder_UpdateAudioFormat(p_dec
) < 0) {
231 // Prepare AudioUnit output audio format info
232 AudioStreamBasicDescription ASBD
= {};
233 unsigned bytesPerSample
= sizeof(Float32
);
234 ASBD
.mFormatID
= kAudioFormatLinearPCM
;
235 ASBD
.mFormatFlags
= kAudioFormatFlagIsFloat
| kAudioFormatFlagIsPacked
;
236 ASBD
.mSampleRate
= 44100;
237 ASBD
.mFramesPerPacket
= 1;
238 ASBD
.mChannelsPerFrame
= 2;
239 ASBD
.mBytesPerFrame
= bytesPerSample
* ASBD
.mChannelsPerFrame
;
240 ASBD
.mBytesPerPacket
= ASBD
.mBytesPerFrame
* ASBD
.mFramesPerPacket
;
241 ASBD
.mBitsPerChannel
= 8 * bytesPerSample
;
243 // Set AudioUnit format
244 status
= AudioUnitSetProperty(p_sys
->outputUnit
,
245 kAudioUnitProperty_StreamFormat
,
246 kAudioUnitScope_Output
, 0, &ASBD
,
247 sizeof(AudioStreamBasicDescription
));
248 if (unlikely(status
!= noErr
)) {
249 msg_Err(p_dec
, "failed setting output format for output unit (%i)", status
);
255 status
= AUGraphInitialize (p_sys
->graph
);
256 if (unlikely(status
!= noErr
)) {
257 if (status
== kAudioUnitErr_InvalidFile
)
258 msg_Err(p_dec
, "failed initializing audiograph: invalid soundfont file");
260 msg_Err(p_dec
, "failed initializing audiograph (%i)", status
);
265 // Prepare MIDI soundbank
266 MusicDeviceMIDIEvent(p_sys
->synthUnit
,
267 kMidiMessage_ControlChange
,
268 kMidiMessage_BankMSBControl
, 0, 0);
271 status
= AUGraphStart(p_sys
->graph
);
272 if (unlikely(status
!= noErr
)) {
273 msg_Err(p_dec
, "failed starting audiograph (%i)", status
);
278 // Initialize date (for PTS)
279 date_Init(&p_sys
->end_date
, p_dec
->fmt_out
.audio
.i_rate
, 1);
281 p_dec
->p_sys
= p_sys
;
282 p_dec
->pf_decode
= DecodeBlock
;
283 p_dec
->pf_flush
= Flush
;
286 // Cleanup if error occured
287 if (ret
!= VLC_SUCCESS
) {
289 DisposeAUGraph(p_sys
->graph
);
296 static void Close (vlc_object_t
*p_this
)
298 decoder_sys_t
*p_sys
= ((decoder_t
*)p_this
)->p_sys
;
301 AUGraphStop(p_sys
->graph
);
302 DisposeAUGraph(p_sys
->graph
);
307 static void Flush (decoder_t
*p_dec
)
309 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
311 date_Set(&p_sys
->end_date
, VLC_TICK_INVALID
);
313 // Turn all sound on all channels off
314 // else 'old' notes could still be playing
315 for (unsigned channel
= 0; channel
< 16; channel
++) {
316 MusicDeviceMIDIEvent(p_sys
->synthUnit
, kMidiMessage_ControlChange
| channel
, kMidiController_AllSoundOff
, 0, 0);
320 static int DecodeBlock (decoder_t
*p_dec
, block_t
*p_block
)
322 decoder_sys_t
*p_sys
= p_dec
->p_sys
;
323 block_t
*p_out
= NULL
;
324 OSStatus status
= noErr
;
326 if (p_block
== NULL
) /* No Drain */
327 return VLCDEC_SUCCESS
;
329 if (p_block
->i_flags
& (BLOCK_FLAG_DISCONTINUITY
|BLOCK_FLAG_CORRUPTED
)) {
331 if (p_block
->i_flags
& BLOCK_FLAG_CORRUPTED
) {
332 block_Release(p_block
);
333 return VLCDEC_SUCCESS
;
337 if ( p_block
->i_pts
!= VLC_TICK_INVALID
&&
338 date_Get(&p_sys
->end_date
) == VLC_TICK_INVALID
) {
339 date_Set(&p_sys
->end_date
, p_block
->i_pts
);
340 } else if (p_block
->i_pts
< date_Get(&p_sys
->end_date
)) {
341 msg_Warn(p_dec
, "MIDI message in the past?");
345 if (p_block
->i_buffer
< 1)
348 uint8_t event
= p_block
->p_buffer
[0];
349 uint8_t data1
= (p_block
->i_buffer
> 1) ? (p_block
->p_buffer
[1]) : 0;
350 uint8_t data2
= (p_block
->i_buffer
> 2) ? (p_block
->p_buffer
[2]) : 0;
352 switch (event
& 0xF0)
354 case kMidiMessage_NoteOff
:
355 case kMidiMessage_NoteOn
:
356 case kMidiMessage_PolyPressure
:
357 case kMidiMessage_ControlChange
:
358 case kMidiMessage_ProgramChange
:
359 case kMidiMessage_ChannelPressure
:
360 case kMidiMessage_PitchWheel
:
361 MusicDeviceMIDIEvent(p_sys
->synthUnit
, event
, data1
, data2
, 0);
364 case kMidiMessage_SysEx
:
365 if (p_block
->i_buffer
< UINT32_MAX
)
366 MusicDeviceSysEx(p_sys
->synthUnit
, p_block
->p_buffer
, (UInt32
)p_block
->i_buffer
);
370 msg_Warn(p_dec
, "unhandled MIDI event: %x", event
& 0xF0);
374 // Calculate frame count
375 // Simplification of 44100 / 1000000
376 // TODO: Other samplerates
378 (p_block
->i_pts
- date_Get(&p_sys
->end_date
)) * 441 / 10000;
383 p_out
= decoder_NewAudioBuffer(p_dec
, frames
);
387 p_out
->i_pts
= date_Get(&p_sys
->end_date
);
388 p_out
->i_length
= date_Increment(&p_sys
->end_date
, frames
)
391 // Prepare Timestamp for the AudioUnit render call
392 AudioTimeStamp timestamp
= {};
393 timestamp
.mFlags
= kAudioTimeStampWordClockTimeValid
;
394 timestamp
.mWordClockTime
= p_out
->i_pts
;
396 // Prepare Buffer for the AudioUnit render call
397 AudioBufferList bufferList
;
398 bufferList
.mNumberBuffers
= 1;
399 bufferList
.mBuffers
[0].mNumberChannels
= 2;
400 bufferList
.mBuffers
[0].mDataByteSize
= frames
* sizeof(Float32
) * 2;
401 bufferList
.mBuffers
[0].mData
= p_out
->p_buffer
;
403 status
= AudioUnitRender(p_sys
->outputUnit
,
406 frames
, &bufferList
);
408 if (status
!= noErr
) {
409 msg_Warn(p_dec
, "rendering audio unit failed: %i", status
);
410 block_Release(p_out
);
415 block_Release(p_block
);
417 decoder_QueueAudio(p_dec
, p_out
);
418 return VLCDEC_SUCCESS
;