mirror of
https://github.com/DrBeef/QuestZDoom.git
synced 2025-03-05 17:10:55 +00:00
889 lines
26 KiB
C
889 lines
26 KiB
C
/* FluidSynth - A Software Synthesizer
|
|
*
|
|
* Copyright (C) 2003 Peter Hanappe and others.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public License
|
|
* as published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the Free
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
* 02111-1307, USA
|
|
*/
|
|
|
|
/* Author: Markus Nentwig, nentwig@users.sourceforge.net
|
|
*/
|
|
|
|
#include "fluid_midi_router.h"
|
|
#include "fluid_midi.h"
|
|
#include "fluid_synth.h"
|
|
#include "fluid_io.h"
|
|
|
|
/**
|
|
* Create a new midi router.
|
|
* @param settings Settings used to configure MIDI router
|
|
* @param handler MIDI event callback
|
|
* @param event_handler_data Caller defined data pointer which gets passed to 'handler'
|
|
* @return New MIDI router instance or NULL on error
|
|
*
|
|
* A midi handler connects to a midi input
|
|
* device and forwards incoming midi events to the synthesizer.
|
|
*/
|
|
fluid_midi_router_t*
|
|
new_fluid_midi_router(fluid_settings_t* settings, handle_midi_event_func_t handler, void* event_handler_data)
|
|
{
|
|
fluid_midi_router_t* router=NULL;
|
|
fluid_midi_router_rule_t* rule=NULL;
|
|
|
|
/* create the router */
|
|
router = FLUID_NEW(fluid_midi_router_t); if (router == NULL){
|
|
FLUID_LOG(FLUID_ERR, "Out of memory");
|
|
return NULL;
|
|
};
|
|
|
|
/* Clear the router, so that error_recovery can safely free all the rules.
|
|
* Dump functions are also NULLed.
|
|
*/
|
|
FLUID_MEMSET(router, 0, sizeof(fluid_midi_router_t));
|
|
/* Retrieve the number of MIDI channels for range limiting */
|
|
fluid_settings_getint(settings, "synth.midi-channels", &router->nr_midi_channels);
|
|
|
|
fluid_mutex_init(router->ruletables_mutex);
|
|
|
|
router->synth = (fluid_synth_t*) event_handler_data;
|
|
router->event_handler=handler;
|
|
router->event_handler_data=event_handler_data;
|
|
|
|
/* Create the default routing rules
|
|
* They accept events on any channel for any range of parameters,
|
|
* and route them unchanged ("result=par*1.0+0") */
|
|
|
|
if (fluid_midi_router_create_default_rules(router) != FLUID_OK) goto error_recovery;
|
|
|
|
return router;
|
|
|
|
error_recovery:
|
|
FLUID_LOG(FLUID_ERR, "new_fluid_midi_router failed");
|
|
fluid_midi_router_destroy_all_rules(router);
|
|
FLUID_FREE(router);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Delete a MIDI router instance.
|
|
* @param router MIDI router to delete
|
|
* @return Always returns 0
|
|
*/
|
|
int
|
|
delete_fluid_midi_router(fluid_midi_router_t* router)
|
|
{
|
|
if (router == NULL) {
|
|
return FLUID_OK;
|
|
}
|
|
fluid_midi_router_destroy_all_rules(router);
|
|
FLUID_FREE(router);
|
|
return FLUID_OK;
|
|
}
|
|
|
|
/*
|
|
* fluid_midi_router_destroy_all_rules(fluid_midi_router_t* router)
|
|
* Purpose:
|
|
* Frees the used memory. This is used only for shutdown!
|
|
*/
|
|
void fluid_midi_router_destroy_all_rules(fluid_midi_router_t* router){
|
|
fluid_midi_router_rule_t* rules[6];
|
|
fluid_midi_router_rule_t* current_rule;
|
|
fluid_midi_router_rule_t* next_rule;
|
|
int i;
|
|
|
|
rules[0]=router->note_rules;
|
|
rules[1]=router->cc_rules;
|
|
rules[2]=router->progchange_rules;
|
|
rules[3]=router->pitchbend_rules;
|
|
rules[4]=router->channel_pressure_rules;
|
|
rules[5]=router->key_pressure_rules;
|
|
for (i=0; i < 6; i++){
|
|
current_rule=rules[i];
|
|
while (current_rule){
|
|
next_rule=current_rule->next;
|
|
FLUID_FREE(current_rule);
|
|
current_rule=next_rule;
|
|
};
|
|
};
|
|
}
|
|
|
|
/*
|
|
* new_fluid_midi_router_rule
|
|
*/
|
|
fluid_midi_router_rule_t* new_fluid_midi_router_rule(void)
|
|
{
|
|
fluid_midi_router_rule_t* rule=FLUID_NEW(fluid_midi_router_rule_t);
|
|
if (rule == NULL) {
|
|
FLUID_LOG(FLUID_ERR, "Out of memory");
|
|
return NULL;
|
|
}
|
|
|
|
FLUID_MEMSET(rule, 0, sizeof(fluid_midi_router_rule_t));
|
|
return rule;
|
|
};
|
|
|
|
/*
|
|
* delete_fluid_midi_router_rule
|
|
*/
|
|
int
|
|
delete_fluid_midi_router_rule(fluid_midi_router_rule_t* rule)
|
|
{
|
|
FLUID_FREE(rule);
|
|
return FLUID_OK;
|
|
}
|
|
|
|
|
|
int fluid_midi_router_create_default_rules(fluid_midi_router_t* router)
|
|
{
|
|
fluid_midi_router_rule_t** rules[6];
|
|
int i;
|
|
|
|
rules[0]=&router->note_rules;
|
|
rules[1]=&router->cc_rules;
|
|
rules[2]=&router->progchange_rules;
|
|
rules[3]=&router->pitchbend_rules;
|
|
rules[4]=&router->channel_pressure_rules;
|
|
rules[5]=&router->key_pressure_rules;
|
|
|
|
for (i=0; i < 6; i++){
|
|
/* Start a new rule. rules[i] is the destination. _end will insert
|
|
* a new rule there into the linked list.
|
|
*/
|
|
if (fluid_midi_router_begin(router, rules[i]) != FLUID_OK) goto error_recovery;
|
|
if (fluid_midi_router_end(router) != FLUID_OK) goto error_recovery;
|
|
};
|
|
|
|
return FLUID_OK;
|
|
error_recovery:
|
|
FLUID_LOG(FLUID_ERR, "fluid_midi_router_create_default_rules failed");
|
|
return FLUID_FAILED;
|
|
};
|
|
|
|
/* Purpose:
|
|
* Resets the new-rule registers in 'router' to their default values.
|
|
* Stores the destination (the linked list into which the new rule is inserted by _end)
|
|
*/
|
|
int fluid_midi_router_begin(fluid_midi_router_t* router, fluid_midi_router_rule_t** dest)
|
|
{
|
|
if (!dest) goto error_recovery;
|
|
router->dest=dest;
|
|
|
|
/* In the days before the router, MIDI data went straight to the synth.
|
|
* So there is no need to check for reasonable values here.
|
|
*/
|
|
router->new_rule_chan_min=0;
|
|
router->new_rule_chan_max=999999;
|
|
router->new_rule_chan_mul=1.;
|
|
router->new_rule_chan_add=0;
|
|
router->new_rule_par1_min=0;
|
|
router->new_rule_par1_max=999999;
|
|
router->new_rule_par1_mul=1.;
|
|
router->new_rule_par1_add=0;
|
|
router->new_rule_par2_min=0;
|
|
router->new_rule_par2_max=999999;
|
|
router->new_rule_par2_mul=1.;
|
|
router->new_rule_par2_add=0;
|
|
|
|
return FLUID_OK;
|
|
error_recovery:
|
|
FLUID_LOG(FLUID_ERR, "fluid_midi_router_begin failed");
|
|
return FLUID_FAILED;
|
|
};
|
|
|
|
/* Purpose:
|
|
* Concludes a sequence of commands:
|
|
* - router_start type
|
|
* - router_chan
|
|
* - router_par1
|
|
* - router_par2
|
|
* - router_end (this call).
|
|
* Creates a new event from the given parameters and stores it in
|
|
* the correct list.
|
|
*/
|
|
|
|
int fluid_midi_router_end(fluid_midi_router_t* router){
|
|
fluid_midi_router_rule_t* rule=new_fluid_midi_router_rule(); if (rule == NULL) goto error_recovery;
|
|
|
|
rule->chan_min=router->new_rule_chan_min;
|
|
rule->chan_max=router->new_rule_chan_max;
|
|
rule->chan_mul=router->new_rule_chan_mul;
|
|
rule->chan_add=router->new_rule_chan_add;
|
|
rule->par1_min=router->new_rule_par1_min;
|
|
rule->par1_max=router->new_rule_par1_max;
|
|
rule->par1_mul=router->new_rule_par1_mul;
|
|
rule->par1_add=router->new_rule_par1_add;
|
|
rule->par2_min=router->new_rule_par2_min;
|
|
rule->par2_max=router->new_rule_par2_max;
|
|
rule->par2_mul=router->new_rule_par2_mul;
|
|
rule->par2_add=router->new_rule_par2_add;
|
|
|
|
/* Now we modify the rules table. Lock it to make sure, that the RT
|
|
* thread is not in the middle of an event. Also prevent, that some
|
|
* other thread modifies the table at the same time.
|
|
*/
|
|
|
|
fluid_mutex_lock(router->ruletables_mutex);
|
|
|
|
rule->next= *(router->dest);
|
|
*router->dest=rule;
|
|
|
|
fluid_mutex_unlock(router->ruletables_mutex);
|
|
|
|
return FLUID_OK;
|
|
|
|
error_recovery:
|
|
FLUID_LOG(FLUID_ERR, "fluid_midi_router_end failed");
|
|
delete_fluid_midi_router_rule(rule);
|
|
return FLUID_FAILED;
|
|
};
|
|
|
|
/**
|
|
* Handle a MIDI event through a MIDI router instance.
|
|
* @param data MIDI router instance #fluid_midi_router_t (DOCME why is it a void *?)
|
|
* @param event MIDI event to handle
|
|
* @return 0 on success, -1 otherwise
|
|
*
|
|
* Purpose: The midi router is called for each event, that is received
|
|
* via the 'physical' midi input. Each event can trigger an arbitrary number
|
|
* of generated events.
|
|
*
|
|
* In default mode, a noteon event is just forwarded to the synth's 'noteon' function,
|
|
* a 'CC' event to the synth's 'CC' function and so on.
|
|
*
|
|
* The router can be used to
|
|
* - filter messages (for example: Pass sustain pedal CCs only to selected channels),
|
|
* - split the keyboard (noteon with notenr < x: to ch 1, >x to ch 2),
|
|
* - layer sounds (for each noteon received on ch 1, create a noteon on ch1, ch2, ch3,...)
|
|
* - velocity scaling (for each noteon event, scale the velocity by 1.27 to give DX7 users
|
|
* a chance)
|
|
* - velocity switching ("v <=100: Angel Choir; V > 100: Hell's Bells")
|
|
* - get rid of aftertouch
|
|
* - ...
|
|
*/
|
|
int
|
|
fluid_midi_router_handle_midi_event(void* data, fluid_midi_event_t* event)
|
|
{
|
|
fluid_midi_router_t* router=(fluid_midi_router_t*)data;
|
|
fluid_synth_t* synth = router->synth;
|
|
fluid_midi_router_rule_t* rule=NULL;
|
|
fluid_midi_router_rule_t* next_rule=NULL;
|
|
int event_has_par2=0; /* Flag, indicates that current event needs two parameters */
|
|
int negative_event=0; /* Flag, indicates that current event releases a key / pedal */
|
|
int par1_max=127; /* Range limit for par1 */
|
|
int par2_max=127; /* Range limit for par2 */
|
|
int ret_val=FLUID_OK;
|
|
|
|
/* Some keyboards report noteoff through a noteon event with vel=0.
|
|
* Convert those to noteoff to ease processing.*/
|
|
if (event->type == NOTE_ON && event->param2 == 0) {
|
|
/* Channel: Remains the same
|
|
* Param 1: Note number, remains the same
|
|
* Param 2: release velocity), set to max
|
|
*/
|
|
event->type = NOTE_OFF;
|
|
event->param2=127;
|
|
};
|
|
|
|
/* Lock the rules table, so that for example the shell thread doesn't
|
|
* clear the rules we are just working with */
|
|
fluid_mutex_lock(router->ruletables_mutex);
|
|
|
|
/* Depending on the event type, choose the correct table of rules.
|
|
* Also invoke the appropriate callback function for the event type
|
|
* to notify external applications.
|
|
* Note: An event type not listed here simply won't find any rules and
|
|
* will be ignored.
|
|
*/
|
|
switch (event->type) {
|
|
case NOTE_ON:
|
|
rule=router->note_rules;
|
|
event_has_par2=1;
|
|
break;
|
|
case NOTE_OFF:
|
|
rule=router->note_rules;
|
|
event_has_par2=1;
|
|
break;
|
|
case CONTROL_CHANGE:
|
|
rule=router->cc_rules;
|
|
event_has_par2=1;
|
|
break;
|
|
case PROGRAM_CHANGE:
|
|
rule=router->progchange_rules;
|
|
break;
|
|
case PITCH_BEND:
|
|
rule=router->pitchbend_rules;
|
|
par1_max=16383;
|
|
break;
|
|
case CHANNEL_PRESSURE:
|
|
rule=router->channel_pressure_rules;
|
|
break;
|
|
case KEY_PRESSURE:
|
|
rule=router->key_pressure_rules;
|
|
event_has_par2=1;
|
|
break;
|
|
case MIDI_SYSTEM_RESET:
|
|
return router->event_handler(router->event_handler_data,event);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* At this point 'rule' contains the first rule in a linked list.
|
|
* Check for all rules, whether channel and parameter ranges match.
|
|
*/
|
|
|
|
while (rule){
|
|
int chan; /* Channel of the generated event */
|
|
int par1; /* par1 of the generated event */
|
|
int par2=0;
|
|
int event_par1=(int)event->param1;
|
|
int event_par2=(int)event->param2;
|
|
fluid_midi_event_t new_event;
|
|
|
|
/* Store the pointer to the next rule right now. If the rule is later flagged for destruction,
|
|
* it may not be accessed anymore.
|
|
*/
|
|
next_rule=rule->next;
|
|
|
|
/* Check, whether the rule is still active. Expired rules cannot be removed immediately,
|
|
* because freeing memory in a realtime thread is no good idea. And the MIDI thread,
|
|
* which calls us here, has raised priority.*/
|
|
if (rule->state == MIDIRULE_DONE){
|
|
goto do_next_rule;
|
|
};
|
|
|
|
/* Channel window */
|
|
if (rule->chan_min > rule->chan_max){
|
|
/* Inverted rule: Exclude everything between max and min (but not min/max) */
|
|
if (event->channel > rule->chan_max && event->channel < rule->chan_min){
|
|
goto do_next_rule;
|
|
}
|
|
} else {
|
|
/* Normal rule: Exclude everything < max or > min (but not min/max) */
|
|
if (event->channel > rule->chan_max || event->channel < rule->chan_min){
|
|
goto do_next_rule;
|
|
}
|
|
};
|
|
|
|
/* Par 1 window */
|
|
if (rule->par1_min > rule->par1_max){
|
|
/* Inverted rule: Exclude everything between max and min (but not min/max) */
|
|
if (event_par1 > rule->par1_max && event_par1 < rule->par1_min){
|
|
goto do_next_rule;
|
|
}
|
|
} else {
|
|
/* Normal rule: Exclude everything < max or > min (but not min/max)*/
|
|
if (event_par1 > rule->par1_max || event_par1 < rule->par1_min){
|
|
goto do_next_rule;
|
|
}
|
|
};
|
|
|
|
/* Par 2 window (only applies to event types, which have 2 pars)
|
|
* For noteoff events, velocity switching doesn't make any sense.
|
|
* Velocity scaling might be useful, though.
|
|
*/
|
|
if (event_has_par2 && event->type != NOTE_OFF){
|
|
if (rule->par2_min > rule->par2_max){
|
|
/* Inverted rule: Exclude everything between max and min (but not min/max) */
|
|
if (event_par2 > rule->par2_max && event_par2 < rule->par2_min){
|
|
goto do_next_rule;
|
|
}
|
|
} else {
|
|
/* Normal rule: Exclude everything < max or > min (but not min/max)*/
|
|
if (event_par2 > rule->par2_max || event_par2 < rule->par2_min){
|
|
goto do_next_rule;
|
|
};
|
|
};
|
|
};
|
|
|
|
/* Channel scaling / offset
|
|
* Note: rule->chan_mul will probably be 0 or 1. If it's 0, input from all
|
|
* input channels is mapped to the same synth channel.
|
|
*/
|
|
chan=(int)((fluid_real_t)event->channel * (fluid_real_t)rule->chan_mul + (fluid_real_t)rule->chan_add + 0.5);
|
|
|
|
/* Par 1 scaling / offset */
|
|
par1=(int)((fluid_real_t)event_par1 * (fluid_real_t)rule->par1_mul + (fluid_real_t)rule->par1_add + 0.5);
|
|
|
|
/* Par 2 scaling / offset, if applicable */
|
|
if (event_has_par2){
|
|
par2=(int)((fluid_real_t)event_par2 * (fluid_real_t)rule->par2_mul + (fluid_real_t)rule->par2_add + 0.5);
|
|
};
|
|
|
|
/* Channel range limiting */
|
|
if (chan < 0){
|
|
chan=0;
|
|
/* Upper limit is hard to implement, because the number of MIDI channels can be changed via settings any time. */
|
|
};
|
|
|
|
/* Par1 range limiting */
|
|
if (par1 < 0){
|
|
par1=0;
|
|
} else if (par1 > par1_max){
|
|
par1=par1_max;
|
|
};
|
|
|
|
/* Par2 range limiting */
|
|
if (event_has_par2){
|
|
if (par2 < 0){
|
|
par2=0;
|
|
} else if (par2 > par2_max){
|
|
par2=par2_max;
|
|
};
|
|
};
|
|
|
|
/* At this point we have to create an event of event->type on 'chan' with par1 (maybe par2).
|
|
* We keep track on the state of noteon and sustain pedal events. If the application tries
|
|
* to delete a rule, it will only be fully removed, if pending noteoff / pedal off events have
|
|
* arrived. In the meantime (MIDIRULE_WAITING) state, it will only let through 'negative' events
|
|
* (noteoff or pedal up).
|
|
*/
|
|
|
|
if (
|
|
event->type == NOTE_ON
|
|
|| (event->type == CONTROL_CHANGE && par1 == SUSTAIN_SWITCH && par2 >= 64)
|
|
){
|
|
/* Noteon or sustain pedal down event generated */
|
|
if (rule->keys_cc[par1] == 0){
|
|
rule->keys_cc[par1]=1;
|
|
rule->pending_events++;
|
|
};
|
|
} else if (
|
|
event->type == NOTE_OFF
|
|
|| (event->type == CONTROL_CHANGE && par1 == SUSTAIN_SWITCH && par2 < 64)
|
|
){
|
|
/* Noteoff or sustain pedal up event generated */
|
|
if (rule->keys_cc[par1] > 0){
|
|
rule->keys_cc[par1]=0;
|
|
rule->pending_events--;
|
|
negative_event=1;
|
|
};
|
|
};
|
|
|
|
if (rule->state == MIDIRULE_WAITING){
|
|
if (negative_event){
|
|
if (rule->pending_events == 0){
|
|
/* There are no more pending events in the rule - all keys are up.
|
|
* Change its state to 'unused', it will be cleared up at the next
|
|
* opportunity to run 'free' safely.
|
|
* Note: After this, the rule may disappear at any time
|
|
*/
|
|
rule->state=MIDIRULE_DONE;
|
|
};
|
|
} else {
|
|
|
|
/* This rule only exists because of some unfinished business:
|
|
* There is still a note or sustain pedal waiting to be
|
|
* released. But the event that came in does not release any.
|
|
* So we just ignore the event and go on with the next rule.
|
|
*/
|
|
goto do_next_rule;
|
|
};
|
|
};
|
|
|
|
/* At this point it is decided, what is sent to the synth.
|
|
* Create a new event and make the appropriate call
|
|
*/
|
|
|
|
fluid_midi_event_set_type(&new_event, event->type);
|
|
fluid_midi_event_set_channel(&new_event, chan);
|
|
/* fluid_midi_event_set_param1(&new_event, par1); */
|
|
/* fluid_midi_event_set_param2(&new_event, par2); */
|
|
new_event.param1 = par1;
|
|
new_event.param2 = par2;
|
|
|
|
/* Don't know what to do with return values: What is, if
|
|
* generating one event fails? Current solution: Make all calls,
|
|
* regardless of failures, but report failure to the caller, as
|
|
* soon as only one call fails. Strategy should be thought
|
|
* through.
|
|
*/
|
|
if (router->event_handler(router->event_handler_data, &new_event) != FLUID_OK) {
|
|
ret_val=FLUID_FAILED;
|
|
}
|
|
|
|
do_next_rule:
|
|
rule=next_rule;
|
|
};
|
|
|
|
/* We are finished with the rule tables. Allow the other threads to do their job. */
|
|
fluid_mutex_unlock(router->ruletables_mutex);
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
int fluid_midi_router_handle_clear(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
|
|
if (ac != 0) {
|
|
fluid_ostream_printf(out, "router_clear needs no arguments.\n");
|
|
goto error_recovery;
|
|
}
|
|
|
|
/* Disable rules and mark for destruction */
|
|
fluid_midi_router_disable_all_rules(router);
|
|
|
|
/* Free unused rules */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
|
|
return 0;
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
int fluid_midi_router_handle_default(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
|
|
if (ac != 0) {
|
|
fluid_ostream_printf(out, "router_default needs no arguments.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Disable rules and mark for destruction */
|
|
fluid_midi_router_disable_all_rules(router);
|
|
|
|
/* Create default rules */
|
|
if (fluid_midi_router_create_default_rules(router) != FLUID_OK){
|
|
FLUID_LOG(FLUID_ERR, "create_default_rules failed");
|
|
goto error_recovery;
|
|
};
|
|
|
|
/* Free unused rules */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
|
|
return 0;
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
int fluid_midi_router_handle_begin(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
fluid_midi_router_rule_t** dest=NULL;
|
|
|
|
if (ac != 1) {
|
|
fluid_ostream_printf(out, "router_begin needs no arguments.\n");
|
|
goto error_recovery;
|
|
}
|
|
|
|
if (FLUID_STRCMP(av[0],"note") == 0){
|
|
dest=& router->note_rules;
|
|
} else if (FLUID_STRCMP(av[0],"cc") == 0){
|
|
dest=& router->cc_rules;
|
|
} else if (FLUID_STRCMP(av[0],"prog") == 0){
|
|
dest=& router->progchange_rules;
|
|
} else if (FLUID_STRCMP(av[0],"pbend") == 0){
|
|
dest=& router->pitchbend_rules;
|
|
} else if (FLUID_STRCMP(av[0],"cpress") == 0){
|
|
dest=& router->channel_pressure_rules;
|
|
} else if (FLUID_STRCMP(av[0],"kpress") == 0){
|
|
dest=& router->key_pressure_rules;
|
|
};
|
|
|
|
if (dest == NULL){
|
|
fluid_ostream_printf(out, "router_begin args: note, cc, prog, pbend, cpress, kpress\n");
|
|
goto error_recovery;
|
|
};
|
|
|
|
if (fluid_midi_router_begin(router, dest) != FLUID_OK){
|
|
goto error_recovery;
|
|
};
|
|
|
|
/* Free unused rules (give it a try) */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
|
|
return 0;
|
|
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
int fluid_midi_router_handle_end(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
|
|
if (ac != 0) {
|
|
fluid_ostream_printf(out, "router_end needs no arguments.");
|
|
goto error_recovery;
|
|
}
|
|
|
|
if (fluid_midi_router_end(router) != FLUID_OK){
|
|
FLUID_LOG(FLUID_ERR, "midi_router_end failed");
|
|
goto error_recovery;
|
|
};
|
|
|
|
/* Free unused rules (give it a try) */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
|
|
return 0;
|
|
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
int fluid_midi_router_handle_chan(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
|
|
if (ac != 4) {
|
|
fluid_ostream_printf(out, "router_chan needs four args: min, max, mul, add.");
|
|
goto error_recovery;
|
|
}
|
|
|
|
router->new_rule_chan_min=atoi(av[0]);
|
|
router->new_rule_chan_max=atoi(av[1]);
|
|
router->new_rule_chan_mul=atoi(av[2]);
|
|
router->new_rule_chan_add=atoi(av[3]);
|
|
|
|
/* Free unused rules (give it a try) */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
|
|
return 0;
|
|
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
int fluid_midi_router_handle_par1(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
|
|
if (ac != 4) {
|
|
fluid_ostream_printf(out, "router_par1 needs four args: min, max, mul, add.");
|
|
goto error_recovery;
|
|
}
|
|
|
|
router->new_rule_par1_min=atoi(av[0]);
|
|
router->new_rule_par1_max=atoi(av[1]);
|
|
router->new_rule_par1_mul=atoi(av[2]);
|
|
router->new_rule_par1_add=atoi(av[3]);
|
|
|
|
/* Free unused rules (give it a try) */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
|
|
return 0;
|
|
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
int fluid_midi_router_handle_par2(fluid_synth_t* synth, int ac, char** av, fluid_ostream_t out)
|
|
{
|
|
fluid_midi_router_t* router=synth->midi_router;
|
|
|
|
if (ac != 4) {
|
|
fluid_ostream_printf(out, "router_par2 needs four args: min, max, mul, add.");
|
|
goto error_recovery;
|
|
}
|
|
|
|
router->new_rule_par2_min=atoi(av[0]);
|
|
router->new_rule_par2_max=atoi(av[1]);
|
|
router->new_rule_par2_mul=atoi(av[2]);
|
|
router->new_rule_par2_add=atoi(av[3]);
|
|
|
|
/* Free unused rules (give it a try) */
|
|
fluid_midi_router_free_unused_rules(router);
|
|
return 0;
|
|
|
|
error_recovery:
|
|
return -1;
|
|
};
|
|
|
|
void fluid_midi_router_disable_all_rules(fluid_midi_router_t* router)
|
|
{
|
|
/* Go through all rules. If the rule has no outstanding events, then
|
|
* flag it for destruction. Otherwise change it to wait mode. Then
|
|
* it will handle the pending events, and flag itself for destruction
|
|
* as soon as all events have arrived.
|
|
*/
|
|
|
|
fluid_midi_router_rule_t* rules[6];
|
|
fluid_midi_router_rule_t* current_rule;
|
|
int i;
|
|
rules[0]=router->note_rules;
|
|
rules[1]=router->cc_rules;
|
|
rules[2]=router->progchange_rules;
|
|
rules[3]=router->pitchbend_rules;
|
|
rules[4]=router->channel_pressure_rules;
|
|
rules[5]=router->key_pressure_rules;
|
|
|
|
/* Lock the rules table. We live on the assumption, that the rules
|
|
* table does not change while we are chewing at it.
|
|
* Changes between the processing of note / cc etc are permitted.
|
|
*/
|
|
for (i=0; i < 6; i++){
|
|
fluid_mutex_lock(router->ruletables_mutex);
|
|
current_rule=rules[i];
|
|
while (current_rule){
|
|
if (current_rule->pending_events == 0){
|
|
/* Flag for destruction */
|
|
current_rule->state=MIDIRULE_DONE;
|
|
} else {
|
|
/* Wait mode */
|
|
current_rule->state=MIDIRULE_WAITING;
|
|
};
|
|
current_rule=current_rule->next;
|
|
};
|
|
fluid_mutex_unlock(router->ruletables_mutex);
|
|
};
|
|
};
|
|
|
|
void fluid_midi_router_free_unused_rules(fluid_midi_router_t* router)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < 6; i++){
|
|
fluid_midi_router_rule_t** p=NULL;
|
|
|
|
/* We assume, that the table does not change while we are at it.
|
|
* Between different types (note, cc etc) we can allow changes.
|
|
*/
|
|
fluid_mutex_lock(router->ruletables_mutex);
|
|
switch(i){
|
|
case 0:
|
|
p=&router->note_rules;
|
|
break;
|
|
case 1:
|
|
p=&router->cc_rules;
|
|
break;
|
|
case 2:
|
|
p=&router->progchange_rules;
|
|
break;
|
|
case 3:
|
|
p=&router->pitchbend_rules;
|
|
break;
|
|
case 4:
|
|
p=&router->channel_pressure_rules;
|
|
break;
|
|
case 5:
|
|
p=&router->key_pressure_rules;
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
|
|
while (*p){
|
|
fluid_midi_router_rule_t* current_rule=*p;
|
|
fluid_midi_router_rule_t* next_rule=current_rule->next;
|
|
|
|
if (current_rule->state == MIDIRULE_DONE){
|
|
/* p points to current_rule.
|
|
* current_rule->next points to next_rule.
|
|
* Unlink current_rule from the chain by setting the content
|
|
* of p to next_rule.
|
|
*/
|
|
|
|
*p=next_rule;
|
|
|
|
/* Now the rule is not in the chain anymore. Destroy it. */
|
|
delete_fluid_midi_router_rule(current_rule);
|
|
|
|
} else {
|
|
/* We have to keep the rule, there is still unfinished business. */
|
|
p = ¤t_rule->next;
|
|
};
|
|
};
|
|
fluid_mutex_unlock(router->ruletables_mutex);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* MIDI event callback function to display event information to stdout
|
|
* @param data MIDI router instance
|
|
* @param event MIDI event data
|
|
* @return 0 on success, -1 otherwise
|
|
*
|
|
* An implementation of the #handle_midi_event_func_t function type, used for
|
|
* displaying MIDI event information between the MIDI driver and router to
|
|
* stdout. Useful for adding into a MIDI router chain for debugging MIDI events.
|
|
*/
|
|
int fluid_midi_dump_prerouter(void* data, fluid_midi_event_t* event)
|
|
{
|
|
switch (event->type) {
|
|
case NOTE_ON:
|
|
fprintf(stdout, "event_pre_noteon %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
case NOTE_OFF:
|
|
fprintf(stdout, "event_pre_noteoff %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
case CONTROL_CHANGE:
|
|
fprintf(stdout, "event_pre_cc %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
case PROGRAM_CHANGE:
|
|
fprintf(stdout, "event_pre_prog %i %i\n", event->channel, event->param1);
|
|
break;
|
|
case PITCH_BEND:
|
|
fprintf(stdout, "event_pre_pitch %i %i\n", event->channel, event->param1);
|
|
break;
|
|
case CHANNEL_PRESSURE:
|
|
fprintf(stdout, "event_pre_cpress %i %i\n", event->channel, event->param1);
|
|
break;
|
|
case KEY_PRESSURE:
|
|
fprintf(stdout, "event_pre_kpress %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
return fluid_midi_router_handle_midi_event((fluid_midi_router_t*) data, event);
|
|
};
|
|
|
|
/**
|
|
* MIDI event callback function to display event information to stdout
|
|
* @param data MIDI router instance
|
|
* @param event MIDI event data
|
|
* @return 0 on success, -1 otherwise
|
|
*
|
|
* An implementation of the #handle_midi_event_func_t function type, used for
|
|
* displaying MIDI event information between the MIDI driver and router to
|
|
* stdout. Useful for adding into a MIDI router chain for debugging MIDI events.
|
|
*/
|
|
int fluid_midi_dump_postrouter(void* data, fluid_midi_event_t* event)
|
|
{
|
|
switch (event->type) {
|
|
case NOTE_ON:
|
|
fprintf(stdout, "event_post_noteon %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
case NOTE_OFF:
|
|
fprintf(stdout, "event_post_noteoff %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
case CONTROL_CHANGE:
|
|
fprintf(stdout, "event_post_cc %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
case PROGRAM_CHANGE:
|
|
fprintf(stdout, "event_post_prog %i %i\n", event->channel, event->param1);
|
|
break;
|
|
case PITCH_BEND:
|
|
fprintf(stdout, "event_post_pitch %i %i\n", event->channel, event->param1);
|
|
break;
|
|
case CHANNEL_PRESSURE:
|
|
fprintf(stdout, "event_post_cpress %i %i\n", event->channel, event->param1);
|
|
break;
|
|
case KEY_PRESSURE:
|
|
fprintf(stdout, "event_post_kpress %i %i %i\n",
|
|
event->channel, event->param1, event->param2);
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
return fluid_synth_handle_midi_event((fluid_synth_t*) data, event);
|
|
};
|