Merge pull request #1081 from mattrtaylor/coreaudio-channel-map

Multi-channel output for the CoreAudio driver.
This commit is contained in:
Tom M 2022-04-21 13:01:39 +02:00 committed by GitHub
commit 486e3f34d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 128 additions and 36 deletions

View file

@ -452,6 +452,31 @@ Developers:
Selects the CoreAudio device to use.
</desc>
</setting>
<setting>
<name>coreaudio.channel-map</name>
<type>str</type>
<def>(empty string)</def>
<desc>
This setting is a comma-separated integer list that maps CoreAudio channels
to output device channels. The value of each position in the list, up to the number of
output channels available on your device, is the zero-based index of the fluidsynth
output channel to route there. Additionally, the special value of -1 will turn off
an output.
For example, the default map for a single stereo output is "0,1". A value of "0,0" will
copy the left channel to the right, a value of "1,0" will flip left and right, and a
value of "-1,1" will play only the right channel.
With a six-channel output device, and the synth.audio-channels and synth.audio-groups
settings both set to "2", a channel map of "-1,-1,0,1,2,3" will result in notes from odd
MIDI channels (audible on the first stereo channel, i.e. mono-indices 0,1) being sent to
outputs 3 and 4, and even MIDI channels (audible on the second stereo channel, i.e. mono-indices 2,3)
being sent to outputs 5 and 6.
If the list specifies fewer than the number of available outputs channels, outputs
beyond those specified will maintain the default channel mapping.
</desc>
</setting>
<setting>
<name>dart.device</name>
<type>str</type>

View file

@ -52,7 +52,8 @@ typedef struct
fluid_audio_func_t callback;
void *data;
unsigned int buffer_size;
float *buffers[2];
unsigned int buffer_count;
float **buffers;
double phase;
} fluid_core_audio_driver_t;
@ -114,6 +115,76 @@ get_num_outputs(AudioDeviceID deviceID)
return total;
}
void
set_channel_map(AudioUnit outputUnit, int audio_channels, const char *map_string)
{
OSStatus status;
long int number_of_channels;
int i, *channel_map;
UInt32 property_size;
Boolean writable = false;
status = AudioUnitGetPropertyInfo(outputUnit,
kAudioOutputUnitProperty_ChannelMap,
kAudioUnitScope_Output,
0,
&property_size, &writable);
if(status != noErr)
{
FLUID_LOG(FLUID_ERR, "Failed to get the channel map size. Status=%ld\n", (long int) status);
return;
}
number_of_channels = property_size / sizeof(int);
if(!number_of_channels)
{
return;
}
channel_map = FLUID_ARRAY(int, number_of_channels);
if(channel_map == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory.\n");
return;
}
FLUID_MEMSET(channel_map, 0xff, property_size);
status = AudioUnitGetProperty(outputUnit,
kAudioOutputUnitProperty_ChannelMap,
kAudioUnitScope_Output,
0,
channel_map, &property_size);
if(status != noErr)
{
FLUID_LOG(FLUID_ERR, "Failed to get the existing channel map. Status=%ld\n", (long int) status);
FLUID_FREE(channel_map);
return;
}
fluid_settings_split_csv(map_string, channel_map, (int) number_of_channels);
for(i = 0; i < number_of_channels; i++)
{
if(channel_map[i] < -1 || channel_map[i] >= audio_channels)
{
FLUID_LOG(FLUID_DBG, "Channel map of output channel %d is out-of-range. Silencing.", i);
channel_map[i] = -1;
}
}
status = AudioUnitSetProperty(outputUnit,
kAudioOutputUnitProperty_ChannelMap,
kAudioUnitScope_Output,
0,
channel_map, property_size);
if(status != noErr)
{
FLUID_LOG(FLUID_ERR, "Failed to set the channel map. Status=%ld\n", (long int) status);
}
FLUID_FREE(channel_map);
}
void
fluid_core_audio_driver_settings(fluid_settings_t *settings)
{
@ -125,6 +196,7 @@ fluid_core_audio_driver_settings(fluid_settings_t *settings)
pa.mElement = kAudioObjectPropertyElementMain;
fluid_settings_register_str(settings, "audio.coreaudio.device", "default", 0);
fluid_settings_register_str(settings, "audio.coreaudio.channel-map", "", 0);
fluid_settings_add_option(settings, "audio.coreaudio.device", "default");
if(OK(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa, 0, 0, &size)))
@ -169,9 +241,9 @@ new_fluid_core_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
fluid_audio_driver_t *
new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data)
{
char *devname = NULL;
char *devname = NULL, *channel_map = NULL;
fluid_core_audio_driver_t *dev = NULL;
int period_size, periods;
int period_size, periods, audio_channels = 1;
double sample_rate;
OSStatus status;
UInt32 size;
@ -245,10 +317,14 @@ new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func
goto error_recovery;
}
fluid_settings_getint(settings, "synth.audio-channels", &audio_channels);
fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
fluid_settings_getint(settings, "audio.periods", &periods);
fluid_settings_getint(settings, "audio.period-size", &period_size);
/* audio channels are in stereo, with a minimum of one pair */
audio_channels = (audio_channels > 0) ? (2 * audio_channels) : 2;
/* get the selected device name. if none is specified, use NULL for the default device. */
if(fluid_settings_dupstr(settings, "audio.coreaudio.device", &devname) == FLUID_OK /* alloc device name */
&& devname && strlen(devname) > 0)
@ -303,11 +379,11 @@ new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func
// necessary from our format to the device's format.
dev->format.mSampleRate = sample_rate; // sample rate of the audio stream
dev->format.mFormatID = kAudioFormatLinearPCM; // encoding type of the audio stream
dev->format.mFormatFlags = kLinearPCMFormatFlagIsFloat;
dev->format.mBytesPerPacket = 2 * sizeof(float);
dev->format.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved;
dev->format.mBytesPerPacket = sizeof(float);
dev->format.mFramesPerPacket = 1;
dev->format.mBytesPerFrame = 2 * sizeof(float);
dev->format.mChannelsPerFrame = 2;
dev->format.mBytesPerFrame = sizeof(float);
dev->format.mChannelsPerFrame = audio_channels;
dev->format.mBitsPerChannel = 8 * sizeof(float);
FLUID_LOG(FLUID_DBG, "mSampleRate %g", dev->format.mSampleRate);
@ -331,6 +407,13 @@ new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func
goto error_recovery;
}
if(fluid_settings_dupstr(settings, "audio.coreaudio.channel-map", &channel_map) == FLUID_OK /* alloc channel map */
&& channel_map && strlen(channel_map) > 0)
{
set_channel_map(dev->outputUnit, audio_channels, channel_map);
}
FLUID_FREE(channel_map); /* free channel map */
status = AudioUnitSetProperty(dev->outputUnit,
kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Input,
@ -346,15 +429,16 @@ new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func
FLUID_LOG(FLUID_DBG, "MaximumFramesPerSlice = %d", dev->buffer_size);
dev->buffers[0] = FLUID_ARRAY(float, dev->buffer_size);
dev->buffers[1] = FLUID_ARRAY(float, dev->buffer_size);
dev->buffers = FLUID_ARRAY(float *, audio_channels);
if(dev->buffers[0] == NULL || dev->buffers[1] == NULL)
if(dev->buffers == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory.");
goto error_recovery;
}
dev->buffer_count = (unsigned int) audio_channels;
// Initialize the audio unit
status = AudioUnitInitialize(dev->outputUnit);
@ -396,14 +480,9 @@ delete_fluid_core_audio_driver(fluid_audio_driver_t *p)
AudioComponentInstanceDispose(dev->outputUnit);
#endif
if(dev->buffers[0])
if(dev->buffers != NULL)
{
FLUID_FREE(dev->buffers[0]);
}
if(dev->buffers[1])
{
FLUID_FREE(dev->buffers[1]);
FLUID_FREE(dev->buffers);
}
FLUID_FREE(dev);
@ -417,30 +496,18 @@ fluid_core_audio_callback(void *data,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
int i, k;
fluid_core_audio_driver_t *dev = (fluid_core_audio_driver_t *) data;
int len = inNumberFrames;
float *buffer = ioData->mBuffers[0].mData;
UInt32 i, nBuffers = ioData->mNumberBuffers;
fluid_audio_func_t callback = (dev->callback != NULL) ? dev->callback : (fluid_audio_func_t) fluid_synth_process;
if(dev->callback)
for(i = 0; i < ioData->mNumberBuffers && i < dev->buffer_count; i++)
{
float *left = dev->buffers[0];
float *right = dev->buffers[1];
FLUID_MEMSET(left, 0, len * sizeof(float));
FLUID_MEMSET(right, 0, len * sizeof(float));
(*dev->callback)(dev->data, len, 0, NULL, 2, dev->buffers);
for(i = 0, k = 0; i < len; i++)
{
buffer[k++] = left[i];
buffer[k++] = right[i];
}
dev->buffers[i] = ioData->mBuffers[i].mData;
FLUID_MEMSET(dev->buffers[i], 0, len * sizeof(float));
}
else
fluid_synth_write_float((fluid_synth_t *) dev->data, len, buffer, 0, 2,
buffer, 1, 2);
callback(dev->data, len, nBuffers, dev->buffers, nBuffers, dev->buffers);
return noErr;
}