require 'audio'
require 'sndfile.so'
module Audio
# libsndfile[http://www.mega-nerd.com/libsndfile/]
#
# = Synopsis
# require 'audio/sndfile'
#
# Audio::Soundfile.open('chunky_bacon.wav') do |sf|
# sound = sf.readf_float(sf.frames)
# puts "Maximum amplitude: #{sound.abs.max}"
# end
#
# = Details
# Refer to the libsndfile api[http://www.mega-nerd.com/libsndfile/api.html].
#
# Usage is quite straightforward: drop the +sf_+ prefix, omit the
# SNDFILE* parameter, and use Sound or Numeric instead of
# (pointer, size) pairs. So, if you have a Soundfile object named +sf+, then
# sf_read_float(SNDFILE, float *ptr, sf_count_t items)
# becomes
# buf = Sound.float(items)
# sf.read_float(buf)
# or
# buf = sf.read_float(items) # creates a new Sound
#
# Exceptions to this pattern are documented below.
#
# Constants are accessed as Soundfile::SF_FORMAT_WAV
class Soundfile
# SF_INFO
attr :info
attr_reader :mode
# mode:: One of %w{r w rw}
# info:: Instance of SF_INFO or nil
def initialize(path, mode='r', info=nil)
if info.nil?
info = SF_INFO.new
end
modes = {:r => SFM_READ, :w => SFM_WRITE, :rw => SFM_RDWR}
unless Numeric === mode
mode = modes[mode.to_sym]
end
unless [SFM_READ, SFM_WRITE, SFM_RDWR].include? mode
raise ArgumentError, "Invalid mode"
end
sf = Sndfile.sf_open(path.to_s, mode, info)
@sf = sf
@info = info
@mode = modes.invert[mode]
if block_given?
yield self
self.close
end
end
class << self
alias_method :open, :new
end
def frames
@info.frames
end
def samplerate
@info.samplerate
end
def channels
@info.channels
end
def format
@info.format
end
def sections
@info.sections
end
def seekable
@info.seekable
end
# The following are equivalent:
# sf_format_check(info) /* C */
# sf.format_check # ruby
def format_check
Sndfile.sf_format_check(@info)
end
TYPES = [nil,:char,:short,:int,:float,:double] #:nodoc:
# Automagic read method. Type is autodetected.
def read(na)
sym = "read_#{TYPES[na.typecode]}".to_sym
self.send sym, na
end
# Automagic write method. Type is autodetected.
def write(na)
sym = "write_#{TYPES[na.typecode]}".to_sym
self.send sym, na, na.size
end
%w{read readf}.each do |r|
%w{short int float double}.each do |t|
tc = TYPES.index(t.to_sym)
c = ', channels' if r == 'readf'
cmd = "#{r}_#{t}"
eval <<-EOF
def #{cmd}(arg)
if Numeric === arg
na = NArray.new(#{tc}, arg#{c})
n = Sndfile.sf_#{cmd}(@sf, na)
Sound.deinterleave(na, channels)
else
na = arg
n = Sndfile.sf_#{cmd}(@sf, na)
na.deinterleave!(channels)
n
end
end
EOF
end
end
%w{write writef}.each do |w|
%w{short int float double}.each do |t|
cmd = "#{w}_#{t}"
eval <<-EOF
def #{cmd}(sound)
raise "Format check failed: \#{perror}" unless format_check
Sndfile.sf_#{cmd}(@sf, sound.interleave)
end
EOF
end
end
def method_missing(name, *args) #:nodoc:
begin
Sndfile.send "sf_#{name}".to_sym, @sf, *args
rescue NameError
super
end
end
def self.const_missing(sym) #:nodoc:
begin
Sndfile.const_get(sym)
rescue NameError
super
end
end
end
end