Ejemplo n.º 1
0
/* Create event_entry and append to the preQueue.
 * May be called from the main thread (usually) but also recursively
 * from the queue thread, when a callback itself does an insert... */
static void
_fluid_seq_queue_pre_remove(fluid_sequencer_t* seq, short src, short dest, int type)
{
	fluid_evt_entry * evtentry = _fluid_seq_heap_get_free(seq->heap);
	if (evtentry == NULL) {
		/* should not happen */
		fluid_log(FLUID_PANIC, "sequencer: no more free events\n");
		return;
	}

	evtentry->next = NULL;
	evtentry->entryType = FLUID_EVT_ENTRY_REMOVE;
	{
		fluid_event_t* evt = &(evtentry->evt);
		fluid_event_set_source(evt, src);
		fluid_event_set_source(evt, src);
		fluid_event_set_dest(evt, dest);
		evt->type = type;
	}

	fluid_mutex_lock(seq->mutex);

	/* append to preQueue */
	if (seq->preQueueLast) {
		seq->preQueueLast->next = evtentry;
	} else {
		seq->preQueue = evtentry;
	}
	seq->preQueueLast = evtentry;

	fluid_mutex_unlock(seq->mutex);
	return;
}
Ejemplo n.º 2
0
/** 
 * Advance a sequencer that isn't using the system timer.
 * @param seq Sequencer object
 * @param msec Time to advance sequencer to (absolute time since sequencer start).
 * @since 1.1.0
 */
void
fluid_sequencer_process(fluid_sequencer_t* seq, unsigned int msec)
{

	/* process prequeue */
	fluid_evt_entry* tmp;
	fluid_evt_entry* next;

	fluid_mutex_lock(seq->mutex);

	/* get the preQueue */
	tmp = seq->preQueue;
	seq->preQueue = NULL;
	seq->preQueueLast = NULL;

	fluid_mutex_unlock(seq->mutex);

	/* walk all the preQueue and process them in order : inserts and removes */
	while (tmp) {
		next = tmp->next;

		if (tmp->entryType == FLUID_EVT_ENTRY_REMOVE) {
			_fluid_seq_queue_remove_entries_matching(seq, tmp);
		} else {
			_fluid_seq_queue_insert_entry(seq, tmp);
		}

		tmp = next;
	}

	/* send queued events */
	fluid_atomic_int_set(&seq->currentMs, msec);
	_fluid_seq_queue_send_queued_events(seq);

}
Ejemplo n.º 3
0
void
_fluid_evt_heap_free(fluid_evt_heap_t* heap)
{
#ifdef HEAP_WITH_DYNALLOC
  fluid_evt_entry *tmp, *next;

  /* LOCK */
  fluid_mutex_lock(heap->mutex);

  tmp = heap->freelist;
  while (tmp) {
    next = tmp->next;
    FLUID_FREE(tmp);
    tmp = next;
  }

  /* UNLOCK */
  fluid_mutex_unlock(heap->mutex);
  fluid_mutex_destroy(heap->mutex);

  FLUID_FREE(heap);

#else
	FLUID_FREE(heap);
#endif
}
Ejemplo n.º 4
0
/* Create event_entry and append to the preQueue.
 * May be called from the main thread (usually) but also recursively
 * from the queue thread, when a callback itself does an insert... */
static short
_fluid_seq_queue_pre_insert(fluid_sequencer_t* seq, fluid_event_t * evt)
{
	fluid_evt_entry * evtentry = _fluid_seq_heap_get_free(seq->heap);
	if (evtentry == NULL) {
		/* should not happen */
		fluid_log(FLUID_PANIC, "sequencer: no more free events\n");
		return -1;
	}

	evtentry->next = NULL;
	evtentry->entryType = FLUID_EVT_ENTRY_INSERT;
	FLUID_MEMCPY(&(evtentry->evt), evt, sizeof(fluid_event_t));

	fluid_mutex_lock(seq->mutex);

	/* append to preQueue */
	if (seq->preQueueLast) {
		seq->preQueueLast->next = evtentry;
	} else {
		seq->preQueue = evtentry;
	}
	seq->preQueueLast = evtentry;

	fluid_mutex_unlock(seq->mutex);

	return (0);
}
Ejemplo n.º 5
0
/**
 * Add a rule to a MIDI router.
 * @param router MIDI router
 * @param rule Rule to add (used directly and should not be accessed again following a
 *   successful call to this function).
 * @param type The type of rule to add (#fluid_midi_router_rule_type)
 * @return #FLUID_OK on success, #FLUID_FAILED otherwise (invalid rule for example)
 * @since 1.1.0
 */
int
fluid_midi_router_add_rule (fluid_midi_router_t *router, fluid_midi_router_rule_t *rule,
                            int type)
{
  fluid_midi_router_rule_t *free_rules, *next_rule;

  fluid_return_val_if_fail (router != NULL, FLUID_FAILED);
  fluid_return_val_if_fail (rule != NULL, FLUID_FAILED);
  fluid_return_val_if_fail (type >= 0 && type < FLUID_MIDI_ROUTER_RULE_COUNT, FLUID_FAILED);


  fluid_mutex_lock (router->rules_mutex);       /* ++ lock */

  /* Take over free rules list, if any (to free outside of lock) */
  free_rules = router->free_rules;
  router->free_rules = NULL;

  rule->next = router->rules[type];
  router->rules[type] = rule;

  fluid_mutex_unlock (router->rules_mutex);     /* -- unlock */


  /* Free any deactivated rules which were waiting for events and are now done */

  for (; free_rules; free_rules = next_rule)
  {
    next_rule = free_rules->next;
    FLUID_FREE (free_rules);
  }

  return FLUID_OK;
}
Ejemplo n.º 6
0
fluid_evt_heap_t*
_fluid_evt_heap_init(int nbEvents)
{
#ifdef HEAP_WITH_DYNALLOC

  int i;
  fluid_evt_heap_t* heap;
  fluid_evt_entry *tmp;

  heap = FLUID_NEW(fluid_evt_heap_t);
  if (heap == NULL) {
    fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
    return NULL;
  }

  heap->freelist = NULL;
  fluid_mutex_init(heap->mutex);

  /* LOCK */
  fluid_mutex_lock(heap->mutex);

  /* Allocate the event entries */
  for (i = 0; i < nbEvents; i++) {
    tmp = FLUID_NEW(fluid_evt_entry);
    tmp->next = heap->freelist;
    heap->freelist = tmp;
  }

  /* UNLOCK */
  fluid_mutex_unlock(heap->mutex);


#else
	int i;
	fluid_evt_heap_t* heap;
	int siz = 2*sizeof(fluid_evt_entry *) + sizeof(fluid_evt_entry)*nbEvents;

	heap = (fluid_evt_heap_t *)FLUID_MALLOC(siz);
  if (heap == NULL) {
    fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
    return NULL;
  }
  FLUID_MEMSET(heap, 0, siz);

  /* link all heap events */
  {
  	fluid_evt_entry *tmp = &(heap->pool);
	  for (i = 0 ; i < nbEvents - 1 ; i++)
 		 	tmp[i].next = &(tmp[i+1]);
 	 	tmp[nbEvents-1].next = NULL;

 	 	/* set head & tail */
 	 	heap->tail = &(tmp[nbEvents-1]);
  	heap->head = &(heap->pool);
  }
#endif
  return (heap);
}
Ejemplo n.º 7
0
/**
 * Clear all rules in a MIDI router. Such a router will drop all events until
 * rules are added.
 * @param router Router to clear all rules from
 * @return #FLUID_OK on success, #FLUID_FAILED otherwise
 * @since 1.1.0
 */
int
fluid_midi_router_clear_rules (fluid_midi_router_t *router)
{
  fluid_midi_router_rule_t *del_rules[FLUID_MIDI_ROUTER_RULE_COUNT];
  fluid_midi_router_rule_t *rule, *next_rule, *prev_rule;
  int i;

  fluid_return_val_if_fail (router != NULL, FLUID_FAILED);

  fluid_mutex_lock (router->rules_mutex);       /* ++ lock */

  for (i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++)
  {
    del_rules[i] = NULL;
    prev_rule = NULL;

    /* Process existing rules */
    for (rule = router->rules[i]; rule; rule = next_rule)
    {
      next_rule = rule->next;

      if (rule->pending_events == 0)    /* Rule has no pending events? */
      { /* Remove rule from rule list */
        if (prev_rule) prev_rule->next = next_rule;
        else if (rule == router->rules[i]) router->rules[i] = next_rule;

        /* Prepend to delete list */
        rule->next = del_rules[i];
        del_rules[i] = rule;
      }
      else
      {
        rule->waiting = TRUE;           /* Pending events, mark as waiting */
        prev_rule = rule;
      }
    }
  }

  fluid_mutex_unlock (router->rules_mutex);     /* -- unlock */


  /* Free old rules outside of lock */

  for (i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++)
  {
    for (rule = del_rules[i]; rule; rule = next_rule)
    {
      next_rule = rule->next;
      FLUID_FREE (rule);
    }
  }

  return FLUID_OK;
}
Ejemplo n.º 8
0
int fluid_samplecache_load(SFData *sf,
                           unsigned int sample_start, unsigned int sample_end, int sample_type,
                           int try_mlock, short **sample_data, char **sample_data24)
{
    fluid_samplecache_entry_t *entry;
    int ret;

    fluid_mutex_lock(samplecache_mutex);

    entry = get_samplecache_entry(sf, sample_start, sample_end, sample_type);
    if (entry == NULL)
    {
        entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type);
        if (entry == NULL)
        {
            ret = -1;
            goto unlock_exit;
        }

        samplecache_list = fluid_list_prepend(samplecache_list, entry);
    }

    if (try_mlock && !entry->mlocked)
    {
        /* Lock the memory to disable paging. It's okay if this fails. It
         * probably means that the user doesn't have the required permission. */
        if (fluid_mlock(entry->sample_data, entry->sample_count * sizeof(short)) == 0)
        {
            if (entry->sample_data24 != NULL)
            {
                entry->mlocked = (fluid_mlock(entry->sample_data24, entry->sample_count) == 0);
            }
            else
            {
                entry->mlocked = TRUE;
            }

            if (!entry->mlocked)
            {
                fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short));
                FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible.");
            }
        }
    }

    entry->num_references++;
    *sample_data = entry->sample_data;
    *sample_data24 = entry->sample_data24;
    ret = entry->sample_count;

unlock_exit:
    fluid_mutex_unlock(samplecache_mutex);
    return ret;
}
Ejemplo n.º 9
0
int fluid_samplecache_unload(const short *sample_data)
{
    fluid_list_t *entry_list;
    fluid_samplecache_entry_t *entry;
    int ret;

    fluid_mutex_lock(samplecache_mutex);

    entry_list = samplecache_list;
    while (entry_list)
    {
        entry = (fluid_samplecache_entry_t *)fluid_list_get(entry_list);

        if (sample_data == entry->sample_data)
        {
            entry->num_references--;

            if (entry->num_references == 0)
            {
                if (entry->mlocked)
                {
                    fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short));
                    if (entry->sample_data24 != NULL)
                    {
                        fluid_munlock(entry->sample_data24, entry->sample_count);
                    }
                }

                samplecache_list = fluid_list_remove(samplecache_list, entry);
                delete_samplecache_entry(entry);
            }

            ret = FLUID_OK;
            goto unlock_exit;
        }

        entry_list = fluid_list_next(entry_list);
    }

    FLUID_LOG(FLUID_ERR, "Trying to free sample data not found in cache.");
    ret = FLUID_FAILED;

unlock_exit:
    fluid_mutex_unlock(samplecache_mutex);
    return ret;
}
Ejemplo n.º 10
0
fluid_evt_entry*
_fluid_seq_heap_get_free(fluid_evt_heap_t* heap)
{
#ifdef HEAP_WITH_DYNALLOC
  fluid_evt_entry* evt = NULL;

  /* LOCK */
  fluid_mutex_lock(heap->mutex);

#if !defined(MACOS9)
  if (heap->freelist == NULL) {
    heap->freelist = FLUID_NEW(fluid_evt_entry);
    if (heap->freelist != NULL) {
      heap->freelist->next = NULL;
    }
  }
#endif

  evt = heap->freelist;

  if (evt != NULL) {
    heap->freelist = heap->freelist->next;
    evt->next = NULL;
  }

  /* UNLOCK */
  fluid_mutex_unlock(heap->mutex);

  return evt;

#else
	fluid_evt_entry* evt;
	if (heap->head == NULL) return NULL;

	/* take from head of the heap */
	/* critical - should threadlock ? */
	evt = heap->head;
	heap->head = heap->head->next;

	return evt;
#endif
}
Ejemplo n.º 11
0
void
_fluid_seq_heap_set_free(fluid_evt_heap_t* heap, fluid_evt_entry* evt)
{
#ifdef HEAP_WITH_DYNALLOC

  /* LOCK */
  fluid_mutex_lock(heap->mutex);

  evt->next = heap->freelist;
  heap->freelist = evt;

  /* UNLOCK */
  fluid_mutex_unlock(heap->mutex);

#else
	/* append to the end of the heap */
	/* critical - should threadlock ? */
	heap->tail->next = evt;
	heap->tail = evt;
	evt->next = NULL;
#endif
}
Ejemplo n.º 12
0
/**
 * Handle a MIDI event through a MIDI router instance.
 * @param data MIDI router instance #fluid_midi_router_t, its a void * so that
 *   this function can be used as a callback for other subsystems
 *   (new_fluid_midi_driver() for example).
 * @param event MIDI event to handle
 * @return #FLUID_OK on success, #FLUID_FAILED 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 (one for each rule that matches).
 *
 * 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_midi_router_rule_t **rulep, *rule, *next_rule, *prev_rule = NULL;
  int event_has_par2 = 0; /* Flag, indicates that current event needs two parameters */
  int par1_max = 127;     /* Range limit for par1 */
  int par2_max = 127;     /* Range limit for par2 */
  int ret_val = FLUID_OK;

  int chan; /* Channel of the generated event */
  int par1; /* par1 of the generated event */
  int par2;
  int event_par1;
  int event_par2;
  fluid_midi_event_t new_event;

  /* 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)
  {
    event->type = NOTE_OFF;
    event->param2 = 127;        /* Release velocity */
  }

  fluid_mutex_lock (router->rules_mutex);   /* ++ lock rules */

  /* Depending on the event type, choose the correct list of rules. */
  switch (event->type)
  {
      case NOTE_ON:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_NOTE];
	event_has_par2 = 1;
	break;
      case NOTE_OFF:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_NOTE];
	event_has_par2 = 1;
	break;
      case CONTROL_CHANGE:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_CC];
	event_has_par2 = 1;
	break;
      case PROGRAM_CHANGE:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_PROG_CHANGE];
	break;
      case PITCH_BEND:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_PITCH_BEND];
	par1_max = 16383;
	break;
      case CHANNEL_PRESSURE:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_CHANNEL_PRESSURE];
	break;
      case KEY_PRESSURE:
	rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_KEY_PRESSURE];
	event_has_par2 = 1;
	break;
      case MIDI_SYSTEM_RESET:
      case MIDI_SYSEX:
        ret_val = router->event_handler (router->event_handler_data,event);
        fluid_mutex_unlock (router->rules_mutex);  /* -- unlock rules */
        return ret_val;
      default:
        rulep = NULL;    /* Event will not be passed on */
	break;
  }

  /* Loop over rules in the list, looking for matches for this event. */
  for (rule = rulep ? *rulep : NULL; rule; prev_rule = rule, rule = next_rule)
  {
    event_par1 = (int)event->param1;
    event_par2 = (int)event->param2;
    next_rule = rule->next;     /* Rule may get removed from list, so get next here */

    /* 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)
	continue;
    }
    else        /* Normal rule: Exclude everything < max or > min (but not min/max) */
    {
      if (event->channel > rule->chan_max || event->channel < rule->chan_min)
	continue;
    }

    /* 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)
	continue;
    }
    else        /* Normal rule: Exclude everything < max or > min (but not min/max)*/
    {
      if (event_par1 > rule->par1_max || event_par1 < rule->par1_min)
	continue;
    }

    /* 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)
	  continue;
      }
      else      /* Normal rule: Exclude everything < max or > min (but not min/max)*/
      {
	if (event_par2 > rule->par2_max || event_par2 < rule->par2_min)
	  continue;
      }
    }

    /* 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);
    else par2 = 0;

    /* Channel range limiting */
    if (chan < 0)
      chan = 0;
    else if (chan >= router->nr_midi_channels)
      chan = router->nr_midi_channels - 1;

    /* 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 while waiting, 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--;

        /* Rule is waiting for negative event to be destroyed? */
        if (rule->waiting)
        {
	  if (rule->pending_events == 0)
          { /* Remove rule from rule list */
            if (prev_rule) prev_rule->next = next_rule;
            else *rulep = next_rule;

            /* Add to free list */
            rule->next = router->free_rules;
            router->free_rules = rule;

            rule = prev_rule;   /* Set rule to previous rule, which gets assigned to the next prev_rule value (in for() statement) */
          }

          goto send_event;      /* Pass the event to complete the cycle */
        }
      }
    }

    /* Rule is still waiting for negative event? (note off or pedal up) */
    if (rule->waiting)
      continue;         /* Skip (rule is inactive except for matching negative event) */

send_event:

    /* 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);
    new_event.param1 = par1;
    new_event.param2 = par2;

    /* FIXME - What should be done on failure?  For now continue to process events, but return failure to caller. */
    if (router->event_handler (router->event_handler_data, &new_event) != FLUID_OK)
      ret_val = FLUID_FAILED;
  }

  fluid_mutex_unlock (router->rules_mutex);         /* -- unlock rules */

  return ret_val;
}
Ejemplo n.º 13
0
/**
 * Set a MIDI router to use default "unity" rules. Such a router will pass all
 * events unmodified.
 * @param router Router to set to default rules.
 * @return #FLUID_OK on success, #FLUID_FAILED otherwise
 * @since 1.1.0
 */
int
fluid_midi_router_set_default_rules (fluid_midi_router_t *router)
{
  fluid_midi_router_rule_t *new_rules[FLUID_MIDI_ROUTER_RULE_COUNT];
  fluid_midi_router_rule_t *del_rules[FLUID_MIDI_ROUTER_RULE_COUNT];
  fluid_midi_router_rule_t *rule, *next_rule, *prev_rule;
  int i, i2;

  fluid_return_val_if_fail (router != NULL, FLUID_FAILED);

  /* Allocate new default rules outside of lock */

  for (i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++)
  {
    new_rules[i] = new_fluid_midi_router_rule ();

    if (!new_rules[i])
    { /* Free already allocated rules */
      for (i2 = 0; i2 < i; i2++)
        delete_fluid_midi_router_rule (new_rules[i2]);

      return FLUID_FAILED;
    }
  }


  fluid_mutex_lock (router->rules_mutex);       /* ++ lock */

  for (i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++)
  {
    del_rules[i] = NULL;
    prev_rule = NULL;

    /* Process existing rules */
    for (rule = router->rules[i]; rule; rule = next_rule)
    {
      next_rule = rule->next;

      if (rule->pending_events == 0)    /* Rule has no pending events? */
      { /* Remove rule from rule list */
        if (prev_rule) prev_rule->next = next_rule;
        else if (rule == router->rules[i]) router->rules[i] = next_rule;

        /* Prepend to delete list */
        rule->next = del_rules[i];
        del_rules[i] = rule;
      }
      else
      {
        rule->waiting = TRUE;          /* Pending events, mark as waiting */
        prev_rule = rule;
      }
    }

    /* Prepend new default rule */
    new_rules[i]->next = router->rules[i];
    router->rules[i] = new_rules[i];
  }

  fluid_mutex_unlock (router->rules_mutex);     /* -- unlock */


  /* Free old rules outside of lock */

  for (i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++)
  {
    for (rule = del_rules[i]; rule; rule = next_rule)
    {
      next_rule = rule->next;
      FLUID_FREE (rule);
    }
  }

  return FLUID_OK;
}