fluidsynth.lua


NAME

fluidsynth   -   a Lua interface to the fluidsynth library


SYNOPSIS

 local FS = require 'fluidsynth'   -- convert midi to wav
 local soundfonts = FS.read_config_file() -- default is ~/.fluidsynth
 local synth1   = FS.new_synth( {
   ['synth.gain']      = 0.4,      -- be careful...
   ['audio.driver']    = 'file',   -- overrides the config file
   ['audio.file.name'] = 'foo.wav',
   ['fast.render']     = true,     -- not part of the C-library API
 } )
 FS.sf_load(synth1, soundfonts)
 local player1  = FS.new_player(synth1,'foo.mid')
 assert(FS.player_play(player1))
 assert(FS.player_join(player1))   -- wait for foo.mid to finish
 os.execute('sleep 1')             -- don't chop final reverb
 FS.delete_synth(synth1) -- deletes player,audio_driver,synth,settings
 os.remove(FS.error_file_name())   -- remove tmp file
 local FS   = require 'fluidsynth' -- an alsa-client soundfont-synth
 local ALSA = require 'midialsa'
 ALSA.client( 'fluidsynth-alsa-client', 1, 0 )
 ALSA.connectfrom( 0, 'Keystation' )
 local soundfonts = FS.read_config_file('/unusual/config_file')
 local synth2 = FS.new_synth( {
   ['audio.driver']      = 'pulseaudio',
   ['audio.periods']     = 2,   -- min, for low latency
   ['audio.period-size'] = 64,  -- min, for low latency
 } )
 FS.sf_load(synth2, soundfonts)
 -- you will need to set a patch before any output can be generated!
 while true do
   local alsaevent = ALSA.input()
   if alsaevent[1]==ALSA.SND_SEQ_EVENT_PORT_UNSUBSCRIBED then break end
   FS.play_event(synth2, alsaevent)
 end
 FS.delete_synth(synth2)
 os.remove(FS.error_file_name()) -- remove tmp file


DESCRIPTION

This Lua module offers a simplified calling interface to the Fluidsynth Library.

It is a relatively thick wrapper. Various higher-level functions are introduced, the library's voluminous output on stderr has been redirected so the module can be used for example within a Curses app, and the return codes on failure have adopted the nil,errormessage convention of Lua so they can be used for example with assert().


FUNCTIONS

These higer-level functions wrap the fluidsynth library functions in a way that retains functionality, but is easy to use and hides some of the dangerous internals.
Unless otherwise stated, these functions all return nil,errormessage on failure.

Included are basic functions: read_config_file, new_synth, sf_load, delete_synth,
functions for playing midi files: new_player, player_play, player_join, player_stop,
functions for playing in real-time: note_on, note_off, patch_change, control_change, pitch_bend, play_event,
functions returning state: is_soundfont, is_midifile, default_settings, all_synth_errors, error_file_name, get

soundfonts = FS.read_config_file(filename)

If filename is nil, the default configuration file is $HOME/.fluidsynth
and if that does not exist then /etc/fluidsynth.conf

This file can also be used as a configuration file for the fluidsynth executable (except that fluidsynth is not able to use the set commands); for example:

 fluidsynth -f ~/.fluidsynth

This module only recognises set, load and select commands; see   man fluidsynth

In the following example, the select command chooses: for channel 9, from soundfont 1 (Chaos4m.sf2) the bank 127 patch 99, which happens to be a percussion set, as you typically want on channel 9.
This is the format:

 set audio.driver pulseaudio
 set synth.polyphony 1024
 load /home/soundfonts/Chaos4m.sf2
 load /home/soundfonts/MyGM.sf2
 load /home/soundfonts/ReallyGoodPiano.sf2
 select 9 1 127 99

Invoking the function soundfonts = FS.read_config_file() (before creating the first synth!) then changes the default settings for audio.driver and synth.polyphony, and returns an array of load and select commands ready for later use by sf_load(synth,soundfonts)

synth = FS.new_synth({['synth.gain']=0.3, ['audio.driver']='alsa',})

When called with no argument, or with a table argument, new_synth wraps the library routines new_fluid_synth(), invoking new_fluid_settings, fluid_settings_setstr(), fluid_settings_setnum(), fluid_settings_setint(), and new_fluid_audio_driver() automatically as needed.

The return value is a C pointer to the synth, so don't change that or the library will crash.

Multiple synths may be started.

The meanings and permitted values of the various parameters are documented in fluidsynth.sourceforge.net/api/ with just two additions:

  • The fast.render parameter is introduced. You should set it to true if and only if you are converting MIDI to WAV and no real-time events will be involved. If fast.render is true the conversion will be done at full CPU speed and will finish an order of magnitude quicker than real time. Look for fast_render in src/fluidsynth.c for example code.
  • If the audio.driver parameter is set to "none" then new_synth() will not automatically create an audio_driver. You will not need this setting until support for midi_router is introduced.

filename2sf_id = FS.sf_load(synth, {'load my_gm.sf2', 'load my_piano.sf2',})

This wraps the library routine fluid_synth_sfload(), calling it once for each soundfont. Often, a synth has more than one soundfont; they go onto a sort of stack, and for a given patch, fluidsynth will use that soundfont closest to the top of the stack which can supply the requested patch. In the above example, my_gm.sf2 is a good general-midi soundfont, except that my_piano.sf2 offers a much nicer piano sound.

In practice, the array of soundfonts will usually be the output of a previous call to read_config_file()
Since version 1.7 it can include both load and select commands.

It returns a table where the keys are filenames of the soundfonts, and the values are the soundfont_ids that represent them. The soundfont_ids are stack indexes starting from 1; They are only needed if you want to invoke fluid_synth_sfont_select(), fluid_synth_sfunload() or fluid_synth_sfreload(), so in most cases you can ignore the return value.

FS.delete_synth(synth)

This does all the administrivia necessary to delete the synth, invoking delete_fluid_player, delete_fluid_audio_driver, delete_fluid_synth and delete_fluid_settings as necessary.

When called with no argument it deletes all running synths.

player = FS.new_player(synth, midifile)

This wraps the library routines new_fluid_player(), and then fluid_player_add() or fluid_player_add_mem(), allowing you to play a MIDI file. The return value is a C pointer.

  • If midifile is the filename of a MIDI file, then fluid_player_add() is is used to play it.
  • If midifile is '-' it is understood to mean stdin, the standard input.
  • If midifile is a byte-string beginning with 'MThd'   (eg: the output of MIDI.opus2midi or MIDI.score2midi)   then fluid_player_add_mem() is used to play it.

One synth may have multiple midi_players running at the same time (eg: to play several midi files, each starting at a different moment). Therefore, you still need to call player_play(player), player_join(player) and player_stop(player) by hand.

FS.player_play(midiplayer)

This corresponds to the library routine fluid_player_play()

FS.player_join(midiplayer)

This corresponds to the library routine fluid_player_join()
It waits until the midiplayer has got to the end of the last note in its midi file.

FS.player_stop(midiplayer)

This calls the library routines fluid_player_stop() and delete_fluid_player()

FS.note_on(synth, channel, note, velocity)

This corresponds to the library routine fluid_synth_noteon()

FS.note_off(synth, channel, note, velocity)

This corresponds to the library routine fluid_synth_noteoff()

FS.patch_change(synth, channel, patch)

This corresponds to the library routine fluid_synth_program_change()

FS.control_change(synth, channel, controller, value)

This corresponds to the library routine fluid_synth_cc()
The fluidsynth C-library implements only a selection of controllers: 0 (bank select), 1 (modulation), 7 (channel volume), 10 (pan), 11 (expression), 64 (sustain pedal); also 91 (reverb), and 93 (chorus). The last two depend on the corresponding generator as defined in the SoundFont.

FS.pitch_bend(synth, channel, val)

This corresponds to the library routine fluid_synth_pitch_bend(). The value should lie between 0 and 16383, where 8192 represents the default, central, pitch-wheel position.

(Note that midialsa uses a different convention, in which the value is from -8192 to 8191 and 0 represents the central position.)

FS.play_event(synth, event)

This is a wrapper for the above note_on, note_off, patch_change, control_change and pitch_bend routines, which accepts events of two different types used in the author's other midi-related modules:

It will currently only handle real-time events, so every event received will be played immediately. It will currently not handle 'note' events (of either type).

local ok = FS.is_soundfont(filename)

This corresponds to the library routine fluid_is_soundfont() which checks for the "RIFF" header in the file. It is useful only to distinguish between SoundFont and MIDI files. It returns only true or false.

local ok = FS.is_midifile(filename)

This corresponds to the library routine fluid_is_midifile() The current implementation only checks for the "MThd" header in the file. It is useful only to distinguish between SoundFont and MIDI files. It returns only true or false.

parameter2default = FS.default_settings()

Returns a table of all the supported parameters, with their default values. This could be useful, for example, in an application, to offer the user a menu of available parameters.

The meanings and permitted values of the various parameters are documented in fluidsynth.sourceforge.net/api/

err_string = FS.all_synth_errors()

Returns a multi-line string containing all the C-library's stderr output.

FS.error_file_name()

Returns the file-name of the temporary-file used to hold all the C-library's stderr output. This file is not removed automatically by delete_synth() because during the lifetime of the process many synths may be started and stopped. Therefore the application should explicity remove the file just before exit:   os.remove(FS.error_file_name())

FS.get(synth, parameter)

Returns the current value of the setting of parameter, invoking either fluid_settings_getstr(), fluid_settings_getnum() or fluid_settings_getint(), as appropriate.


EXAMPLES

fluadity
a synthesiser and midi-to-wav converter
midi2wavs
converts each midi-channel into a separate .wav file

These are also available in the examples directory in the tarball.
See: luarocks.org/modules/peterbillam/fluidsynth/, choose the latest version, click on src and download it, unzip the .src.rock, then un-tar the .tar.gz.


DOWNLOAD

This module is available as a LuaRock in luarocks.org/modules/peterbillam so you should be able to install it with the command:
  $ su
  Password:
  # luarocks install fluidsynth

or:
  # luarocks install https://pjb.com.au/comp/lua/fluidsynth-2.3-0.rockspec

It depends on the fluidsynth library and its header-files; for example on Debian you may need:
  # aptitude install libfluidsynth1 libfluidsynth-dev

or on Centos you may need:
  # yum install fluidsynth-devel

You can see the source-code in:
  https://pjb.com.au/comp/lua/fluidsynth-2.3.tar.gz


CHANGES

 20201103 2.3 adapt to gcc9 and libfluidsynth 2.1
 20171112 2.1 fix compiler warnings on 64-bit machines
 20150424 2.0 keep ptrs in C arrays and return indexes; C returns nil on error
 20150421     include lua5.3, and move pod and doc back to luarocks.org
 20140913 1.8 introduce get(synth, parameter) to get a current setting
 20140904 1.7 get both 'load' and 'select' commands from the config file
 20140901 1.6 delete_synth doesn't automatically remove TmpFile
 20140830 1.5 new_player knows '-' is stdin, and can play in-memory MIDI-data
 20140828 1.4 eliminate Settings2numSynths and M.delete_settings
 20140827 1.3 use fluid_get_sysconf, fluid_get_userconf, config file 'set k v'
 20140826 1.2 sf_load takes 2nd arg as an array; ~/.config/fluidsynth k = v
 20140825 1.1 new calling-interface at much higher level
 20140818 1.0 first working version


AUTHOR

Peter Billam   https://pjb.com.au/comp/contact.html


SEE ALSO

 man fluidsynth
 /usr/include/fluidsynth.h
 /usr/include/fluidsynth/*.h
 /usr/share/soundfonts/*.sf2
 /usr/share/sounds/sf2/*.sf2
 http://fluidsynth.sourceforge.net/api/
 http://www.pjb.com.au
 http://www.pjb.com.au/muscript/gm.html
 http://www.pjb.com.au/comp/index.html#lua
 http://www.pjb.com.au/comp/lua/fluidsynth.html
 http://www.pjb.com.au/comp/lua/midialsa.html
 http://www.pjb.com.au/comp/lua/MIDI.html
 http://www.pjb.com.au/midi/fluadity.html
 http://www.pjb.com.au/midi/midi2wavs.html