Add support for 4-pin range sensors, the velocity sensor, and LED status.
FossilOrigin-Name: ab34614152b3a25488aa4b55c45ec4a6f625bde42cd794209464825bb7654040
This commit is contained in:
parent
07b2d81523
commit
5a4fcbae87
4 changed files with 124 additions and 43 deletions
|
|
@ -48,11 +48,13 @@ class FluidSynth
|
|||
end
|
||||
|
||||
|
||||
### Instance a new fluidsynth interface.
|
||||
###
|
||||
def initialize( host=DEFAULT_HOST, port=DEFAULT_PORT )
|
||||
@host = host
|
||||
@port = port
|
||||
@lastnote = nil
|
||||
@velocity = 80
|
||||
@velocity = 0
|
||||
end
|
||||
|
||||
# The TCP socket to fluidsynth.
|
||||
|
|
@ -96,6 +98,14 @@ class FluidSynth
|
|||
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.
|
||||
###
|
||||
|
|
@ -132,13 +142,6 @@ class FluidSynth
|
|||
end
|
||||
|
||||
|
||||
### Selects an instrument +index+ within the currently loaded sf2 for channel 0.
|
||||
###
|
||||
def instrument( index )
|
||||
self.send "select 0 1 0 #{index}"
|
||||
end
|
||||
|
||||
|
||||
### Loads a resource (sf2, etc) from +path+.
|
||||
###
|
||||
def load( path )
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class GPIO
|
|||
value: BASE + "gpio#{pin}" + "value"
|
||||
}
|
||||
|
||||
self.export rescue Errno::EBUSY nil
|
||||
self.export rescue nil
|
||||
end
|
||||
|
||||
# The GPIO PIN number.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# -*- ruby -*-
|
||||
# vim: set noet sta sw=4 ts=4 :
|
||||
|
||||
require 'pathname'
|
||||
require 'forwardable'
|
||||
require 'configurability'
|
||||
require 'loggability'
|
||||
|
|
@ -17,7 +18,11 @@ class Theremin
|
|||
Forwardable
|
||||
|
||||
# The speed of sound, in centmeters/sec.
|
||||
SONICSPEED = 34300
|
||||
SONICSPEED = 34300
|
||||
|
||||
# The soundfont that is loaded to index 1.
|
||||
DEFAULT_FONT = Pathname.pwd + 'Theremin.sf2'
|
||||
|
||||
|
||||
# Loggability API
|
||||
log_as :theremin
|
||||
|
|
@ -62,6 +67,7 @@ class Theremin
|
|||
# The soundfont to use for the synth. Will look in the
|
||||
# current working directory by default, but absolute paths
|
||||
# can be used for system sounds.
|
||||
# The "Theremin.sf2" soundfont is always loaded into index 1!
|
||||
setting :font, default: 'Theremin.sf2' do |val|
|
||||
val = Pathname( val )
|
||||
val = Pathname.pwd + val unless val.absolute?
|
||||
|
|
@ -75,7 +81,7 @@ class Theremin
|
|||
|
||||
##
|
||||
# A reasonable distance in feet for the theremin to map notes.
|
||||
setting :distance_max, default: 4
|
||||
setting :distance_max, default: 3
|
||||
|
||||
##
|
||||
# Initial synth volume.
|
||||
|
|
@ -90,7 +96,11 @@ class Theremin
|
|||
setting :chorus, default: true
|
||||
|
||||
##
|
||||
# What instrument to use from the loaded SF2.
|
||||
# What font to initially use. 1 is the theremin. 2 is user loaded.
|
||||
setting :font_index, default: 1
|
||||
|
||||
##
|
||||
# What instrument to initially use from the loaded SF2.
|
||||
setting :instrument_index, default: 1
|
||||
|
||||
##
|
||||
|
|
@ -118,16 +128,14 @@ class Theremin
|
|||
### Instance a new Theremin daemon.
|
||||
###
|
||||
def initialize
|
||||
# TODO: @pins = %i[ pitch_trigger pitch_echo velocity_trigger velocity_echo ]
|
||||
@pins = %i[ pitch_trigger ]
|
||||
|
||||
self.setup_gpio
|
||||
|
||||
@pins = %i[ pitch_trigger pitch_echo velocity_trigger velocity_echo ]
|
||||
@timeout = 0
|
||||
@synth = FluidSynth.new( self.class.host, self.class.port )
|
||||
@running = false
|
||||
@threads = []
|
||||
|
||||
self.setup_gpio
|
||||
|
||||
rescue => err
|
||||
self.log.warn( err.message )
|
||||
self.stop rescue nil
|
||||
|
|
@ -154,12 +162,15 @@ class Theremin
|
|||
|
||||
self.gain( self.class.gain )
|
||||
self.glide( self.class.glide )
|
||||
self.load( self.class.font.to_s )
|
||||
self.load( DEFAULT_FONT )
|
||||
self.load( self.class.font.to_s ) unless self.class.font == DEFAULT_FONT
|
||||
self.reverb( self.class.reverb )
|
||||
self.chorus( self.class.chorus )
|
||||
self.instrument( self.class.instrument_index )
|
||||
self.instrument( self.class.font_index, self.class.instrument_index )
|
||||
|
||||
@threads << self.note_thread
|
||||
@threads << self.velocity_thread
|
||||
self.led( :green )
|
||||
self.log.warn "Waiting for interaction, or ctrl-c to exit."
|
||||
@threads.map( &:join )
|
||||
end
|
||||
|
|
@ -171,6 +182,7 @@ class Theremin
|
|||
@running = false
|
||||
self.pins.each {|pin| self.class.send( pin ).unexport rescue nil }
|
||||
self.synth.socket.close
|
||||
self.led( :red )
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -187,12 +199,12 @@ class Theremin
|
|||
]
|
||||
|
||||
return self.running_thread do
|
||||
distance = self.distance
|
||||
distance = self.distance( :pitch )
|
||||
note = self.distance_to_note( distance )
|
||||
|
||||
# Only play if within maximum distance.
|
||||
#
|
||||
if distance <= self.class.distance_max
|
||||
if distance > 0 && distance <= self.class.distance_max
|
||||
self.log.debug "Pitch distance %0.2f (note %s)" % [ distance, note ]
|
||||
|
||||
# Hold the note if it is unchanged within slew amount.
|
||||
|
|
@ -218,6 +230,32 @@ class Theremin
|
|||
end
|
||||
|
||||
|
||||
### Measure velocity distance, convert for synth notes.
|
||||
###
|
||||
def velocity_thread
|
||||
self.log.info "Starting velocity 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 )
|
||||
|
||||
# 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
|
||||
else
|
||||
synth.velocity = 0
|
||||
end
|
||||
|
||||
sleep self.class.sample_interval
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
### Create a new thread loop that returns when @running is false.
|
||||
###
|
||||
def running_thread( &block )
|
||||
|
|
@ -239,9 +277,12 @@ class Theremin
|
|||
raise "GPIO pins are required configuration options: %p" % [ self.pins ]
|
||||
end
|
||||
|
||||
self.class.pitch_trigger.edge( :both )
|
||||
# self.class.pitch_echo.edge( :both )
|
||||
# TODO: self.class.velocity_echo.edge( :both )
|
||||
self.class.pitch_trigger.mode( :out )
|
||||
self.class.velocity_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 )
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -258,22 +299,35 @@ class Theremin
|
|||
end
|
||||
|
||||
|
||||
### TODO: make generic for a trigger/echo pin pair
|
||||
### Returns detected distance in feet.
|
||||
### For a given +distance+ in feet, convert into a velocity range (0-127).
|
||||
###
|
||||
def distance
|
||||
self.class.pitch_trigger.mode( :out )
|
||||
self.class.pitch_trigger.write( 1 )
|
||||
def distance_to_velocity( distance )
|
||||
velocity_start = 127
|
||||
velocity_end = 1
|
||||
distance_min = 0
|
||||
|
||||
slope = 1.0 * ( velocity_end - velocity_start ) / ( self.class.distance_max - distance_min )
|
||||
return velocity_start + ( slope * ( distance - distance_min ) ).round
|
||||
end
|
||||
|
||||
|
||||
|
||||
### Returns detected distance in feet for a given +device+.
|
||||
###
|
||||
def distance( device )
|
||||
device_trigger = self.class.send( "#{device}_trigger".to_sym )
|
||||
device_echo = self.class.send( "#{device}_echo".to_sym )
|
||||
|
||||
device_trigger.write( 1 )
|
||||
sleep 0.00001
|
||||
self.class.pitch_trigger.write( 0 )
|
||||
self.class.pitch_trigger.mode( :in )
|
||||
device_trigger.write( 0 )
|
||||
|
||||
duration = Time.now
|
||||
while self.class.pitch_trigger.read == 0
|
||||
while device_echo.read == 0
|
||||
starttime = Time.now
|
||||
break if starttime - duration > GPIO::TIMEOUT
|
||||
end
|
||||
while self.class.pitch_trigger.read == 1
|
||||
while device_echo.read == 1
|
||||
stoptime = Time.now
|
||||
break if stoptime - duration > GPIO::TIMEOUT
|
||||
end
|
||||
|
|
@ -289,5 +343,20 @@ class Theremin
|
|||
rescue
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
### Set the LED. Maybe it'll be visible.
|
||||
###
|
||||
def led( color )
|
||||
case color
|
||||
when :red
|
||||
File.open( '/sys/class/leds/led0/brightness', 'w' ){|l| l.puts(0) }
|
||||
File.open( '/sys/class/leds/led1/brightness', 'w' ){|l| l.puts(1) }
|
||||
when :green
|
||||
File.open( '/sys/class/leds/led0/brightness', 'w' ){|l| l.puts(1) }
|
||||
File.open( '/sys/class/leds/led1/brightness', 'w' ){|l| l.puts(0) }
|
||||
end
|
||||
end
|
||||
|
||||
end # class Theremin
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue