easy_audio 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 40a26cab3088ab5982f88fcdae9d10b1f89adbba
4
+ data.tar.gz: 96c4ec815952cc8dca68bd8392612a6654a61d3c
5
+ SHA512:
6
+ metadata.gz: a5aafb3994e2f8775cf7df6759e0ca8bf8d3e353afb6879892a40fb6e4bd9bea34f640d73d47f98dcb00d93e3b0049a5d190072bcb27f8c4d7c394a956429f1b
7
+ data.tar.gz: 9cb68b967815c5e07abcea57239d25ed5fef403e223a0e1d5c2e3db789ed6ff9b77ef72508a1ada2d77bba753c9818c59f5d309398ae490c240873aedb46d92a
@@ -0,0 +1,3 @@
1
+ .yardoc
2
+ doc
3
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2014, Loren Segal
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of the copyright holder nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL LOREN SEGAL BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,57 @@
1
+ # EasyAudio
2
+
3
+ EasyAudio is a simplified wrapper for the [portaudio][portaudio] library, which
4
+ allows to you play or record audio directly from your sound card.
5
+
6
+ ## Installing
7
+
8
+ ```sh
9
+ $ gem install easy_audio
10
+ ```
11
+
12
+ Note: if you are on a Linux or Windows machine you will need to manually
13
+ install portaudio to a location in your library paths. The gem will attempt
14
+ to install this automatically on OS X through [Homebrew][brew].
15
+
16
+ ## Usage
17
+
18
+ Here's how you can easily play a sine wave at 440hz:
19
+
20
+ ```ruby
21
+ require 'easy_audio'
22
+
23
+ EasyAudio.easy_open(&EasyAudio::Waveforms::SINE)
24
+ sleep 2 # play for 2 seconds
25
+ ```
26
+
27
+ Here's a triangle wave that increases its frequency over 3 seconds:
28
+
29
+ ```ruby
30
+ require 'easy_audio'
31
+
32
+ stream = EasyAudio.easy_open(freq: 220, &EasyAudio::Waveforms::TRIANGLE)
33
+ Thread.new { loop { stream.frequency += 50; sleep 0.2 } }
34
+ sleep 3
35
+ ```
36
+
37
+ Record audio from your microphone and play it back a second later:
38
+
39
+ ```ruby
40
+ require 'easy_audio'
41
+
42
+ EasyAudio.easy_open(in: true, out: true, latency: 1.0) { current_sample }
43
+ sleep 10 # for 10 seconds
44
+ ```
45
+
46
+ ## Documentation
47
+
48
+ See the API documentation on [rubydoc.info][docs].
49
+
50
+ ## License
51
+
52
+ EasyAudio is copyright © 2014 by Loren Segal and licensed under the BSD
53
+ license. See the LICENSE file for more information.
54
+
55
+ [portaudio]: http://portaudio.com
56
+ [brew]: http://brew.sh
57
+ [docs]: http://rubydoc.info/gems/easy_audio/frames
@@ -0,0 +1,35 @@
1
+ task :default => :install
2
+
3
+ task :install do
4
+ have_portaudio = false
5
+ print "Checking for portaudio..."
6
+ begin
7
+ require "ffi-portaudio"
8
+ have_portaudio = true
9
+ puts " yes."
10
+ rescue LoadError
11
+ puts "no."
12
+ end
13
+
14
+ if !have_portaudio
15
+ success = false
16
+ puts "Portaudio is missing, attempting to install..."
17
+
18
+ if RbConfig::CONFIG['host_os'].match(/darwin/)
19
+ puts "Detected Mac OS X, installing with Homebrew..."
20
+ begin
21
+ sh "brew install portaudio"
22
+ success = true if $? == 0
23
+ rescue
24
+ puts "Could not install portaudio. Do you have Homebrew installed?"
25
+ end
26
+ else
27
+ puts "Only OS X installation currently supported. Install portaudio " +
28
+ "from http://portaudio.com and reinstall."
29
+ end
30
+
31
+ if success
32
+ puts "Installed portaudio. Continuing installation of easy_audio..."
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "easy_audio"
3
+ s.summary = "EasyAudio is a simplified wrapper for the portaudio library."
4
+ s.description = "EasyAudio allows you to play or record from your sound card."
5
+ s.version = File.read("lib/easy_audio.rb")[/VERSION = "(.+?)"/, 1]
6
+ s.author = "Loren Segal"
7
+ s.email = "lsegal@soen.ca"
8
+ s.homepage = "http://github.com/lsegal/easy_audio"
9
+ s.platform = Gem::Platform::RUBY
10
+ s.files = `git ls-files`.split(/\s+/)
11
+ s.extensions = ["Rakefile"]
12
+ s.license = "BSD"
13
+
14
+ s.add_runtime_dependency "ffi-portaudio", "~> 0.0"
15
+ end
@@ -0,0 +1,7 @@
1
+ # A triangle wave that increases in frequency over 3 seconds
2
+ require_relative '../lib/easy_audio'
3
+
4
+ stream = EasyAudio.easy_open(freq: 220, &EasyAudio::Waveforms::TRIANGLE)
5
+
6
+ Thread.new { loop { stream.frequency += 50; sleep 0.2 } }
7
+ sleep 3
@@ -0,0 +1,5 @@
1
+ # Play a one second sine wave at 440hz (A note)
2
+ require_relative '../lib/easy_audio'
3
+
4
+ EasyAudio.easy_open(&EasyAudio::Waveforms::SINE)
5
+ sleep 1
@@ -0,0 +1,6 @@
1
+ # Record data from the microphone and
2
+ # play it back on output with a 1 second delay
3
+ require_relative '../lib/easy_audio'
4
+
5
+ EasyAudio.easy_open(in: true, out: true, latency: 1.0) { current_sample }
6
+ sleep 10 # for 10 seconds
@@ -0,0 +1,237 @@
1
+ require 'ffi-portaudio'
2
+
3
+ # Easy Audio is a library to simplify the Portaudio interface
4
+ # @see http://portaudio.com
5
+ module EasyAudio
6
+ VERSION = "0.1.0"
7
+
8
+ # Represents a single buffer passed to the {Stream} process block
9
+ class StreamPacket < Struct.new(:samples, :num_samples, :time_info, :status_info, :user_data)
10
+ end
11
+
12
+ # Represents a single audio input/output stream. See {#initialize} for usage
13
+ # examples.
14
+ class Stream < FFI::PortAudio::Stream
15
+ include FFI::PortAudio
16
+
17
+ # @!method start
18
+ # Starts processing the stream.
19
+
20
+ # @!method stop
21
+ # Stops processing the stream.
22
+
23
+ # Creates a new stream for processing audio. Call {#start} to start
24
+ # processing.
25
+ #
26
+ # @option opts :sample_rate [Fixnum] (44100) the sample rate to play at.
27
+ # @option opts :frame_size [Fixnum] (256) the number of frames per buffer.
28
+ # @option opts :in [Boolean] whether to use the default input device.
29
+ # @option opts :out [Boolean] whether to use the default output device.
30
+ # @option opts :in_chans [Fixnum] (2) the number of channels to process from
31
+ # the input device.
32
+ # @option opts :out_chans [Fixnum] (2) the number of channels to process
33
+ # from the output device.
34
+ # @option opts :latency [Float] (0.0) the default latency for processing.
35
+ # @yield [buffer] runs the provided block against the sample buffer data
36
+ # @yieldparam buffer [StreamPacket] the sample data to process
37
+ # @yieldreturn [Array<Float>] return an array of interlaced floating points
38
+ # for each channel in {#output_channels}.
39
+ # @example Process audio from input (microphone) and playback on output
40
+ # EasyAudio::Stream.new(in: true, out: true) do |buffer|
41
+ # buffer.samples # echos the stream to output
42
+ # end
43
+ # @see #start
44
+ def initialize(opts = {}, &block)
45
+ pa_start
46
+
47
+ @fn = block
48
+ @sample_rate = opts[:sample_rate] || 44100
49
+ @frame_size = opts[:frame_size] || 256
50
+ @input_channels = opts[:in_chans] || 1
51
+ @output_channels = opts[:out_chans] || 1
52
+ @latency = opts[:latency] || 0.01
53
+
54
+ input, output = nil, nil
55
+ if opts[:in] || opts[:in_chans]
56
+ device = API.Pa_GetDefaultInputDevice
57
+ input = stream_for(device, @input_channels, @latency)
58
+ end
59
+
60
+ if opts[:out] || opts[:out_chans] || !input
61
+ device = API.Pa_GetDefaultOutputDevice
62
+ output = stream_for(device, @output_channels, @latency)
63
+ end
64
+
65
+ open(input, output, @sample_rate, @frame_size)
66
+ end
67
+
68
+ attr_accessor :fn, :sample_rate, :frame_size
69
+ attr_reader :input_channels, :output_channels, :latency
70
+
71
+ private
72
+
73
+ # Don't override this function. Pass in a `process` Proc object to
74
+ # {#initialize} instead.
75
+ def process(input, output, frames, time_info, status, user_data)
76
+ result = run_process(input, output, frames, time_info, status, user_data)
77
+ unless Array === result
78
+ result = Array.new(frames * @output_channels).map {0}
79
+ end
80
+
81
+ output.write_array_of_float(result)
82
+ :paContinue
83
+ rescue => e
84
+ puts e.message + "\n " + e.backtrace.join("\n ")
85
+ :paAbort
86
+ end
87
+
88
+ def run_process(input, output, frames, time_info, status, user_data)
89
+ inbuf = nil
90
+ if input.address != 0
91
+ inbuf = input.read_array_of_float(frames * @input_channels)
92
+ end
93
+
94
+ buffer = StreamPacket.new(inbuf, frames, time_info, status, user_data)
95
+ @fn ? @fn.call(buffer) : nil
96
+ end
97
+
98
+ def stream_for(device, channels, latency)
99
+ API::PaStreamParameters.new.tap do |stream|
100
+ info = API.Pa_GetDeviceInfo(device)
101
+ stream[:device] = device
102
+ stream[:suggestedLatency] = latency
103
+ stream[:hostApiSpecificStreamInfo] = nil
104
+ stream[:channelCount] = channels
105
+ stream[:sampleFormat] = API::Float32
106
+ end
107
+ end
108
+
109
+ def pa_start
110
+ return if @@stream_started
111
+ API.Pa_Initialize
112
+ at_exit { API.Pa_Terminate }
113
+ @@stream_started = true
114
+ end
115
+
116
+ @@stream_started = false
117
+ end
118
+
119
+ # A simplified {Stream} class whose processor block only processes a single
120
+ # frame at a time. See {Waveforms} for a set of pre-fabricated EasyStream
121
+ # processor blocks for examples of how to process a single stream.
122
+ #
123
+ # Note that instead of passing state information as an argument to the block,
124
+ # state is instead stored in the class itself, and the block is instance
125
+ # evaluated. This makes it a bit slower to process, but much more convenient
126
+ # for creating blocks.
127
+ #
128
+ # See {#initialize} for usage examples.
129
+ class EasyStream < Stream
130
+
131
+ # {include:Stream#initialize}
132
+ #
133
+ # @option (see Stream#initialize)
134
+ # @option opts :freq [Float] (440.0) the frequency to generate {#step}
135
+ # values at.
136
+ # @option opts :amp [Float] (1.0) the amplitude to scale values to.
137
+ # @yield a process block that processes one frame at a time.
138
+ # @yieldreturn [Array<Float>] return an array of interlaced floating points
139
+ # for each channel in {#output_channels}.
140
+ # @example Process audio from input (microphone) and playback on output
141
+ # EasyAudio::EasyStream.new(in: true, out: true) { current_sample }.start
142
+ # @example Play a sine wave.
143
+ # EasyAudio::EasyStream.new(&EasyAudio::Waveforms::SINE).start
144
+ # @example Play a square wave.
145
+ # EasyAudio::EasyStream.new(&EasyAudio::Waveforms::SQUARE).start
146
+ def initialize(opts = {}, &block)
147
+ @frequency = opts[:freq] || 440.0
148
+ @amp = opts[:amp] || 1.0
149
+ @frame = 0
150
+ @channel = 0
151
+
152
+ super(opts, &block)
153
+ end
154
+
155
+ attr_accessor :amp, :frequency, :frame
156
+ attr_reader :step, :channel, :samples, :num_frames
157
+ attr_reader :time_info, :status_info, :user_data, :i, :current_sample
158
+
159
+ private
160
+
161
+ def run_process(input, output, frames, time_info, status, user_data)
162
+ @samples = nil
163
+ if input.address != 0
164
+ @samples = input.read_array_of_float(frames * @input_channels)
165
+ end
166
+
167
+ @current_sample = nil
168
+ @num_frames = frames
169
+ @time_info = time_info
170
+ @status_info = status
171
+ @user_data = user_data
172
+
173
+ result = Array.new(frames * @output_channels)
174
+ if @fn
175
+ @i = 0
176
+ frames.times do
177
+ @step = @frame * (@frequency / @sample_rate.to_f) % 1.0
178
+ @output_channels.times do |ch|
179
+ @channel = ch
180
+ @current_sample = @samples[@i] if @samples
181
+ result[@i] = @amp.to_f * (instance_exec(&@fn) || 0.0)
182
+ @i += 1
183
+ end
184
+ @frame = (@frame + 1) % 1000000
185
+ end
186
+ else
187
+ result = result.map {0}
188
+ end
189
+
190
+ result
191
+ end
192
+ end
193
+
194
+ module_function
195
+
196
+ # Quickly opens a {Stream} and calls {Stream#start}.
197
+ #
198
+ # @option (see Stream#initialize)
199
+ # @yield (see Stream#initialize)
200
+ # @yieldparam (see Stream#initialize)
201
+ # @yieldreturn (see Stream#initialize)
202
+ def open(opts = {}, &block)
203
+ Stream.new(opts, &block).tap {|s| s.start }
204
+ end
205
+
206
+ # Quickly opens an {EasyStream} and calls {Stream#start}.
207
+ #
208
+ # @option (see EasyStream#initialize)
209
+ # @yield (see EasyStream#initialize)
210
+ # @yieldreturn (see EasyStream#initialize)
211
+ # @example Process audio from input (microphone) and playback on output
212
+ # EasyAudio.easy_open(in: true, out: true) { current_sample }
213
+ # @example Play a sine wave.
214
+ # EasyAudio.easy_open(&EasyAudio::Waveforms::SINE)
215
+ # @example Play a square wave.
216
+ # EasyAudio.easy_open(&EasyAudio::Waveforms::SQUARE)
217
+ # @see EasyStream#initialize
218
+ def easy_open(opts = {}, &block)
219
+ EasyStream.new(opts, &block).tap {|s| s.start }
220
+ end
221
+
222
+ # A collection of pre-fabricated waveforms that can be plugged into
223
+ # {EasyStream} or {easy_open}.
224
+ module Waveforms
225
+ # Generates a sine wave
226
+ SINE = -> { Math.sin(2 * Math::PI * step) }
227
+
228
+ # Generates a square wave
229
+ SQUARE = -> { step < 0.5 ? -1 : 1 }
230
+
231
+ # Generates a triangle wave
232
+ TRIANGLE = -> { 1 - 4 * (step.round - step).abs }
233
+
234
+ # Generates a sawtooth wave
235
+ SAW = -> { 2 * (step - step.round) }
236
+ end
237
+ end
@@ -0,0 +1,44 @@
1
+ {
2
+ "actions": [
3
+ {
4
+ "action": "fs-sedfiles",
5
+ "files": ["lib/easy_audio.rb"],
6
+ "arguments": {
7
+ "search": "VERSION = ['\"](.+?)['\"]",
8
+ "replace": "VERSION = \"$version\""
9
+ }
10
+ },
11
+ {
12
+ "action": "git-commit",
13
+ "files": ["lib/easy_audio.rb"]
14
+ },
15
+ {
16
+ "action": "git-merge",
17
+ "arguments": {
18
+ "branch": "master"
19
+ }
20
+ },
21
+ {
22
+ "action": "archive-git-full",
23
+ "files": ["git.tgz"],
24
+ "publish": [{
25
+ "action": "git-push",
26
+ "arguments": {
27
+ "remotes": "origin",
28
+ "refs": "master v$version"
29
+ }
30
+ }]
31
+ },
32
+ {
33
+ "action": "gem-build",
34
+ "files": ["*.gemspec"],
35
+ "publish": [
36
+ {
37
+ "action": "gem-push",
38
+ "files": ["*.gem"],
39
+ "credentials": "lsegal.rubygems"
40
+ }
41
+ ]
42
+ }
43
+ ]
44
+ }
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy_audio
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Loren Segal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi-portaudio
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.0'
27
+ description: EasyAudio allows you to play or record from your sound card.
28
+ email: lsegal@soen.ca
29
+ executables: []
30
+ extensions:
31
+ - Rakefile
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ - easy_audio.gemspec
39
+ - examples/increase_frequency.rb
40
+ - examples/one_second_sine.rb
41
+ - examples/record_microphone_delay.rb
42
+ - lib/easy_audio.rb
43
+ - samus.json
44
+ homepage: http://github.com/lsegal/easy_audio
45
+ licenses:
46
+ - BSD
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.2.2
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: EasyAudio is a simplified wrapper for the portaudio library.
68
+ test_files: []
69
+ has_rdoc: