easy_audio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: