mirror of
https://github.com/ZDoom/fluidsynth.git
synced 2024-12-14 14:31:40 +00:00
1201 lines
30 KiB
C
1201 lines
30 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 Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
2002 : API design by Peter Hanappe and Antoine Schmitt
|
|
August 2002 : Implementation by Antoine Schmitt as@gratin.org
|
|
as part of the infiniteCD author project
|
|
http://www.infiniteCD.org/
|
|
*/
|
|
|
|
#include "fluid_event_priv.h"
|
|
#include "fluid_sys.h" // timer, threads, etc...
|
|
#include "fluid_list.h"
|
|
|
|
/***************************************************************
|
|
*
|
|
* SEQUENCER
|
|
*/
|
|
|
|
#define FLUID_SEQUENCER_EVENTS_MAX 1000
|
|
|
|
/* Private data for SEQUENCER */
|
|
struct _fluid_sequencer_t {
|
|
unsigned int startMs;
|
|
fluid_atomic_int_t currentMs;
|
|
int useSystemTimer;
|
|
double scale; // ticks per second
|
|
fluid_list_t* clients;
|
|
fluid_seq_id_t clientsID;
|
|
/* for queue + heap */
|
|
fluid_evt_entry* preQueue;
|
|
fluid_evt_entry* preQueueLast;
|
|
fluid_timer_t* timer;
|
|
int queue0StartTime;
|
|
short prevCellNb;
|
|
fluid_evt_entry* queue0[256][2];
|
|
fluid_evt_entry* queue1[255][2];
|
|
fluid_evt_entry* queueLater;
|
|
fluid_evt_heap_t* heap;
|
|
fluid_mutex_t mutex;
|
|
#if FLUID_SEQ_WITH_TRACE
|
|
char *tracebuf;
|
|
char *traceptr;
|
|
int tracelen;
|
|
#endif
|
|
};
|
|
|
|
/* Private data for clients */
|
|
typedef struct _fluid_sequencer_client_t {
|
|
fluid_seq_id_t id;
|
|
char* name;
|
|
fluid_event_callback_t callback;
|
|
void* data;
|
|
} fluid_sequencer_client_t;
|
|
|
|
/* prototypes */
|
|
static short _fluid_seq_queue_init(fluid_sequencer_t* seq, int nbEvents);
|
|
static void _fluid_seq_queue_end(fluid_sequencer_t* seq);
|
|
static short _fluid_seq_queue_pre_insert(fluid_sequencer_t* seq, fluid_event_t * evt);
|
|
static void _fluid_seq_queue_pre_remove(fluid_sequencer_t* seq, fluid_seq_id_t src, fluid_seq_id_t dest, int type);
|
|
static int _fluid_seq_queue_process(void* data, unsigned int msec); // callback from timer
|
|
static void _fluid_seq_queue_insert_entry(fluid_sequencer_t* seq, fluid_evt_entry * evtentry);
|
|
static void _fluid_seq_queue_remove_entries_matching(fluid_sequencer_t* seq, fluid_evt_entry* temp);
|
|
static void _fluid_seq_queue_send_queued_events(fluid_sequencer_t* seq);
|
|
static void _fluid_free_evt_queue(fluid_evt_entry** first, fluid_evt_entry** last);
|
|
|
|
|
|
/* API implementation */
|
|
|
|
/**
|
|
* Create a new sequencer object which uses the system timer. Use
|
|
* new_fluid_sequencer2() to specify whether the system timer or
|
|
* fluid_sequencer_process() is used to advance the sequencer.
|
|
* @return New sequencer instance
|
|
*/
|
|
fluid_sequencer_t*
|
|
new_fluid_sequencer (void)
|
|
{
|
|
return new_fluid_sequencer2 (TRUE);
|
|
}
|
|
|
|
/**
|
|
* Create a new sequencer object.
|
|
* @param use_system_timer If TRUE, sequencer will advance at the rate of the
|
|
* system clock. If FALSE, call fluid_sequencer_process() to advance
|
|
* the sequencer.
|
|
* @return New sequencer instance
|
|
* @since 1.1.0
|
|
*/
|
|
fluid_sequencer_t*
|
|
new_fluid_sequencer2 (int use_system_timer)
|
|
{
|
|
fluid_sequencer_t* seq;
|
|
|
|
seq = FLUID_NEW(fluid_sequencer_t);
|
|
if (seq == NULL) {
|
|
fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
FLUID_MEMSET(seq, 0, sizeof(fluid_sequencer_t));
|
|
|
|
seq->scale = 1000; // default value
|
|
seq->useSystemTimer = use_system_timer ? 1 : 0;
|
|
seq->startMs = seq->useSystemTimer ? fluid_curtime() : 0;
|
|
seq->clients = NULL;
|
|
seq->clientsID = 0;
|
|
|
|
if (-1 == _fluid_seq_queue_init(seq, FLUID_SEQUENCER_EVENTS_MAX)) {
|
|
FLUID_FREE(seq);
|
|
fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
#if FLUID_SEQ_WITH_TRACE
|
|
seq->tracelen = 1024*100;
|
|
seq->tracebuf = (char *)FLUID_MALLOC(seq->tracelen);
|
|
if (seq->tracebuf == NULL) {
|
|
_fluid_seq_queue_end(seq);
|
|
FLUID_FREE(seq);
|
|
fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
|
|
return NULL;
|
|
}
|
|
seq->traceptr = seq->tracebuf;
|
|
#endif
|
|
|
|
return(seq);
|
|
}
|
|
|
|
/**
|
|
* Free a sequencer object.
|
|
* @param seq Sequencer to delete
|
|
*/
|
|
void
|
|
delete_fluid_sequencer (fluid_sequencer_t* seq)
|
|
{
|
|
fluid_return_if_fail(seq != NULL);
|
|
|
|
/* cleanup clients */
|
|
while (seq->clients) {
|
|
fluid_sequencer_client_t *client = (fluid_sequencer_client_t*)seq->clients->data;
|
|
fluid_sequencer_unregister_client(seq, client->id);
|
|
}
|
|
|
|
_fluid_seq_queue_end(seq);
|
|
|
|
/* if (seq->clients) {
|
|
fluid_list_t *tmp = seq->clients;
|
|
while (tmp != NULL) {
|
|
fluid_sequencer_client_t *client = (fluid_sequencer_client_t*)tmp->data;
|
|
if (client->name) FLUID_FREE(client->name);
|
|
tmp = tmp->next;
|
|
}
|
|
delete_fluid_list(seq->clients);
|
|
seq->clients = NULL;
|
|
}*/
|
|
|
|
#if FLUID_SEQ_WITH_TRACE
|
|
if (seq->tracebuf != NULL)
|
|
FLUID_FREE(seq->tracebuf);
|
|
seq->tracebuf = NULL;
|
|
#endif
|
|
|
|
FLUID_FREE(seq);
|
|
}
|
|
|
|
/**
|
|
* Check if a sequencer is using the system timer or not.
|
|
* @param seq Sequencer object
|
|
* @return TRUE if system timer is being used, FALSE otherwise.
|
|
* @since 1.1.0
|
|
*/
|
|
int
|
|
fluid_sequencer_get_use_system_timer (fluid_sequencer_t* seq)
|
|
{
|
|
return seq->useSystemTimer;
|
|
}
|
|
|
|
|
|
#if FLUID_SEQ_WITH_TRACE
|
|
|
|
/* trace */
|
|
void
|
|
fluid_seq_dotrace(fluid_sequencer_t* seq, char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int len, remain = seq->tracelen - (seq->traceptr - seq->tracebuf);
|
|
if (remain <= 0) return;
|
|
|
|
va_start (args, fmt);
|
|
len = FLUID_VSNPRINTF (seq->traceptr, remain, fmt, args);
|
|
va_end (args);
|
|
|
|
if (len > 0) {
|
|
if (len <= remain) {
|
|
// all written, with 0 at end
|
|
seq->traceptr += len;
|
|
} else {
|
|
// not enough room, set to end
|
|
seq->traceptr = seq->tracebuf + seq->tracelen;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Clear sequencer trace buffer.
|
|
* @param seq Sequencer object
|
|
*/
|
|
void
|
|
fluid_seq_cleartrace(fluid_sequencer_t* seq)
|
|
{
|
|
seq->traceptr = seq->tracebuf;
|
|
}
|
|
|
|
/**
|
|
* Get sequencer trace buffer.
|
|
* @param seq Sequencer object
|
|
*/
|
|
char *
|
|
fluid_seq_gettrace(fluid_sequencer_t* seq)
|
|
{
|
|
return seq->tracebuf;
|
|
}
|
|
#else
|
|
|
|
void fluid_seq_dotrace(fluid_sequencer_t* seq, char *fmt, ...) {}
|
|
|
|
#endif // FLUID_SEQ_WITH_TRACE
|
|
|
|
/* clients */
|
|
|
|
/**
|
|
* Register a sequencer client.
|
|
* @param seq Sequencer object
|
|
* @param name Name of sequencer client
|
|
* @param callback Sequencer client callback or NULL for a source client.
|
|
* @param data User data to pass to the \a callback
|
|
* @return Unique sequencer ID or #FLUID_FAILED on error
|
|
*
|
|
* Clients can be sources or destinations of events. Sources don't need to
|
|
* register a callback.
|
|
*/
|
|
fluid_seq_id_t
|
|
fluid_sequencer_register_client (fluid_sequencer_t* seq, const char *name,
|
|
fluid_event_callback_t callback, void* data)
|
|
{
|
|
fluid_sequencer_client_t * client;
|
|
char * nameCopy;
|
|
|
|
client = FLUID_NEW(fluid_sequencer_client_t);
|
|
if (client == NULL) {
|
|
fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
|
|
return FLUID_FAILED;
|
|
}
|
|
|
|
nameCopy = FLUID_STRDUP(name);
|
|
if (nameCopy == NULL) {
|
|
fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
|
|
FLUID_FREE(client);
|
|
return FLUID_FAILED;
|
|
}
|
|
|
|
seq->clientsID++;
|
|
|
|
client->name = nameCopy;
|
|
client->id = seq->clientsID;
|
|
client->callback = callback;
|
|
client->data = data;
|
|
|
|
seq->clients = fluid_list_append(seq->clients, (void *)client);
|
|
|
|
return (client->id);
|
|
}
|
|
|
|
/**
|
|
* Unregister a previously registered client.
|
|
* @param seq Sequencer object
|
|
* @param id Client ID as returned by fluid_sequencer_register_client().
|
|
*/
|
|
void
|
|
fluid_sequencer_unregister_client (fluid_sequencer_t* seq, fluid_seq_id_t id)
|
|
{
|
|
fluid_list_t *tmp;
|
|
|
|
if (seq->clients == NULL) return;
|
|
|
|
tmp = seq->clients;
|
|
while (tmp) {
|
|
fluid_sequencer_client_t *client = (fluid_sequencer_client_t*)tmp->data;
|
|
|
|
if (client->id == id) {
|
|
if (client->name)
|
|
FLUID_FREE(client->name);
|
|
seq->clients = fluid_list_remove_link(seq->clients, tmp);
|
|
delete1_fluid_list(tmp);
|
|
FLUID_FREE(client);
|
|
return;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Count a sequencers registered clients.
|
|
* @param seq Sequencer object
|
|
* @return Count of sequencer clients.
|
|
*/
|
|
int
|
|
fluid_sequencer_count_clients(fluid_sequencer_t* seq)
|
|
{
|
|
if (seq->clients == NULL)
|
|
return 0;
|
|
return fluid_list_size(seq->clients);
|
|
}
|
|
|
|
/**
|
|
* Get a client ID from its index (order in which it was registered).
|
|
* @param seq Sequencer object
|
|
* @param index Index of register client
|
|
* @return Client ID or #FLUID_FAILED if not found
|
|
*/
|
|
fluid_seq_id_t fluid_sequencer_get_client_id (fluid_sequencer_t* seq, int index)
|
|
{
|
|
fluid_list_t *tmp = fluid_list_nth(seq->clients, index);
|
|
if (tmp == NULL) {
|
|
return FLUID_FAILED;
|
|
} else {
|
|
fluid_sequencer_client_t *client = (fluid_sequencer_client_t*)tmp->data;
|
|
return client->id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the name of a registered client.
|
|
* @param seq Sequencer object
|
|
* @param id Client ID
|
|
* @return Client name or NULL if not found. String is internal and should not
|
|
* be modified or freed.
|
|
*/
|
|
char *
|
|
fluid_sequencer_get_client_name(fluid_sequencer_t* seq, fluid_seq_id_t id)
|
|
{
|
|
fluid_list_t *tmp;
|
|
|
|
if (seq->clients == NULL)
|
|
return NULL;
|
|
|
|
tmp = seq->clients;
|
|
while (tmp) {
|
|
fluid_sequencer_client_t *client = (fluid_sequencer_client_t*)tmp->data;
|
|
|
|
if (client->id == id)
|
|
return client->name;
|
|
|
|
tmp = tmp->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Check if a client is a destination client.
|
|
* @param seq Sequencer object
|
|
* @param id Client ID
|
|
* @return TRUE if client is a destination client, FALSE otherwise or if not found
|
|
*/
|
|
int
|
|
fluid_sequencer_client_is_dest(fluid_sequencer_t* seq, fluid_seq_id_t id)
|
|
{
|
|
fluid_list_t *tmp;
|
|
|
|
if (seq->clients == NULL) return FALSE;
|
|
|
|
tmp = seq->clients;
|
|
while (tmp) {
|
|
fluid_sequencer_client_t *client = (fluid_sequencer_client_t*)tmp->data;
|
|
|
|
if (client->id == id)
|
|
return (client->callback != NULL);
|
|
|
|
tmp = tmp->next;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Send an event immediately.
|
|
* @param seq Sequencer object
|
|
* @param evt Event to send (copied)
|
|
*/
|
|
/* Event not actually copied, but since its used immediately it virtually is. */
|
|
void
|
|
fluid_sequencer_send_now(fluid_sequencer_t* seq, fluid_event_t* evt)
|
|
{
|
|
fluid_seq_id_t destID = fluid_event_get_dest(evt);
|
|
|
|
/* find callback */
|
|
fluid_list_t *tmp = seq->clients;
|
|
while (tmp) {
|
|
fluid_sequencer_client_t *dest = (fluid_sequencer_client_t*)tmp->data;
|
|
|
|
if (dest->id == destID) {
|
|
if (dest->callback)
|
|
(dest->callback)(fluid_sequencer_get_tick(seq),
|
|
evt, seq, dest->data);
|
|
return;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule an event for sending at a later time.
|
|
* @param seq Sequencer object
|
|
* @param evt Event to send
|
|
* @param time Time value in ticks (in milliseconds with the default time scale of 1000).
|
|
* @param absolute TRUE if \a time is absolute sequencer time (time since sequencer
|
|
* creation), FALSE if relative to current time.
|
|
* @return #FLUID_OK on success, #FLUID_FAILED otherwise
|
|
*/
|
|
int
|
|
fluid_sequencer_send_at (fluid_sequencer_t* seq, fluid_event_t* evt,
|
|
unsigned int time, int absolute)
|
|
{
|
|
unsigned int now = fluid_sequencer_get_tick(seq);
|
|
|
|
/* set absolute */
|
|
if (!absolute)
|
|
time = now + time;
|
|
|
|
/* time stamp event */
|
|
fluid_event_set_time(evt, time);
|
|
|
|
/* queue for processing later */
|
|
return _fluid_seq_queue_pre_insert(seq, evt);
|
|
}
|
|
|
|
/**
|
|
* Remove events from the event queue.
|
|
* @param seq Sequencer object
|
|
* @param source Source client ID to match or -1 for wildcard
|
|
* @param dest Destination client ID to match or -1 for wildcard
|
|
* @param type Event type to match or -1 for wildcard (#fluid_seq_event_type)
|
|
*/
|
|
void
|
|
fluid_sequencer_remove_events (fluid_sequencer_t* seq, fluid_seq_id_t source,
|
|
fluid_seq_id_t dest, int type)
|
|
{
|
|
_fluid_seq_queue_pre_remove(seq, source, dest, type);
|
|
}
|
|
|
|
|
|
/*************************************
|
|
time
|
|
**************************************/
|
|
|
|
/**
|
|
* Get the current tick of a sequencer.
|
|
* @param seq Sequencer object
|
|
* @return Current tick value
|
|
*/
|
|
unsigned int
|
|
fluid_sequencer_get_tick (fluid_sequencer_t* seq)
|
|
{
|
|
unsigned int absMs = seq->useSystemTimer ? (int) fluid_curtime() : fluid_atomic_int_get(&seq->currentMs);
|
|
double nowFloat;
|
|
unsigned int now;
|
|
nowFloat = ((double)(absMs - seq->startMs))*seq->scale/1000.0f;
|
|
now = nowFloat;
|
|
return now;
|
|
}
|
|
|
|
/**
|
|
* Set the time scale of a sequencer.
|
|
* @param seq Sequencer object
|
|
* @param scale Sequencer scale value in ticks per second
|
|
* (default is 1000 for 1 tick per millisecond, max is 1000.0)
|
|
*
|
|
* If there are already scheduled events in the sequencer and the scale is changed
|
|
* the events are adjusted accordingly.
|
|
*/
|
|
void
|
|
fluid_sequencer_set_time_scale (fluid_sequencer_t* seq, double scale)
|
|
{
|
|
if (scale <= 0) {
|
|
fluid_log(FLUID_WARN, "sequencer: scale <= 0 : %f\n", scale);
|
|
return;
|
|
}
|
|
|
|
if (scale > 1000.0)
|
|
// Otherwise : problems with the timer = 0ms...
|
|
scale = 1000.0;
|
|
|
|
if (seq->scale != scale) {
|
|
double oldScale = seq->scale;
|
|
|
|
// stop timer
|
|
if (seq->timer) {
|
|
delete_fluid_timer(seq->timer);
|
|
seq->timer = NULL;
|
|
}
|
|
|
|
seq->scale = scale;
|
|
|
|
// change start0 so that cellNb is preserved
|
|
seq->queue0StartTime = (seq->queue0StartTime + seq->prevCellNb)*(seq->scale/oldScale) - seq->prevCellNb;
|
|
|
|
// change all preQueue events for new scale
|
|
{
|
|
fluid_evt_entry* tmp;
|
|
tmp = seq->preQueue;
|
|
while (tmp) {
|
|
if (tmp->entryType == FLUID_EVT_ENTRY_INSERT)
|
|
tmp->evt.time = tmp->evt.time*seq->scale/oldScale;
|
|
|
|
tmp = tmp->next;
|
|
}
|
|
}
|
|
|
|
/* re-start timer */
|
|
if (seq->useSystemTimer) {
|
|
seq->timer = new_fluid_timer((int)(1000/seq->scale), _fluid_seq_queue_process, (void *)seq, TRUE, FALSE, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a sequencer's time scale.
|
|
* @param seq Sequencer object.
|
|
* @return Time scale value in ticks per second.
|
|
*/
|
|
double
|
|
fluid_sequencer_get_time_scale(fluid_sequencer_t* seq)
|
|
{
|
|
return seq->scale;
|
|
}
|
|
|
|
|
|
/**********************
|
|
|
|
the queue
|
|
|
|
**********************/
|
|
|
|
/*
|
|
The queue stores all future events to be processed.
|
|
|
|
Data structures
|
|
|
|
There is a heap, allocated at init time, for managing a pool
|
|
of event entries, that is description of an event, its time,
|
|
and whether it is a normal event or a removal command.
|
|
|
|
The queue is separated in two arrays, and a list. The first
|
|
array 'queue0' corresponds to the events to be sent in the
|
|
next 256 ticks (0 to 255), the second array 'queue1' contains
|
|
the events to be send from now+256 to now+65535. The list
|
|
called 'queueLater' contains the events to be sent later than
|
|
that. In each array, one cell contains a list of events having
|
|
the same time (in the queue0 array), or the same time/256 (in
|
|
the queue1 array), and a pointer to the last event in the list
|
|
of the cell so as to be able to insert fast at the end of the
|
|
list (i.e. a cell = 2 pointers). The 'queueLater' list is
|
|
ordered by time and by post time. This way, inserting 'soon'
|
|
events is fast (below 65535 ticks, that is about 1 minute if 1
|
|
tick=1ms). Inserting later events is more slow, but this is a
|
|
realtime engine, isn't it ?
|
|
|
|
The queue0 starts at queue0StartTime. When 256 ticks have
|
|
elapsed, the queue0 array is emptied, and the first cell of
|
|
the queue1 array is expanded in the queue0 array, according to
|
|
the time of each event. The queue1 array is shifted to the
|
|
left, and the first events of the queueLater list are inserte
|
|
in the last cell of the queue1 array.
|
|
|
|
We remember the previously managed cell in queue0 in the
|
|
prevCellNb variable. When processing the current cell, we
|
|
process the events in between (late events).
|
|
|
|
Functions
|
|
|
|
The main thread functions first get an event entry from the
|
|
heap, and copy the given event into it, then merely enqueue it
|
|
in a preQueue. This is in order to protect the data structure:
|
|
everything is managed in the callback (thread or interrupt,
|
|
depending on the architecture).
|
|
|
|
All queue data structure management is done in a timer
|
|
callback: '_fluid_seq_queue_process'. The
|
|
_fluid_seq_queue_process function first process the preQueue,
|
|
inserting or removing event entrys from the queue, then
|
|
processes the queue, by sending events ready to be sent at the
|
|
current time.
|
|
|
|
Critical sections between the main thread (or app) and the
|
|
sequencer thread (or interrupt) are:
|
|
|
|
- the heap management (if two threads get a free event at the
|
|
same time)
|
|
- the preQueue access.
|
|
|
|
These are really small and fast sections (merely a pointer or
|
|
two changing value). They are not protected by a mutex for now
|
|
(August 2002). Waiting for crossplatform mutex solutions. When
|
|
changing this code, beware that the
|
|
_fluid_seq_queue_pre_insert function may be called by the
|
|
callback of the queue thread (ex : a note event inserts a
|
|
noteoff event).
|
|
|
|
*/
|
|
|
|
|
|
/********************/
|
|
/* API */
|
|
/********************/
|
|
|
|
static short
|
|
_fluid_seq_queue_init(fluid_sequencer_t* seq, int maxEvents)
|
|
{
|
|
seq->heap = _fluid_evt_heap_init(maxEvents);
|
|
if (seq->heap == NULL) {
|
|
fluid_log(FLUID_PANIC, "sequencer: Out of memory\n");
|
|
return -1;
|
|
}
|
|
|
|
seq->preQueue = NULL;
|
|
seq->preQueueLast = NULL;
|
|
|
|
FLUID_MEMSET(seq->queue0, 0, 2*256*sizeof(fluid_evt_entry *));
|
|
FLUID_MEMSET(seq->queue1, 0, 2*255*sizeof(fluid_evt_entry *));
|
|
|
|
seq->queueLater = NULL;
|
|
seq->queue0StartTime = fluid_sequencer_get_tick(seq);
|
|
seq->prevCellNb = -1;
|
|
|
|
fluid_mutex_init(seq->mutex);
|
|
|
|
/* start timer */
|
|
if (seq->useSystemTimer) {
|
|
seq->timer = new_fluid_timer((int)(1000/seq->scale), _fluid_seq_queue_process,
|
|
(void *)seq, TRUE, FALSE, TRUE);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_end(fluid_sequencer_t* seq)
|
|
{
|
|
int i;
|
|
|
|
/* free all remaining events */
|
|
_fluid_free_evt_queue(&seq->preQueue, &seq->preQueueLast);
|
|
for (i = 0; i < 256; i++)
|
|
_fluid_free_evt_queue(&(seq->queue0[i][0]), &(seq->queue0[i][1]));
|
|
for (i = 0; i < 255; i++)
|
|
_fluid_free_evt_queue(&(seq->queue1[i][0]), &(seq->queue1[i][1]));
|
|
_fluid_free_evt_queue(&seq->queueLater, NULL);
|
|
|
|
|
|
if (seq->timer) {
|
|
delete_fluid_timer(seq->timer);
|
|
seq->timer = NULL;
|
|
}
|
|
|
|
if (seq->heap) {
|
|
_fluid_evt_heap_free(seq->heap);
|
|
seq->heap = NULL;
|
|
}
|
|
|
|
fluid_mutex_destroy(seq->mutex);
|
|
}
|
|
|
|
|
|
|
|
/********************/
|
|
/* queue management */
|
|
/********************/
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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, fluid_seq_id_t src, fluid_seq_id_t 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;
|
|
}
|
|
|
|
static void
|
|
_fluid_free_evt_queue(fluid_evt_entry** first, fluid_evt_entry** last)
|
|
{
|
|
fluid_evt_entry* tmp2;
|
|
fluid_evt_entry* tmp = *first;
|
|
while (tmp != NULL) {
|
|
tmp2 = tmp->next;
|
|
FLUID_FREE(tmp);
|
|
tmp = tmp2;
|
|
}
|
|
*first = NULL;
|
|
if (last != NULL) {
|
|
*last = NULL;
|
|
}
|
|
}
|
|
|
|
/* Callback from timer (may be in a different thread, or in an interrupt) */
|
|
static int
|
|
_fluid_seq_queue_process(void* data, unsigned int msec)
|
|
{
|
|
fluid_sequencer_t* seq = (fluid_sequencer_t *)data;
|
|
fluid_sequencer_process(seq, msec);
|
|
/* continue timer */
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
_fluid_seq_queue_print_later(fluid_sequencer_t* seq)
|
|
{
|
|
int count = 0;
|
|
fluid_evt_entry* tmp = seq->queueLater;
|
|
|
|
printf("queueLater:\n");
|
|
|
|
while (tmp) {
|
|
unsigned int delay = tmp->evt.time - seq->queue0StartTime;
|
|
printf("queueLater: Delay = %i\n", delay);
|
|
tmp = tmp->next;
|
|
count++;
|
|
}
|
|
printf("queueLater: Total of %i events\n", count);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
_fluid_seq_queue_insert_queue0(fluid_sequencer_t* seq, fluid_evt_entry* tmp, int cell)
|
|
{
|
|
if (seq->queue0[cell][1] == NULL) {
|
|
seq->queue0[cell][1] = seq->queue0[cell][0] = tmp;
|
|
} else {
|
|
seq->queue0[cell][1]->next = tmp;
|
|
seq->queue0[cell][1] = tmp;
|
|
}
|
|
tmp->next = NULL;
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_insert_queue1(fluid_sequencer_t* seq, fluid_evt_entry* tmp, int cell)
|
|
{
|
|
if (seq->queue1[cell][1] == NULL) {
|
|
seq->queue1[cell][1] = seq->queue1[cell][0] = tmp;
|
|
} else {
|
|
seq->queue1[cell][1]->next = tmp;
|
|
seq->queue1[cell][1] = tmp;
|
|
}
|
|
tmp->next = NULL;
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_insert_queue_later(fluid_sequencer_t* seq, fluid_evt_entry* evtentry)
|
|
{
|
|
fluid_evt_entry* prev;
|
|
fluid_evt_entry* tmp;
|
|
unsigned int time = evtentry->evt.time;
|
|
|
|
/* insert in 'queueLater', after the ones that have the same
|
|
* time */
|
|
|
|
/* first? */
|
|
if ((seq->queueLater == NULL)
|
|
|| (seq->queueLater->evt.time > time)) {
|
|
evtentry->next = seq->queueLater;
|
|
seq->queueLater = evtentry;
|
|
return;
|
|
}
|
|
|
|
/* walk queueLater */
|
|
/* this is the only slow thing : if the event is more
|
|
than 65535 ticks after the current time */
|
|
|
|
prev = seq->queueLater;
|
|
tmp = prev->next;
|
|
while (tmp) {
|
|
if (tmp->evt.time > time) {
|
|
/* insert before tmp */
|
|
evtentry->next = tmp;
|
|
prev->next = evtentry;
|
|
return;
|
|
}
|
|
prev = tmp;
|
|
tmp = prev->next;
|
|
}
|
|
|
|
/* last */
|
|
evtentry->next = NULL;
|
|
prev->next = evtentry;
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_insert_entry(fluid_sequencer_t* seq, fluid_evt_entry * evtentry)
|
|
{
|
|
/* time is relative to seq origin, in ticks */
|
|
fluid_event_t * evt = &(evtentry->evt);
|
|
unsigned int time = evt->time;
|
|
unsigned int delay;
|
|
|
|
if (seq->queue0StartTime > 0) {
|
|
/* queue0StartTime could be < 0 if the scale changed a
|
|
lot early, breaking the following comparison
|
|
*/
|
|
if (time < (unsigned int)seq->queue0StartTime) {
|
|
/* we are late, send now */
|
|
fluid_sequencer_send_now(seq, evt);
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, evtentry);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (seq->prevCellNb >= 0) {
|
|
/* prevCellNb could be -1 is seq was just started - unlikely */
|
|
/* prevCellNb can also be -1 if cellNb was reset to 0 in
|
|
_fluid_seq_queue_send_queued_events() */
|
|
if (time <= (unsigned int)(seq->queue0StartTime + seq->prevCellNb)) {
|
|
/* we are late, send now */
|
|
fluid_sequencer_send_now(seq, evt);
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, evtentry);
|
|
return;
|
|
}
|
|
}
|
|
|
|
delay = time - seq->queue0StartTime;
|
|
|
|
if (delay > 65535) {
|
|
_fluid_seq_queue_insert_queue_later(seq, evtentry);
|
|
|
|
} else if (delay > 255) {
|
|
_fluid_seq_queue_insert_queue1(seq, evtentry, delay/256 - 1);
|
|
|
|
} else {
|
|
_fluid_seq_queue_insert_queue0(seq, evtentry, delay);
|
|
}
|
|
}
|
|
|
|
static int
|
|
_fluid_seq_queue_matchevent(fluid_event_t* evt, int templType, fluid_seq_id_t templSrc, fluid_seq_id_t templDest)
|
|
{
|
|
int eventType;
|
|
|
|
if (templSrc != -1 && templSrc != fluid_event_get_source(evt))
|
|
return 0;
|
|
|
|
if (templDest != -1 && templDest != fluid_event_get_dest(evt))
|
|
return 0;
|
|
|
|
if (templType == -1)
|
|
return 1;
|
|
|
|
eventType = fluid_event_get_type(evt);
|
|
|
|
if (templType == eventType)
|
|
return 1;
|
|
|
|
if (templType == FLUID_SEQ_ANYCONTROLCHANGE)
|
|
if (eventType == FLUID_SEQ_PITCHBEND ||
|
|
eventType == FLUID_SEQ_MODULATION ||
|
|
eventType == FLUID_SEQ_SUSTAIN ||
|
|
eventType == FLUID_SEQ_PAN ||
|
|
eventType == FLUID_SEQ_VOLUME ||
|
|
eventType == FLUID_SEQ_REVERBSEND ||
|
|
eventType == FLUID_SEQ_CONTROLCHANGE ||
|
|
eventType == FLUID_SEQ_CHORUSSEND)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_remove_entries_matching(fluid_sequencer_t* seq, fluid_evt_entry* templ)
|
|
{
|
|
/* we walk everything : this is slow, but that is life */
|
|
int i, type;
|
|
fluid_seq_id_t src, dest;
|
|
|
|
src = templ->evt.src;
|
|
dest = templ->evt.dest;
|
|
type = templ->evt.type;
|
|
|
|
/* we can set it free now */
|
|
_fluid_seq_heap_set_free(seq->heap, templ);
|
|
|
|
/* queue0 */
|
|
for (i = 0 ; i < 256 ; i++) {
|
|
fluid_evt_entry* tmp = seq->queue0[i][0];
|
|
fluid_evt_entry* prev = NULL;
|
|
while (tmp) {
|
|
/* remove and/or walk */
|
|
if (_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) {
|
|
/* remove */
|
|
if (prev) {
|
|
prev->next = tmp->next;
|
|
if (tmp == seq->queue0[i][1]) // last one in list
|
|
seq->queue0[i][1] = prev;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = prev->next;
|
|
} else {
|
|
/* first one in list */
|
|
seq->queue0[i][0] = tmp->next;
|
|
if (tmp == seq->queue0[i][1]) // last one in list
|
|
seq->queue0[i][1] = NULL;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = seq->queue0[i][0];
|
|
}
|
|
} else {
|
|
prev = tmp;
|
|
tmp = prev->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* queue1 */
|
|
for (i = 0 ; i < 255 ; i++) {
|
|
fluid_evt_entry* tmp = seq->queue1[i][0];
|
|
fluid_evt_entry* prev = NULL;
|
|
while (tmp) {
|
|
if (_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) {
|
|
/* remove */
|
|
if (prev) {
|
|
prev->next = tmp->next;
|
|
if (tmp == seq->queue1[i][1]) // last one in list
|
|
seq->queue1[i][1] = prev;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = prev->next;
|
|
} else {
|
|
/* first one in list */
|
|
seq->queue1[i][0] = tmp->next;
|
|
if (tmp == seq->queue1[i][1]) // last one in list
|
|
seq->queue1[i][1] = NULL;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = seq->queue1[i][0];
|
|
}
|
|
} else {
|
|
prev = tmp;
|
|
tmp = prev->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* queueLater */
|
|
{
|
|
fluid_evt_entry* tmp = seq->queueLater;
|
|
fluid_evt_entry* prev = NULL;
|
|
while (tmp) {
|
|
if (_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) {
|
|
/* remove */
|
|
if (prev) {
|
|
prev->next = tmp->next;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = prev->next;
|
|
} else {
|
|
seq->queueLater = tmp->next;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = seq->queueLater;
|
|
}
|
|
} else {
|
|
prev = tmp;
|
|
tmp = prev->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_send_cell_events(fluid_sequencer_t* seq, int cellNb)
|
|
{
|
|
fluid_evt_entry* next;
|
|
fluid_evt_entry* tmp;
|
|
|
|
tmp = seq->queue0[cellNb][0];
|
|
while (tmp) {
|
|
fluid_sequencer_send_now(seq, &(tmp->evt));
|
|
|
|
next = tmp->next;
|
|
|
|
_fluid_seq_heap_set_free(seq->heap, tmp);
|
|
tmp = next;
|
|
}
|
|
seq->queue0[cellNb][0] = NULL;
|
|
seq->queue0[cellNb][1] = NULL;
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_slide(fluid_sequencer_t* seq)
|
|
{
|
|
short i;
|
|
fluid_evt_entry* next;
|
|
fluid_evt_entry* tmp;
|
|
int count = 0;
|
|
|
|
/* do the slide */
|
|
seq->queue0StartTime += 256;
|
|
|
|
/* sort all queue1[0] into queue0 according to new queue0StartTime */
|
|
tmp = seq->queue1[0][0];
|
|
while (tmp) {
|
|
unsigned int delay = tmp->evt.time - seq->queue0StartTime;
|
|
next = tmp->next;
|
|
if (delay > 255) {
|
|
/* should not happen !! */
|
|
/* append it to queue1[1] */
|
|
_fluid_seq_queue_insert_queue1(seq, tmp, 1);
|
|
} else {
|
|
_fluid_seq_queue_insert_queue0(seq, tmp, delay);
|
|
}
|
|
tmp = next;
|
|
count++;
|
|
}
|
|
|
|
/* slide all queue1[i] into queue1[i-1] */
|
|
for (i = 1 ; i < 255 ; i++) {
|
|
seq->queue1[i-1][0] = seq->queue1[i][0];
|
|
seq->queue1[i-1][1] = seq->queue1[i][1];
|
|
}
|
|
seq->queue1[254][0] = NULL;
|
|
seq->queue1[254][1] = NULL;
|
|
|
|
|
|
/* append queueLater to queue1[254] */
|
|
count = 0;
|
|
tmp = seq->queueLater;
|
|
while (tmp) {
|
|
unsigned int delay = tmp->evt.time - seq->queue0StartTime;
|
|
|
|
if (delay > 65535) {
|
|
break;
|
|
}
|
|
|
|
next = tmp->next;
|
|
|
|
/* append it */
|
|
_fluid_seq_queue_insert_queue1(seq, tmp, 254);
|
|
tmp = next;
|
|
count++;
|
|
}
|
|
|
|
seq->queueLater = tmp;
|
|
}
|
|
|
|
static void
|
|
_fluid_seq_queue_send_queued_events(fluid_sequencer_t* seq)
|
|
{
|
|
unsigned int nowTicks = fluid_sequencer_get_tick(seq);
|
|
short cellNb;
|
|
|
|
cellNb = seq->prevCellNb + 1;
|
|
while (cellNb <= (int)(nowTicks - seq->queue0StartTime)) {
|
|
if (cellNb == 256) {
|
|
cellNb = 0;
|
|
_fluid_seq_queue_slide(seq);
|
|
} /* slide */
|
|
|
|
/* process queue0[cellNb] */
|
|
_fluid_seq_queue_send_cell_events(seq, cellNb);
|
|
|
|
/* the current scale may have changed through a callback event */
|
|
nowTicks = fluid_sequencer_get_tick(seq);
|
|
|
|
/* next cell */
|
|
cellNb++;
|
|
}
|
|
|
|
seq->prevCellNb = cellNb - 1;
|
|
}
|