├── spec
├── spec.opts
├── data
│ ├── what.mp3
│ ├── what.wav
│ └── what2.wav
├── spec_helper.rb
├── sound_info_spec.rb
├── buffer_spec.rb
└── sound_spec.rb
├── .gitignore
├── lib
├── ruby-audio.rb
└── ruby-audio
│ ├── buffer.rb
│ ├── sound_info.rb
│ └── sound.rb
├── ext
└── rubyaudio_ext
│ ├── ra_soundinfo.h
│ ├── extconf.rb
│ ├── ra_sound.h
│ ├── ra_buffer.h
│ ├── rubyaudio_ext.c
│ ├── ra_soundinfo.c
│ ├── ra_buffer.c
│ └── ra_sound.c
├── ruby-audio.gemspec
├── README.rdoc
├── Rakefile
└── LICENSE
/spec/spec.opts:
--------------------------------------------------------------------------------
1 | --colour
2 | -f nested
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/*
2 | /rdoc/
3 | *.bundle
4 | *.so
5 | *.dll
6 | /tmp
7 | /ports
--------------------------------------------------------------------------------
/spec/data/what.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warhammerkid/ruby-audio/HEAD/spec/data/what.mp3
--------------------------------------------------------------------------------
/spec/data/what.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warhammerkid/ruby-audio/HEAD/spec/data/what.wav
--------------------------------------------------------------------------------
/spec/data/what2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warhammerkid/ruby-audio/HEAD/spec/data/what2.wav
--------------------------------------------------------------------------------
/lib/ruby-audio.rb:
--------------------------------------------------------------------------------
1 | begin
2 | # Fat binaries for Windows
3 | RUBY_VERSION =~ /(\d+.\d+)/
4 | require "#{$1}/rubyaudio_ext"
5 | rescue LoadError
6 | require "rubyaudio_ext"
7 | end
8 | require 'ruby-audio/buffer'
9 | require 'ruby-audio/sound_info'
10 | require 'ruby-audio/sound'
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require 'spec'
3 | rescue LoadError
4 | require 'rubygems'
5 | gem 'rspec'
6 | require 'spec'
7 | end
8 | require 'spec/autorun'
9 |
10 | $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11 | require 'ruby-audio'
12 |
13 | def fixture file_name
14 | File.join(File.dirname(__FILE__), 'data', file_name)
15 | end
16 |
17 | def io_fixture file_name=nil
18 | if file_name
19 | path = fixture(file_name)
20 | StringIO.new(File.read(path))
21 | else
22 | StringIO.new
23 | end
24 | end
--------------------------------------------------------------------------------
/lib/ruby-audio/buffer.rb:
--------------------------------------------------------------------------------
1 | module RubyAudio
2 | # The Buffer class contains sound data read out of the sound. It
3 | # can store a fixed maximum number of multi-channel audio frames of a specifi
4 | # data type. Valid types are short, int,
5 | # float, and double. The channel count must match up
6 | # to the channel count of the sounds being read and written to. Trying to read
7 | # into a buffer with the wrong number of channels will result in an error.
8 | #
9 | # Example:
10 | # buf = RubyAudio::Buffer.float(1000)
11 | # buf = RubyAudio::Buffer.new("float", 1000, 1)
12 | class Buffer < CBuffer
13 | [:short, :int, :float, :double].each do |type|
14 | eval "def self.#{type}(frames, channels=1); self.new(:#{type}, frames, channels); end"
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/ra_soundinfo.h:
--------------------------------------------------------------------------------
1 | #ifndef RA_SOUNDINFO_H
2 | #define RA_SOUNDINFO_H
3 |
4 | #include
5 | #include
6 |
7 | void Init_ra_soundinfo();
8 |
9 | /*** Initialization and Memory Manangement ***/
10 | static VALUE ra_soundinfo_allocate(VALUE klass);
11 | static void ra_soundinfo_free(SF_INFO *info);
12 |
13 | /*** Instance Methods ***/
14 | static VALUE ra_soundinfo_valid(VALUE self);
15 | static VALUE ra_soundinfo_frames(VALUE self);
16 | static VALUE ra_soundinfo_samplerate(VALUE self);
17 | static VALUE ra_soundinfo_samplerate_set(VALUE self, VALUE new_samplerate);
18 | static VALUE ra_soundinfo_channels(VALUE self);
19 | static VALUE ra_soundinfo_channels_set(VALUE self, VALUE new_channels);
20 | static VALUE ra_soundinfo_format(VALUE self);
21 | static VALUE ra_soundinfo_format_set(VALUE self, VALUE new_format);
22 | static VALUE ra_soundinfo_sections(VALUE self);
23 | static VALUE ra_soundinfo_seekable(VALUE self);
24 |
25 | #endif // #ifndef RA_SOUNDINFO_H
--------------------------------------------------------------------------------
/ruby-audio.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | Gem::Specification.new do |s|
4 | s.name = 'ruby-audio'
5 | s.version = '1.6.1'
6 | s.platform = Gem::Platform::RUBY
7 | s.authors = ['Stephen Augenstein']
8 | s.email = ['perl.programmer@gmail.com']
9 | s.homepage = 'http://github.com/warhammerkid/ruby-audio'
10 | s.summary = 'libsndfile wrapper for ruby'
11 | s.description = 'ruby-audio wraps around libsndfile to provide simplified sound reading and writing support to ruby programs'
12 |
13 | s.files = Dir['ruby-audio.gemspec', 'README.rdoc', 'LICENSE', 'Rakefile', 'lib/**/*.rb', 'spec/**/*.{rb,opts,wav,mp3}', 'ext/**/*.{c,h,rb}']
14 | s.test_files = Dir['spec/**/*_spec.rb']
15 | s.extensions = Dir["ext/**/extconf.rb"]
16 |
17 | s.requirements << 'libsndfile (http://www.mega-nerd.com/libsndfile/)'
18 |
19 | s.has_rdoc = true
20 | s.extra_rdoc_files = Dir['README.rdoc', 'ext/**/*.c']
21 | s.rdoc_options = ['--line-numbers', '--main', 'README.rdoc']
22 | end
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/extconf.rb:
--------------------------------------------------------------------------------
1 | require 'mkmf'
2 |
3 | $CFLAGS.gsub!("-arch i386", "")
4 | $LDFLAGS.gsub!("-arch i386", "")
5 |
6 | dir_config('sndfile')
7 |
8 | # Mega-Nerd windows installer installs as libsndfile-1.dll
9 | if RUBY_PLATFORM =~ /(mswin|mingw|cygwin)/
10 | sndfile_lib = 'sndfile-1'
11 | else
12 | sndfile_lib = 'sndfile'
13 | end
14 |
15 | INCLUDE_DIRS = ['/opt/local/include', '/usr/local/include', 'C:/Program Files (x86)/Mega-Nerd/libsndfile/include', 'C:/Program Files/Mega-Nerd/libsndfile/include']
16 | LIB_DIRS = ['/opt/local/lib', '/usr/local/lib', 'C:/Program Files (x86)/Mega-Nerd/libsndfile/bin', 'C:/Program Files/Mega-Nerd/libsndfile/bin']
17 |
18 | # libsndfile requirements
19 | find_header 'sndfile.h', *INCLUDE_DIRS
20 | unless ['sndfile-1', 'sndfile'].any? {|lib| find_library lib, 'sf_open', *LIB_DIRS}
21 | fail <<-EOM
22 | Can't find libsndfile (http://www.mega-nerd.com/libsndfile/)
23 |
24 | Try passing --with-sndfile-dir or --with-sndfile-lib and --with-sndfile-include
25 | options to extconf. If there are spaces in the path on windows, it may not work.
26 | EOM
27 | end
28 |
29 | # Check for format support
30 | have_const('SF_FORMAT_OGG', 'sndfile.h')
31 |
32 | create_makefile 'rubyaudio_ext'
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/ra_sound.h:
--------------------------------------------------------------------------------
1 | #ifndef RA_SOUND_H
2 | #define RA_SOUND_H
3 |
4 | #include
5 | #include
6 | #include "ra_soundinfo.h"
7 | #include "ra_buffer.h"
8 |
9 | typedef struct {
10 | SNDFILE *snd;
11 | VALUE info;
12 | VALUE vio_source;
13 | int mode;
14 | int closed;
15 | } RA_SOUND;
16 |
17 | void Init_ra_sound();
18 |
19 | /*** Initialization and Memory Manangement ***/
20 | static VALUE ra_sound_allocate(VALUE klass);
21 | static void ra_sound_mark(RA_SOUND *snd);
22 | static void ra_sound_free(RA_SOUND *snd);
23 |
24 | /*** Singleton Methods ***/
25 | static VALUE ra_sound_s_open(int argc, VALUE *argv, VALUE klass);
26 |
27 | /*** Instance Methods ***/
28 | static VALUE ra_sound_init(VALUE self, VALUE path, VALUE mode, VALUE info);
29 | static VALUE ra_sound_info(VALUE self);
30 | static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence);
31 | static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames);
32 | static VALUE ra_sound_write(VALUE self, VALUE buf);
33 | static VALUE ra_sound_addbuf(VALUE self, VALUE buf);
34 | static VALUE ra_sound_close(VALUE self);
35 | static VALUE ra_sound_close_safe(VALUE self);
36 | static VALUE ra_sound_closed(VALUE self);
37 |
38 | #endif // #ifndef RA_SOUND_H
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/ra_buffer.h:
--------------------------------------------------------------------------------
1 | #ifndef RA_BUFFER_H
2 | #define RA_BUFFER_H
3 |
4 | #include
5 |
6 | typedef enum {
7 | RA_BUFFER_TYPE_SHORT,
8 | RA_BUFFER_TYPE_INT,
9 | RA_BUFFER_TYPE_FLOAT,
10 | RA_BUFFER_TYPE_DOUBLE
11 | } BUFFER_TYPE;
12 |
13 | typedef struct {
14 | BUFFER_TYPE type;
15 | void *data;
16 | long size;
17 | long real_size;
18 | int channels;
19 | } RA_BUFFER;
20 |
21 | void Init_ra_buffer();
22 |
23 | /*** Initialization and Memory Manangement ***/
24 | static VALUE ra_buffer_allocate(VALUE klass);
25 | static void ra_buffer_free(RA_BUFFER *buf);
26 |
27 | /*** Instance Methods ***/
28 | static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self);
29 | static VALUE ra_buffer_init_copy(VALUE copy, VALUE buf);
30 | static VALUE ra_buffer_channels(VALUE self);
31 | static VALUE ra_buffer_size(VALUE self);
32 | static VALUE ra_buffer_real_size(VALUE self);
33 | static VALUE ra_buffer_real_size_set(VALUE self, VALUE new_real_size);
34 | static VALUE ra_buffer_type(VALUE self);
35 | static VALUE ra_buffer_each(VALUE self);
36 | static VALUE ra_buffer_aref(VALUE self, VALUE index);
37 | static VALUE ra_buffer_index_get(RA_BUFFER *buf, long i);
38 | static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val);
39 | static void ra_buffer_index_set(RA_BUFFER *buf, long i, VALUE val);
40 |
41 | #endif // #ifndef RA_BUFFER_H
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = ruby-audio
2 |
3 | Gemified release of ruby/audio. ruby-audio wraps around
4 | libsndfile[http://www.mega-nerd.com/libsndfile/] to provide simplified sound
5 | reading and writing support to ruby programs. The core is the RubyAudio::Sound
6 | class, which is a subclass of RubyAudio::CSound. RubyAudio::Buffer contains
7 | sound data and RubyAudio::SoundInfo contains information about the format of a
8 | sound file.
9 |
10 | == About This Release
11 |
12 | Since it wasn't being maintained, I've taken it under my wing to get it usable.
13 | Please contact me with any questions or comments.
14 |
15 | == Installation
16 |
17 | gem install ruby-audio --source="http://gemcutter.org"
18 |
19 | === Prerequisites
20 |
21 | - libsndfile[http://www.mega-nerd.com/libsndfile/]
22 |
23 | == License
24 |
25 | Copyright (C) 2010 Stephen Augenstein
26 |
27 | This program is free software; you can redistribute it and/or modify
28 | it under the terms of the GNU General Public License as published by
29 | the Free Software Foundation; either version 2 of the License, or
30 | (at your option) any later version.
31 |
32 | This program is distributed in the hope that it will be useful,
33 | but WITHOUT ANY WARRANTY; without even the implied warranty of
34 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 | GNU General Public License for more details.
36 |
37 | You should have received a copy of the GNU General Public License
38 | along with this program; if not, write to the Free Software
39 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
40 |
--------------------------------------------------------------------------------
/spec/sound_info_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper.rb"
2 |
3 | describe RubyAudio::SoundInfo do
4 | it "should initialize with default properties" do
5 | info = RubyAudio::SoundInfo.new
6 | info.channels.should == 0
7 | info.samplerate.should == 0
8 | info.format.should == 0
9 | end
10 |
11 | it "should allow setting properties on initialize" do
12 | info = RubyAudio::SoundInfo.new(:channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16)
13 | info.channels.should == 1
14 | info.samplerate.should == 48000
15 | info.format.should == RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
16 | end
17 |
18 | it "should allow setting properties after initialize" do
19 | info = RubyAudio::SoundInfo.new
20 | info.channels = 3
21 | info.channels.should == 3
22 | end
23 |
24 | it "should not be valid if properties invalid" do
25 | info = RubyAudio::SoundInfo.new(:channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV)
26 | info.valid?.should == false
27 | info.format = RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
28 | info.valid?.should == true
29 | end
30 |
31 | it "should allow cloning" do
32 | info = RubyAudio::SoundInfo.new(:channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16)
33 | info2 = info.clone
34 | info.object_id.should_not == info2.object_id
35 | info.channels.should == info2.channels
36 | info.samplerate.should == info2.samplerate
37 | info.format.should == info2.format
38 | end
39 |
40 | it "should calculate main and sub format from format" do
41 | info = RubyAudio::SoundInfo.new(:format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16)
42 | info.main_format.should == "FORMAT_WAV"
43 | info.sub_format.should == "FORMAT_PCM_16"
44 |
45 | info = RubyAudio::SoundInfo.new(:format => RubyAudio::FORMAT_WAVEX|RubyAudio::FORMAT_MS_ADPCM)
46 | info.main_format.should == "FORMAT_WAVEX"
47 | info.sub_format.should == "FORMAT_MS_ADPCM"
48 | end
49 |
50 | it "should return the length of an audio file" do
51 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
52 | snd.info.length.should == 26413 / 16000.0
53 | end
54 | end
55 | end
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 | require 'rake/rdoctask'
4 | require 'rake/gempackagetask'
5 | require 'spec/rake/spectask'
6 | require 'rake/extensiontask'
7 | require 'rake/extensioncompiler'
8 | require 'mini_portile'
9 |
10 | desc 'Default: run the specs.'
11 | task :default => :spec
12 |
13 | # I don't want to depend on bundler, so we do it the bundler way without it
14 | gemspec_path = 'ruby-audio.gemspec'
15 | spec = begin
16 | eval(File.read(File.join(File.dirname(__FILE__), gemspec_path)), TOPLEVEL_BINDING, gemspec_path)
17 | rescue LoadError => e
18 | original_line = e.backtrace.find { |line| line.include?(gemspec_path) }
19 | msg = "There was a LoadError while evaluating #{gemspec_path}:\n #{e.message}"
20 | msg << " from\n #{original_line}" if original_line
21 | msg << "\n"
22 | puts msg
23 | exit
24 | end
25 |
26 | $recipes = {}
27 | LIBSNDFILE_VERSION = '1.0.24'
28 | $recipes[:libsndfile] = MiniPortile.new "libsndfile", LIBSNDFILE_VERSION
29 | $recipes[:libsndfile].files << "http://www.mega-nerd.com/libsndfile/files/libsndfile-#{LIBSNDFILE_VERSION}.tar.gz"
30 | $recipes.each { |_, recipe| recipe.host = Rake::ExtensionCompiler.mingw_host }
31 |
32 | Spec::Rake::SpecTask.new do |t|
33 | t.spec_opts = ['--options', 'spec/spec.opts']
34 | end
35 |
36 | desc 'Generate documentation'
37 | Rake::RDocTask.new(:rdoc) do |rdoc|
38 | rdoc.rdoc_dir = 'rdoc'
39 | rdoc.title = spec.name
40 | rdoc.options += spec.rdoc_options
41 | rdoc.rdoc_files.include(*spec.extra_rdoc_files)
42 | rdoc.rdoc_files.include("lib")
43 | end
44 |
45 | Rake::GemPackageTask.new(spec) do |pkg|
46 | pkg.need_zip = false
47 | pkg.need_tar = false
48 | end
49 |
50 | Rake::ExtensionTask.new('rubyaudio_ext', spec) do |ext|
51 | if RUBY_PLATFORM =~ /mswin|mingw/ then
52 | # No cross-compile on win, so compile extension to lib/1.[89]
53 | RUBY_VERSION =~ /(\d+\.\d+)/
54 | ext.lib_dir = "lib/#{$1}"
55 | else
56 | ext.cross_compile = true
57 | ext.cross_platform = 'x86-mingw32'
58 | ext.cross_config_options << "--with-sndfile-dir=#{$recipes[:libsndfile].path}"
59 | ext.cross_compiling do |gem_spec|
60 | gem_spec.post_install_message = "You installed the binary version of this gem!"
61 | end
62 | end
63 | end
64 |
65 | namespace :cross do
66 | task :libsndfile do
67 | recipe = $recipes[:libsndfile]
68 | checkpoint = "ports/.#{recipe.name}-#{recipe.version}-#{recipe.host}.installed"
69 | unless File.exist?(checkpoint)
70 | recipe.cook
71 | touch checkpoint
72 | end
73 | recipe.activate
74 | end
75 | end
76 | task :cross => ["cross:libsndfile"]
77 |
78 | desc "Build gem packages"
79 | task :gems do
80 | sh "rake cross native gem RUBY_CC_VERSION=1.8.7:1.9.2"
81 | end
--------------------------------------------------------------------------------
/spec/buffer_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper.rb"
2 |
3 | describe RubyAudio::Buffer do
4 | it "should initialize properly" do
5 | buf = RubyAudio::Buffer.new('float', 100, 2)
6 | buf.channels.should == 2
7 | buf.size.should == 100
8 | buf.real_size.should == 0
9 | buf.type.should == :float
10 | end
11 |
12 | it "should support pretty typed constructors" do
13 | lambda {
14 | [:short, :int, :float, :double].each do |type|
15 | buf = RubyAudio::Buffer.send(type, 100)
16 | end
17 | }.should_not raise_error
18 | end
19 |
20 | it "should allow [] access on integer single channel buffers" do
21 | buf = RubyAudio::Buffer.int(100, 1)
22 | buf[0] = 1.3
23 | buf[0].should == 1
24 |
25 | buf = RubyAudio::Buffer.short(100, 1)
26 | buf[20] = 614
27 | buf[20].should == 614
28 | end
29 |
30 | it "should allow [] access on floating point single channel buffers" do
31 | buf = RubyAudio::Buffer.double(100, 1)
32 | buf[30] = 1.375
33 | buf[30].should == 1.375
34 |
35 | buf = RubyAudio::Buffer.float(100, 1)
36 | buf[12] = 5
37 | buf[12].should == 5.0
38 | end
39 |
40 | it "should allow [] access on multi-channel buffers" do
41 | buf = RubyAudio::Buffer.double(100, 2)
42 | buf[0] = [0.5, 0.3]
43 | buf[0].should == [0.5, 0.3]
44 | end
45 |
46 | it "should raise exception if channel count of set frame does not match buffer's" do
47 | lambda {
48 | buf = RubyAudio::Buffer.double(100, 2)
49 | buf[0] = [0.4, 0.8, 0.8]
50 | }.should raise_error(RubyAudio::Error, "array length must match channel count")
51 | end
52 |
53 | it "should return nil on out-of-bounds [] access" do
54 | buf = RubyAudio::Buffer.float(100)
55 | buf[101].should == nil
56 | buf[-1].should == nil
57 | end
58 |
59 | it "should truncate invalid real size" do
60 | buf = RubyAudio::Buffer.float(100)
61 | buf.real_size = 101
62 | buf.real_size.should == 100
63 | end
64 |
65 | it "should support cloning/duping" do
66 | buf = RubyAudio::Buffer.int(100)
67 | buf[4] = 100
68 |
69 | buf2 = buf.dup
70 | buf2.size.should == buf.size
71 | buf2[4].should == 100
72 |
73 | buf[4] = 140
74 | buf2[4].should == 100
75 | end
76 |
77 | context ".each" do
78 | before(:each) do
79 | @buf = RubyAudio::Buffer.int(10, 2)
80 | 10.times do |i|
81 | @buf[i] = [i, i]
82 | end
83 | end
84 |
85 | it "should yield the elements in order" do
86 | i = 0
87 | @buf.each do |left, right|
88 | left.should == i
89 | right.should == i
90 | i += 1
91 | end
92 | end
93 |
94 | it "shouldn't do anything on an empty buffer" do
95 | buf = RubyAudio::Buffer.int(50, 2)
96 |
97 | buf.each do
98 | fail "This shouldn't be executed"
99 | end
100 | end
101 |
102 | it "should support usage through returned enumerable" do
103 | enum = @buf.each
104 | enum.any? {|frame| frame[0] == 5}.should == true
105 | end
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/lib/ruby-audio/sound_info.rb:
--------------------------------------------------------------------------------
1 | module RubyAudio
2 | # Class SoundInfo provides information about open sound files'
3 | # format, length, samplerate, channels, and other things.
4 | #
5 | # Example:
6 | # snd = RubyAudio::Sound.open("snd.wav")
7 | # puts snd.info.channels #=> 2
8 | # puts snd.info.samplerate #=> 48000
9 | # snd.close
10 | #
11 | # In addition it can be used to specify the format of new sound files:
12 | #
13 | # info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
14 | # snd = RubyAudio::Sound.open("new.wav", 'w', info)
15 | class SoundInfo < CSoundInfo
16 | # Creates a new SoundInfo object and populates it using the given data
17 | #
18 | # Example:
19 | # info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
20 | def initialize options={}
21 | # Populate from options if given
22 | unless options.empty?
23 | options.each {|key,value| send("#{key}=", value)}
24 | end
25 | end
26 |
27 | # Returns a new SoundInfo object that has the same channel
28 | # count, sample rate, and format. This is useful in creating a new sound with
29 | # the same format as an already existing sound.
30 | #
31 | # Example:
32 | # snd1 = RubyAudio::Sound.open("snd.wav")
33 | # snd2 = RubyAudio::Sound.open("snd2.wav", 'w', snd1.info.clone)
34 | def clone
35 | SoundInfo.new(:channels => channels, :samplerate => samplerate, :format => format)
36 | end
37 |
38 | alias_method :seekable?, :seekable
39 |
40 | # Returns the main format constant as a string
41 | #
42 | # Example:
43 | # info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
44 | # info.main_format
45 | # #=> "FORMAT_WAV"
46 | def main_format
47 | calculate_format if @main_format.nil?
48 | @main_format
49 | end
50 |
51 | # Returns the sub format constant as a string
52 | #
53 | # Example:
54 | # info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
55 | # info.sub_format
56 | # #=> "FORMAT_PCM_16"
57 | def sub_format
58 | calculate_format if @sub_format.nil?
59 | @sub_format
60 | end
61 |
62 | # Returns the length of the audio file in seconds
63 | def length
64 | frames / samplerate.to_f
65 | end
66 |
67 | private
68 | def calculate_format
69 | RubyAudio.constants.grep(/FORMAT_/).map(&:to_s).each do |f|
70 | next if f.include?('MASK') # Skip mask constants
71 |
72 | val = RubyAudio.const_get(f)
73 | if val > RubyAudio::FORMAT_SUBMASK
74 | # Main format
75 | @main_format = f if format & RubyAudio::FORMAT_TYPEMASK == val
76 | else
77 | # Sub format
78 | @sub_format = f if format & RubyAudio::FORMAT_SUBMASK == val
79 | end
80 | end
81 | end
82 | end
83 | end
--------------------------------------------------------------------------------
/lib/ruby-audio/sound.rb:
--------------------------------------------------------------------------------
1 | module RubyAudio
2 | # Class Sound wraps libsndfile to provide simple reading and
3 | # writing for a wide variety of file formats
4 | #
5 | # Reading Example:
6 | # RubyAudio::Sound.open('sound.wav') do |snd|
7 | # buf = snd.read(:float, 100)
8 | # puts buf.real_size #=> 100
9 | # end
10 | #
11 | # Writing Example:
12 | # buf = RubyAudio::Buffer.float(1000)
13 | # out = nil
14 | # ['snd1.wav', 'snd2.wav', 'snd3.wav'].each do |file|
15 | # RubyAudio::Sound.open(file) do |snd|
16 | # out = RubyAudio::Sound.open('out.wav', 'w', snd.info.clone) if out.nil?
17 | #
18 | # while snd.read(buf) != 0
19 | # out.write(buf)
20 | # end
21 | # end
22 | # end
23 | # out.close if out
24 | class Sound < CSound
25 | # Creates a new Sound object for the audio file at the given path.
26 | # Mode defaults to "r", but valid modes are "r",
27 | # "w", and "rw".
28 | #
29 | # When creating a new sound, a valid SoundInfo object must be
30 | # passed in, as libsndfile uses it to determine the output format.
31 | # info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
32 | # snd = RubyAudio::Sound.new "new.wav", 'r', info
33 | def initialize(path, mode='r', info=nil)
34 | info ||= SoundInfo.new
35 | super(path, mode, info)
36 | end
37 |
38 | # Seeks to a given offset anInteger in the sound according to the value
39 | # of whence:
40 | #
41 | # IO::SEEK_CUR | Seeks to _frames_ plus current position
42 | # --------------+----------------------------------------------------
43 | # IO::SEEK_END | Seeks to _frames_ plus end of stream (you probably
44 | # | want a negative value for _frames_)
45 | # --------------+----------------------------------------------------
46 | # IO::SEEK_SET | Seeks to the absolute location given by _frames_
47 | def seek(frames, whence=IO::SEEK_SET)
48 | super(frames, whence)
49 | end
50 |
51 | # Reads a given number of frames from the sound into a buffer
52 | #
53 | # When given a buffer as the first argument, it reads data into that buffer
54 | # reading an optional number of frames as the second argument. It returns
55 | # the number of frames read.
56 | #
57 | # Example:
58 | # buf = RubyAudio::Buffer.float(1000)
59 | # snd.read(buf) #=> 1000
60 | # snd.read(buf, 50) #=> 50
61 | #
62 | # When given a string or symbol as the first argument, it interprets this as
63 | # the data type and creates a new buffer of the given size to read the data
64 | # into. The buffer is correctly initialized with the proper number of channels
65 | # to hold data from that sound.
66 | #
67 | # Example:
68 | # buf = snd.read("int", 1000)
69 | def read(*args)
70 | case args[0]
71 | when Buffer
72 | buf = args[0]
73 | size = args[1] || buf.size
74 | return super(buf, size)
75 | when Symbol, String
76 | type = args[0]
77 | buf = Buffer.new(type, args[1], info.channels)
78 | super(buf, buf.size)
79 | return buf
80 | else
81 | raise ArgumentError, "invalid arguments"
82 | end
83 | end
84 | end
85 | end
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/rubyaudio_ext.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | void Init_ra_buffer();
5 | void Init_ra_soundinfo();
6 | void Init_ra_sound();
7 |
8 | VALUE eRubyAudioError;
9 |
10 | /*
11 | * Document-module: RubyAudio
12 | */
13 | /*
14 | * Document-class: RubyAudio::Error
15 | *
16 | * Error class for RubyAudio
17 | */
18 | void Init_rubyaudio_ext() {
19 | // Create RubyAudio module and other setup
20 | VALUE mRubyAudio = rb_define_module("RubyAudio");
21 | eRubyAudioError = rb_define_class_under(mRubyAudio, "Error", rb_eStandardError);
22 |
23 | // Set up classes
24 | Init_ra_buffer();
25 | Init_ra_soundinfo();
26 | Init_ra_sound();
27 |
28 | // Export libsndfile constants
29 | // Major formats
30 | rb_define_const(mRubyAudio, "FORMAT_WAV", /* 0x010000 */ INT2FIX(SF_FORMAT_WAV));
31 | rb_define_const(mRubyAudio, "FORMAT_AIFF", /* 0x020000 */ INT2FIX(SF_FORMAT_AIFF));
32 | rb_define_const(mRubyAudio, "FORMAT_AU", /* 0x030000 */ INT2FIX(SF_FORMAT_AU));
33 | rb_define_const(mRubyAudio, "FORMAT_RAW", /* 0x040000 */ INT2FIX(SF_FORMAT_RAW));
34 | rb_define_const(mRubyAudio, "FORMAT_PAF", /* 0x050000 */ INT2FIX(SF_FORMAT_PAF));
35 | rb_define_const(mRubyAudio, "FORMAT_SVX", /* 0x060000 */ INT2FIX(SF_FORMAT_SVX));
36 | rb_define_const(mRubyAudio, "FORMAT_NIST", /* 0x070000 */ INT2FIX(SF_FORMAT_NIST));
37 | rb_define_const(mRubyAudio, "FORMAT_VOC", /* 0x080000 */ INT2FIX(SF_FORMAT_VOC));
38 | rb_define_const(mRubyAudio, "FORMAT_IRCAM", /* 0x0A0000 */ INT2FIX(SF_FORMAT_IRCAM));
39 | rb_define_const(mRubyAudio, "FORMAT_W64", /* 0x0B0000 */ INT2FIX(SF_FORMAT_W64));
40 | rb_define_const(mRubyAudio, "FORMAT_MAT4", /* 0x0C0000 */ INT2FIX(SF_FORMAT_MAT4));
41 | rb_define_const(mRubyAudio, "FORMAT_MAT5", /* 0x0D0000 */ INT2FIX(SF_FORMAT_MAT5));
42 | rb_define_const(mRubyAudio, "FORMAT_PVF", /* 0x0E0000 */ INT2FIX(SF_FORMAT_PVF));
43 | rb_define_const(mRubyAudio, "FORMAT_XI", /* 0x0F0000 */ INT2FIX(SF_FORMAT_XI));
44 | rb_define_const(mRubyAudio, "FORMAT_HTK", /* 0x100000 */ INT2FIX(SF_FORMAT_HTK));
45 | rb_define_const(mRubyAudio, "FORMAT_SDS", /* 0x110000 */ INT2FIX(SF_FORMAT_SDS));
46 | rb_define_const(mRubyAudio, "FORMAT_AVR", /* 0x120000 */ INT2FIX(SF_FORMAT_AVR));
47 | rb_define_const(mRubyAudio, "FORMAT_WAVEX", /* 0x130000 */ INT2FIX(SF_FORMAT_WAVEX));
48 | rb_define_const(mRubyAudio, "FORMAT_SD2", /* 0x160000 */ INT2FIX(SF_FORMAT_SD2));
49 | rb_define_const(mRubyAudio, "FORMAT_FLAC", /* 0x170000 */ INT2FIX(SF_FORMAT_FLAC));
50 | rb_define_const(mRubyAudio, "FORMAT_CAF", /* 0x180000 */ INT2FIX(SF_FORMAT_CAF));
51 | #ifdef HAVE_CONST_SF_FORMAT_OGG
52 | rb_define_const(mRubyAudio, "FORMAT_OGG", /* 0x200000 */ INT2FIX(SF_FORMAT_OGG));
53 | #endif
54 |
55 | // Subtypes from here on
56 | rb_define_const(mRubyAudio, "FORMAT_PCM_S8", /* 0x0001 */ INT2FIX(SF_FORMAT_PCM_S8));
57 | rb_define_const(mRubyAudio, "FORMAT_PCM_16", /* 0x0002 */ INT2FIX(SF_FORMAT_PCM_16));
58 | rb_define_const(mRubyAudio, "FORMAT_PCM_24", /* 0x0003 */ INT2FIX(SF_FORMAT_PCM_24));
59 | rb_define_const(mRubyAudio, "FORMAT_PCM_32", /* 0x0004 */ INT2FIX(SF_FORMAT_PCM_32));
60 | rb_define_const(mRubyAudio, "FORMAT_PCM_U8", /* 0x0005 */ INT2FIX(SF_FORMAT_PCM_U8));
61 | rb_define_const(mRubyAudio, "FORMAT_FLOAT", /* 0x0006 */ INT2FIX(SF_FORMAT_FLOAT));
62 | rb_define_const(mRubyAudio, "FORMAT_DOUBLE", /* 0x0007 */ INT2FIX(SF_FORMAT_DOUBLE));
63 | rb_define_const(mRubyAudio, "FORMAT_ULAW", /* 0x0010 */ INT2FIX(SF_FORMAT_ULAW));
64 | rb_define_const(mRubyAudio, "FORMAT_ALAW", /* 0x0011 */ INT2FIX(SF_FORMAT_ALAW));
65 | rb_define_const(mRubyAudio, "FORMAT_IMA_ADPCM", /* 0x0012 */ INT2FIX(SF_FORMAT_IMA_ADPCM));
66 | rb_define_const(mRubyAudio, "FORMAT_MS_ADPCM", /* 0x0013 */ INT2FIX(SF_FORMAT_MS_ADPCM));
67 | rb_define_const(mRubyAudio, "FORMAT_GSM610", /* 0x0020 */ INT2FIX(SF_FORMAT_GSM610));
68 | rb_define_const(mRubyAudio, "FORMAT_VOX_ADPCM", /* 0x0021 */ INT2FIX(SF_FORMAT_VOX_ADPCM));
69 | rb_define_const(mRubyAudio, "FORMAT_G721_32", /* 0x0030 */ INT2FIX(SF_FORMAT_G721_32));
70 | rb_define_const(mRubyAudio, "FORMAT_G723_24", /* 0x0031 */ INT2FIX(SF_FORMAT_G723_24));
71 | rb_define_const(mRubyAudio, "FORMAT_G723_40", /* 0x0032 */ INT2FIX(SF_FORMAT_G723_40));
72 | rb_define_const(mRubyAudio, "FORMAT_DWVW_12", /* 0x0040 */ INT2FIX(SF_FORMAT_DWVW_12));
73 | rb_define_const(mRubyAudio, "FORMAT_DWVW_16", /* 0x0041 */ INT2FIX(SF_FORMAT_DWVW_16));
74 | rb_define_const(mRubyAudio, "FORMAT_DWVW_24", /* 0x0042 */ INT2FIX(SF_FORMAT_DWVW_24));
75 | rb_define_const(mRubyAudio, "FORMAT_DWVW_N", /* 0x0043 */ INT2FIX(SF_FORMAT_DWVW_N));
76 | rb_define_const(mRubyAudio, "FORMAT_DPCM_8", /* 0x0050 */ INT2FIX(SF_FORMAT_DPCM_8));
77 | rb_define_const(mRubyAudio, "FORMAT_DPCM_16", /* 0x0051 */ INT2FIX(SF_FORMAT_DPCM_16));
78 | #ifdef HAVE_CONST_SF_FORMAT_OGG
79 | rb_define_const(mRubyAudio, "FORMAT_VORBIS", /* 0x0060 */ INT2FIX(SF_FORMAT_VORBIS));
80 | #endif
81 |
82 | // Endian-ness options
83 | rb_define_const(mRubyAudio, "ENDIAN_FILE", /* 0x00000000 */ INT2FIX(SF_ENDIAN_FILE));
84 | rb_define_const(mRubyAudio, "ENDIAN_LITTLE", /* 0x10000000 */ INT2FIX(SF_ENDIAN_LITTLE));
85 | rb_define_const(mRubyAudio, "ENDIAN_BIG", /* 0x20000000 */ INT2FIX(SF_ENDIAN_BIG));
86 | rb_define_const(mRubyAudio, "ENDIAN_CPU", /* 0x30000000 */ INT2FIX(SF_ENDIAN_CPU));
87 |
88 | // Format masks
89 | rb_define_const(mRubyAudio, "FORMAT_SUBMASK", /* 0x0000FFFF */ INT2FIX(SF_FORMAT_SUBMASK));
90 | rb_define_const(mRubyAudio, "FORMAT_TYPEMASK", /* 0x0FFF0000 */ INT2FIX(SF_FORMAT_TYPEMASK));
91 | rb_define_const(mRubyAudio, "FORMAT_ENDMASK", /* 0x30000000 */ INT2FIX(SF_FORMAT_ENDMASK));
92 | }
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/ra_soundinfo.c:
--------------------------------------------------------------------------------
1 | #include "ra_soundinfo.h"
2 |
3 | extern VALUE eRubyAudioError;
4 |
5 | /*
6 | * Class CSoundInfo is a very light wrapper around the
7 | * SF_INFO struct exposed by libsndfile. It provides information
8 | * about open sound files like format, length, samplerate, channels, and other
9 | * things. In addition it can be used to specify the format of new sound files.
10 | */
11 | void Init_ra_soundinfo() {
12 | VALUE mRubyAudio = rb_define_module("RubyAudio");
13 | VALUE cRASoundInfo = rb_define_class_under(mRubyAudio, "CSoundInfo", rb_cObject);
14 | rb_define_alloc_func(cRASoundInfo, ra_soundinfo_allocate);
15 | rb_define_method(cRASoundInfo, "valid?", ra_soundinfo_valid, 0);
16 | rb_define_method(cRASoundInfo, "frames", ra_soundinfo_frames, 0);
17 | rb_define_method(cRASoundInfo, "samplerate", ra_soundinfo_samplerate, 0);
18 | rb_define_method(cRASoundInfo, "samplerate=", ra_soundinfo_samplerate_set, 1);
19 | rb_define_method(cRASoundInfo, "channels", ra_soundinfo_channels, 0);
20 | rb_define_method(cRASoundInfo, "channels=", ra_soundinfo_channels_set, 1);
21 | rb_define_method(cRASoundInfo, "format", ra_soundinfo_format, 0);
22 | rb_define_method(cRASoundInfo, "format=", ra_soundinfo_format_set, 1);
23 | rb_define_method(cRASoundInfo, "sections", ra_soundinfo_sections, 0);
24 | rb_define_method(cRASoundInfo, "seekable", ra_soundinfo_seekable, 0);
25 | }
26 |
27 | static VALUE ra_soundinfo_allocate(VALUE klass) {
28 | SF_INFO *info = ALLOC(SF_INFO);
29 | memset(info, 0, sizeof(SF_INFO));
30 | VALUE self = Data_Wrap_Struct(klass, NULL, ra_soundinfo_free, info);
31 | return self;
32 | }
33 |
34 | static void ra_soundinfo_free(SF_INFO *info) {
35 | xfree(info);
36 | }
37 |
38 | /*
39 | * call-seq:
40 | * info.valid? => true or false
41 | *
42 | * Calls sf_format_check on the underlying SF_INFO
43 | * struct and returns true or false based on validity. Used when creating a new
44 | * sound file to check that the format has enough information to properly create
45 | * a new sound.
46 | */
47 | static VALUE ra_soundinfo_valid(VALUE self) {
48 | SF_INFO *info;
49 | Data_Get_Struct(self, SF_INFO, info);
50 | return sf_format_check(info) ? Qtrue : Qfalse;
51 | }
52 |
53 | /*
54 | * call-seq:
55 | * info.frames => integer
56 | *
57 | * Returns the number of frames in the associated sound file.
58 | */
59 | static VALUE ra_soundinfo_frames(VALUE self) {
60 | SF_INFO *info;
61 | Data_Get_Struct(self, SF_INFO, info);
62 | return OFFT2NUM(info->frames);
63 | }
64 |
65 | /*
66 | * call-seq:
67 | * info.samplerate => integer
68 | *
69 | * Returns the samplerate of the associated sound file.
70 | */
71 | static VALUE ra_soundinfo_samplerate(VALUE self) {
72 | SF_INFO *info;
73 | Data_Get_Struct(self, SF_INFO, info);
74 | return INT2FIX(info->samplerate);
75 | }
76 |
77 | /*
78 | * call-seq:
79 | * info.samplerate = integer => integer
80 | *
81 | * Set the samplerate for a new sound created with the given info object.
82 | */
83 | static VALUE ra_soundinfo_samplerate_set(VALUE self, VALUE new_samplerate) {
84 | SF_INFO *info;
85 | Data_Get_Struct(self, SF_INFO, info);
86 | info->samplerate = FIX2INT(new_samplerate);
87 | return new_samplerate;
88 | }
89 |
90 | /*
91 | * call-seq:
92 | * info.channels => integer
93 | *
94 | * Returns the number of channels in the associated sound file.
95 | */
96 | static VALUE ra_soundinfo_channels(VALUE self) {
97 | SF_INFO *info;
98 | Data_Get_Struct(self, SF_INFO, info);
99 | return INT2FIX(info->channels);
100 | }
101 |
102 | /*
103 | * call-seq:
104 | * info.channels = integer => integer
105 | *
106 | * Set the number of channels for a new sound created with the given info object.
107 | */
108 | static VALUE ra_soundinfo_channels_set(VALUE self, VALUE new_channels) {
109 | SF_INFO *info;
110 | Data_Get_Struct(self, SF_INFO, info);
111 | info->channels = FIX2INT(new_channels);
112 | return new_channels;
113 | }
114 |
115 | /*
116 | * call-seq:
117 | * info.format => integer
118 | *
119 | * Returns the format as a combination of binary flags of the associated sound file.
120 | */
121 | static VALUE ra_soundinfo_format(VALUE self) {
122 | SF_INFO *info;
123 | Data_Get_Struct(self, SF_INFO, info);
124 | return INT2FIX(info->format);
125 | }
126 |
127 | /*
128 | * call-seq:
129 | * info.format = integer => integer
130 | *
131 | * Set the format for a new sound created with the given info object.
132 | *
133 | * info = RubyAudio::CSoundInfo.new
134 | * info.format = RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
135 | */
136 | static VALUE ra_soundinfo_format_set(VALUE self, VALUE new_format) {
137 | SF_INFO *info;
138 | Data_Get_Struct(self, SF_INFO, info);
139 | info->format = FIX2INT(new_format);
140 | return new_format;
141 | }
142 |
143 | /*
144 | * call-seq:
145 | * info.sections => integer
146 | *
147 | * Returns the number of sections in the associated sound file.
148 | */
149 | static VALUE ra_soundinfo_sections(VALUE self) {
150 | SF_INFO *info;
151 | Data_Get_Struct(self, SF_INFO, info);
152 | return INT2FIX(info->sections);
153 | }
154 |
155 | /*
156 | * call-seq:
157 | * info.seekable => true or false
158 | *
159 | * Whether seeking is supported for the associated sound file.
160 | */
161 | static VALUE ra_soundinfo_seekable(VALUE self) {
162 | SF_INFO *info;
163 | Data_Get_Struct(self, SF_INFO, info);
164 | return info->seekable ? Qtrue : Qfalse;
165 | }
--------------------------------------------------------------------------------
/spec/sound_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper.rb"
2 |
3 | describe RubyAudio::Sound do
4 | after :each do
5 | File.delete(fixture('temp.wav')) if File.exists?(fixture('temp.wav'))
6 | end
7 |
8 | it "should open a standard wav without issues" do
9 | lambda {
10 | RubyAudio::Sound.open(fixture('what.wav'))
11 | }.should_not raise_error
12 | end
13 |
14 | it "should open an IO conformer without issues" do
15 | lambda {
16 | RubyAudio::Sound.open(io_fixture('what.wav'))
17 | }.should_not raise_error
18 | end
19 |
20 | it "should raise an exception if the mode is invalid" do
21 | lambda {
22 | RubyAudio::Sound.open(fixture('what.wav'), 'q')
23 | }.should raise_error(ArgumentError, 'invalid access mode q')
24 | end
25 |
26 | it "should close the sound on block exit" do
27 | s = nil
28 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
29 | snd.closed?.should be_false
30 | s = snd
31 | end
32 | s.closed?.should be_true
33 | end
34 |
35 | it "should raise an exception for an unsupported file" do
36 | lambda {
37 | RubyAudio::Sound.open(fixture('what.mp3'))
38 | }.should raise_error(RubyAudio::Error, "File contains data in an unknown format.")
39 | end
40 |
41 | it "should raise an exception if file does not exist" do
42 | lambda {
43 | RubyAudio::Sound.open(fixture('what.mp3')+'.not')
44 | }.should raise_error(RubyAudio::Error, "System error : No such file or directory.")
45 | end
46 |
47 | it "should have the proper sound info" do
48 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
49 | snd.info.channels.should == 1
50 | snd.info.samplerate.should == 16000
51 | snd.info.format.should == RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
52 | end
53 | end
54 |
55 | it "should allow seeking" do
56 | lambda {
57 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
58 | snd.seek(100)
59 | buf = snd.read(:float, 100)
60 | buf[0].should > 0
61 | end
62 | }.should_not raise_error
63 | end
64 |
65 | it "should allow seeking in IO conformers" do
66 | lambda {
67 | RubyAudio::Sound.open(io_fixture('what.wav')) do |snd|
68 | snd.seek(100)
69 | buf = snd.read(:float, 100)
70 | buf[0].should > 0
71 | end
72 | }.should_not raise_error
73 | end
74 |
75 | it "should raise exceptions for invalid seeks" do
76 | lambda {
77 | RubyAudio::Sound.open(fixture('what.wav')) {|snd| snd.seek(-1)}
78 | }.should raise_error(RubyAudio::Error, "invalid seek")
79 | lambda {
80 | RubyAudio::Sound.open(fixture('what.wav')) {|snd| snd.seek(1000000)}
81 | }.should raise_error(RubyAudio::Error, "invalid seek")
82 | end
83 |
84 | it "should allow reading samples from the sound" do
85 | RubyAudio::Sound.open(fixture('what2.wav')) do |snd|
86 | buf = snd.read(:float, 1000)
87 | buf.size.should == 1000
88 | buf.real_size.should == 1000
89 | buf[999].length.should == 2
90 | end
91 | end
92 |
93 | it "should allow reading samples from IO conformers" do
94 | RubyAudio::Sound.open(io_fixture('what2.wav')) do |snd|
95 | buf = snd.read(:float, 1000)
96 | buf.size.should == 1000
97 | buf.real_size.should == 1000
98 | buf[999].length.should == 2
99 | end
100 | end
101 |
102 | it "should allow reading into an existing buffer" do
103 | buf = RubyAudio::Buffer.float(1000)
104 | buf.real_size.should == 0
105 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
106 | snd.read(buf)
107 | end
108 | buf.real_size.should == 1000
109 | buf[99].should > 0
110 | end
111 |
112 | it "should allow reading into an existing buffer partially" do
113 | buf = RubyAudio::Buffer.float(1000)
114 | buf.real_size.should == 0
115 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
116 | snd.read(buf, 100)
117 | end
118 | buf.real_size.should == 100
119 | buf[99].should > 0
120 | buf[100].should == nil
121 | end
122 |
123 | it "should allow downmixing to mono on read" do
124 | buf = RubyAudio::Buffer.int(100, 1)
125 | buf2 = RubyAudio::Buffer.int(100, 2)
126 | RubyAudio::Sound.open(fixture('what2.wav'), 'r') do |snd|
127 | snd.read(buf)
128 | snd.seek(0)
129 | snd.read(buf2)
130 |
131 | f = buf2[99]
132 | buf[99].should == (f[0] + f[1]) / 2
133 | end
134 | end
135 |
136 | it "should allow upmixing from mono on read" do
137 | buf = RubyAudio::Buffer.int(100, 1)
138 | buf2 = RubyAudio::Buffer.int(100, 2)
139 | RubyAudio::Sound.open(fixture('what2.wav'), 'r') do |snd|
140 | snd.read(buf2)
141 | snd.seek(0)
142 | snd.read(buf)
143 |
144 | buf2[99].should == [buf[99], buf[99]]
145 | end
146 | end
147 |
148 | it "should fail read on unsupported up/downmixing" do
149 | buf = RubyAudio::Buffer.float(100, 5)
150 | lambda {
151 | RubyAudio::Sound.open(fixture('what2.wav')) do |snd|
152 | snd.read(buf)
153 | end
154 | }.should raise_error(RubyAudio::Error, "unsupported mix from 5 to 2")
155 | end
156 |
157 | it "should allow writing to a new sound" do
158 | in_buf = RubyAudio::Buffer.float(100)
159 | out_buf = RubyAudio::Buffer.float(100)
160 | out_info = nil
161 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
162 | snd.read(in_buf)
163 | out_info = snd.info.clone
164 | end
165 |
166 | RubyAudio::Sound.open(fixture('temp.wav'), 'rw', out_info) do |snd|
167 | snd.write(in_buf)
168 | snd.seek(0)
169 | snd.read(out_buf)
170 | end
171 |
172 | out_buf[50].should == in_buf[50]
173 | end
174 |
175 | it "should allow writing to an IO conformer" do
176 | in_buf = RubyAudio::Buffer.float(100)
177 | out_buf = RubyAudio::Buffer.float(100)
178 | out_info = nil
179 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
180 | snd.read(in_buf)
181 | out_info = snd.info.clone
182 | end
183 |
184 | RubyAudio::Sound.open(io_fixture, 'rw', out_info) do |snd|
185 | snd.write(in_buf)
186 | snd.seek(0)
187 | snd.read(out_buf)
188 | end
189 |
190 | out_buf[50].should == in_buf[50]
191 | end
192 |
193 | it "should allow writing to a new sound using <<" do
194 | in_buf = RubyAudio::Buffer.float(100)
195 | out_buf = RubyAudio::Buffer.float(100)
196 | out_info = nil
197 | RubyAudio::Sound.open(fixture('what.wav')) do |snd|
198 | snd.read(in_buf)
199 | out_info = snd.info.clone
200 | end
201 |
202 | RubyAudio::Sound.open(fixture('temp.wav'), 'rw', out_info) do |snd|
203 | snd << in_buf
204 | snd.seek(0)
205 | snd.read(out_buf)
206 | end
207 |
208 | out_buf[50].should == in_buf[50]
209 | end
210 |
211 | it "should fail write on channel mismatch" do
212 | buf = RubyAudio::Buffer.float(100, 5)
213 | info = RubyAudio::SoundInfo.new :channels => 2, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
214 | lambda {
215 | RubyAudio::Sound.open(fixture('temp.wav'), 'w', info) do |snd|
216 | snd.write(buf)
217 | end
218 | }.should raise_error(RubyAudio::Error, "channel count mismatch: 5 vs 2")
219 | end
220 | end
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/ra_buffer.c:
--------------------------------------------------------------------------------
1 | #include "ra_buffer.h"
2 |
3 | ID ra_short_sym, ra_int_sym, ra_float_sym, ra_double_sym;
4 | extern VALUE eRubyAudioError;
5 |
6 | // Before RFLOAT_VALUE, value was in a different place in the struct
7 | #ifndef RFLOAT_VALUE
8 | #define RFLOAT_VALUE(v) (RFLOAT(v)->value)
9 | #endif
10 |
11 | /*
12 | * Class CBuffer is a very light wrapper around a standard C array
13 | * that can be read from and written to by libsndfile.
14 | */
15 | void Init_ra_buffer() {
16 | VALUE mRubyAudio = rb_define_module("RubyAudio");
17 | VALUE cRABuffer = rb_define_class_under(mRubyAudio, "CBuffer", rb_cObject);
18 | rb_include_module(cRABuffer, rb_mEnumerable);
19 | rb_define_alloc_func(cRABuffer, ra_buffer_allocate);
20 | rb_define_method(cRABuffer, "initialize", ra_buffer_init, -1);
21 | rb_define_method(cRABuffer, "initialize_copy", ra_buffer_init_copy, 1);
22 | rb_define_method(cRABuffer, "channels", ra_buffer_channels, 0);
23 | rb_define_method(cRABuffer, "size", ra_buffer_size, 0);
24 | rb_define_method(cRABuffer, "real_size", ra_buffer_real_size, 0);
25 | rb_define_method(cRABuffer, "real_size=", ra_buffer_real_size_set, 1);
26 | rb_define_method(cRABuffer, "type", ra_buffer_type, 0);
27 | rb_define_method(cRABuffer, "each", ra_buffer_each, 0);
28 | rb_define_method(cRABuffer, "[]", ra_buffer_aref, 1);
29 | rb_define_method(cRABuffer, "[]=", ra_buffer_aset, 2);
30 |
31 | ra_short_sym = rb_intern("short");
32 | ra_int_sym = rb_intern("int");
33 | ra_float_sym = rb_intern("float");
34 | ra_double_sym = rb_intern("double");
35 | }
36 |
37 | static VALUE ra_buffer_allocate(VALUE klass) {
38 | RA_BUFFER *buf = ALLOC(RA_BUFFER);
39 | memset(buf, 0, sizeof(RA_BUFFER));
40 | VALUE self = Data_Wrap_Struct(klass, NULL, ra_buffer_free, buf);
41 | return self;
42 | }
43 |
44 | static void ra_buffer_free(RA_BUFFER *buf) {
45 | if(buf->data != NULL) xfree(buf->data);
46 | xfree(buf);
47 | }
48 |
49 | /*
50 | * Uses size, channels, and type to allocate a properly sized array and set data
51 | * to the pointer for that data. Returns size.
52 | */
53 | static long ra_buffer_alloc_data(RA_BUFFER *buf) {
54 | long size = 0;
55 | switch(buf->type) {
56 | case RA_BUFFER_TYPE_SHORT:
57 | size = sizeof(short) * buf->size * buf->channels;
58 | break;
59 | case RA_BUFFER_TYPE_INT:
60 | size = sizeof(int) * buf->size * buf->channels;
61 | break;
62 | case RA_BUFFER_TYPE_FLOAT:
63 | size = sizeof(float) * buf->size * buf->channels;
64 | break;
65 | case RA_BUFFER_TYPE_DOUBLE:
66 | size = sizeof(double) * buf->size * buf->channels;
67 | break;
68 | }
69 | buf->data = (void*)xmalloc(size);
70 | memset(buf->data, 0, size);
71 | return size;
72 | }
73 |
74 | /*
75 | * call-seq:
76 | * RubyAudio::CBuffer.new(type, size, channels=1) => buf
77 | *
78 | * Returns a new CBuffer object which can contain the given number
79 | * of audio frames of the given data type.
80 | *
81 | * buf = RubyAudio::CBuffer.new("float", 1000)
82 | */
83 | static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self) {
84 | RA_BUFFER *buf;
85 | Data_Get_Struct(self, RA_BUFFER, buf);
86 |
87 | // Check args
88 | if(argc < 2) rb_raise(rb_eArgError, "At least 2 arguments required");
89 |
90 | // Get type of object
91 | const char *buf_type;
92 | switch(TYPE(argv[0])) {
93 | case T_SYMBOL:
94 | buf_type = rb_id2name(SYM2ID(argv[0]));
95 | if(!buf_type) rb_raise(rb_eArgError, "bad type");
96 | break;
97 | case T_STRING:
98 | buf_type = RSTRING_PTR(argv[0]);
99 | break;
100 | default:
101 | rb_raise(rb_eArgError, "bad type");
102 | break;
103 | }
104 |
105 | // Populate channels
106 | buf->channels = (argc == 3) ? FIX2INT(argv[2]) : 1;
107 |
108 | // Allocate data array based on type
109 | buf->size = FIX2LONG(argv[1]);
110 | buf->real_size = 0;
111 | if(strcmp(buf_type, "short") == 0) buf->type = RA_BUFFER_TYPE_SHORT;
112 | else if(strcmp(buf_type, "int") == 0) buf->type = RA_BUFFER_TYPE_INT;
113 | else if(strcmp(buf_type, "float") == 0) buf->type = RA_BUFFER_TYPE_FLOAT;
114 | else if(strcmp(buf_type, "double") == 0) buf->type = RA_BUFFER_TYPE_DOUBLE;
115 | else rb_raise(rb_eArgError, "Invalid type: %s", buf_type);
116 | ra_buffer_alloc_data(buf);
117 |
118 | // Return self
119 | return self;
120 | }
121 |
122 | /* :nodoc: */
123 | static VALUE ra_buffer_init_copy(VALUE copy, VALUE buf) {
124 | if (copy == buf) return copy;
125 |
126 | // Checks
127 | rb_check_frozen(copy);
128 | if (!rb_obj_is_instance_of(buf, rb_obj_class(copy))) {
129 | rb_raise(rb_eTypeError, "wrong argument class");
130 | }
131 |
132 | RA_BUFFER *copy_struct, *buf_struct;
133 | Data_Get_Struct(copy, RA_BUFFER, copy_struct);
134 | Data_Get_Struct(buf, RA_BUFFER, buf_struct);
135 |
136 | // Clone data
137 | memcpy(copy_struct, buf_struct, sizeof(RA_BUFFER));
138 | long size = ra_buffer_alloc_data(copy_struct);
139 | memcpy(copy_struct->data, buf_struct->data, size);
140 |
141 | return copy;
142 | }
143 |
144 | /*
145 | * call-seq:
146 | * buf.channels => integer
147 | *
148 | * Returns the number of channels in a frame of the buffer.
149 | */
150 | static VALUE ra_buffer_channels(VALUE self) {
151 | RA_BUFFER *buf;
152 | Data_Get_Struct(self, RA_BUFFER, buf);
153 | return INT2FIX(buf->channels);
154 | }
155 |
156 | /*
157 | * call-seq:
158 | * buf.size => integer
159 | *
160 | * Returns the number of frames the buffer can store.
161 | */
162 | static VALUE ra_buffer_size(VALUE self) {
163 | RA_BUFFER *buf;
164 | Data_Get_Struct(self, RA_BUFFER, buf);
165 | return LONG2FIX(buf->size);
166 | }
167 |
168 | /*
169 | * call-seq:
170 | * buf.real_size => integer
171 | *
172 | * Returns the number of frames of actual data are currently stored in the
173 | * buffer.
174 | */
175 | static VALUE ra_buffer_real_size(VALUE self) {
176 | RA_BUFFER *buf;
177 | Data_Get_Struct(self, RA_BUFFER, buf);
178 | return LONG2FIX(buf->real_size);
179 | }
180 |
181 | /*:nodoc:*/
182 | static VALUE ra_buffer_real_size_set(VALUE self, VALUE real_size) {
183 | RA_BUFFER *buf;
184 | Data_Get_Struct(self, RA_BUFFER, buf);
185 |
186 | long new_real_size = FIX2LONG(real_size);
187 | if(new_real_size > buf->size) {
188 | buf->real_size = buf->size;
189 | } else if(new_real_size < 0) {
190 | buf->real_size = 0;
191 | } else {
192 | buf->real_size = new_real_size;
193 | }
194 |
195 | return LONG2FIX(buf->real_size);
196 | }
197 |
198 | /*
199 | * call-seq:
200 | * buf.type => symbol
201 | *
202 | * Returns the type of audio data being stored. :short,
203 | * :int, :float, or :double.
204 | */
205 | static VALUE ra_buffer_type(VALUE self) {
206 | RA_BUFFER *buf;
207 | Data_Get_Struct(self, RA_BUFFER, buf);
208 | switch(buf->type) {
209 | case RA_BUFFER_TYPE_SHORT: return ID2SYM(ra_short_sym);
210 | case RA_BUFFER_TYPE_INT: return ID2SYM(ra_int_sym);
211 | case RA_BUFFER_TYPE_FLOAT: return ID2SYM(ra_float_sym);
212 | case RA_BUFFER_TYPE_DOUBLE: return ID2SYM(ra_double_sym);
213 | }
214 | }
215 |
216 | /*
217 | * call-seq:
218 | * buf.each {|frame| block } => buf
219 | * buf.each => anEnumerator
220 | *
221 | * Iterates through each frame in the buffer. Each frame is either a number or
222 | * an array of numbers if there are multiple channels.
223 | */
224 | static VALUE ra_buffer_each(VALUE self) {
225 | RA_BUFFER *buf;
226 | Data_Get_Struct(self, RA_BUFFER, buf);
227 |
228 | RETURN_ENUMERATOR(self, 0, 0);
229 |
230 | long i;
231 | for(i = 0; i < buf->real_size; i++) {
232 | rb_yield(ra_buffer_aref(self, LONG2FIX(i)));
233 | }
234 | return self;
235 | }
236 |
237 | /*
238 | * call-seq:
239 | * buf[integer] => frame
240 | *
241 | * Returns a frame of audio data at the given offset.
242 | *
243 | * buf = snd.read(:float, 100) # Mono sound
244 | * buf[5] #=> 0.4
245 | *
246 | * buf2 = snd2.read(:float, 100) # Stereo sound
247 | * buf[5] #=> [0.4, 0.3]
248 | */
249 | static VALUE ra_buffer_aref(VALUE self, VALUE index) {
250 | RA_BUFFER *buf;
251 | Data_Get_Struct(self, RA_BUFFER, buf);
252 |
253 | // Bounds check
254 | long f = FIX2LONG(index);
255 | if(f < 0 || f >= buf->real_size) return Qnil;
256 | long i = f * buf->channels;
257 |
258 | if(buf->channels == 1) {
259 | return ra_buffer_index_get(buf, i);
260 | } else {
261 | VALUE frame = rb_ary_new();
262 | long j;
263 | for(j = 0; j < buf->channels; j++) {
264 | rb_ary_push(frame, ra_buffer_index_get(buf, i+j));
265 | }
266 | return frame;
267 | }
268 | }
269 |
270 | static VALUE ra_buffer_index_get(RA_BUFFER *buf, long i) {
271 | switch(buf->type) {
272 | case RA_BUFFER_TYPE_SHORT: return INT2FIX((int)((short*)buf->data)[i]);
273 | case RA_BUFFER_TYPE_INT: return INT2FIX(((int*)buf->data)[i]);
274 | case RA_BUFFER_TYPE_FLOAT: return rb_float_new((double)((float*)buf->data)[i]);
275 | case RA_BUFFER_TYPE_DOUBLE: return rb_float_new(((double*)buf->data)[i]);
276 | }
277 | }
278 |
279 | /*
280 | * call-seq:
281 | * buf[integer] = numeric => numeric
282 | * buf[integer] = array => array
283 | *
284 | * Sets the frame of audio data at the given offset to the value. For
285 | * multi-channel audio, pass in an array of values.
286 | *
287 | * buf = RubyAudio::Buffer.int(100, 1)
288 | * buf[0] = 5
289 | *
290 | * buf = RubyAudio::Buffer.double(100, 2)
291 | * buf[0] = [0.5, 0.3]
292 | */
293 | static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val) {
294 | RA_BUFFER *buf;
295 | Data_Get_Struct(self, RA_BUFFER, buf);
296 |
297 | // Bounds check
298 | long f = FIX2LONG(index);
299 | if(f < 0 || f >= buf->size) rb_raise(eRubyAudioError, "setting frame out of bounds");
300 | long i = f * buf->channels;
301 |
302 | // Set data
303 | if(buf->channels == 1) {
304 | ra_buffer_index_set(buf, i, val);
305 | } else {
306 | if(TYPE(val) != T_ARRAY) rb_raise(eRubyAudioError, "must pass in array for multi-channel buffer");
307 | long length = RARRAY_LEN(val);
308 | if(length != buf->channels) rb_raise(eRubyAudioError, "array length must match channel count");
309 |
310 | long j;
311 | for(j = 0; j < length; j++) {
312 | ra_buffer_index_set(buf, i+j, rb_ary_entry(val, j));
313 | }
314 | }
315 |
316 | // Bump real_size
317 | if(f + 1 > buf->real_size) {
318 | buf->real_size = f + 1;
319 | }
320 |
321 | return val;
322 | }
323 |
324 | static void ra_buffer_index_set(RA_BUFFER *buf, long i, VALUE val) {
325 | if(buf->type == RA_BUFFER_TYPE_SHORT || buf->type == RA_BUFFER_TYPE_INT) {
326 | // Convert val to an integer
327 | VALUE int_obj = rb_Integer(val);
328 | if(TYPE(int_obj) != T_FIXNUM) rb_raise(eRubyAudioError, "could not convert frame value to an integer");
329 | long int_val = FIX2LONG(int_obj);
330 |
331 | // Set it
332 | if(buf->type == RA_BUFFER_TYPE_SHORT) ((short*)buf->data)[i] = (short)int_val;
333 | else ((int*)buf->data)[i] = (int)int_val;
334 | } else {
335 | // Convert val to a float
336 | double float_val = RFLOAT_VALUE(rb_Float(val));
337 |
338 | // Set it
339 | if(buf->type == RA_BUFFER_TYPE_FLOAT) ((float*)buf->data)[i] = (float)float_val;
340 | else ((double*)buf->data)[i] = float_val;
341 | }
342 | }
--------------------------------------------------------------------------------
/ext/rubyaudio_ext/ra_sound.c:
--------------------------------------------------------------------------------
1 | #include "ra_sound.h"
2 |
3 | extern VALUE eRubyAudioError;
4 | ID id_size;
5 | ID id_seek;
6 | ID id_read;
7 | ID id_write;
8 | ID id_tell;
9 |
10 | /*
11 | * Class CSound is a very light wrapper around the
12 | * SNDFILE struct exposed by libsndfile.
13 | */
14 | void Init_ra_sound() {
15 | VALUE mRubyAudio = rb_define_module("RubyAudio");
16 | VALUE cRASound = rb_define_class_under(mRubyAudio, "CSound", rb_cObject);
17 | rb_define_alloc_func(cRASound, ra_sound_allocate);
18 | rb_define_singleton_method(cRASound, "open", ra_sound_s_open, -1);
19 | rb_define_method(cRASound, "initialize", ra_sound_init, 3);
20 | rb_define_method(cRASound, "info", ra_sound_info, 0);
21 | rb_define_method(cRASound, "seek", ra_sound_seek, 2);
22 | rb_define_method(cRASound, "read", ra_sound_read, 2);
23 | rb_define_method(cRASound, "write", ra_sound_write, 1);
24 | rb_define_method(cRASound, "<<", ra_sound_addbuf, 1);
25 | rb_define_method(cRASound, "close", ra_sound_close, 0);
26 | rb_define_method(cRASound, "closed?", ra_sound_closed, 0);
27 |
28 | // Get refs to commonly used symbols and ids
29 | id_size = rb_intern("size");
30 | id_seek = rb_intern("seek");
31 | id_read = rb_intern("read");
32 | id_write = rb_intern("write");
33 | id_tell = rb_intern("tell");
34 | }
35 |
36 | static VALUE ra_sound_allocate(VALUE klass) {
37 | RA_SOUND *snd = ALLOC(RA_SOUND);
38 | memset(snd, 0, sizeof(RA_SOUND));
39 | VALUE self = Data_Wrap_Struct(klass, ra_sound_mark, ra_sound_free, snd);
40 | return self;
41 | }
42 |
43 | static void ra_sound_mark(RA_SOUND *snd) {
44 | if(snd) {
45 | rb_gc_mark(snd->info);
46 | if(snd->vio_source) rb_gc_mark(snd->vio_source);
47 | }
48 | }
49 |
50 | static void ra_sound_free(RA_SOUND *snd) {
51 | if(!snd->closed && snd->snd != NULL) sf_close(snd->snd);
52 | xfree(snd);
53 | }
54 |
55 | /*
56 | * call-seq:
57 | * CSound.open(...) => snd
58 | * CSound.open(...) {|snd| block } => obj
59 | *
60 | * With no associated block, open is a synonym for
61 | * CSound.new. If the optional code block is given, it will be
62 | * passed snd as an argument, and the CSound object will automatically be
63 | * closed when the block terminates. In this instance, CSound.open
64 | * returns the value of the block.
65 | */
66 | static VALUE ra_sound_s_open(int argc, VALUE *argv, VALUE klass) {
67 | VALUE obj = rb_class_new_instance(argc, argv, klass);
68 | if(!rb_block_given_p()) return obj;
69 | return rb_ensure(rb_yield, obj, ra_sound_close_safe, obj);
70 | }
71 |
72 | static sf_count_t ra_vir_size(void *user_data) {
73 | VALUE io = (VALUE)user_data;
74 | return NUM2OFFT(rb_funcall(io, id_size, 0));
75 | }
76 |
77 | static sf_count_t ra_vir_seek(sf_count_t offset, int whence, void *user_data) {
78 | VALUE io = (VALUE)user_data;
79 | rb_funcall(io, id_seek, 2, OFFT2NUM(offset), INT2FIX(whence));
80 | return NUM2OFFT(rb_funcall(io, id_tell, 0));
81 | }
82 |
83 | static sf_count_t ra_vir_read(void *ptr, sf_count_t count, void *user_data) {
84 | VALUE io = (VALUE)user_data;
85 | if(count <= 0) return 0;
86 |
87 | // It would be nice if we could create a fake buffer string with ptr as the target
88 | VALUE read = rb_funcall(io, id_read, 1, OFFT2NUM(count));
89 | sf_count_t len = RSTRING_LEN(read);
90 | memcpy(ptr, RSTRING_PTR(read), RSTRING_LEN(read));
91 | return len;
92 | }
93 |
94 | static sf_count_t ra_vir_write(const void *ptr, sf_count_t count, void *user_data) {
95 | VALUE io = (VALUE)user_data;
96 | if(count <= 0) return 0;
97 |
98 | // It would be nice if we could create a fake string with ptr as the source
99 | VALUE str = rb_str_new(ptr, count);
100 | VALUE wrote = rb_funcall(io, id_write, 1, str);
101 | return NUM2OFFT(wrote);
102 | }
103 |
104 | static sf_count_t ra_vir_tell(void *user_data) {
105 | VALUE io = (VALUE)user_data;
106 | return NUM2OFFT(rb_funcall(io, id_tell, 0));
107 | }
108 |
109 | /*
110 | * call-seq:
111 | * CSound.new(path, mode, info) => snd
112 | * CSound.new(io, mode, info) => snd
113 | *
114 | * Returns a new CSound object for the audio file at the given path
115 | * or using the given fixed-length IO-like object with the given mode. Valid modes
116 | * are "r", "w", or "rw".
117 | * StringIO is the only valid IO-like object in the standard library,
118 | * although any object that implements size, seek,
119 | * read, write, and tell will work.
120 | */
121 | static VALUE ra_sound_init(VALUE self, VALUE source, VALUE mode, VALUE info) {
122 | RA_SOUND *snd;
123 | Data_Get_Struct(self, RA_SOUND, snd);
124 |
125 | // Get mode
126 | const char *m = StringValueCStr(mode);
127 | if(strcmp(m, "rw") == 0) snd->mode = SFM_RDWR;
128 | else if(strcmp(m, "r") == 0) snd->mode = SFM_READ;
129 | else if(strcmp(m, "w") == 0) snd->mode = SFM_WRITE;
130 | else rb_raise(rb_eArgError, "invalid access mode %s", m);
131 |
132 | // Set info
133 | snd->info = info;
134 |
135 | // Open sound file
136 | SF_INFO *sf_info;
137 | Data_Get_Struct(info, SF_INFO, sf_info);
138 | if(TYPE(source) == T_STRING) {
139 | // Open sound file at the path
140 | const char *p = StringValueCStr(source);
141 | snd->snd = sf_open(p, snd->mode, sf_info);
142 | } else {
143 | // Check if the source implements the right methods
144 | if(!rb_respond_to(source, id_size)) rb_raise(eRubyAudioError, "source does not implement size");
145 | if(!rb_respond_to(source, id_seek)) rb_raise(eRubyAudioError, "source does not implement seek");
146 | if(!rb_respond_to(source, id_read)) rb_raise(eRubyAudioError, "source does not implement read");
147 | if(!rb_respond_to(source, id_write)) rb_raise(eRubyAudioError, "source does not implement write");
148 | if(!rb_respond_to(source, id_tell)) rb_raise(eRubyAudioError, "source does not implement tell");
149 |
150 | // Open sound using the virtual IO API
151 | snd->vio_source = source;
152 | SF_VIRTUAL_IO vir_io = {ra_vir_size, ra_vir_seek, ra_vir_read, ra_vir_write, ra_vir_tell};
153 | snd->snd = sf_open_virtual(&vir_io, snd->mode, sf_info, (void*)source);
154 | }
155 | if(snd->snd == NULL) rb_raise(eRubyAudioError, sf_strerror(snd->snd));
156 | snd->closed = 0;
157 |
158 | return self;
159 | }
160 |
161 | /*
162 | * call-seq:
163 | * snd.info => CSoundInfo
164 | *
165 | * Returns the info object associated with the sound.
166 | */
167 | static VALUE ra_sound_info(VALUE self) {
168 | RA_SOUND *snd;
169 | Data_Get_Struct(self, RA_SOUND, snd);
170 | return snd->info;
171 | }
172 |
173 | /*
174 | * call-seq:
175 | * snd.seek(frames, whence) => 0
176 | *
177 | * Seeks to a given offset anInteger in the sound according to the value
178 | * of whence:
179 | *
180 | * IO::SEEK_CUR | Seeks to _frames_ plus current position
181 | * --------------+----------------------------------------------------
182 | * IO::SEEK_END | Seeks to _frames_ plus end of stream (you probably
183 | * | want a negative value for _frames_)
184 | * --------------+----------------------------------------------------
185 | * IO::SEEK_SET | Seeks to the absolute location given by _frames_
186 | */
187 | static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence) {
188 | RA_SOUND *snd;
189 | Data_Get_Struct(self, RA_SOUND, snd);
190 | if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
191 |
192 | if(sf_seek(snd->snd, (sf_count_t)NUM2OFFT(frames), FIX2INT(whence)) == -1) {
193 | rb_raise(eRubyAudioError, "invalid seek");
194 | }
195 |
196 | return INT2FIX(0);
197 | }
198 |
199 | #define DEFINE_RA_SOUND_READ_TYPE(itype) \
200 | static void ra_sound_read_##itype(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) { \
201 | static itype temp[1024]; \
202 | int temp_len = 1024; \
203 | itype *data = (itype*)buf->data; \
204 | itype mix_sum; \
205 | \
206 | /* Get info struct */ \
207 | SF_INFO *info; \
208 | Data_Get_Struct(snd->info, SF_INFO, info); \
209 | \
210 | /* Up/Downmix based on channel matching */ \
211 | sf_count_t read = 0, r, amount; \
212 | int i, k; \
213 | if(buf->channels == info->channels) { /* Simply read data without mix */ \
214 | read = sf_readf_##itype(snd->snd, data, frames); \
215 | } else if(buf->channels == 1) { /* Downmix to mono */ \
216 | sf_count_t max = temp_len / info->channels; \
217 | int channels; \
218 | \
219 | while(read < frames) { \
220 | /* Calculate # of frames to read */ \
221 | amount = frames - read; \
222 | if(amount > max) amount = max; \
223 | \
224 | r = sf_readf_##itype(snd->snd, temp, amount); \
225 | if(r == 0) break; \
226 | \
227 | /* Mix channels together by averaging all channels and store to buffer */ \
228 | for(i = 0; i < r; i++) { \
229 | mix_sum = 0; \
230 | for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k]; \
231 | data[read] = mix_sum/info->channels; \
232 | read++; \
233 | } \
234 | } \
235 | } else if(info->channels == 1) { /* Upmix from mono by copying channel */ \
236 | while(read < frames) { \
237 | /* Calculate # of frames to read */ \
238 | amount = frames - read; \
239 | if(amount > temp_len) amount = temp_len; \
240 | \
241 | r = sf_readf_##itype(snd->snd, temp, amount); \
242 | if(r == 0) break; \
243 | \
244 | /* Write every frame channel times to the buffer */ \
245 | for(i = 0; i < r; i++) { \
246 | for(k = 0; k < buf->channels; k++) { \
247 | data[read * buf->channels + k] = temp[i]; \
248 | } \
249 | read++; \
250 | } \
251 | } \
252 | } else { \
253 | rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels); \
254 | } \
255 | \
256 | buf->real_size = read; \
257 | }
258 | DEFINE_RA_SOUND_READ_TYPE(short);
259 | DEFINE_RA_SOUND_READ_TYPE(int);
260 | DEFINE_RA_SOUND_READ_TYPE(float);
261 | DEFINE_RA_SOUND_READ_TYPE(double);
262 |
263 | /*
264 | * call-seq:
265 | * snd.read(buf, frames) => integer
266 | *
267 | * Tries to read the given number of frames into the buffer and returns the
268 | * number of frames actually read.
269 | */
270 | static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames) {
271 | RA_SOUND *snd;
272 | Data_Get_Struct(self, RA_SOUND, snd);
273 | if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
274 |
275 | // Get buffer struct
276 | RA_BUFFER *b;
277 | Data_Get_Struct(buf, RA_BUFFER, b);
278 |
279 | // Double-check frame count against buffer size
280 | sf_count_t f = (sf_count_t)NUM2OFFT(frames);
281 | if(f < 0 || f > b->size) {
282 | rb_raise(eRubyAudioError, "frame count invalid");
283 | }
284 |
285 | // Shortcut for 0 frame reads
286 | if(f == 0) {
287 | b->real_size = 0;
288 | return INT2FIX(b->real_size);;
289 | }
290 |
291 | // Read based on type
292 | switch(b->type) {
293 | case RA_BUFFER_TYPE_SHORT:
294 | ra_sound_read_short(snd, b, f);
295 | break;
296 | case RA_BUFFER_TYPE_INT:
297 | ra_sound_read_int(snd, b, f);
298 | break;
299 | case RA_BUFFER_TYPE_FLOAT:
300 | ra_sound_read_float(snd, b, f);
301 | break;
302 | case RA_BUFFER_TYPE_DOUBLE:
303 | ra_sound_read_double(snd, b, f);
304 | break;
305 | }
306 |
307 | // Return read
308 | return INT2FIX(b->real_size);
309 | }
310 |
311 | /*
312 | * call-seq:
313 | * snd.write(buf) => integer
314 | *
315 | * Writes the entire contents of the given buffer to the sound and returns the
316 | * number of frames written.
317 | */
318 | static VALUE ra_sound_write(VALUE self, VALUE buf) {
319 | RA_SOUND *snd;
320 | Data_Get_Struct(self, RA_SOUND, snd);
321 | if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
322 |
323 | // Get buffer struct
324 | RA_BUFFER *b;
325 | Data_Get_Struct(buf, RA_BUFFER, b);
326 |
327 | // Get info struct
328 | SF_INFO *info;
329 | Data_Get_Struct(snd->info, SF_INFO, info);
330 |
331 | // Check buffer channels matches actual channels
332 | if(b->channels != info->channels) {
333 | rb_raise(eRubyAudioError, "channel count mismatch: %d vs %d", b->channels, info->channels);
334 | }
335 |
336 | // Write data
337 | sf_count_t written = 0;
338 | switch(b->type) {
339 | case RA_BUFFER_TYPE_SHORT:
340 | written = sf_writef_short(snd->snd, b->data, b->real_size);
341 | break;
342 | case RA_BUFFER_TYPE_INT:
343 | written = sf_writef_int(snd->snd, b->data, b->real_size);
344 | break;
345 | case RA_BUFFER_TYPE_FLOAT:
346 | written = sf_writef_float(snd->snd, b->data, b->real_size);
347 | break;
348 | case RA_BUFFER_TYPE_DOUBLE:
349 | written = sf_writef_double(snd->snd, b->data, b->real_size);
350 | break;
351 | }
352 |
353 | return OFFT2NUM(written);
354 | }
355 |
356 | /*
357 | * call-seq:
358 | * snd << buf => snd
359 | *
360 | * Writes the given buffer to the string.
361 | *
362 | * snd << buf1 << buf2
363 | */
364 | static VALUE ra_sound_addbuf(VALUE self, VALUE buf) {
365 | ra_sound_write(self, buf);
366 | return self;
367 | }
368 |
369 | /*
370 | * call-seq:
371 | * snd.close => nil
372 | *
373 | * Closes snd and frees up all memory associated with the sound. The
374 | * sound is unavailable for any further data operations; an error is raised if
375 | * such an attempt is made. Sounds are automatically closed when they are claimed
376 | * by the garbage collector.
377 | */
378 | static VALUE ra_sound_close(VALUE self) {
379 | RA_SOUND *snd;
380 | Data_Get_Struct(self, RA_SOUND, snd);
381 | if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
382 |
383 | sf_close(snd->snd);
384 | snd->snd = NULL;
385 | snd->closed = 1;
386 | return Qnil;
387 | }
388 |
389 | static VALUE ra_sound_close_safe(VALUE self) {
390 | return rb_rescue(ra_sound_close, self, 0, 0);
391 | }
392 |
393 | /*
394 | * call-seq:
395 | * snd.closed? => true or false
396 | *
397 | * Whether or not the current sound is closed to further operations.
398 | */
399 | static VALUE ra_sound_closed(VALUE self) {
400 | RA_SOUND *snd;
401 | Data_Get_Struct(self, RA_SOUND, snd);
402 | return snd->closed ? Qtrue : Qfalse;
403 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Library General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License
307 | along with this program; if not, write to the Free Software
308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
309 |
310 |
311 | Also add information on how to contact you by electronic and paper mail.
312 |
313 | If the program is interactive, make it output a short notice like this
314 | when it starts in an interactive mode:
315 |
316 | Gnomovision version 69, Copyright (C) year name of author
317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 | This is free software, and you are welcome to redistribute it
319 | under certain conditions; type `show c' for details.
320 |
321 | The hypothetical commands `show w' and `show c' should show the appropriate
322 | parts of the General Public License. Of course, the commands you use may
323 | be called something other than `show w' and `show c'; they could even be
324 | mouse-clicks or menu items--whatever suits your program.
325 |
326 | You should also get your employer (if you work as a programmer) or your
327 | school, if any, to sign a "copyright disclaimer" for the program, if
328 | necessary. Here is a sample; alter the names:
329 |
330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
332 |
333 | , 1 April 1989
334 | Ty Coon, President of Vice
335 |
336 | This General Public License does not permit incorporating your program into
337 | proprietary programs. If your program is a subroutine library, you may
338 | consider it more useful to permit linking proprietary applications with the
339 | library. If this is what you want to do, use the GNU Library General
340 | Public License instead of this License.
341 |
--------------------------------------------------------------------------------