Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend MIDI auto connect #1023

Merged
merged 12 commits into from
Sep 11, 2022
2 changes: 1 addition & 1 deletion doc/fluidsettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ Developers:
<name>alsa.device</name>
<type>str</type>
<def>default</def>
<desc>ALSA MIDI hardware device to use for RAW ALSA MIDI driver (not to be confused with the MIDI port).</desc>
<desc>ALSA MIDI hardware device to use for RAW ALSA MIDI driver (not to be confused with the MIDI port). Since fluidsynth 2.3.0 this setting will be populated with available devices when fluidsynth starts up.</desc>
</setting>
<setting>
<name>alsa_seq.device</name>
Expand Down
109 changes: 101 additions & 8 deletions src/drivers/fluid_alsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <sys/poll.h>
#include <math.h>

#include "fluid_lash.h"

Expand Down Expand Up @@ -629,7 +630,86 @@ static fluid_thread_return_t fluid_alsa_audio_run_s16(void *d)

void fluid_alsa_rawmidi_driver_settings(fluid_settings_t *settings)
{
int card = -1;
int err = 0;
snd_rawmidi_info_t *info;

fluid_settings_register_str(settings, "midi.alsa.device", "default", 0);
fluid_settings_add_option(settings, "midi.alsa.device", "default");

snd_rawmidi_info_alloca(&info);

/* Enumeration of ALSA Raw MIDI devices */
err = snd_card_next(&card);

while((err == 0) && (card >= 0))
{
int device = -1;
snd_ctl_t *ctl;
char card_name[32];

FLUID_SNPRINTF(card_name, sizeof(card_name), "hw:%d", card);
err = snd_ctl_open(&ctl, card_name, 0);

if(err == 0)
{
for(;;)
{
int num_subs;
int subdevice;
char newoption[64];
const char *name;
const char *sub_name;

err = snd_ctl_rawmidi_next_device(ctl, &device);

if((err < 0) || (device < 0))
{
break;
}

snd_rawmidi_info_set_device(info, device);
snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
err = snd_ctl_rawmidi_info(ctl, info);

if(err == 0)
{
num_subs = snd_rawmidi_info_get_subdevices_count(info);
}
else
{
break;
}

for(subdevice = 0; subdevice < num_subs; ++subdevice)
{
snd_rawmidi_info_set_subdevice(info, subdevice);
err = snd_ctl_rawmidi_info(ctl, info);

if(err == 0)
{
name = snd_rawmidi_info_get_name(info);
sub_name = snd_rawmidi_info_get_subdevice_name(info);

if(subdevice == 0 && sub_name[0] == '\0')
{
FLUID_SNPRINTF(newoption, sizeof(newoption), "hw:%d,%d %s", card, device, name);
fluid_settings_add_option(settings, "midi.alsa.device", newoption);
break;
}
else
{
FLUID_SNPRINTF(newoption, sizeof(newoption), "hw:%d,%d,%d %s", card, device, subdevice, sub_name);
fluid_settings_add_option(settings, "midi.alsa.device", newoption);
}
}
}
}
}

snd_ctl_close(ctl);
err = snd_card_next(&card);
}
}

/*
Expand Down Expand Up @@ -919,6 +999,15 @@ static void fluid_alsa_seq_autoconnect_port_info(fluid_alsa_seq_driver_t *dev, s
}

FLUID_LOG(FLUID_INFO, "Connection of %s succeeded", pname);

/* Calculate the next port to be auto-connected. When all ports have been connected
* in sequence, start again from port 0 for the next auto-connection. */
dev->autoconn_dest.port++;

if(dev->autoconn_dest.port >= dev->port_count)
{
dev->autoconn_dest.port = 0;
}
}

// Autoconnect a single client port (by id) to autoconnect_dest if it has right type/capabilities
Expand All @@ -939,16 +1028,19 @@ static void fluid_alsa_seq_autoconnect_port(fluid_alsa_seq_driver_t *dev, int cl
fluid_alsa_seq_autoconnect_port_info(dev, pinfo);
}

// Connect available ALSA MIDI inputs to the provided port_info
static void fluid_alsa_seq_autoconnect(fluid_alsa_seq_driver_t *dev, const snd_seq_port_info_t *dest_pinfo)
// Connect available ALSA MIDI inputs
static void fluid_alsa_seq_autoconnect(fluid_alsa_seq_driver_t *dev)
{
int err;
snd_seq_t *seq = dev->seq_handle;
snd_seq_client_info_t *cinfo;
snd_seq_port_info_t *pinfo;

// subscribe to future new clients/ports showing up
if((err = snd_seq_connect_from(seq, snd_seq_port_info_get_port(dest_pinfo),
/* Subscribe to system:announce for future new clients/ports showing up.
* This subscription is made to the first port, and does not rotate
* dev->autoconn_dest, because this subscription never provides
* MIDI channel messages, only system (ALSA) messages. */
if((err = snd_seq_connect_from(seq, dev->autoconn_dest.port,
SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE)) < 0)
{
FLUID_LOG(FLUID_ERR, "snd_seq_connect_from() failed: %s", snd_strerror(err));
Expand All @@ -957,8 +1049,6 @@ static void fluid_alsa_seq_autoconnect(fluid_alsa_seq_driver_t *dev, const snd_s
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);

dev->autoconn_dest = *snd_seq_port_info_get_addr(dest_pinfo);

snd_seq_client_info_set_client(cinfo, -1);

while(snd_seq_query_next_client(seq, cinfo) >= 0)
Expand Down Expand Up @@ -1100,7 +1190,7 @@ new_fluid_alsa_seq_driver(fluid_settings_t *settings,
FLUID_MEMSET(port_info, 0, snd_seq_port_info_sizeof());

fluid_settings_getint(settings, "synth.midi-channels", &midi_channels);
dev->port_count = midi_channels / 16;
dev->port_count = ceil(midi_channels / 16.0f);

snd_seq_port_info_set_capability(port_info,
SND_SEQ_PORT_CAP_WRITE |
Expand Down Expand Up @@ -1140,7 +1230,10 @@ new_fluid_alsa_seq_driver(fluid_settings_t *settings,

if(dev->autoconn_inputs)
{
fluid_alsa_seq_autoconnect(dev, port_info);
/* The next port to be auto-connected, starting by port 0 */
dev->autoconn_dest = *snd_seq_port_info_get_addr(port_info);
dev->autoconn_dest.port = 0;
fluid_alsa_seq_autoconnect(dev);
}

/* tell the lash server our client id */
Expand Down
58 changes: 56 additions & 2 deletions src/drivers/fluid_winmidi.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
* and forward these messages on distinct MIDI channels set.
* 1.1)For example, if the user chooses 2 devices at index 0 and 1, the user
* must specify this by putting the name "0;1" in midi.winmidi.device setting.
* We get a fictif device composed of real devices (0,1). This fictif device
* We get a fictive device composed of real devices (0,1). This fictive device
* behaves like a device with 32 MIDI channels whose messages are forwarded to
* driver output as this:
* - MIDI messages from real device 0 are output to MIDI channels set 0 to 15.
* - MIDI messages from real device 1 are output to MIDI channels set 15 to 31.
* The above example assumes the setting synth.midi-channels value 32, but the
* default value for this setting is 16, in which case there will be no mapping.
*
* 1.2)Now another example with the name "1;0". The driver will forward
* MIDI messages as this:
Expand All @@ -57,6 +59,13 @@
* or use the multi device naming "0" (specifying only device index 0).
* Both naming choice allows the driver to handle the same single device.
*
* 3)If the device name is "default" and the setting "midi.autoconnect" is enabled,
* then all the available devices are opened, applying the appropriate channel
* mappings to each device (the first device is mapped to the 16 first channels,
* the second one to the next 16 channels, and so on with the limit of the
* synth.midi-channels setting. After arriving to the channels limit, the mapping
* restars with the channels 1-16.
* If the device name is specified, then midi.autoconnect setting is ignored.
*/

#include "fluidsynth_priv.h"
Expand Down Expand Up @@ -465,6 +474,31 @@ fluid_winmidi_parse_device_name(fluid_winmidi_driver_t *dev, char *dev_name)
return dev_count;
}

static void fluid_winmidi_autoconnect_build_name(char *name)
{
char new_name[MAXPNAMELEN] = { 0 };
int i, j, n = 0;
int num = midiInGetNumDevs();

for (i = 0; i < num; ++i)
{
char x[4];
j = FLUID_SNPRINTF(x, sizeof(x), "%d;", i);
n += j;
if (n >= sizeof(new_name))
{
FLUID_LOG(FLUID_DBG, "winmidi: autoconnect dev name exceeds MAXPNAMELEN (%d), num (%d), n (%d)", MAXPNAMELEN, num, n);
return;
}
strncat(new_name, x, j);
}

name[n - 1] = 0;

FLUID_MEMSET(name, 0, MAXPNAMELEN);
FLUID_STRCPY(name, new_name);
}

/*
* new_fluid_winmidi_driver
*/
Expand All @@ -476,6 +510,9 @@ new_fluid_winmidi_driver(fluid_settings_t *settings,
MMRESULT res;
int i, j;
int max_devices; /* maximum number of devices to handle */
int autoconnect_inputs = 0;
int midi_channels = 16;
int ch_map = 0;
char strError[MAXERRORLENGTH];
char dev_name[MAXPNAMELEN];

Expand All @@ -493,6 +530,15 @@ new_fluid_winmidi_driver(fluid_settings_t *settings,
FLUID_STRCPY(dev_name, "default");
}

fluid_settings_getint(settings, "midi.autoconnect", &autoconnect_inputs);
fluid_settings_getint(settings, "synth.midi-channels", &midi_channels);

if((strcmp(dev_name, "default") == 0) && (autoconnect_inputs != 0))
{
fluid_winmidi_autoconnect_build_name(dev_name);
FLUID_LOG(FLUID_DBG, "winmidi: autoconnect device name is now '%s'", dev_name);
}

/* parse device name, get the maximum number of devices to handle */
max_devices = fluid_winmidi_parse_device_name(NULL, dev_name);

Expand Down Expand Up @@ -526,7 +572,15 @@ new_fluid_winmidi_driver(fluid_settings_t *settings,
device_infos_t *dev_infos = &dev->dev_infos[i];
dev_infos->dev = dev; /* driver structure */
dev_infos->midi_num = i; /* device order number */
dev_infos->channel_map = i * 16; /* map from input to output */
dev_infos->channel_map = ch_map; /* map from input to output */
/* calculate the next channel mapping, up to synth.midi-channels */
ch_map += 16;

if(ch_map >= midi_channels)
{
ch_map = 0;
}

FLUID_LOG(FLUID_DBG, "opening device at index %d", dev_infos->dev_idx);
res = midiInOpen(&dev_infos->hmidiin, dev_infos->dev_idx,
(DWORD_PTR) fluid_winmidi_callback,
Expand Down