Setup espeak TTS. Fix "velocity" -> "gain".
I was originally thinking the velocity sensor would adjust the midi note velocity. The downside to this is that you have to wait for a note to play to "feel" the volume change. When changed to alter the gain of the synth itself, it is much smoother -- works for any sound, even those that have already started playing. FossilOrigin-Name: 9b5a893d5b6e3aed1e11d0b984b5f498d9b67f55cda304bbef4179944ca6260f
This commit is contained in:
parent
5a4fcbae87
commit
7bc47cfc4d
4 changed files with 58 additions and 35 deletions
|
|
@ -7,6 +7,7 @@ require 'configurability'
|
|||
require 'loggability'
|
||||
require 'gpio'
|
||||
require 'fluidsynth'
|
||||
require 'open3'
|
||||
|
||||
|
||||
# A class that coordinates GPIO events and translates
|
||||
|
|
@ -52,14 +53,14 @@ class Theremin
|
|||
end
|
||||
|
||||
##
|
||||
# The GPIO pin the velocity trigger is on.
|
||||
setting :velocity_trigger do |val|
|
||||
# The GPIO pin the gain trigger is on.
|
||||
setting :gain_trigger do |val|
|
||||
GPIO.new( val ) if val
|
||||
end
|
||||
|
||||
##
|
||||
# The GPIO pin the velocity echo measurement is on.
|
||||
setting :velocity_echo do |val|
|
||||
# The GPIO pin the gain echo measurement is on.
|
||||
setting :gain_echo do |val|
|
||||
GPIO.new( val ) if val
|
||||
end
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ class Theremin
|
|||
|
||||
##
|
||||
# Initial synth volume.
|
||||
setting :gain, default: 1.0
|
||||
setting :gain, default: 0.0
|
||||
|
||||
##
|
||||
# Enable reverb?
|
||||
|
|
@ -128,12 +129,14 @@ class Theremin
|
|||
### Instance a new Theremin daemon.
|
||||
###
|
||||
def initialize
|
||||
@pins = %i[ pitch_trigger pitch_echo velocity_trigger velocity_echo ]
|
||||
@timeout = 0
|
||||
@synth = FluidSynth.new( self.class.host, self.class.port )
|
||||
@running = false
|
||||
@threads = []
|
||||
@pins = %i[ pitch_trigger pitch_echo gain_trigger gain_echo ]
|
||||
@timeout = 0
|
||||
@synth = FluidSynth.new( self.class.host, self.class.port )
|
||||
@speech_io = nil
|
||||
@running = false
|
||||
@threads = []
|
||||
|
||||
self.setup_speech
|
||||
self.setup_gpio
|
||||
|
||||
rescue => err
|
||||
|
|
@ -150,6 +153,9 @@ class Theremin
|
|||
# The Fluidsynth instance.
|
||||
attr_reader :synth
|
||||
|
||||
# An IO to stream to espeak.
|
||||
attr_reader :speech_io
|
||||
|
||||
# Is this Theremin currently running?
|
||||
attr_reader :running
|
||||
|
||||
|
|
@ -169,8 +175,9 @@ class Theremin
|
|||
self.instrument( self.class.font_index, self.class.instrument_index )
|
||||
|
||||
@threads << self.note_thread
|
||||
@threads << self.velocity_thread
|
||||
@threads << self.gain_thread
|
||||
self.led( :green )
|
||||
self.speak "Hi. I'm Maude."
|
||||
self.log.warn "Waiting for interaction, or ctrl-c to exit."
|
||||
@threads.map( &:join )
|
||||
end
|
||||
|
|
@ -182,6 +189,7 @@ class Theremin
|
|||
@running = false
|
||||
self.pins.each {|pin| self.class.send( pin ).unexport rescue nil }
|
||||
self.synth.socket.close
|
||||
self.speech_io.close if self.speech_io && !self.speech_io.closed?
|
||||
self.led( :red )
|
||||
end
|
||||
|
||||
|
|
@ -230,25 +238,25 @@ class Theremin
|
|||
end
|
||||
|
||||
|
||||
### Measure velocity distance, convert for synth notes.
|
||||
### Measure gain distance, convert for any playing notes.
|
||||
###
|
||||
def velocity_thread
|
||||
self.log.info "Starting velocity measurements (every %s/sec) within %d feet." % [
|
||||
def gain_thread
|
||||
self.log.info "Starting gain measurements (every %s/sec) within %d feet." % [
|
||||
self.class.sample_interval,
|
||||
self.class.distance_max
|
||||
]
|
||||
|
||||
return self.running_thread do
|
||||
distance = self.distance( :velocity )
|
||||
velocity = self.distance_to_velocity( distance )
|
||||
distance = self.distance( :gain )
|
||||
gain = self.distance_to_gain( distance )
|
||||
|
||||
# Only set if within maximum distance.
|
||||
#
|
||||
if distance > 0 && distance <= self.class.distance_max
|
||||
self.log.debug "Velocity distance %0.2f (velocity %s)" % [ distance, velocity ]
|
||||
synth.velocity = velocity
|
||||
self.log.debug "Gain distance %0.2f (gain %s)" % [ distance, gain ]
|
||||
synth.gain( gain )
|
||||
else
|
||||
synth.velocity = 0
|
||||
synth.gain( 0 )
|
||||
end
|
||||
|
||||
sleep self.class.sample_interval
|
||||
|
|
@ -256,6 +264,15 @@ class Theremin
|
|||
end
|
||||
|
||||
|
||||
### Open a pipe to espeak, and wait on input.
|
||||
##
|
||||
def setup_speech
|
||||
self.log.info "Starting background espeak process."
|
||||
cmd = %w[ espeak -k20 -v f5 -s 160 -a 150 ]
|
||||
@speech_io, stdout, stderr, wait_thr = Open3.popen3( *cmd )
|
||||
end
|
||||
|
||||
|
||||
### Create a new thread loop that returns when @running is false.
|
||||
###
|
||||
def running_thread( &block )
|
||||
|
|
@ -278,11 +295,11 @@ class Theremin
|
|||
end
|
||||
|
||||
self.class.pitch_trigger.mode( :out )
|
||||
self.class.velocity_trigger.mode( :out )
|
||||
self.class.gain_trigger.mode( :out )
|
||||
self.class.pitch_echo.mode( :in )
|
||||
self.class.pitch_echo.edge( :both )
|
||||
self.class.velocity_echo.mode( :in )
|
||||
self.class.velocity_echo.edge( :both )
|
||||
self.class.gain_echo.mode( :in )
|
||||
self.class.gain_echo.edge( :both )
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -295,23 +312,22 @@ class Theremin
|
|||
distance_min = 0
|
||||
|
||||
slope = 1.0 * ( midi_end - midi_start ) / ( self.class.distance_max - distance_min )
|
||||
return midi_start + ( slope * ( distance - distance_min ) ).round
|
||||
return ( midi_start + ( slope * ( distance - distance_min ) ) ).round
|
||||
end
|
||||
|
||||
|
||||
### For a given +distance+ in feet, convert into a velocity range (0-127).
|
||||
### For a given +distance+ in feet, convert into a gain range (0-5).
|
||||
###
|
||||
def distance_to_velocity( distance )
|
||||
velocity_start = 127
|
||||
velocity_end = 1
|
||||
distance_min = 0
|
||||
def distance_to_gain( distance )
|
||||
gain_start = 5.0
|
||||
gain_end = 0.0
|
||||
distance_min = 0
|
||||
|
||||
slope = 1.0 * ( velocity_end - velocity_start ) / ( self.class.distance_max - distance_min )
|
||||
return velocity_start + ( slope * ( distance - distance_min ) ).round
|
||||
slope = 1.0 * ( gain_end - gain_start ) / ( self.class.distance_max - distance_min )
|
||||
return ( gain_start + ( slope * ( distance - distance_min ) ) ).round( 2 )
|
||||
end
|
||||
|
||||
|
||||
|
||||
### Returns detected distance in feet for a given +device+.
|
||||
###
|
||||
def distance( device )
|
||||
|
|
@ -358,5 +374,12 @@ class Theremin
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
### Send text to the speech engine.
|
||||
###
|
||||
def speak( phrase )
|
||||
self.speech_io.puts( phrase )
|
||||
end
|
||||
|
||||
end # class Theremin
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue