audio-playback 0.0.2
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.
- 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
|