Permalink
Browse files

Back to separate channels

After consulting LAD and thinking a bit more, I decided to go back to an
internal representation of multiple channels instead of interleaved data. I
also opted for a much simplified API, much more like an NArray (which,
actually, it is a subclass of). Now it is easy to work with channels in-place,
and to get/set from interleaved data as needed.

darcs-hash:20051115225541-28bda-84dbc1490a274d7e37051c9543bdd35c2e81e41e.gz
  • Loading branch information...
1 parent a984678 commit e183a4b73b38390c1c4de2bf7af2b57241777744 @fugalh committed Nov 15, 2005
Showing with 82 additions and 149 deletions.
  1. +58 −99 lib/audio.rb
  2. +24 −50 test/test_audio.rb
View
@@ -2,129 +2,84 @@
# TODO: lots of testing, libsndfile, libsamplerate, portaudio
module Audio
- # Construct with one of:
- # Sound.new(:sfloat,len,channels=1)
- # Sound.byte(len,channels=1)
- # Sound.sint(len,channels=1) # short int
- # Sound.int(len,channels=1)
- # Sound.sfloat(len,channels=1) # single-precision float
- # Sound.float(len,channels=1) # double-precision float
+ # A Sound is a NArray with some audio convenience methods. It should always
+ # have the shape (n,m) where n is the number of frames and m is the number of
+ # channels. (Even when m=1)
#
- # See also the NArray documentation.
#
- # Data is stored interleaved in narray, as an NArray (obviously). Get and set
- # frames with [] and []=, get the raw data via narray. Get only one channel
- # (as a new NArray) with channel.
- class Sound
- # The actual data. Channels are interleaved.
- attr_reader :narray
- # number of channels
- attr_reader :channels
-
- # type:: one of [:byte,:sint,:int,:sfloat,:float]. See NArray
- # documentation. In particular, notice that :float means double
- # precision float.
- # len:: length in frames
- # channels:: Number of channels
- def initialize(type,len,channels=1)
- raise ArgumentError, "Too few channels" unless channels > 0
-
- typecode = case type.to_sym
- when :byte, :char : 1
- when :sint, :short : 2
- when :int, :long : 3
- when :sfloat : 4
- when :float, :double : 5
- else
- raise ArgumentError, "Invalid type"
- end
-
- @channels = channels
- @narray = NArray.new(typecode, @channels * len)
- @mask = NArray.byte(@narray.size).indgen
- @mask %= @channels
- end
-
+ class Sound < NArray
# The number of frames
- def size
- @narray.size / @channels
+ def frames
+ self.shape[0]
end
- alias_method :length, :size
- # Returns a new NArray with the data from channel i
- def channel(i)
- unless (0...@channels).include? i
- raise IndexError, "Channel index out of range"
- end
- @narray[@mask.eq(i)]
+ # The number of channels
+ def channels
+ self.shape[1]
end
- # One of [:byte, :sint, :int, :sfloat, :float].
- def type
- [nil,:byte,:sint,:int,:sfloat,:float][@narray.typecode]
+ # Returns a new Sound with the data from channel i
+ def channel(i)
+ self[true,i]
end
# A frame, i.e. an array containing the samples at position i from each
# channel. For a two-channel sound:
- # sound[i] #=> [0.42, 0.12]
- def [](i)
- unless (0...size).include? i
+ # sound.frame(i) #=> [0.42, 0.12]
+ #
+ # You may prefer to do this the NArray way: s[i,false]
+ def frame(i)
+ unless (0...frames).include? i
raise IndexError, "Index out of range"
end
- j = 2*i
- @narray[j..j+@channels-1]
+ self[i,false]
end
- alias_method :frame, :[]
-
- # For a two-channel sound:
- # sound[i] = [0.2, 0.24]
- # sound[i] = 0.42 #=> sound[i] = [0.42, 0.42]
- def []=(i,val)
- if Numeric === val
- val = Array.new(@channels,val)
- end
- raise ArgumentError, "Value must be Array or Numeric" unless Array === val
- raise ArgumentError, "Expected Array of size #{@channels}" unless val.size == @channels
- j = 2*i
- @narray[j..j+@channels-1] = val
+ # For a two-channel sound:
+ # s.set_frame(i,[0.42,0.24])
+ # s.set_frame(i,0.42) #=> s.set_frame(i,[0.42,0.42])
+ #
+ # You may prefer to do this the NArray way: s[i,false] = val
+ def set_frame(i,val)
+ self[i,false] = val
end
- alias_method :frame=, :[]=
def each_frame
- self.size.times do |i|
- yield self[i]
+ frames.times do |i|
+ yield frame(i)
end
end
- alias_method :each, :each_frame
- include Enumerable
- def resize!(newlen)
- oldlen = size
- if newlen > oldlen
- old = @narray
- @narray = NArray.new(old.typecode,newlen*@channels)
- @narray[0...oldlen*@channels] = old
- else
- @narray = @narray[0...newlen*@channels]
- end
- size
+ # Return a Sound with the channels interleaved.
+ # Sound[[0,1],[2,3]].interleaved #=> Sound[[0,2,1,3]]
+ def interleave
+ self.transpose(1,0).reshape!(self.size,1)
end
+ alias_method :interleaved, :interleave
- def self.byte(len, channels=1)
- self.new(:byte,len,channels)
- end
- def self.sint(len, channels=1)
- self.new(:sint,len,channels)
- end
- def self.int(len, channels=1)
- self.new(:int,len,channels)
+ # Fill this Sound's channels with deinterleaved data.
+ # Sound[[0,0],[0,0]].interleaved = NArray[0,2,1,3] #=> Sound[[0,1],[2,3]]
+ def interleave=(o)
+ self[] = o2.reshape(channels,frames).transpose(1,0)
end
- def self.sfloat(len, channels=1)
- self.new(:sfloat,len,channels)
+ alias_method :interleaved=, :interleave=
+
+
+ # Creates a new Sound with the specified number of channels from the
+ # interleaved data in narray. narray should be evenly divisible by
+ # channels.
+ def self.deinterleave(narray,channels)
+ unless narray.size % channels == 0
+ raise ArgumentError, "narray not evenly divisible by channels"
+ end
+ frames = narray.size/channels
+ s = Sound.new(narray.typecode,frames,channels)
+ s.interleaved = narray
+ s
end
- def self.float(len, channels=1)
- self.new(:float,len,channels)
+
+ %w{byte sint int sfloat float}.each do |t|
+ eval "def self.#{t}(frames,channels=1); super(frames,channels); end"
end
# alias class methods
@@ -134,6 +89,10 @@ class << self
alias_method :long, :int
alias_method :double, :float
end
- end
+ # One of [:byte, :sint, :int, :sfloat, :float].
+ def type
+ [nil,:byte,:sint,:int,:sfloat,:float][typecode]
+ end
+ end
end
View
@@ -3,75 +3,49 @@
class AudioTest < Test::Unit::TestCase
include Audio
- def test_channel
- c = Sound.new(:float,10)
- assert_equal :float, c.type
- assert_equal 10, c.size
-
- c = Sound.char(20)
- assert_equal :byte, c.type
- assert_equal 20, c.size
-
- c = Sound.short(10)
- assert_equal :sint, c.type
- assert_equal 10, c.size
-
- c = Sound.long(10)
- assert_equal :int, c.type
- assert_equal 10, c.size
-
- c = Sound.sfloat(10)
- assert_equal :sfloat, c.type
- assert_equal 10, c.size
+ def test_sound
+ %w{byte sint int sfloat float}.each do |t|
+ eval "s = Sound.#{t}(10); assert_equal :#{t}, s.type"
+ end
+ s = Sound.float(6).indgen!
+ assert_equal [6,1], s.shape
+ assert_equal 1, s.channels
+ assert_equal 6, s.frames
+ s.reshape!(3,2)
+ assert_equal 2, s.channels
+ assert_equal 3, s.frames
+ assert_equal [[0.0,3.0,1.0,4.0,2.0,5.0]], s.interleave.to_a
+ assert_equal 6, s.size
- c = Sound.double(10)
- assert_equal :float, c.type
- assert_equal 10, c.size
- end
+ s = Sound.char(10); assert_equal :byte, s.type
+ s = Sound.short(10); assert_equal :sint, s.type
+ s = Sound.long(10); assert_equal :int, s.type
+ s = Sound.double(10); assert_equal :float, s.type
- def test_sound
s = Sound.sfloat(10)
assert_equal 10, s.size
+ assert_equal 10, s.frames
assert_equal 1, s.channels
assert_equal :sfloat, s.type
+ assert_kind_of NArray, s
s.channels.times do |i|
c = s.channel(i)
- assert_instance_of NArray, c
+ assert_kind_of NArray, c
+ assert_instance_of Sound, c
assert_equal 10, c.size
end
s = Sound.double(10,2)
assert_equal 2, s.channels
assert_equal :float, s.type
- assert_equal 10, s.length
+ assert_equal 10, s.frames
c = s.channel(1)
- assert_instance_of NArray, c
+ assert_instance_of Sound, c
assert_equal 10, c.size
- assert_nothing_raised do
- c[5] = 0.42
- s[4] = 0.24
- s[3] = [0.24, 0.42]
- end
- assert_equal [0.24, 0.42], s[3].to_a
- assert_equal [0.24, 0.24], s[4].to_a
- assert_not_equal [0.0, 0.42], s[5].to_a
-
- s.resize!(20)
- assert_equal 20, s.size
- assert_equal [0.24, 0.42], s[3].to_a
- assert_equal [0.24, 0.24], s[4].to_a
-
- s.resize!(5)
- assert_equal [0.24, 0.24], s[4].to_a
-
- s.resize!(4)
- assert_raise IndexError do
- s[4]
- end
s.each_frame do |a|
- assert_instance_of NArray, a
+ assert_instance_of Sound, a
assert_kind_of Numeric, a[0]
assert_kind_of Numeric, a[1]
end

0 comments on commit e183a4b

Please sign in to comment.