Skip to content

Commit

Permalink
Merge pull request #1081 from mattrtaylor/coreaudio-channel-map
Browse files Browse the repository at this point in the history
Multi-channel output for the CoreAudio driver.
  • Loading branch information
derselbst committed Apr 21, 2022
2 parents 5dcae73 + b80ad6e commit 486e3f3
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 36 deletions.
25 changes: 25 additions & 0 deletions doc/fluidsettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
139 changes: 103 additions & 36 deletions src/drivers/fluid_coreaudio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
{
Expand All @@ -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)))
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);

Expand Down Expand Up @@ -396,14 +480,9 @@ delete_fluid_core_audio_driver(fluid_audio_driver_t *p)
AudioComponentInstanceDispose(dev->outputUnit);
#endif

if(dev->buffers[0])
{
FLUID_FREE(dev->buffers[0]);
}

if(dev->buffers[1])
if(dev->buffers != NULL)
{
FLUID_FREE(dev->buffers[1]);
FLUID_FREE(dev->buffers);
}

FLUID_FREE(dev);
Expand All @@ -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;
}
Expand Down

0 comments on commit 486e3f3

Please sign in to comment.