Okay, so here's the patch that enables fast, predictable MIDI file rendering. Whether it goes into 1.0.9 or 1.1.0 is up to you, but I do hope that someone will commit it - so I haven't been working in vain!
Notes about the patch: * It includes the previous posted patch for slave timers, and this version enables them by default. * Public API changes: There are new functions for file rendering, new_fluid_file_renderer, delete_fluid_file_renderer and fluid_file_renderer_process_block. There is also a fluid_player_get_status function. * The sequencer has been left out of the picture for now. * fluid_aufile.c has been broken into two files, fluid_aufile.c and fluid_filerenderer.c. The new file renderer is in fluid_filerenderer.c and fluid_aufile.c now contains only a system-time-based wrapper around the file renderer. * there is a new option -F or --fast-render=filename that enables the new functionality. Man page and --help has been updated to show the new switch. (Are there more places we need to print it?) Enough said - now go enjoy the new patch! // David
Index: include/fluidsynth/audio.h =================================================================== --- include/fluidsynth/audio.h (revision 162) +++ include/fluidsynth/audio.h (arbetskopia) @@ -61,8 +61,28 @@ FLUIDSYNTH_API void delete_fluid_audio_driver(fluid_audio_driver_t* driver); +/** + * Create a new file renderer and open the file. + * @param synth The synth that creates audio data. + * @param filename Output filename + * @param period_size Sample count, amount of samples to write to the file at + * every call to fluid_file_renderer_process_block(). + * @return the new object, or NULL on failure + */ +FLUIDSYNTH_API fluid_file_renderer_t* new_fluid_file_renderer(fluid_synth_t* synth, + char* filename, int period_size); +/** + * Write period_size samples to file. + * @return FLUID_OK or FLUID_FAILED if an error occurred + */ +FLUIDSYNTH_API int fluid_file_renderer_process_block(fluid_file_renderer_t* dev); +/** + * Close file and destroy the file renderer object. + */ +FLUIDSYNTH_API void delete_fluid_file_renderer(fluid_file_renderer_t* dev); + #ifdef __cplusplus } #endif Index: include/fluidsynth/types.h =================================================================== --- include/fluidsynth/types.h (revision 162) +++ include/fluidsynth/types.h (arbetskopia) @@ -42,6 +42,7 @@ typedef struct _fluid_sample_t fluid_sample_t; typedef struct _fluid_mod_t fluid_mod_t; typedef struct _fluid_audio_driver_t fluid_audio_driver_t; +typedef struct _fluid_file_renderer_t fluid_file_renderer_t; typedef struct _fluid_player_t fluid_player_t; typedef struct _fluid_midi_event_t fluid_midi_event_t; typedef struct _fluid_midi_driver_t fluid_midi_driver_t; Index: include/fluidsynth/midi.h =================================================================== --- include/fluidsynth/midi.h (revision 162) +++ include/fluidsynth/midi.h (arbetskopia) @@ -101,6 +101,13 @@ * The MIDI player allows you to play MIDI files with the FLUID Synth */ +enum fluid_player_status +{ + FLUID_PLAYER_READY, + FLUID_PLAYER_PLAYING, + FLUID_PLAYER_DONE +}; + FLUIDSYNTH_API fluid_player_t* new_fluid_player(fluid_synth_t* synth); FLUIDSYNTH_API int delete_fluid_player(fluid_player_t* player); FLUIDSYNTH_API int fluid_player_add(fluid_player_t* player, char* midifile); @@ -110,6 +117,7 @@ FLUIDSYNTH_API int fluid_player_set_loop(fluid_player_t* player, int loop); FLUIDSYNTH_API int fluid_player_set_midi_tempo(fluid_player_t* player, int tempo); FLUIDSYNTH_API int fluid_player_set_bpm(fluid_player_t* player, int bpm); +FLUIDSYNTH_API int fluid_player_get_status(fluid_player_t* player); #ifdef __cplusplus } Index: src/fluid_settings.c =================================================================== --- src/fluid_settings.c (revision 162) +++ src/fluid_settings.c (arbetskopia) @@ -26,6 +26,7 @@ #include "fluid_adriver.h" #include "fluid_mdriver.h" #include "fluid_settings.h" +#include "fluid_midi.h" /* maximum allowed components of a settings variable (separated by '.') */ #define MAX_SETTINGS_TOKENS 8 /* currently only a max of 3 are used */ @@ -195,6 +196,7 @@ { fluid_synth_settings(settings); fluid_shell_settings(settings); + fluid_player_settings(settings); fluid_audio_driver_settings(settings); fluid_midi_driver_settings(settings); } Index: src/fluid_aufile.c =================================================================== --- src/fluid_aufile.c (revision 162) +++ src/fluid_aufile.c (arbetskopia) @@ -39,14 +39,10 @@ fluid_audio_driver_t driver; fluid_audio_func_t callback; void* data; + fluid_file_renderer_t* renderer; int period_size; double sample_rate; - FILE* file; fluid_timer_t* timer; - float* left; - float* right; - short* buf; - int buf_size; unsigned int samples; } fluid_file_audio_driver_t; @@ -61,7 +57,7 @@ /************************************************************** * * 'file' audio driver - * + * */ void fluid_file_audio_driver_settings(fluid_settings_t* settings) @@ -92,22 +88,18 @@ dev->data = synth; dev->callback = (fluid_audio_func_t) fluid_synth_process; dev->samples = 0; - dev->left = FLUID_ARRAY(float, dev->period_size); - dev->right = FLUID_ARRAY(float, dev->period_size); - dev->buf = FLUID_ARRAY(short, 2 * dev->period_size); - dev->buf_size = 2 * dev->period_size * sizeof(short); if (fluid_settings_getstr(settings, "audio.file.name", &filename) == 0) { FLUID_LOG(FLUID_ERR, "No file name specified"); goto error_recovery; } - dev->file = fopen(filename, "wb"); - if (dev->file == NULL) { - FLUID_LOG(FLUID_ERR, "Failed to open the file '%s'", filename); + dev->renderer = new_fluid_file_renderer(synth, filename, dev->period_size); + if (dev->renderer == NULL) { goto error_recovery; } + msec = (int) (0.5 + dev->period_size / dev->sample_rate * 1000.0); dev->timer = new_fluid_timer(msec, fluid_file_audio_run_s16, (void*) dev, 1, 0); if (dev->timer == NULL) { @@ -133,23 +125,11 @@ if (dev->timer != NULL) { delete_fluid_timer(dev->timer); } - - if (dev->file != NULL) { - fclose(dev->file); + + if (dev->renderer != NULL) { + delete_fluid_file_renderer(dev->renderer); } - if (dev->left != NULL) { - FLUID_FREE(dev->left); - } - - if (dev->right != NULL) { - FLUID_FREE(dev->right); - } - - if (dev->buf != NULL) { - FLUID_FREE(dev->buf); - } - FLUID_FREE(dev); return FLUID_OK; } @@ -157,7 +137,6 @@ static int fluid_file_audio_run_s16(void* d, unsigned int clock_time) { fluid_file_audio_driver_t* dev = (fluid_file_audio_driver_t*) d; - int n, offset; unsigned int sample_time; sample_time = (unsigned int) (dev->samples / dev->sample_rate * 1000.0); @@ -165,19 +144,7 @@ return 1; } - fluid_synth_write_s16(dev->data, dev->period_size, dev->buf, 0, 2, dev->buf, 1, 2); - - for (offset = 0; offset < dev->buf_size; offset += n) { - - n = fwrite((char*) dev->buf + offset, 1, dev->buf_size - offset, dev->file); - if (n < 0) { - FLUID_LOG(FLUID_ERR, "Audio output file write error: %s", - strerror (errno)); - return 0; - } - } - dev->samples += dev->period_size; - return 1; + return fluid_file_renderer_process_block(dev->renderer) == FLUID_OK ? 1 : 0; } Index: src/fluid_settings.h =================================================================== --- src/fluid_settings.h (revision 162) +++ src/fluid_settings.h (arbetskopia) @@ -35,18 +35,18 @@ typedef int (*fluid_str_update_t)(void* data, char* name, char* value); typedef int (*fluid_int_update_t)(void* data, char* name, int value); -/** returns 0 if the value has been resgister correctly, non-zero +/** returns 0 if the value has been registered correctly, non-zero otherwise */ int fluid_settings_register_str(fluid_settings_t* settings, char* name, char* def, int hints, fluid_str_update_t fun, void* data); -/** returns 0 if the value has been resgister correctly, non-zero +/** returns 0 if the value has been registered correctly, non-zero otherwise */ int fluid_settings_register_num(fluid_settings_t* settings, char* name, double min, double max, double def, int hints, fluid_num_update_t fun, void* data); -/** returns 0 if the value has been resgister correctly, non-zero +/** returns 0 if the value has been registered correctly, non-zero otherwise */ int fluid_settings_register_int(fluid_settings_t* settings, char* name, int min, int max, int def, int hints, fluid_int_update_t fun, void* data); Index: src/fluid_synth.c =================================================================== --- src/fluid_synth.c (revision 162) +++ src/fluid_synth.c (arbetskopia) @@ -331,6 +331,70 @@ } /*************************************************************** + * FLUID SAMPLE TIMERS + * Timers that use written audio data as timing reference + */ +struct _fluid_sample_timer_t +{ + fluid_sample_timer_t* next; /* Single linked list of timers */ + unsigned long starttick; + fluid_timer_callback_t callback; + void* data; + int isfinished; +}; + +/* + * fluid_sample_timer_process - called when synth->ticks is updated + */ +void fluid_sample_timer_process(fluid_synth_t* synth) +{ + fluid_sample_timer_t* st; + for (st=synth->sample_timers; st; st=st->next) { + if (st->isfinished) { + continue; + } + + long msec = (long) (1000.0*((double) (synth->ticks - st->starttick))/synth->sample_rate); + int cont = (*st->callback)(st->data, msec); + if (cont == 0) { + st->isfinished = 1; + } + } +} + +fluid_sample_timer_t* new_fluid_sample_timer(fluid_synth_t* synth, fluid_timer_callback_t callback, void* data) +{ + fluid_sample_timer_t* result = FLUID_NEW(fluid_sample_timer_t); + if (result == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + result->starttick = synth->ticks; + result->isfinished = 0; + result->data = data; + result->callback = callback; + result->next = synth->sample_timers; + synth->sample_timers = result; + return result; +} + +int delete_fluid_sample_timer(fluid_synth_t* synth, fluid_sample_timer_t* timer) +{ + fluid_sample_timer_t** ptr = &synth->sample_timers; + while (*ptr) { + if (*ptr == timer) { + *ptr = timer->next; + FLUID_FREE(timer); + return FLUID_OK; + } + ptr = &((*ptr)->next); + } + FLUID_LOG(FLUID_ERR,"delete_fluid_sample_timer failed, no timer found"); + return FLUID_FAILED; +} + + +/*************************************************************** * * FLUID SYNTH */ @@ -2029,6 +2093,10 @@ /* fluid_mutex_unlock(synth->busy); /\* Allow other threads to touch the synth *\/ */ + fluid_sample_timer_process(synth); + + fluid_check_fpe("fluid_sample_timer_process"); + return 0; } Index: src/fluid_filerenderer.c =================================================================== --- src/fluid_filerenderer.c (revision 0) +++ src/fluid_filerenderer.c (revision 0) @@ -0,0 +1,119 @@ +/* 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 + */ + + /* + * Low-level routines for file output. + */ + +#include <stdio.h> +#include "fluidsynth_priv.h" + +struct _fluid_file_renderer_t { + FILE* file; + fluid_synth_t* synth; + short* buf; + int period_size; + int buf_size; +}; + +void delete_fluid_file_renderer(fluid_file_renderer_t* dev) +{ + if (dev == NULL) { + return; + } + + if (dev->file != NULL) { + fclose(dev->file); + } + + if (dev->buf != NULL) { + FLUID_FREE(dev->buf); + } + + FLUID_FREE(dev); + return; +} + + +/* + * Create a new file renderer object and open the file. + */ + +fluid_file_renderer_t* new_fluid_file_renderer(fluid_synth_t* synth, char* filename, int period_size) +{ + fluid_file_renderer_t* dev; + + dev = FLUID_NEW(fluid_file_renderer_t); + if (dev == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + FLUID_MEMSET(dev, 0, sizeof(fluid_file_renderer_t)); + + dev->synth = synth; + dev->period_size = period_size; + dev->buf_size = 2 * dev->period_size * sizeof(short); + + dev->buf = FLUID_ARRAY(short, 2 * dev->period_size); + if (dev->buf == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_recovery; + } + + if (filename == NULL) { + FLUID_LOG(FLUID_ERR, "No file name specified"); + goto error_recovery; + } + + dev->file = fopen(filename, "wb"); + if (dev->file == NULL) { + FLUID_LOG(FLUID_ERR, "Failed to open the file '%s'", filename); + goto error_recovery; + } + + return dev; + + error_recovery: + delete_fluid_file_renderer(dev); + return NULL; + +} + +/* + * Write period_size samples to file. + */ +int fluid_file_renderer_process_block(fluid_file_renderer_t* dev) +{ + int n, offset; + + fluid_synth_write_s16(dev->synth, dev->period_size, dev->buf, 0, 2, dev->buf, 1, 2); + + for (offset = 0; offset < dev->buf_size; offset += n) { + + n = fwrite((char*) dev->buf + offset, 1, dev->buf_size - offset, dev->file); + if (n < 0) { + FLUID_LOG(FLUID_ERR, "Audio output file write error: %s", + strerror (errno)); + return FLUID_FAILED; + } + } + return FLUID_OK; +} + Index: src/fluidsynth.c =================================================================== --- src/fluidsynth.c (revision 162) +++ src/fluidsynth.c (arbetskopia) @@ -176,7 +176,32 @@ } } +void fast_render_loop(fluid_synth_t* synth, fluid_player_t* player) +{ + fluid_file_renderer_t* renderer; + char* filename = NULL; + int period_size = 0; + fluid_settings_getint(synth->settings, "audio.period-size", &period_size); + fluid_settings_getstr(synth->settings, "audio.file.name", &filename); + + if (filename == NULL || period_size <= 0) { + fprintf(stderr, "Failed to fetch parameters for file renderer\n"); + } + + renderer = new_fluid_file_renderer(synth, filename, period_size); + if (!renderer) { + return; + } + + while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { + if (fluid_file_renderer_process_block(renderer) != FLUID_OK) { + break; + } + } + delete_fluid_file_renderer(renderer); +} + #ifdef HAVE_SIGNAL_H /* * handle_signal @@ -212,8 +237,9 @@ int audio_channels = 0; int with_server = 0; int dump = 0; + int fast_render = 0; int connect_lash = 1; - char *optchars = "a:C:c:df:G:g:hijK:L:lm:no:p:R:r:sVvz:"; + char *optchars = "a:C:c:df:F:G:g:hijK:L:lm:no:p:R:r:sVvz:"; #ifdef LASH_ENABLED int enabled_lash = 0; /* set to TRUE if lash gets enabled */ fluid_lash_args_t *lash_args; @@ -239,6 +265,7 @@ {"connect-jack-outputs", 0, 0, 'j'}, {"disable-lash", 0, 0, 'l'}, {"dump", 0, 0, 'd'}, + {"fast-render", 1, 0, 'F'}, {"gain", 1, 0, 'g'}, {"help", 0, 0, 'h'}, {"load-config", 1, 0, 'f'}, @@ -322,6 +349,10 @@ case 'f': config_file = optarg; break; + case 'F': + fluid_settings_setstr(settings, "audio.file.name", optarg); + fast_render = 1; + break; case 'G': audio_groups = atoi(optarg); break; @@ -476,11 +507,20 @@ /* signal(SIGINT, handle_signal); */ #endif + if (fast_render) { + midi_in = 0; + interactive = 0; + with_server = 0; + fluid_settings_setstr(settings, "player.timing-source", "sample"); + } + /* start the synthesis thread */ - adriver = new_fluid_audio_driver(settings, synth); - if (adriver == NULL) { - fprintf(stderr, "Failed to create the audio driver\n"); - goto cleanup; + if (!fast_render) { + adriver = new_fluid_audio_driver(settings, synth); + if (adriver == NULL) { + fprintf(stderr, "Failed to create the audio driver\n"); + goto cleanup; + } } @@ -569,6 +609,10 @@ fluid_usershell(settings, cmd_handler); } + if (fast_render) { + fast_render_loop(synth, player); + } + cleanup: #if !defined(MACINTOSH) && !defined(WIN32) @@ -644,7 +688,7 @@ print_welcome() { printf("FluidSynth version %s\n" - "Copyright (C) 2000-2006 Peter Hanappe and others.\n" + "Copyright (C) 2000-2009 Peter Hanappe and others.\n" "Distributed under the LGPL license.\n" "SoundFont(R) is a registered trademark of E-mu Systems, Inc.\n\n", FLUIDSYNTH_VERSION); @@ -668,6 +712,8 @@ " Number of audio buffers\n"); printf(" -d, --dump\n" " Dump incoming and outgoing MIDI events to stdout\n"); + printf(" -F, --fast-render=[file]\n" + " Render MIDI file to raw audio data and store in [file]\n"); printf(" -f, --load-config\n" " Load command configuration file (shell commands)\n"); printf(" -G, --audio-groups\n" Index: src/fluid_synth.h =================================================================== --- src/fluid_synth.h (revision 162) +++ src/fluid_synth.h (arbetskopia) @@ -81,7 +81,6 @@ int offset; }; - /* * fluid_synth_t */ @@ -144,6 +143,8 @@ * Note: This simple scheme does -not- provide 100 % protection against * thread problems, for example from MIDI thread and shell thread */ + fluid_sample_timer_t* sample_timers; /* List of timers triggered after a block has been processed */ + #ifdef LADSPA fluid_LADSPA_FxUnit_t* LADSPA_FxUnit; /** Effects unit for LADSPA support */ #endif @@ -208,6 +209,10 @@ void fluid_synth_dither_s16(int *dither_index, int len, float* lin, float* rin, void* lout, int loff, int lincr, void* rout, int roff, int rincr); + +fluid_sample_timer_t* new_fluid_sample_timer(fluid_synth_t* synth, fluid_timer_callback_t callback, void* data); +int delete_fluid_sample_timer(fluid_synth_t* synth, fluid_sample_timer_t* timer); + /* * misc */ Index: src/fluidsynth_priv.h =================================================================== --- src/fluidsynth_priv.h (revision 162) +++ src/fluidsynth_priv.h (arbetskopia) @@ -232,6 +232,7 @@ typedef struct _fluid_hashtable_t fluid_hashtable_t; typedef struct _fluid_client_t fluid_client_t; typedef struct _fluid_server_socket_t fluid_server_socket_t; +typedef struct _fluid_sample_timer_t fluid_sample_timer_t; /*************************************************************** * Index: src/fluid_midi.c =================================================================== --- src/fluid_midi.c (revision 162) +++ src/fluid_midi.c (arbetskopia) @@ -1120,6 +1120,7 @@ fluid_player_t* new_fluid_player(fluid_synth_t* synth) { int i; + char* timing_source; fluid_player_t* player; player = FLUID_NEW(fluid_player_t); if (player == NULL) { @@ -1133,13 +1134,22 @@ player->track[i] = NULL; } player->synth = synth; - player->timer = NULL; + player->system_timer = NULL; + player->sample_timer = NULL; player->playlist = NULL; player->current_file = NULL; player->division = 0; player->send_program_change = 1; player->miditempo = 480000; player->deltatime = 4.0; + + player->use_system_timer = 0; + if (fluid_settings_getstr(synth->settings, "player.timing-source", &timing_source) != 0) { + if (strcmp(timing_source, "system") == 0) { + player->use_system_timer = 1; + } + } + return player; } @@ -1159,6 +1169,17 @@ return FLUID_OK; } +/** + * Registers settings related to the MIDI player + */ +void fluid_player_settings(fluid_settings_t* settings) +{ + /* player.timing-source can be either "system" (use system timer) + or "sample" (use timer based on number of written samples) */ + fluid_settings_register_str(settings, "player.timing-source", "sample", 0, NULL, NULL); +} + + int fluid_player_reset(fluid_player_t* player) { int i; @@ -1327,10 +1348,20 @@ player->status = FLUID_PLAYER_PLAYING; - player->timer = new_fluid_timer((int) player->deltatime, fluid_player_callback, + if (player->use_system_timer) { + player->system_timer = new_fluid_timer((int) player->deltatime, fluid_player_callback, (void*) player, 1, 0); - if (player->timer == NULL) { - return FLUID_FAILED; + if (player->system_timer == NULL) { + return FLUID_FAILED; + } + } else { + player->sample_timer = new_fluid_sample_timer(player->synth, fluid_player_callback, + (void*) player); + + if (player->sample_timer == NULL) { + return FLUID_FAILED; + } + fluid_player_callback(player, 0); /* Process the first events before the first block */ } return FLUID_OK; } @@ -1342,14 +1373,24 @@ */ int fluid_player_stop(fluid_player_t* player) { - if (player->timer != NULL) { - delete_fluid_timer(player->timer); + if (player->system_timer != NULL) { + delete_fluid_timer(player->system_timer); } + if (player->sample_timer != NULL) { + delete_fluid_sample_timer(player->synth, player->sample_timer); + } player->status = FLUID_PLAYER_DONE; - player->timer = NULL; + player->sample_timer = NULL; + player->system_timer = NULL; return FLUID_OK; } + +int fluid_player_get_status(fluid_player_t* player) +{ + return player->status; +} + /* FIXME - Looping seems to not actually be implemented? */ /** @@ -1403,7 +1444,21 @@ */ int fluid_player_join(fluid_player_t* player) { - return player->timer? fluid_timer_join(player->timer) : FLUID_OK; + if (player->system_timer) { + return fluid_timer_join(player->system_timer); + } else if (player->sample_timer) { + /* Busy-wait loop, since there's no thread to wait for... */ + while (player->status == FLUID_PLAYER_PLAYING) { +#if defined(WIN32) + Sleep(10); +#elif defined(MACOS9) + /* FIXME: How do we sleep in Macos9? */ +#else + usleep(10000); +#endif + } + } + return FLUID_OK; } /************************************************************************ Index: src/fluid_midi.h =================================================================== --- src/fluid_midi.h (revision 162) +++ src/fluid_midi.h (arbetskopia) @@ -171,13 +171,6 @@ MIDI_SEQUENCER_EVENT = 0x7f }; -enum fluid_player_status -{ - FLUID_PLAYER_READY, - FLUID_PLAYER_PLAYING, - FLUID_PLAYER_DONE -}; - enum fluid_driver_status { FLUID_MIDI_READY, @@ -249,10 +242,12 @@ int ntracks; fluid_track_t *track[MAX_NUMBER_OF_TRACKS]; fluid_synth_t* synth; - fluid_timer_t* timer; + fluid_timer_t* system_timer; + fluid_sample_timer_t* sample_timer; fluid_list_t* playlist; char* current_file; char send_program_change; /* should we ignore the program changes? */ + char use_system_timer; /* if zero, use sample timers, otherwise use system clock timer */ int start_ticks; /* the number of tempo ticks passed at the last tempo change */ int cur_ticks; /* the number of tempo ticks passed */ int begin_msec; /* the time (msec) of the beginning of the file */ @@ -270,7 +265,9 @@ int fluid_player_reset(fluid_player_t* player); int fluid_player_load(fluid_player_t* player, char *filename); +void fluid_player_settings(fluid_settings_t* settings); + /* * fluid_midi_file */ Index: src/Makefile.am =================================================================== --- src/Makefile.am (revision 162) +++ src/Makefile.am (arbetskopia) @@ -121,6 +121,7 @@ fluid_tuning.h \ fluid_voice.c \ fluid_voice.h \ + fluid_filerenderer.c \ fluid_aufile.c INCLUDES = -I$(top_srcdir)/include $(LASH_CFLAGS) $(LADCCA_CFLAGS) \ Index: doc/fluidsynth.1 =================================================================== --- doc/fluidsynth.1 (revision 162) +++ doc/fluidsynth.1 (arbetskopia) @@ -70,6 +70,9 @@ .B \-d, \-\-dump Dump incoming and outgoing MIDI events to stdout .TP +.B \-F, \-\-fast\-render=[file] +Render MIDI file to raw audio data and store in [file] +.TP .B \-f, \-\-load\-config Load command configuration file (shell commands) .TP
_______________________________________________ fluid-dev mailing list fluid-dev@nongnu.org http://lists.nongnu.org/mailman/listinfo/fluid-dev