# -*- ruby -*- # vim: set noet sta sw=4 ts=4 : require 'socket' require 'configurability' require 'loggability' # Communicate with a running FluidSynth daemon via it's TCP socket API. # class FluidSynth extend Configurability, Loggability # The default host for the fluidsynth server. DEFAULT_HOST = 'localhost' # The default port for the fluidsynth server. DEFAULT_PORT = 9800 # Loggability API log_as :fluidsynth # Configuration API # configurability( :fluidsynth ) do ## # How many simultaneous notes can play. Keep low for better CPU. setting :polyphony, default: 6 ## # Reverb amount. setting :reverb_size, default: 0.9 ## # Reverb gain level. setting :reverb_gain, default: 0.7 ## # Chorus depth. setting :chorus_depth, default: 64.0 ## # Chorus gain level. setting :chorus_gain, default: 1.5 end ### Instance a new fluidsynth interface. ### def initialize( host=DEFAULT_HOST, port=DEFAULT_PORT ) @host = host @port = port @lastnote = nil @velocity = 0 end # The TCP socket to fluidsynth. attr_reader :socket # The previous note played, to enable glides. attr_reader :lastnote # The current velocity to play new notes at. attr_accessor :velocity ### Open the TCP socket to the synth and perform initial setups. ### def connect @socket = TCPSocket.new( @host, @port ) self.setup_effects self.polyphony( self.class.polyphony ) end ### Setup polyphony amount. ### def polyphony( val ) self.send "synth.polyphony #{val}" end ### Stop all notes and reset effects. ### def panic self.send "reset" end ### Alter the default volume. ### def gain( level ) self.send "gain #{level}" end ### Select a loaded soundfont and instrument by index. ### (Always using channel 0.) ### def instrument( font_idx, instrument_idx ) self.send "select 0 #{font_idx} 0 #{instrument_idx}" end ### Turn glide (portamento) on or off. Val is an integer that ### represents 128ms. 4 == 512ms. ### def glide( val ) if val.zero? self.send "cc 0 68 0" self.send "cc 0 65 0" self.send "cc 0 5 0" else self.send "setlegatomode 0 1" self.send "cc 0 68 127" # legato self.send "cc 0 65 127" # portamento self.send "cc 0 5 #{val}" end end ### Play a midi +note+ at +velocity+, then silence the previous note. ### (This allows portamento to function if enabled.) ### def playnote( note, velocity=self.velocity ) return if @lastnote == note self.send "noteon 0 #{note} #{velocity}" self.stopnote @lastnote = note end ### Silences the previous note played. ### def stopnote return unless @lastnote self.send "noteoff 0 #{@lastnote}" end ### Loads a resource (sf2, etc) from +path+. ### def load( path ) self.send "load #{path}" end ### Enable/disable reverb. ### def reverb( toggle=true ) toggle = toggle ? 1 : 0 self.send "set synth.reverb.active #{toggle}" end ### Enable/disable chorus. ### def chorus( toggle=true ) toggle = toggle ? 1 : 0 self.send "set synth.chorus.active #{toggle}" end ######### protected ######### ### Initial effect setup. ### def setup_effects self.send "set synth.reverb.room-size 0.9" self.send "set synth.reverb.level 0.7" self.send "set synth.chorus.level 1.5" self.send "set synth.chorus.depth 64.0" end ### Issue a command to the synth, after possible logging. ### def send( cmd ) self.log.debug( cmd ) self.socket.puts( cmd ) end end