mirror of
https://github.com/ZDoom/fluidsynth.git
synced 2024-11-27 14:32:12 +00:00
ALSA sequencer driver opens several ports if midi channels bigger than 16.
Filter on/off optimization is deactivated.
This commit is contained in:
parent
6bf2150ad7
commit
e05b491a13
11 changed files with 326 additions and 176 deletions
|
@ -1,3 +1,21 @@
|
|||
2004-05-05 Peter Hanappe <peter@hanappe.com>
|
||||
|
||||
* src/fluid_alsa.c (new_fluid_alsa_seq_driver): The alsa driver
|
||||
now opens several ports if the synthesizer is configured for more
|
||||
than 16 MIDI channels.
|
||||
|
||||
* src/fluid_voice.c (fluid_voice_write): I removed the filter
|
||||
on/off optimization. The filter is always on and serves as an
|
||||
anti-aliasing filter.
|
||||
|
||||
2004-05-04 Peter Hanappe <peter@hanappe.com>
|
||||
|
||||
* src/fluid_synth.c (new_fluid_synth): The number of MIDI channels
|
||||
now has to be a multiple of 16. The synth checks that this is the
|
||||
case and changes the settings accordingly. I removed the sanity
|
||||
checks for the min/max value of the number of MIDI channels since
|
||||
this is already done by the settings object.
|
||||
|
||||
2004-03-30 Josh Green <jgreen@users.sourceforge.net>
|
||||
|
||||
* src/fluid_voice.c (fluid_voice_write): Altered filter turn-off
|
||||
|
|
|
@ -10,25 +10,27 @@ would simply not exist. Many thanks!
|
|||
In alphabetic order:
|
||||
|
||||
Paul Barton-Davis
|
||||
Samuel Bianchini <biank@online.fr>
|
||||
Raoul Bonisch <jkl345@gmx.net>
|
||||
Jake Commander <jakec@ukfirst.co.uk>
|
||||
Francois Dechelle <Francois.Dechelle@ircam.fr>
|
||||
Tim Goetze <tim@quitte.de>
|
||||
Anthony Green <green@redhat.com>
|
||||
Josh Green <jgreen@users.sourceforge.net>
|
||||
Bob Ham <bob@ham.org>
|
||||
Peter Hanappe <peter@hanappe.com>
|
||||
Jezar <jezar@dreampoint.co.uk>
|
||||
Fernando Pablo Lopez-Lezcano <nando@ccrma.Stanford.EDU>
|
||||
Johnathan Lee <jlee@music.columbia.edu>
|
||||
Stephane Letz <letz@grame.fr>
|
||||
Samuel Bianchini <biank at online dot fr>
|
||||
Raoul Bonisch <jkl345 at gmx dot net>
|
||||
Jake Commander <jakec at ukfirst dot co dot uk>
|
||||
Francois Dechelle <Francois dot Dechelle at ircam dot fr>
|
||||
Tim Goetze <tim at quitte dot de>
|
||||
Anthony Green <green at redhat dot com>
|
||||
Josh Green <jgreen at users dot sourceforge dot net>
|
||||
Bob Ham <bob at ham dot org>
|
||||
Peter Hanappe <peter at hanappe dot com>
|
||||
Jezar <jezar at dreampoint dot co dot uk>
|
||||
Fernando Pablo Lopez-Lezcano <nando at ccrma dot Stanford dot EDU>
|
||||
Johnathan Lee <jlee at music dot columbia dot edu>
|
||||
Stephane Letz <letz at grame dot fr>
|
||||
Juergen Mueller
|
||||
Markus Nentwig <nentwig@users.sourceforge.net>
|
||||
Markus Nentwig <nentwig at users dot sourceforge dot net>
|
||||
David Olofson <david at olofson dot net>
|
||||
Dave Phillips
|
||||
Daniel Pressnitzer <pressnit@ircam.fr>
|
||||
Norbert Schnell <Norbert.Schnell@ircam.fr>
|
||||
Daniel Pressnitzer <pressnit at ircam dot fr>
|
||||
Norbert Schnell <Norbert dot Schnell at ircam dot fr>
|
||||
Joshua Scholar
|
||||
Antoine Schmitt <as@gratin.org>
|
||||
Werner Schweer <ws@seh.de>
|
||||
Martin Uddén <nanook@lysator.liu.se>
|
||||
Antoine Schmitt <as at gratin dot org>
|
||||
Werner Schweer <ws at seh dot de>
|
||||
Stephan Tassart <Stephan dot Tassart at st dot com>
|
||||
Martin Uddén <nanook at lysator dot liu dot se>
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
|
||||
Bugs and incomplete code
|
||||
------------------------
|
||||
|
||||
Bugs, errors, and incomplete code
|
||||
---------------------------------
|
||||
|
||||
- Add support for "loop till release" instruments (currently broken)
|
||||
- Filter on/off optimization causes clicks
|
||||
- Get TCP server working for windows
|
||||
- The synth consumes too much CPU when no voices are playing.
|
||||
- Multi-channel audio output
|
||||
- Filter on/off optimization causes clicks
|
||||
- Phase sync'ed samples should start simultaneously.
|
||||
- Add fluid_synth_remove_sfont()
|
||||
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
@ -16,23 +14,12 @@ Validation
|
|||
- Validation tests: create soundfont with basic wave forms [sine,
|
||||
square, triangle]; make test midi file; compare with SBLive output;
|
||||
"regression" test
|
||||
- Validate reverb
|
||||
- Validate chorus
|
||||
- compare performance with timidity
|
||||
|
||||
JG:
|
||||
> Its often hard to get the right Reverb/Chorus/Gain settings as
|
||||
> well, so often I end up turning off Reverb and Chorus which can help.
|
||||
|
||||
JG:
|
||||
> Well actually, it almost seems like every instrument is kind of flat. It
|
||||
> just doesn't sound real nice.
|
||||
|
||||
- Analyse performance
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- Multi-channel audio output
|
||||
- Write documention on tuning
|
||||
- fluid_synth_program_select2() with name of soundfont instead of font_id
|
||||
- fluid_synth_set_gen2()
|
||||
|
@ -72,7 +59,9 @@ Requests
|
|||
- DirectSound 3D and EAX
|
||||
- Pause and resume the synthesizer/audio thread (run synthesizer as a daemon)
|
||||
|
||||
- set loop on/off on a sample (1 - with name sample, 2 - name sf and name preset) Use set_gen?
|
||||
- set loop on/off on a sample (1 - with name sample, 2 - name sf and name preset)
|
||||
|
||||
Use set_gen?
|
||||
|
||||
set_gen: GEN_SAMPLEMODE (54):
|
||||
Loop during release: 1,
|
||||
|
@ -86,8 +75,15 @@ Requests
|
|||
Fluid 1.1:
|
||||
--------------------------------------------
|
||||
|
||||
Top of the list
|
||||
- Use FIFOs to send events to the audio thread
|
||||
- Redo sfloader api using "interface" api
|
||||
- Clean multi-channel audio implementation
|
||||
- 3D audio output
|
||||
- Sample streaming, load/unload sample on demand
|
||||
|
||||
|
||||
SFLoader API:
|
||||
- "interface" api
|
||||
- redo sfloader api using "interface" api
|
||||
|
||||
Sample streaming
|
||||
|
|
|
@ -149,6 +149,28 @@ FLUIDSYNTH_API int fluid_synth_program_reset(fluid_synth_t* synth);
|
|||
FLUIDSYNTH_API int fluid_synth_system_reset(fluid_synth_t* synth);
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Low level access
|
||||
*
|
||||
*/
|
||||
|
||||
/** Create and start voices using a preset. The id passed as
|
||||
* argument will be used as the voice group id. */
|
||||
FLUIDSYNTH_API int fluid_synth_start(fluid_synth_t* synth, unsigned int id,
|
||||
fluid_preset_t* preset, int audio_chan,
|
||||
int midi_chan, int key, int vel);
|
||||
|
||||
/** Stop the voices in the voice group defined by id. */
|
||||
FLUIDSYNTH_API int fluid_synth_stop(fluid_synth_t* synth, unsigned int id);
|
||||
|
||||
/** Change the value of a generator of the voices in the voice group
|
||||
* defined by id. */
|
||||
/* FLUIDSYNTH_API int fluid_synth_ctrl(fluid_synth_t* synth, int id, */
|
||||
/* int gen, float value, */
|
||||
/* int absolute, int normalized); */
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* SoundFont management
|
||||
|
|
|
@ -138,11 +138,11 @@ static void* fluid_alsa_midi_run(void* d);
|
|||
typedef struct {
|
||||
fluid_midi_driver_t driver;
|
||||
snd_seq_t *seq_handle;
|
||||
int seq_port;
|
||||
struct pollfd *pfd;
|
||||
int npfd;
|
||||
pthread_t thread;
|
||||
int status;
|
||||
int port_count;
|
||||
} fluid_alsa_seq_driver_t;
|
||||
|
||||
fluid_midi_driver_t* new_fluid_alsa_seq_driver(fluid_settings_t* settings,
|
||||
|
@ -151,7 +151,6 @@ fluid_midi_driver_t* new_fluid_alsa_seq_driver(fluid_settings_t* settings,
|
|||
int delete_fluid_alsa_seq_driver(fluid_midi_driver_t* p);
|
||||
static void* fluid_alsa_seq_run(void* d);
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* Alsa audio driver
|
||||
|
@ -825,6 +824,37 @@ void fluid_alsa_seq_driver_settings(fluid_settings_t* settings)
|
|||
fluid_settings_register_str(settings, "midi.alsa_seq.id", "pid", 0, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
static char* fluid_alsa_seq_full_id(char* id, char* buf, int len)
|
||||
{
|
||||
if (id != NULL) {
|
||||
if (FLUID_STRCMP(id, "pid") == 0) {
|
||||
snprintf(buf, len, "FLUID Synth (%d)", getpid());
|
||||
} else {
|
||||
snprintf(buf, len, "FLUID Synth (%s)", id);
|
||||
}
|
||||
} else {
|
||||
snprintf(buf, len, "FLUID Synth");
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char* fluid_alsa_seq_full_name(char* id, int port, char* buf, int len)
|
||||
{
|
||||
if (id != NULL) {
|
||||
if (FLUID_STRCMP(id, "pid") == 0) {
|
||||
snprintf(buf, len, "Synth input port (%d:%d)", getpid(), port);
|
||||
} else {
|
||||
snprintf(buf, len, "Synth input port (%s:%d)", id, port);
|
||||
}
|
||||
} else {
|
||||
snprintf(buf, len, "Synth input port");
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* new_fluid_alsa_seq_driver
|
||||
*/
|
||||
|
@ -843,6 +873,9 @@ new_fluid_alsa_seq_driver(fluid_settings_t* settings,
|
|||
char* id;
|
||||
char full_id[64];
|
||||
char full_name[64];
|
||||
char id_pid[16];
|
||||
snd_seq_port_info_t *port_info = NULL;
|
||||
int midi_channels;
|
||||
|
||||
/* not much use doing anything */
|
||||
if (handler == NULL) {
|
||||
|
@ -857,33 +890,29 @@ new_fluid_alsa_seq_driver(fluid_settings_t* settings,
|
|||
return NULL;
|
||||
}
|
||||
FLUID_MEMSET(dev, 0, sizeof(fluid_alsa_seq_driver_t));
|
||||
dev->seq_port = -1;
|
||||
dev->driver.data = data;
|
||||
dev->driver.handler = handler;
|
||||
|
||||
|
||||
/* get the device name. if none is specified, use the default device. */
|
||||
fluid_settings_getstr(settings, "midi.alsa_seq.device", &device);
|
||||
if (device == NULL) {
|
||||
device = "default";
|
||||
}
|
||||
|
||||
fluid_settings_getstr(settings, "midi.alsa_seq.id", &id);
|
||||
if (id == NULL) {
|
||||
sprintf(id_pid, "%d", getpid());
|
||||
id = id_pid;
|
||||
}
|
||||
|
||||
/* open the sequencer INPUT only, non-blocking */
|
||||
if ((err = snd_seq_open(&dev->seq_handle, device, SND_SEQ_OPEN_INPUT,
|
||||
SND_SEQ_NONBLOCK)) < 0) {
|
||||
err = snd_seq_open(&dev->seq_handle, device, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
|
||||
if (err < 0) {
|
||||
FLUID_LOG(FLUID_ERR, "Error opening ALSA sequencer");
|
||||
goto error_recovery;
|
||||
}
|
||||
|
||||
/* tell the ladcca server our client id */
|
||||
#ifdef HAVE_LADCCA
|
||||
{
|
||||
int enable_ladcca = 0;
|
||||
fluid_settings_getint (settings, "ladcca.enable", &enable_ladcca);
|
||||
if (enable_ladcca)
|
||||
cca_alsa_client_id (fluid_cca_client, snd_seq_client_id (dev->seq_handle));
|
||||
}
|
||||
#endif /* HAVE_LADCCA */
|
||||
|
||||
/* get # of MIDI file descriptors */
|
||||
count = snd_seq_poll_descriptors_count(dev->seq_handle, POLLIN);
|
||||
if (count > 0) { /* make sure there are some */
|
||||
|
@ -903,34 +932,49 @@ new_fluid_alsa_seq_driver(fluid_settings_t* settings,
|
|||
}
|
||||
}
|
||||
FLUID_FREE(pfd);
|
||||
|
||||
fluid_settings_getstr(settings, "midi.alsa_seq.id", &id);
|
||||
|
||||
if (id != NULL) {
|
||||
if (FLUID_STRCMP(id, "pid") == 0) {
|
||||
snprintf(full_id, 64, "FLUID Synth (%d)", getpid());
|
||||
snprintf(full_name, 64, "Synth input port (%d)", getpid());
|
||||
} else {
|
||||
snprintf(full_id, 64, "FLUID Synth (%s)", id);
|
||||
snprintf(full_name, 64, "Synth input port (%s)", id);
|
||||
}
|
||||
} else {
|
||||
snprintf(full_id, 64, "FLUID Synth");
|
||||
snprintf(full_name, 64, "Synth input port");
|
||||
}
|
||||
|
||||
/* set the client name */
|
||||
snd_seq_set_client_name (dev->seq_handle, full_id);
|
||||
snd_seq_set_client_name(dev->seq_handle, fluid_alsa_seq_full_id(id, full_id, 64));
|
||||
|
||||
if ((dev->seq_port = snd_seq_create_simple_port (dev->seq_handle,
|
||||
full_name,
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION)) < 0)
|
||||
{
|
||||
|
||||
/* create the ports */
|
||||
snd_seq_port_info_alloca(&port_info);
|
||||
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;
|
||||
|
||||
snd_seq_port_info_set_capability(port_info,
|
||||
SND_SEQ_PORT_CAP_WRITE |
|
||||
SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
snd_seq_port_info_set_type(port_info,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
snd_seq_port_info_set_midi_channels(port_info, 16);
|
||||
snd_seq_port_info_set_port_specified(port_info, 1);
|
||||
|
||||
for (i = 0; i < dev->port_count; i++) {
|
||||
|
||||
snd_seq_port_info_set_name(port_info, fluid_alsa_seq_full_name(id, i, full_name, 64));
|
||||
snd_seq_port_info_set_port(port_info, i);
|
||||
|
||||
err = snd_seq_create_port(dev->seq_handle, port_info);
|
||||
if (err < 0) {
|
||||
FLUID_LOG(FLUID_ERR, "Error creating ALSA sequencer port");
|
||||
goto error_recovery;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* tell the ladcca server our client id */
|
||||
#ifdef HAVE_LADCCA
|
||||
{
|
||||
int enable_ladcca = 0;
|
||||
fluid_settings_getint (settings, "ladcca.enable", &enable_ladcca);
|
||||
if (enable_ladcca)
|
||||
cca_alsa_client_id (fluid_cca_client, snd_seq_client_id (dev->seq_handle));
|
||||
}
|
||||
#endif /* HAVE_LADCCA */
|
||||
|
||||
|
||||
dev->status = FLUID_MIDI_READY;
|
||||
|
||||
|
@ -1003,9 +1047,6 @@ delete_fluid_alsa_seq_driver(fluid_midi_driver_t* p)
|
|||
return FLUID_FAILED;
|
||||
}
|
||||
}
|
||||
if (dev->seq_port >= 0) {
|
||||
snd_seq_delete_simple_port (dev->seq_handle, dev->seq_port);
|
||||
}
|
||||
if (dev->seq_handle) {
|
||||
snd_seq_close(dev->seq_handle);
|
||||
}
|
||||
|
@ -1023,6 +1064,7 @@ fluid_alsa_seq_run(void* d)
|
|||
snd_seq_event_t *seq_ev;
|
||||
fluid_midi_event_t evt;
|
||||
fluid_alsa_seq_driver_t* dev = (fluid_alsa_seq_driver_t*) d;
|
||||
int channel;
|
||||
|
||||
/* make sure the other threads can cancel this thread any time */
|
||||
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) {
|
||||
|
@ -1051,43 +1093,43 @@ fluid_alsa_seq_run(void* d)
|
|||
{
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
evt.type = NOTE_ON;
|
||||
evt.channel = seq_ev->data.note.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.note.channel;
|
||||
evt.param1 = seq_ev->data.note.note;
|
||||
evt.param2 = seq_ev->data.note.velocity;
|
||||
break;
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
evt.type = NOTE_OFF;
|
||||
evt.channel = seq_ev->data.note.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.note.channel;
|
||||
evt.param1 = seq_ev->data.note.note;
|
||||
evt.param2 = seq_ev->data.note.velocity;
|
||||
break;
|
||||
case SND_SEQ_EVENT_KEYPRESS:
|
||||
evt.type = KEY_PRESSURE;
|
||||
evt.channel = seq_ev->data.note.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.note.channel;
|
||||
evt.param1 = seq_ev->data.note.note;
|
||||
evt.param2 = seq_ev->data.note.velocity;
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
evt.type = CONTROL_CHANGE;
|
||||
evt.channel = seq_ev->data.control.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel;
|
||||
evt.param1 = seq_ev->data.control.param;
|
||||
evt.param2 = seq_ev->data.control.value;
|
||||
break;
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
evt.type = PITCH_BEND;
|
||||
evt.channel = seq_ev->data.control.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel;
|
||||
|
||||
/* ALSA pitch bend is -8192 - 8191, we adjust it here */
|
||||
evt.param1 = seq_ev->data.control.value + 8192;
|
||||
break;
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
evt.type = PROGRAM_CHANGE;
|
||||
evt.channel = seq_ev->data.control.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel;
|
||||
evt.param1 = seq_ev->data.control.value;
|
||||
break;
|
||||
case SND_SEQ_EVENT_CHANPRESS:
|
||||
evt.type = CHANNEL_PRESSURE;
|
||||
evt.channel = seq_ev->data.control.channel;
|
||||
evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel;
|
||||
evt.param1 = seq_ev->data.control.value;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -318,8 +318,8 @@ fluid_channel_get_num(fluid_channel_t* chan)
|
|||
*/
|
||||
void fluid_channel_set_interp_method(fluid_channel_t* chan, int new_method)
|
||||
{
|
||||
chan->interp_method=new_method;
|
||||
};
|
||||
chan->interp_method = new_method;
|
||||
}
|
||||
|
||||
/* Purpose:
|
||||
* Returns the index of the interpolation method used on this channel,
|
||||
|
@ -328,7 +328,7 @@ void fluid_channel_set_interp_method(fluid_channel_t* chan, int new_method)
|
|||
int fluid_channel_get_interp_method(fluid_channel_t* chan)
|
||||
{
|
||||
return chan->interp_method;
|
||||
};
|
||||
}
|
||||
|
||||
unsigned int fluid_channel_get_sfontnum(fluid_channel_t* chan)
|
||||
{
|
||||
|
|
|
@ -113,6 +113,10 @@ fluid_mod_get_value(fluid_mod_t* mod, fluid_channel_t* chan, fluid_voice_t* voic
|
|||
{
|
||||
fluid_real_t v1 = 0.0, v2 = 1.0;
|
||||
fluid_real_t range1 = 127.0, range2 = 127.0;
|
||||
|
||||
if (chan == NULL) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* 'special treatment' for default controller
|
||||
*
|
||||
|
|
|
@ -352,7 +352,7 @@ new_fluid_synth(fluid_settings_t *settings)
|
|||
}
|
||||
FLUID_MEMSET(synth, 0, sizeof(fluid_synth_t));
|
||||
|
||||
/* fluid_mutex_init(synth->busy); */
|
||||
fluid_mutex_init(synth->busy);
|
||||
|
||||
synth->settings = settings;
|
||||
|
||||
|
@ -375,14 +375,13 @@ new_fluid_synth(fluid_settings_t *settings)
|
|||
(fluid_num_update_t) fluid_synth_update_gain, synth);
|
||||
|
||||
/* do some basic sanity checking on the settings */
|
||||
if (synth->midi_channels < 16) {
|
||||
FLUID_LOG(FLUID_WARN, "Requested number of MIDI channels is smaller than 16. "
|
||||
"Changing this setting to 16.");
|
||||
synth->midi_channels = 16;
|
||||
} else if (synth->midi_channels > 1024) {
|
||||
FLUID_LOG(FLUID_WARN, "Requested number of MIDI channels is too big (%d). "
|
||||
"Limiting this setting to 1024.", synth->midi_channels);
|
||||
synth->midi_channels = 1024;
|
||||
|
||||
if (synth->midi_channels % 16 != 0) {
|
||||
int n = synth->midi_channels / 16;
|
||||
synth->midi_channels = (n + 1) * 16;
|
||||
fluid_settings_setint(settings, "synth.midi-channels", synth->midi_channels);
|
||||
FLUID_LOG(FLUID_WARN, "Requested number of MIDI channels is not a multiple of 16. "
|
||||
"I'll increase the number of channels to the next multiple.");
|
||||
}
|
||||
|
||||
if (synth->audio_channels < 1) {
|
||||
|
@ -695,7 +694,8 @@ delete_fluid_synth(fluid_synth_t* synth)
|
|||
FLUID_FREE(synth->LADSPA_FxUnit);
|
||||
#endif
|
||||
|
||||
/* fluid_mutex_destroy(synth->busy); */
|
||||
fluid_mutex_destroy(synth->busy);
|
||||
|
||||
FLUID_FREE(synth);
|
||||
|
||||
return FLUID_OK;
|
||||
|
@ -720,24 +720,12 @@ int
|
|||
fluid_synth_noteon(fluid_synth_t* synth, int chan, int key, int vel)
|
||||
{
|
||||
fluid_channel_t* channel;
|
||||
int r;
|
||||
/* fluid_mutex_lock(synth->busy); /\* Don't interfere with the audio thread *\/ */
|
||||
/* fluid_mutex_unlock(synth->busy); */
|
||||
int r = FLUID_FAILED;
|
||||
|
||||
/* check the ranges of the arguments */
|
||||
if ((chan < 0) || (chan >= synth->midi_channels)) {
|
||||
FLUID_LOG(FLUID_WARN, "Channel out of range");
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
if ((key < 0) || (key >= 128)) {
|
||||
FLUID_LOG(FLUID_WARN, "Key out of range");
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
if ((vel < 0) || (vel >= 128)) {
|
||||
FLUID_LOG(FLUID_WARN, "Velocity out of range");
|
||||
return FLUID_FAILED;
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
/* notes with velocity zero go to noteoff */
|
||||
|
@ -745,8 +733,6 @@ fluid_synth_noteon(fluid_synth_t* synth, int chan, int key, int vel)
|
|||
return fluid_synth_noteoff(synth, chan, key);
|
||||
}
|
||||
|
||||
synth->noteid++;
|
||||
|
||||
channel = synth->channel[chan];
|
||||
|
||||
/* make sure this channel has a preset */
|
||||
|
@ -758,12 +744,10 @@ fluid_synth_noteon(fluid_synth_t* synth, int chan, int key, int vel)
|
|||
(fluid_curtime() - synth->start) / 1000.0f,
|
||||
0.0f, 0, "channel has no preset");
|
||||
}
|
||||
return FLUID_FAILED;
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
r = fluid_preset_noteon(channel->preset, synth, chan, key, vel);
|
||||
|
||||
return r;
|
||||
return fluid_synth_start(synth, synth->noteid++, channel->preset, 0, chan, key, vel);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1852,10 +1836,10 @@ fluid_synth_one_block(fluid_synth_t* synth, int do_not_mix_fx_to_out)
|
|||
* of the algorithm previously in fluid_synth_alloc_voice.
|
||||
*/
|
||||
fluid_voice_t*
|
||||
fluid_synth_free_voice_by_kill (fluid_synth_t* synth)
|
||||
fluid_synth_free_voice_by_kill(fluid_synth_t* synth)
|
||||
{
|
||||
int i;
|
||||
fluid_real_t best_prio=999999.;
|
||||
fluid_real_t best_prio = 999999.;
|
||||
fluid_real_t this_voice_prio;
|
||||
fluid_voice_t* voice;
|
||||
int best_voice_index=-1;
|
||||
|
@ -1868,12 +1852,13 @@ fluid_synth_free_voice_by_kill (fluid_synth_t* synth)
|
|||
voice = synth->voice[i];
|
||||
|
||||
/* safeguard against an available voice. */
|
||||
if (_AVAILABLE(voice))
|
||||
if (_AVAILABLE(voice)) {
|
||||
return voice;
|
||||
|
||||
}
|
||||
|
||||
/* Determine, how 'important' a voice is.
|
||||
* Start with an arbitrary number */
|
||||
this_voice_prio=10000.;
|
||||
this_voice_prio = 10000.;
|
||||
|
||||
/* Is this voice on the drum channel?
|
||||
* Then it is very important.
|
||||
|
@ -1882,13 +1867,15 @@ fluid_synth_free_voice_by_kill (fluid_synth_t* synth)
|
|||
* of the time in release phase.
|
||||
*/
|
||||
if (voice->chan == 9){
|
||||
this_voice_prio+=4000;
|
||||
this_voice_prio += 4000;
|
||||
|
||||
} else if (_RELEASED(voice)){
|
||||
/* The key for this voice has been released. Consider it much less important
|
||||
* than a voice, which is still held.
|
||||
*/
|
||||
this_voice_prio-=2000.;
|
||||
};
|
||||
this_voice_prio -= 2000.;
|
||||
}
|
||||
|
||||
if (_SUSTAINED(voice)){
|
||||
/* The sustain pedal is held down on this channel.
|
||||
* Consider it less important than non-sustained channels.
|
||||
|
@ -1896,8 +1883,8 @@ fluid_synth_free_voice_by_kill (fluid_synth_t* synth)
|
|||
* is used to play 'more-voices-than-fingers', so it shouldn't hurt
|
||||
* if we kill one voice.
|
||||
*/
|
||||
this_voice_prio-=1000;
|
||||
};
|
||||
this_voice_prio -= 1000;
|
||||
}
|
||||
|
||||
/* We are not enthusiastic about releasing voices, which have just been started.
|
||||
* Otherwise hitting a chord may result in killing notes belonging to that very same
|
||||
|
@ -1905,12 +1892,12 @@ fluid_synth_free_voice_by_kill (fluid_synth_t* synth)
|
|||
* So subtract the age of the voice from the priority - an older voice is just a little
|
||||
* bit less important than a younger voice.
|
||||
* This is a number between roughly 0 and 100.*/
|
||||
this_voice_prio -= (synth->noteid-fluid_voice_get_id(voice));
|
||||
this_voice_prio -= (synth->noteid - fluid_voice_get_id(voice));
|
||||
|
||||
/* take a rough estimate of loudness into account. Louder voices are more important. */
|
||||
if (voice->volenv_section != FLUID_VOICE_ENVATTACK){
|
||||
this_voice_prio += voice->volenv_val*1000.;
|
||||
};
|
||||
this_voice_prio += voice->volenv_val * 1000.;
|
||||
}
|
||||
|
||||
/* check if this voice has less priority than the previous candidate. */
|
||||
if (this_voice_prio < best_prio)
|
||||
|
@ -1918,11 +1905,13 @@ fluid_synth_free_voice_by_kill (fluid_synth_t* synth)
|
|||
best_prio = this_voice_prio;
|
||||
}
|
||||
|
||||
if (best_voice_index < 0)
|
||||
if (best_voice_index < 0) {
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
voice = synth->voice[best_voice_index];
|
||||
fluid_voice_off (voice);
|
||||
fluid_voice_off(voice);
|
||||
|
||||
return voice;
|
||||
}
|
||||
|
||||
|
@ -1934,6 +1923,7 @@ fluid_synth_alloc_voice(fluid_synth_t* synth, fluid_sample_t* sample, int chan,
|
|||
{
|
||||
int i, k;
|
||||
fluid_voice_t* voice = NULL;
|
||||
fluid_channel_t* channel = NULL;
|
||||
|
||||
/* fluid_mutex_lock(synth->busy); /\* Don't interfere with the audio thread *\/ */
|
||||
/* fluid_mutex_unlock(synth->busy); */
|
||||
|
@ -1952,7 +1942,7 @@ fluid_synth_alloc_voice(fluid_synth_t* synth, fluid_sample_t* sample, int chan,
|
|||
|
||||
/* No success yet? Then stop a running voice. */
|
||||
if (voice == NULL) {
|
||||
voice = fluid_synth_free_voice_by_kill (synth);
|
||||
voice = fluid_synth_free_voice_by_kill(synth);
|
||||
}
|
||||
|
||||
if (voice == NULL) {
|
||||
|
@ -1969,15 +1959,19 @@ fluid_synth_alloc_voice(fluid_synth_t* synth, fluid_sample_t* sample, int chan,
|
|||
}
|
||||
|
||||
FLUID_LOG(FLUID_INFO, "noteon\t%d\t%d\t%d\t%05d\t%.3f\t%.3f\t%.3f\t%d",
|
||||
chan, key, vel, synth->noteid,
|
||||
chan, key, vel, synth->storeid,
|
||||
(float) synth->ticks / 44100.0f,
|
||||
(fluid_curtime() - synth->start) / 1000.0f,
|
||||
0.0f,
|
||||
k);
|
||||
}
|
||||
|
||||
if (fluid_voice_init(voice, sample, synth->channel[chan], key, vel,
|
||||
synth->noteid, synth->ticks, synth->gain) != FLUID_OK) {
|
||||
if (chan >= 0) {
|
||||
channel = synth->channel[chan];
|
||||
}
|
||||
|
||||
if (fluid_voice_init(voice, sample, channel, key, vel,
|
||||
synth->storeid, synth->ticks, synth->gain) != FLUID_OK) {
|
||||
FLUID_LOG(FLUID_WARN, "Failed to initialize voice");
|
||||
return NULL;
|
||||
}
|
||||
|
@ -2881,3 +2875,55 @@ int fluid_synth_handle_midi_event(void* data, fluid_midi_event_t* event)
|
|||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
|
||||
int fluid_synth_start(fluid_synth_t* synth, unsigned int id, fluid_preset_t* preset,
|
||||
int audio_chan, int midi_chan, int key, int vel)
|
||||
{
|
||||
int r;
|
||||
|
||||
/* check the ranges of the arguments */
|
||||
if ((midi_chan < 0) || (midi_chan >= synth->midi_channels)) {
|
||||
FLUID_LOG(FLUID_WARN, "Channel out of range");
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
if ((key < 0) || (key >= 128)) {
|
||||
FLUID_LOG(FLUID_WARN, "Key out of range");
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
if ((vel <= 0) || (vel >= 128)) {
|
||||
FLUID_LOG(FLUID_WARN, "Velocity out of range");
|
||||
return FLUID_FAILED;
|
||||
}
|
||||
|
||||
fluid_mutex_lock(synth->busy); /* One at a time, please */
|
||||
|
||||
synth->storeid = id;
|
||||
r = fluid_preset_noteon(preset, synth, midi_chan, key, vel);
|
||||
|
||||
fluid_mutex_unlock(synth->busy);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int fluid_synth_stop(fluid_synth_t* synth, unsigned int id)
|
||||
{
|
||||
int i;
|
||||
fluid_voice_t* voice;
|
||||
int status = FLUID_FAILED;
|
||||
int count = 0;
|
||||
|
||||
for (i = 0; i < synth->nvoice; i++) {
|
||||
|
||||
voice = synth->voice[i];
|
||||
|
||||
if (_ON(voice) && (fluid_voice_get_id(voice) == id)) {
|
||||
count++;
|
||||
fluid_voice_noteoff(voice);
|
||||
status = FLUID_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -59,10 +59,10 @@
|
|||
* ENUM
|
||||
*/
|
||||
enum fluid_loop {
|
||||
FLUID_UNLOOPED,
|
||||
FLUID_LOOP_DURING_RELEASE,
|
||||
FLUID_NOTUSED,
|
||||
FLUID_LOOP
|
||||
FLUID_UNLOOPED = 0,
|
||||
FLUID_LOOP_DURING_RELEASE = 1,
|
||||
FLUID_NOTUSED = 2,
|
||||
FLUID_LOOP_UNTIL_RELEASE = 3
|
||||
};
|
||||
|
||||
enum fluid_synth_status
|
||||
|
@ -103,13 +103,14 @@ struct _fluid_synth_t
|
|||
fluid_list_t* unloading; /** the soundfonts that need to be unloaded */
|
||||
#endif
|
||||
|
||||
double gain; /** master gain */
|
||||
double gain; /** master gain */
|
||||
fluid_channel_t** channel; /** the channels */
|
||||
int num_channels; /** the number of channels */
|
||||
int nvoice; /** the length of the synthesis process array */
|
||||
int num_channels; /** the number of channels */
|
||||
int nvoice; /** the length of the synthesis process array */
|
||||
fluid_voice_t** voice; /** the synthesis processes */
|
||||
unsigned int noteid; /** the id is incremented for every new note. it's used for noteoff's */
|
||||
int nbuf; /** How many audio buffers are used? (depends on nr of audio channels / groups)*/
|
||||
unsigned int noteid; /** the id is incremented for every new note. it's used for noteoff's */
|
||||
unsigned int storeid;
|
||||
int nbuf; /** How many audio buffers are used? (depends on nr of audio channels / groups)*/
|
||||
|
||||
fluid_real_t** left_buf;
|
||||
fluid_real_t** right_buf;
|
||||
|
@ -132,7 +133,7 @@ struct _fluid_synth_t
|
|||
fluid_tuning_t* cur_tuning; /** current tuning in the iteration */
|
||||
|
||||
fluid_midi_router_t* midi_router; /* The midi router. Could be done nicer. */
|
||||
/*fluid_mutex_t busy;*/ /* Indicates, whether the audio thread is currently running.
|
||||
fluid_mutex_t busy; /* Indicates, whether the audio thread is currently running.
|
||||
* Note: This simple scheme does -not- provide 100 % protection against
|
||||
* thread problems, for example from MIDI thread and shell thread
|
||||
*/
|
||||
|
|
|
@ -200,8 +200,8 @@ delete_fluid_voice(fluid_voice_t* voice)
|
|||
*/
|
||||
int
|
||||
fluid_voice_init(fluid_voice_t* voice, fluid_sample_t* sample,
|
||||
fluid_channel_t* channel, int key, int vel,
|
||||
unsigned int id, unsigned int start_time, fluid_real_t gain)
|
||||
fluid_channel_t* channel, int key, int vel, unsigned int id,
|
||||
unsigned int start_time, fluid_real_t gain)
|
||||
{
|
||||
/* Note: The voice parameters will be initialized later, when the
|
||||
* generators have been retrieved from the sound font. Here, only
|
||||
|
@ -213,7 +213,6 @@ fluid_voice_init(fluid_voice_t* voice, fluid_sample_t* sample,
|
|||
voice->key = (unsigned char) key;
|
||||
voice->vel = (unsigned char) vel;
|
||||
voice->channel = channel;
|
||||
voice->preset = fluid_channel_get_preset(channel);
|
||||
voice->mod_count = 0;
|
||||
voice->sample = sample;
|
||||
voice->start_time = start_time;
|
||||
|
@ -635,13 +634,33 @@ fluid_voice_write(fluid_voice_t* voice,
|
|||
/* if filter has not yet started and filter cutoff and Q don't
|
||||
exceed "audible" thresholds, then don't turn on the filter.
|
||||
Once the filter is turned on, it remains on. */
|
||||
if (voice->filter_startup && fres > FLUID_MAX_AUDIBLE_FILTER_FC
|
||||
&& voice->q_lin < FLUID_MIN_AUDIBLE_FILTER_Q)
|
||||
dsp_use_filter_flag = 0;
|
||||
else if (fres < 5) fres = 5;
|
||||
/* if (voice->filter_startup */
|
||||
/* && (fres > FLUID_MAX_AUDIBLE_FILTER_FC) */
|
||||
/* && (voice->q_lin < FLUID_MIN_AUDIBLE_FILTER_Q)) { */
|
||||
/* dsp_use_filter_flag = 0; */
|
||||
/* } else if (fres < 5) { */
|
||||
/* fres = 5; */
|
||||
/* } */
|
||||
|
||||
/* I removed the optimization of turning the filter off when the
|
||||
* resonance frequence is above the maximum frequency. Instead, the
|
||||
* filter frequence is set to a maximum of 0.45 times the sampling
|
||||
* rate. For a 44100 kHz sampling rate, this amounts to 19845
|
||||
* Hz. The reasing is that were problems with anti-aliasing when the
|
||||
* synthesizer was run at lower sampling rates. Thanks to Stephan
|
||||
* Tassart for pointing me to this bug. By turning the filter on and
|
||||
* clipping the maximum filter frequency at 0.45*srate, the filter
|
||||
* is used as an anti-aliasing filter. */
|
||||
|
||||
if (fres > 0.45f * voice->output_rate) {
|
||||
fres = 0.45f * voice->output_rate;
|
||||
} else if (fres < 5) {
|
||||
fres = 5;
|
||||
}
|
||||
|
||||
|
||||
/* if filter enabled and there is a significant frequency change.. */
|
||||
if (dsp_use_filter_flag && (abs(fres - voice->last_fres) > 0.01)) {
|
||||
if (/*dsp_use_filter_flag &&*/ (abs(fres - voice->last_fres) > 0.01)) {
|
||||
|
||||
/* The filter coefficients have to be recalculated (filter
|
||||
* parameters have changed). Recalculation for various reasons is
|
||||
|
@ -744,7 +763,7 @@ fluid_voice_write(fluid_voice_t* voice,
|
|||
* may require several runs. */
|
||||
fluid_check_fpe("voice_write DSP processing");
|
||||
|
||||
if (((_SAMPLEMODE(voice) == FLUID_LOOP) && (voice->volenv_section < FLUID_VOICE_ENVRELEASE))
|
||||
if (((_SAMPLEMODE(voice) == FLUID_LOOP_UNTIL_RELEASE) && (voice->volenv_section < FLUID_VOICE_ENVRELEASE))
|
||||
|| (_SAMPLEMODE(voice) == FLUID_LOOP_DURING_RELEASE)) {
|
||||
|
||||
/* At which index does the loop point occur in the output buffer?
|
||||
|
@ -1149,18 +1168,21 @@ fluid_voice_update_param(fluid_voice_t* voice, int gen)
|
|||
break;
|
||||
|
||||
case GEN_FILTERFC:
|
||||
/* The resonance frequency is converted from absolute cents to midicents
|
||||
* .val and .mod are both used, this permits real-time modulation.
|
||||
* The allowed range is tested in the 'fluid_ct2hz' function [PH,20021214]
|
||||
/* The resonance frequency is converted from absolute cents to
|
||||
* midicents .val and .mod are both used, this permits real-time
|
||||
* modulation. The allowed range is tested in the 'fluid_ct2hz'
|
||||
* function [PH,20021214]
|
||||
*/
|
||||
voice->fres = _GEN(voice, GEN_FILTERFC);
|
||||
|
||||
/* The synthesis loop will have to recalculate the filter coefficients. */
|
||||
/* The synthesis loop will have to recalculate the filter
|
||||
* coefficients. */
|
||||
voice->last_fres = -1.0f;
|
||||
break;
|
||||
|
||||
case GEN_FILTERQ:
|
||||
/* The generator contains 'centibels' (1/10 dB) => divide by 10 to obtain dB */
|
||||
/* The generator contains 'centibels' (1/10 dB) => divide by 10 to
|
||||
* obtain dB */
|
||||
q_dB = _GEN(voice, GEN_FILTERQ) / 10.0f;
|
||||
|
||||
/* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */
|
||||
|
@ -1611,7 +1633,7 @@ fluid_voice_noteoff(fluid_voice_t* voice)
|
|||
fluid_real_t env_value = - ((-200. * log (amp) / log (10.) - attn_and_lfo) / 960.0 - 1);
|
||||
env_value = (env_value < 0.0 ? 0.0 : env_value);
|
||||
env_value = (env_value > 1.0 ? 1.0 : env_value);
|
||||
voice->volenv_val=env_value;
|
||||
voice->volenv_val = env_value;
|
||||
}
|
||||
}
|
||||
voice->volenv_section = FLUID_VOICE_ENVRELEASE;
|
||||
|
@ -1793,8 +1815,8 @@ fluid_real_t fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t* voice
|
|||
if ((mod->dest == GEN_ATTENUATION)
|
||||
&& ((mod->flags1 & FLUID_MOD_CC) || (mod->flags2 & FLUID_MOD_CC))) {
|
||||
|
||||
fluid_real_t current_val=fluid_mod_get_value(mod, voice->channel, voice);
|
||||
fluid_real_t v=fabs(mod->amount);
|
||||
fluid_real_t current_val = fluid_mod_get_value(mod, voice->channel, voice);
|
||||
fluid_real_t v = fabs(mod->amount);
|
||||
|
||||
if ((mod->src1 == FLUID_MOD_PITCHWHEEL)
|
||||
|| (mod->flags1 & FLUID_MOD_BIPOLAR)
|
||||
|
@ -1882,7 +1904,7 @@ void fluid_voice_check_sample_sanity(fluid_voice_t* voice)
|
|||
return;
|
||||
}
|
||||
|
||||
if ((_SAMPLEMODE(voice) == FLUID_LOOP)
|
||||
if ((_SAMPLEMODE(voice) == FLUID_LOOP_UNTIL_RELEASE)
|
||||
|| (_SAMPLEMODE(voice) == FLUID_LOOP_DURING_RELEASE)) {
|
||||
/* Keep the loop start point within the sample data */
|
||||
if (voice->loopstart < min_index_loop){
|
||||
|
@ -1928,10 +1950,10 @@ void fluid_voice_check_sample_sanity(fluid_voice_t* voice)
|
|||
|
||||
/* Run startup specific code (only once, when the voice is started) */
|
||||
if (voice->check_sample_sanity_flag & FLUID_SAMPLESANITY_STARTUP){
|
||||
if (max_index_loop-min_index_loop < FLUID_MIN_LOOP_SIZE){
|
||||
if (_SAMPLEMODE(voice) == FLUID_LOOP
|
||||
if (max_index_loop - min_index_loop < FLUID_MIN_LOOP_SIZE){
|
||||
if ((_SAMPLEMODE(voice) == FLUID_LOOP_UNTIL_RELEASE)
|
||||
|| (_SAMPLEMODE(voice) == FLUID_LOOP_DURING_RELEASE)){
|
||||
voice->gen[GEN_SAMPLEMODE].val=FLUID_UNLOOPED;
|
||||
voice->gen[GEN_SAMPLEMODE].val = FLUID_UNLOOPED;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1942,8 +1964,7 @@ void fluid_voice_check_sample_sanity(fluid_voice_t* voice)
|
|||
|
||||
/* Is this voice run in loop mode, or does it run straight to the
|
||||
end of the waveform data? */
|
||||
if (((_SAMPLEMODE(voice) == FLUID_LOOP)
|
||||
&& (voice->volenv_section < FLUID_VOICE_ENVRELEASE))
|
||||
if (((_SAMPLEMODE(voice) == FLUID_LOOP_UNTIL_RELEASE) && (voice->volenv_section < FLUID_VOICE_ENVRELEASE))
|
||||
|| (_SAMPLEMODE(voice) == FLUID_LOOP_DURING_RELEASE)) {
|
||||
/* Yes, it will loop as soon as it reaches the loop point. In
|
||||
* this case we must prevent, that the playback pointer (phase)
|
||||
|
@ -1959,7 +1980,7 @@ void fluid_voice_check_sample_sanity(fluid_voice_t* voice)
|
|||
int index_in_sample = fluid_phase_index(voice->phase);
|
||||
if (index_in_sample >= voice->loopend){
|
||||
/* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Phase after 2nd loop point!"); */
|
||||
fluid_phase_set_int(voice->phase,voice->loopstart);
|
||||
fluid_phase_set_int(voice->phase, voice->loopstart);
|
||||
}
|
||||
}
|
||||
/* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Sample from %i to %i, loop from %i to %i", voice->start, voice->end, voice->loopstart, voice->loopend); */
|
||||
|
|
|
@ -80,7 +80,6 @@ struct _fluid_voice_t
|
|||
unsigned char key; /* the key, quick acces for noteoff */
|
||||
unsigned char vel; /* the velocity */
|
||||
fluid_channel_t* channel;
|
||||
fluid_preset_t* preset;
|
||||
fluid_gen_t gen[GEN_LAST];
|
||||
fluid_mod_t mod[FLUID_NUM_MOD];
|
||||
int mod_count;
|
||||
|
@ -260,7 +259,6 @@ void fluid_voice_check_sample_sanity(fluid_voice_t* voice);
|
|||
|
||||
#define fluid_voice_set_id(_voice, _id) { (_voice)->id = (_id); }
|
||||
#define fluid_voice_get_chan(_voice) (_voice)->chan
|
||||
#define fluid_voice_get_preset(_voice) (_voice)->preset
|
||||
|
||||
|
||||
#define _PLAYING(voice) (((voice)->status == FLUID_VOICE_ON) || ((voice)->status == FLUID_VOICE_SUSTAINED))
|
||||
|
|
Loading…
Reference in a new issue