audio-playback 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +81 -0
- data/bin/playback +66 -0
- data/lib/audio-playback.rb +55 -0
- data/lib/audio-playback/commandline.rb +50 -0
- data/lib/audio-playback/device.rb +61 -0
- data/lib/audio-playback/device/output.rb +113 -0
- data/lib/audio-playback/device/stream.rb +134 -0
- data/lib/audio-playback/file.rb +37 -0
- data/lib/audio-playback/playback.rb +152 -0
- data/lib/audio-playback/playback/frame.rb +72 -0
- data/lib/audio-playback/playback/frame_set.rb +117 -0
- data/lib/audio-playback/playback/stream_data.rb +52 -0
- data/lib/audio-playback/sound.rb +54 -0
- data/test/device/output_test.rb +141 -0
- data/test/device/stream_test.rb +54 -0
- data/test/device_test.rb +75 -0
- data/test/file_test.rb +105 -0
- data/test/helper.rb +59 -0
- data/test/media/1-mono-44100.aiff +0 -0
- data/test/media/1-mono-44100.wav +0 -0
- data/test/media/1-stereo-44100.aiff +0 -0
- data/test/media/1-stereo-44100.wav +0 -0
- data/test/playback/frame_set_test.rb +115 -0
- data/test/playback/frame_test.rb +46 -0
- data/test/playback/stream_data_test.rb +27 -0
- data/test/playback_test.rb +111 -0
- data/test/sound_test.rb +80 -0
- metadata +213 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6610c16cd941a9fac1b38c72500b6d7cabb6e065
|
4
|
+
data.tar.gz: c6e6902a5c28a811f05a0e5bbc1cd425cef60fb1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e05488f906b9f0783b9b5fca1665bef683a37c0dfbf7c432bb5df866b34da7a11f32e57ba91bf5a3d3cdd6648ceab83d504c78934ff2be5ef5abf58315274ed4
|
7
|
+
data.tar.gz: 01dca1cea7cab6165770841b57ee11e7a7d136add803cd6a0ca6ac608325a43f4aa9e2ffad191a98ca880a85e59bd93d109b21a1ae6300f07c83faf28733f02b
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2015 Ari Russo
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Audio Playback
|
2
|
+
|
3
|
+
Play audio files at the command line or using Ruby
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
These packages must be installed first:
|
8
|
+
|
9
|
+
* portaudio
|
10
|
+
* libsndfile
|
11
|
+
|
12
|
+
Install the gem using
|
13
|
+
|
14
|
+
gem install audio-playback
|
15
|
+
|
16
|
+
Or if you're using Bundler, add this to your Gemfile
|
17
|
+
|
18
|
+
gem "audio-playback"
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Command line
|
23
|
+
|
24
|
+
`playback [filename] [options]`
|
25
|
+
|
26
|
+
#### options:
|
27
|
+
|
28
|
+
* `-l` Latency in seconds. Defaults to use the default latency for the selected output device
|
29
|
+
|
30
|
+
* `-b` Buffer size in bytes. Defaults to 4096
|
31
|
+
|
32
|
+
* `-c` Output audio to the given channel(s). Eg `-c 0,1` will direct audio to channels 0 and 1. Defaults to use all available channels
|
33
|
+
|
34
|
+
* `-o` Output device id or name. Defaults to the system default
|
35
|
+
|
36
|
+
* `-v` or `--verbose` Verbose
|
37
|
+
|
38
|
+
* `--list-devices` List the available audio output devices
|
39
|
+
|
40
|
+
|
41
|
+
#### example:
|
42
|
+
|
43
|
+
`playback test/media/1-stereo-44100.wav -v -c 1`
|
44
|
+
|
45
|
+
### With Ruby
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
require "audio-playback"
|
49
|
+
|
50
|
+
# Prompt the user to select an audio output
|
51
|
+
@output = AudioPlayback::Device::Output.gets
|
52
|
+
|
53
|
+
options = {
|
54
|
+
:channels => [0,1],
|
55
|
+
:latency => 1,
|
56
|
+
:output_device => @output
|
57
|
+
}
|
58
|
+
|
59
|
+
@playback = AudioPlayback.play("test/media/1-stereo-44100.wav", options)
|
60
|
+
|
61
|
+
@playback.block
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
#### options:
|
66
|
+
|
67
|
+
* `:buffer_size` Buffer size in bytes. Defaults to 4096
|
68
|
+
|
69
|
+
* `:channel` or `:channels` Output audio to the given channel(s). Eg `:channels => [0,1]` will direct the audio to channels 0 and 1. Defaults to use all available channels
|
70
|
+
|
71
|
+
* `:latency` Latency in seconds. Defaults to use the default latency for the selected output device
|
72
|
+
|
73
|
+
* `:logger` Logger object
|
74
|
+
|
75
|
+
* `:output_device` Output device id or name
|
76
|
+
|
77
|
+
## License
|
78
|
+
|
79
|
+
Licensed under Apache 2.0, See the file LICENSE
|
80
|
+
|
81
|
+
Copyright (c) 2015 [Ari Russo](http://arirusso.com)
|
data/bin/playback
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
#
|
5
|
+
# ## Usage
|
6
|
+
#
|
7
|
+
# `playback [filename] [options]`
|
8
|
+
#
|
9
|
+
# #### options:
|
10
|
+
#
|
11
|
+
# * `-l` Latency in seconds. Defaults to use the default latency for the selected output device
|
12
|
+
#
|
13
|
+
# * `-b` Buffer size in bytes. Defaults to 4096
|
14
|
+
#
|
15
|
+
# * `-c` Output audio to the given channel(s). Eg `-c 0,1` will direct audio to channels 0 and 1. Defaults to use all available channels
|
16
|
+
#
|
17
|
+
# * `-o` Output device id or name. Defaults to the system default
|
18
|
+
#
|
19
|
+
# * `-v` Verbose
|
20
|
+
#
|
21
|
+
#
|
22
|
+
|
23
|
+
require "audio-playback"
|
24
|
+
require "audio-playback/commandline"
|
25
|
+
require "optparse"
|
26
|
+
|
27
|
+
def help(opts)
|
28
|
+
puts(opts)
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
|
32
|
+
options = {}
|
33
|
+
|
34
|
+
parser = OptionParser.new do |opts|
|
35
|
+
|
36
|
+
opts.banner = "Usage: playback [file] [options]"
|
37
|
+
opts.separator ""
|
38
|
+
opts.separator "Specific options:"
|
39
|
+
|
40
|
+
AudioPlayback::Commandline::OPTIONS.each do |key, spec|
|
41
|
+
opts.on(spec[:short], spec[:long], spec[:type], spec[:name]) do |value|
|
42
|
+
if value.is_a?(TrueClass) && !spec[:when_true].nil?
|
43
|
+
value = spec[:when_true]
|
44
|
+
end
|
45
|
+
options[key] = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on_tail("-h", "--help", "Show this message") { help(opts) }
|
50
|
+
|
51
|
+
opts.on_tail("--version", "Show version") do
|
52
|
+
puts AudioPlayback::VERSION
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
help(opts) if ARGV.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
parser.parse!
|
60
|
+
|
61
|
+
if options[:list_devices]
|
62
|
+
AudioPlayback.list_devices
|
63
|
+
else
|
64
|
+
playback = AudioPlayback.play(ARGV[0], options)
|
65
|
+
playback.block
|
66
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#
|
2
|
+
# AudioPlayback
|
3
|
+
# Play audio files at the command line or with Ruby
|
4
|
+
#
|
5
|
+
# (c)2015 Ari Russo
|
6
|
+
# Apache 2.0 License
|
7
|
+
# https://github.com/arirusso/audio-playback
|
8
|
+
#
|
9
|
+
|
10
|
+
# libs
|
11
|
+
require "ffi/libc"
|
12
|
+
require "ffi-portaudio"
|
13
|
+
require "forwardable"
|
14
|
+
require "ruby-audio"
|
15
|
+
|
16
|
+
# modules
|
17
|
+
require "audio-playback/device"
|
18
|
+
require "audio-playback/playback"
|
19
|
+
|
20
|
+
# classes
|
21
|
+
require "audio-playback/file"
|
22
|
+
require "audio-playback/sound"
|
23
|
+
|
24
|
+
# Play audio files
|
25
|
+
module AudioPlayback
|
26
|
+
|
27
|
+
VERSION = "0.0.2"
|
28
|
+
|
29
|
+
# Convenience method to play an audio file
|
30
|
+
# @param [::File, String] file_path
|
31
|
+
# @param [Hash] options
|
32
|
+
# @option options [Fixnum] :buffer_size Buffer size in bytes. Defaults to 4096
|
33
|
+
# @option options [Array<Fixnum>, Fixnum] :channels (or: :channel) Output audio to the given channel(s). Eg `:channels => [0,1]` will direct the audio to channels 0 and 1. Defaults to use all available channels
|
34
|
+
# @option options [Float] :latency Latency in seconds. Defaults to use the default latency for the selected output device
|
35
|
+
# @option options [IO] :logger Logger object
|
36
|
+
# @option options [Fixnum, String] :output_device Output device id or name
|
37
|
+
def self.play(file_path, options = {})
|
38
|
+
sound = Sound.load(file_path, options)
|
39
|
+
output = Device::Output.by_name(options[:output_device]) || Device::Output.by_id(options[:output_device]) || Device.default_output
|
40
|
+
Playback.play(sound, output, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# List the available audio output devices
|
44
|
+
# @return [Array<String>]
|
45
|
+
def self.list_devices
|
46
|
+
Device::Output.list
|
47
|
+
end
|
48
|
+
|
49
|
+
# Ensure that the audio system is initialized
|
50
|
+
# @return [Boolean]
|
51
|
+
def self.ensure_initialized
|
52
|
+
@initialized ||= FFI::PortAudio::API.Pa_Initialize
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module AudioPlayback
|
2
|
+
|
3
|
+
# Using the AudioPlayback module from the command line
|
4
|
+
module Commandline
|
5
|
+
|
6
|
+
OPTIONS = {
|
7
|
+
:buffer_size => {
|
8
|
+
:short => "-b",
|
9
|
+
:long => "--buffer-size [bytes]",
|
10
|
+
:type => Integer,
|
11
|
+
:name => "Buffer size"
|
12
|
+
},
|
13
|
+
|
14
|
+
:channels => {
|
15
|
+
:short => "-c",
|
16
|
+
:long => "--channels [channel1, channel2]",
|
17
|
+
:type => Array,
|
18
|
+
:name => "Direct to channel(s)"
|
19
|
+
},
|
20
|
+
|
21
|
+
:latency => {
|
22
|
+
:short => "-l",
|
23
|
+
:long => "--latency [seconds]",
|
24
|
+
:type => Float,
|
25
|
+
:name => "Latency"
|
26
|
+
},
|
27
|
+
|
28
|
+
:list_devices => {
|
29
|
+
:long => "--list-devices",
|
30
|
+
:name => "List devices"
|
31
|
+
},
|
32
|
+
|
33
|
+
:logger => {
|
34
|
+
:short => "-v",
|
35
|
+
:long => "--verbose",
|
36
|
+
:name => "Run verbosely",
|
37
|
+
:when_true => $>
|
38
|
+
},
|
39
|
+
|
40
|
+
:output_device => {
|
41
|
+
:short => "-o",
|
42
|
+
:long => "--output [name or id]",
|
43
|
+
:type => String,
|
44
|
+
:name => "Output device for playback"
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "audio-playback/device/output"
|
2
|
+
require "audio-playback/device/stream"
|
3
|
+
|
4
|
+
module AudioPlayback
|
5
|
+
|
6
|
+
# Audio IO devices
|
7
|
+
module Device
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# All output devices
|
12
|
+
# @return [Array<Output>]
|
13
|
+
def outputs
|
14
|
+
AudioPlayback.ensure_initialized
|
15
|
+
if @devices.nil?
|
16
|
+
count = FFI::PortAudio::API.Pa_GetDeviceCount
|
17
|
+
ids = (0..count-1).to_a.select { |id| output?(id) }
|
18
|
+
@devices = ids.map { |id| Output.new(id) }
|
19
|
+
end
|
20
|
+
@devices
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get a device by its ID
|
24
|
+
# @param [Fixnum] id
|
25
|
+
# @return [Output]
|
26
|
+
def by_id(id)
|
27
|
+
outputs.find { |device| [device, device.id].include?(id) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get a device by its name
|
31
|
+
# @param [String] name
|
32
|
+
# @return [Output]
|
33
|
+
def by_name(name)
|
34
|
+
outputs.find { |device| device.name == name }
|
35
|
+
end
|
36
|
+
|
37
|
+
# The system default output
|
38
|
+
# @return [Output]
|
39
|
+
def default_output
|
40
|
+
by_id(FFI::PortAudio::API.Pa_GetDefaultOutputDevice)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get system device info given a device ID
|
44
|
+
# @param [Fixnum] id
|
45
|
+
# @return [FFI::PortAudio::API::PaDeviceInfo]
|
46
|
+
def device_info(id)
|
47
|
+
FFI::PortAudio::API.Pa_GetDeviceInfo(id)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Is the device with the given ID an output?
|
53
|
+
# @param [Fixnum] id
|
54
|
+
# @return [Boolean]
|
55
|
+
def output?(id)
|
56
|
+
device_info(id)[:maxOutputChannels] > 0
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module AudioPlayback
|
2
|
+
|
3
|
+
module Device
|
4
|
+
# An output device
|
5
|
+
class Output
|
6
|
+
|
7
|
+
attr_reader :id, :name, :resource
|
8
|
+
|
9
|
+
# All output devices
|
10
|
+
# @return [Array<Output>]
|
11
|
+
def self.all
|
12
|
+
Device.outputs
|
13
|
+
end
|
14
|
+
|
15
|
+
# Prints ids and names of each device to standard out
|
16
|
+
# @return [Array<String>]
|
17
|
+
def self.list
|
18
|
+
all.map do |device|
|
19
|
+
name = "#{device.id}. #{device.name}"
|
20
|
+
$>.puts(name)
|
21
|
+
name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Streamlined console prompt that asks the user (via standard in) to select a device
|
26
|
+
# When their input is received, the device is selected and enabled
|
27
|
+
# @return [Output]
|
28
|
+
def self.gets
|
29
|
+
device = nil
|
30
|
+
puts ""
|
31
|
+
puts "Select an audio output..."
|
32
|
+
while device.nil?
|
33
|
+
list
|
34
|
+
print "> "
|
35
|
+
selection = $stdin.gets.chomp
|
36
|
+
if selection != ""
|
37
|
+
selection = Integer(selection) rescue nil
|
38
|
+
device = all.find { |d| d.id == selection } unless selection.nil?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
device
|
42
|
+
end
|
43
|
+
|
44
|
+
# Select an output device by ID
|
45
|
+
# @param [Fixnum] id
|
46
|
+
# @return [Output]
|
47
|
+
def self.by_id(id)
|
48
|
+
Device.by_id(id)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Select an output device by name
|
52
|
+
# @param [String] name
|
53
|
+
# @return [Output]
|
54
|
+
def self.by_name(name)
|
55
|
+
Device.by_name(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Fixnum] id
|
59
|
+
# @param [Hash] options
|
60
|
+
# @option options [Float] :latency Device latency in seconds
|
61
|
+
def initialize(id, options = {})
|
62
|
+
# Init audio output resource
|
63
|
+
AudioPlayback.ensure_initialized
|
64
|
+
populate(id, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Device latency in seconds
|
68
|
+
# @return [Float]
|
69
|
+
def latency
|
70
|
+
@resource[:suggestedLatency]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Number of channels the device supports
|
74
|
+
# @return [Fixnum]
|
75
|
+
def num_channels
|
76
|
+
@resource[:channelCount]
|
77
|
+
end
|
78
|
+
|
79
|
+
# ID of the device
|
80
|
+
# @return [Fixnum]
|
81
|
+
def id
|
82
|
+
@resource[:device]
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# The underlying resource info struct for this output
|
88
|
+
# @return [FFI::PortAudio::API::PaDeviceInfo]
|
89
|
+
def info
|
90
|
+
@info ||= Device.device_info(id)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Populate the output
|
94
|
+
# @param [Fixnum] id
|
95
|
+
# @param [Hash] options
|
96
|
+
# @option options [Float] :latency
|
97
|
+
# @return [FFI::PortAudio::API::PaStreamParameters]
|
98
|
+
def populate(id, options = {})
|
99
|
+
@resource = FFI::PortAudio::API::PaStreamParameters.new
|
100
|
+
@resource[:device] = id
|
101
|
+
@name = info[:name]
|
102
|
+
@resource[:suggestedLatency] = options[:latency] || info[:defaultHighOutputLatency]
|
103
|
+
@resource[:hostApiSpecificStreamInfo] = nil
|
104
|
+
@resource[:channelCount] = info[:maxOutputChannels]
|
105
|
+
@resource[:sampleFormat] = FFI::PortAudio::API::Float32
|
106
|
+
@resource
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|