Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

cleaned up vector behavior with ArrayExtensions

  • Loading branch information...
commit e52de7604a2de082d32bc470fc4c4f8cc35f4ed1 1 parent 9cb21d4
@dfl authored
View
3  History.txt
@@ -1,3 +1,6 @@
+=== 0.1.2 / 2012-05-16
+* cleaned up vector behavior for Generator#ticks, using ArrayExtensions#to_v etc
+
=== 0.1.1 / 2012-05-16
* enhancements to Speaker
View
2  README.txt
@@ -4,7 +4,7 @@
== DESCRIPTION:
-A real-time audio dsp library for ruby based on ffi-portaudio
+A real-time audio DSP library for ruby based on ffi-portaudio
== FEATURES/PROBLEMS:
View
26 lib/radspberry/dsp/base.rb
@@ -47,11 +47,11 @@ def ticks samples, kperiod=nil
[].tap do |output|
samples.in_groups_of( kperiod, 0.0 ) do |frame|
yield( self )
- output += self.ticks(kperiod)
+ output += self.ticks(kperiod).to_a # concat frames
end
- end
+ end.to_v
else
- (1..samples).map{ tick }
+ samples.times.map{ tick }.to_v
end
end
@@ -62,7 +62,7 @@ def to_wav( seconds, filename=nil )
samples = self.sampleRate * seconds
if block_given?
inv = 1.0 / samples
- data = (1..samples).map{ |s| yield(self, s * inv); self.tick }
+ data = samples.times.map{ |s| yield(self, s * inv); self.tick }
else
data = self.ticks( samples )
end
@@ -104,7 +104,7 @@ def tick input
end
def ticks samples
- (@gain * @chain.inject( Vector[*Array.zeros(samples)] ){|x,o| Vector[*o.ticks(x)] } ).to_a
+ @gain * @chain.inject( Vector.zeros(samples) ){|x,o| o.ticks(x) }
end
end
@@ -115,16 +115,16 @@ def self.[] *mix
def initialize mix
raise ArugmentError, "must be array" unless mix.is_a?(Array)
- @mix = mix
+ @mix = mix
@gain = 1.0 / Math.sqrt( @mix.size )
end
def tick
- @gain * @mix.inject( 0.0 ){|sum,o| sum + o.tick }
+ @gain * @mix.tick_sum
end
def ticks samples
- (@gain * @mix.inject( Vector[*Array.zeros(samples)] ){|sum,o| sum + Vector[*o.ticks(samples)] } ).to_a
+ @gain * @mix.ticks_sum( samples )
end
end
@@ -146,9 +146,9 @@ def tick
end
def ticks samples
- a = Vector[*@a.ticks(samples)]
- b = Vector[*@b.ticks(samples)]
- ( (b-a)*@fade + a ).to_a # TODO cos fade?
+ a = @a.ticks(samples)
+ b = @b.ticks(samples)
+ (b-a)*@fade + a # TODO cos fade?
end
end
@@ -183,7 +183,7 @@ def tick
end
def ticks samples
- (@mix.each_with_index.inject( Vector[*Array.zeros(samples)] ){|sum,(o,i)| sum + @gains[i] * Vector[*o.ticks(samples)] } ).to_a
+ @mix.each_with_index.inject( Vector.zeros(samples) ){|sum,(o,i)| sum + @gains[i] * o.ticks(samples) }
end
end
@@ -202,7 +202,7 @@ def tick
end
def ticks samples
- (@gain * @chain.inject( Vector[*@gen.ticks(samples)] ){|x,o| Vector[*o.ticks(x)] } ).to_a
+ @gain * @chain.inject( @gen.ticks(samples) ){|x,o| o.ticks(x) }
end
end
View
7 lib/radspberry/dsp/filter.rb
@@ -4,10 +4,10 @@ class Biquad < Processor # interpolating biquad, Direct-form 1
include Math
def initialize( num=[1.0,0,0], den=[1.0,0,0], opts={} )
+ @interpolate = opts[:interpolate]
+ @denorm = ANTI_DENORMAL
update( Vector[*num], Vector[*den] )
normalize if @a[0] != 1.0
- @denorm = ANTI_DENORMAL
- @interpolate = opts[:interpolate]
clear
end
@@ -69,7 +69,7 @@ def tick input
end
def freq= arg
- @w0= TWO_PI * arg * inv_srate # normalize freq [0,PI)
+ @w0 = TWO_PI * arg * inv_srate # normalize freq [0,PI)
recalc
end
end
@@ -89,6 +89,7 @@ def process
class Hpf < Biquad
def initialize( f, q=nil )
+ @interpolate = true
@inv_q = q ? 1.0 / q : SQRT2 # default to butterworth
self.freq = f # triggers recalc
clear
View
14 lib/radspberry/dsp/math.rb
@@ -63,18 +63,18 @@ def clamp x, min=(0.0..1.0), max=nil
end
class LookupTable # linear interpolated, input goes from 0 to 1
- def initialize bits=7
- @size = 2 ** bits
- scale = 1.0 / @size
- @table = (0..@size).map{|x| yield( scale * x ) }
+ def initialize opts={}
+ opts.reverse_merge! :bits => 7, :scale => 1.0, :offset => 0
+ @size, @scale, @offset = 2 ** opts[:bits], opts[:scale], opts[:offset]
+ @table = @size.times.map{|x| yield( x.to_f / @size ) }
end
- def []( arg ) # from 0 to 1
+ def []( arg ) # input goes from 0 to 1
offset = arg * @size
idx = offset.floor
frac = offset - idx
- return @table.last if idx >= @size
- DSP.xfade( @table[idx], @table[idx+1], frac )
+ output = idx >= @size ? @table.last : DSP.xfade( @table[idx], @table[idx+1], frac )
+ # output = @scale * output + offset
end
end
View
22 lib/radspberry/dsp/oscillator.rb
@@ -64,7 +64,6 @@ def tick
end
class RpmSquare < RpmSaw
-
def initialize( freq = DEFAULT_FREQ, phase=0 )
super
end
@@ -75,13 +74,22 @@ def tick
end
end
- class RpmNoise < RpmSaw
- param_accessor :beta, :default => 1234 # no range clamping
+ class RpmNoise < PhasorOscillator
+ # param_accessor :beta, :default => 1234 # no range clamping
+
+ def initialize( seed = 1234 )
+ @beta = seed # || self.beta
+ super
+ end
- def initialize( seed = nil )
- @beta = seed if seed
- clear
+ def clear
+ @last_out = 0
+ end
+
+ def tick
+ @last_out = sin( TWO_PI * tock + @beta * @last_out )
end
- end
+ end
+
end
View
10 lib/radspberry/dsp/speaker.rb
@@ -57,6 +57,7 @@ class AudioStream < FFI::PortAudio::Stream
def initialize gen, frameSize=2**12, gain=1.0 # 1024
@synth = gen # responds to tick
@gain = gain
+ @muted = false
raise ArgumentError, "#{synth.class} doesn't respond to ticks!" unless @synth.respond_to?(:ticks)
init!( frameSize )
start
@@ -67,13 +68,10 @@ def process input, output, framesPerBuffer, timeInfo, statusFlags, userData
if @muted
out = Array.zeros( framesPerBuffer )
else
- if @gain == 1.0
- out = @synth.ticks( framesPerBuffer )
- else
- out = (Vector[ *@synth.ticks( framesPerBuffer ) ] * @gain).to_a
- end
+ out = @synth.ticks( framesPerBuffer )
+ out *= @gain unless gain == 1.0
end
- output.write_array_of_float out
+ output.write_array_of_float out.to_a
:paContinue
end
View
32 lib/radspberry/dsp/super_saw.rb
@@ -2,28 +2,24 @@ module DSP
# based on Adam Szabo's thesis from csc.kth.se
class SuperSaw < Oscillator
- attr_accessor :spread
-
- def initialize freq = DEFAULT_FREQ, spread=0.5, num=7
- @master = Phasor.new
- @phasors = (1..num-1).map{ Phasor.new }
- @spread = spread
+ param_accessor :spread, :default => 0.5, :after_set => Proc.new{detune_phasors}
+
+ def initialize freq = DEFAULT_FREQ
+ @master = Phasor.new
+ @hpf = Hpf.new( @master.freq )
setup_tables
- @hpf = Hpf.new( @master.freq )
+ @phasors = @@offsets.size.times.map{ Phasor.new }
randomize_phase
+ @spread = self.spread # set default
self.freq = freq
end
-
- def spread= x
- @spread = DSP.clamp(x)
- detune_phasors
- end
def randomize_phase
+ @master.phase = DSP.random
@phasors.each{|p| p.phase = DSP.random }
end
- def clear
+ def clear # call this on note on
@hpf.clear
randomize_phase
end
@@ -35,20 +31,20 @@ def freq= f
def tick
osc = @@center[ @spread ] * @master.tick
- osc += @@side[ @spread ] * @phasors.inject(0){|sum,p| sum + p.tick }
+ osc += @@side[ @spread ] * @phasors.tick_sum #inject(0){|sum,p| sum + p.tick }
@hpf.tick( osc )
end
def ticks samples
- osc = @@center[ @spread ] * Vector[*@master.ticks(samples)]
- osc = @@side[ @spread ] * @phasors.inject( osc ){|sum,p| sum + Vector[*p.ticks(samples)] }
- @hpf.ticks( osc.to_a )
+ osc = @@center[ @spread ] * @master.ticks(samples)
+ osc = @@side[ @spread ] * @phasors.ticks_sum( samples, osc ) #inject( osc ){|sum,p| sum + p.ticks(samples).to_v }
+ @hpf.ticks( osc )
end
private
def detune_phasors
- @phasors.each_with_index{ |p,i| p.freq = (1 + @@detune[@spread] * @@offsets[i]) * @freq }
+ @phasors.each_with_index{|p,i| p.freq = (1 + @@detune[@spread] * @@offsets[i]) * @freq }
end
def setup_tables
View
2  lib/radspberry/midi.rb
@@ -89,7 +89,7 @@ def [] gen
p event
DSP::Speaker.volume = event.velocity / 128.0
@gen.freq = MIDI::krystal_freq( event.note )
- else :all_notes_off
+ else # :all_notes_off
DSP::Speaker.volume = 0
end
end
View
59 lib/radspberry/ruby_extensions.rb
@@ -1,17 +1,51 @@
module ArrayExtensions
- def full_of(val,num)
- [].fill(val,0...num)
+
+ def to_v
+ Vector[*self]
+ end
+
+ def tick_sum inp=0.0
+ inject( inp ){|sum,p| sum + p.tick }
+ end
+
+ def ticks_sum samples, inp=nil # vector manipulation
+ inp ||= Vector.zeros(samples)
+ inject( inp ){|sum,p| sum + p.ticks(samples) }
+ end
+
+ module ClassMethods
+ def full_of(val,count)
+ [].fill(val,0...count)
+ end
+
+ def zeros count
+ full_of(0,count)
+ end
end
- def zeros num
- full_of(0,num)
+ def self.included(base)
+ base.extend ClassMethods
end
+
end
-Array.send :extend, ArrayExtensions
+Array.send :include, ArrayExtensions
+
-module ModuleExtensions
+module VectorExtensions
+ def full_of(val,count)
+ Array.full_of(val,count).to_v
+ end
+
+ def zeros count
+ full_of(0.0,count)
+ end
+end
+Vector.send :extend, VectorExtensions
+
+
+module ModuleExtensions
- def param_accessor symbol, opts={}
+ def param_accessor symbol, opts={}, &block
opts = { :range => opts } if opts.is_a?(Range)
opts.reverse_merge! :range => (0..1)
var = nil
@@ -27,7 +61,7 @@ def param_accessor symbol, opts={}
## define getter
if opts[:default]
- module_eval "def #{symbol}() #{var} || #{opts[:default]}; end" # TODO allow Proc.call(self) ?
+ module_eval "def #{symbol}() #{var} || #{opts[:default]}; end"
else
module_eval "def #{symbol}() #{var}; end"
end
@@ -35,10 +69,17 @@ def param_accessor symbol, opts={}
## define setter
if opts[:range]
min,max = opts[:range].first.to_f, opts[:range].last.to_f
- module_eval "def #{symbol}=(val) #{var} = DSP.clamp(val,#{min},#{max}); end"
+ module_eval <<-STR
+ def #{symbol}=(val)
+ #{var} = DSP.clamp(val,#{min},#{max})
+ #{"after_set_#{symbol}" if opts[:after_set]}
+ end
+ STR
+ define_method "after_set_#{symbol}", opts[:after_set] if opts[:after_set]
else
module_eval "def #{symbol}=(val) #{var} = val; end"
end
end
end
+
Module.send :include, ModuleExtensions
View
27 test/test_module_extensions.rb
@@ -0,0 +1,27 @@
+require "test/unit"
+require "radspberry/ruby_extensions"
+
+require 'active_support/core_ext/hash/reverse_merge'
+
+class TestModuleExtensions < Test::Unit::TestCase
+
+ class Tester
+ param_accessor :spread, :default => 0.5, :after_set => Proc.new{ @bang = true }
+ attr_accessor :bang
+
+ def initialize
+ @bang = false
+ end
+ end
+
+ def test_param_accessor_after_set
+ t = Tester.new
+ assert_equal false, t.bang
+ t.spread = 10
+ assert_equal true, t.bang
+ assert_equal 1.0, t.spread
+ end
+
+end
+
+
View
38 test/test_radspberry.rb
@@ -2,7 +2,39 @@
require "radspberry"
class TestRadspberry < Test::Unit::TestCase
- def test_sanity
- flunk "write tests or I will kneecap you"
- end
+ include DSP
+
+ # def test_phasor
+ # p = Phasor.new
+ # assert_equal 0.0, p.tick
+ # end
+
+ def test_speaker
+ Speaker[ Phasor.new ]
+ sleep 1
+
+ puts "changing frequency"
+ Speaker.synth.freq /= 2
+ sleep 1
+
+ puts "changing frequency"
+ Speaker.synth.freq /= 2
+ sleep 1
+
+ puts "starting crossfader (supersaw with rpmnoise)"
+ chain = XFader[ o1=SuperSaw.new, o2=RpmNoise.new ]
+ Speaker[ chain ]
+ o1.spread = 0.8
+ chain.fade = 0
+ sleep 5
+ 10.times do
+ puts "crossfade to noise... (#{chain.fade += 0.1})"
+ sleep 0.5
+ end
+
+ puts "muting"
+ Speaker.mute
+ sleep 1
+ end
+
end
Please sign in to comment.
Something went wrong with that request. Please try again.