2023-05-09 03:32:09 +00:00
|
|
|
# -*- 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
|
|
|
|
|
|
|
|
|
|
|
2023-05-12 05:46:35 +00:00
|
|
|
### Instance a new fluidsynth interface.
|
|
|
|
|
###
|
2023-05-09 03:32:09 +00:00
|
|
|
def initialize( host=DEFAULT_HOST, port=DEFAULT_PORT )
|
|
|
|
|
@host = host
|
|
|
|
|
@port = port
|
|
|
|
|
@lastnote = nil
|
2023-05-12 06:44:09 +00:00
|
|
|
@velocity = 80
|
2023-05-09 03:32:09 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2023-05-12 05:46:35 +00:00
|
|
|
### 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
|
|
|
|
|
|
|
|
|
|
|
2023-05-09 03:32:09 +00:00
|
|
|
### 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
|
|
|
|
|
|
|
|
|
|
|