audio-playback 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/bin/playback +8 -1
- data/lib/audio-playback.rb +11 -5
- data/lib/audio-playback/commandline.rb +27 -1
- data/lib/audio-playback/device.rb +3 -3
- data/lib/audio-playback/device/output.rb +5 -5
- data/lib/audio-playback/device/stream.rb +44 -19
- data/lib/audio-playback/playback.rb +127 -21
- data/lib/audio-playback/playback/frame.rb +8 -8
- data/lib/audio-playback/playback/frame_set.rb +3 -3
- data/lib/audio-playback/playback/mixer.rb +8 -8
- data/lib/audio-playback/playback/stream_data.rb +35 -10
- data/lib/audio-playback/position.rb +89 -0
- data/test/device/output_test.rb +4 -4
- data/test/playback/stream_data_test.rb +97 -9
- data/test/playback_test.rb +258 -2
- data/test/position_test.rb +167 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0040badaf2c68a1886248157f4448ab24609dccf
|
4
|
+
data.tar.gz: f22b0f7d1615d6b863e493ea5744574ea06a9321
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfdddbf4c6f4ea7050ddeff2a9ddf2a37d1484d0fe15d2f056f86001b6fed3cb2aefd02ae6488ed2fa08ec06eef0125e280ea1f215eb9f53803d443e5b67e350
|
7
|
+
data.tar.gz: 04f79ca3da8f374692d5a79ace294362ecfa79455f46180ac5d8c9e6eff00d779c2a9647218674d9b2b47ead49e09c7ce5da05e37e497a3ee923321ac02b9760
|
data/README.md
CHANGED
@@ -35,10 +35,18 @@ Or if you're using Bundler, add this to your Gemfile
|
|
35
35
|
|
36
36
|
* `-c` Output audio to the given channel(s). Eg `-c 0,1` will direct audio to channels 0 and 1. Defaults to use channels 0 and 1 on the selected device
|
37
37
|
|
38
|
+
* `-d` Duration. Will stop after the given amount of time. Eg `-d 56` stops after 56 seconds of playback
|
39
|
+
|
40
|
+
* `-e` End position. Will stop at the given absolute time, irregardless of seek. Eg `-e 56` stops at 56 seconds. `-s 01:09:30 -e 01:10:00` stops at 1 hour 10 minutes after 30 seconds of playback
|
41
|
+
|
38
42
|
* `-o` Output device id or name. Defaults to the system default
|
39
43
|
|
44
|
+
* `-s` Seek to given time position. Eg `-s 56` seeks to 56 seconds and `-s 01:10:00` seeks to 1 hour 10 min.
|
45
|
+
|
40
46
|
* `-v` or `--verbose` Verbose
|
41
47
|
|
48
|
+
* `--loop` Loop playback continuously
|
49
|
+
|
42
50
|
* `--list-devices` List the available audio output devices
|
43
51
|
|
44
52
|
|
@@ -94,4 +102,4 @@ More Ruby code examples:
|
|
94
102
|
|
95
103
|
Licensed under Apache 2.0, See the file LICENSE
|
96
104
|
|
97
|
-
Copyright (c) 2015-
|
105
|
+
Copyright (c) 2015-2017 [Ari Russo](http://arirusso.com)
|
data/bin/playback
CHANGED
@@ -14,12 +14,19 @@ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
|
14
14
|
#
|
15
15
|
# * `-c` Output audio to the given channel(s). Eg `-c 0,1` will direct audio to channels 0 and 1. Defaults to use channels 0 and 1 on the selected device
|
16
16
|
#
|
17
|
+
# * `-d` Duration. Will stop after the given amount of time. Eg `-d 56` stops after 56 seconds of playback
|
18
|
+
#
|
19
|
+
# * `-e` End position. Will stop at the given absolute time, irregardless of seek. Eg `-e 56` stops at 56 seconds. `-s 01:09:30 -e 01:10:00` stops at 1 hour 10 minutes after 30 seconds of playback
|
20
|
+
#
|
17
21
|
# * `-o` Output device id or name. Defaults to the system default
|
18
22
|
#
|
23
|
+
# * `-s` Seek to given time position. Eg `-s 56` seeks to 56 seconds and `-s 01:10:00` seeks to 1 hour 10 min.
|
24
|
+
#
|
19
25
|
# * `-v` or `--verbose` Verbose
|
20
|
-
#
|
26
|
+
#
|
21
27
|
# * `--list-devices` List the available audio output devices
|
22
28
|
#
|
29
|
+
# * `--loop` Loop playback continuously
|
23
30
|
#
|
24
31
|
|
25
32
|
require "audio-playback"
|
data/lib/audio-playback.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# AudioPlayback
|
3
3
|
# Play audio files at the command line or with Ruby
|
4
4
|
#
|
5
|
-
# (c)2015 Ari Russo
|
5
|
+
# (c)2015-2017 Ari Russo
|
6
6
|
# Apache 2.0 License
|
7
7
|
# https://github.com/arirusso/audio-playback
|
8
8
|
#
|
@@ -19,25 +19,31 @@ require "audio-playback/playback"
|
|
19
19
|
|
20
20
|
# classes
|
21
21
|
require "audio-playback/file"
|
22
|
+
require "audio-playback/position"
|
22
23
|
require "audio-playback/sound"
|
23
24
|
|
24
25
|
# Play audio files
|
25
26
|
module AudioPlayback
|
26
27
|
|
27
|
-
VERSION = "0.0.
|
28
|
+
VERSION = "0.0.8"
|
28
29
|
|
29
30
|
# Convenience method to play an audio file
|
30
31
|
# @param [Array<::File>, Array<String>, ::File, String] file_paths
|
31
32
|
# @param [Hash] options
|
32
|
-
# @option options [
|
33
|
-
# @option options [Array<
|
33
|
+
# @option options [Integer] :buffer_size Buffer size in bytes. Defaults to 4096
|
34
|
+
# @option options [Array<Integer>, Integer] :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
|
35
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
36
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
37
|
+
# @option options [Boolean] :is_looping (or :loop) Whether to loop audio
|
34
38
|
# @option options [Float] :latency Latency in seconds. Defaults to use the default latency for the selected output device
|
35
39
|
# @option options [IO] :logger Logger object
|
36
|
-
# @option options [
|
40
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
41
|
+
# @option options [Integer, String] :output_device (or: :output) Output device id or name
|
37
42
|
def self.play(file_paths, options = {})
|
38
43
|
sounds = Array(file_paths).map { |path| Sound.load(path, options) }
|
39
44
|
requested_device = options[:output_device] || options[:output]
|
40
45
|
output = Device::Output.by_name(requested_device) || Device::Output.by_id(requested_device) || Device.default_output
|
46
|
+
options[:is_looping] ||= options[:loop]
|
41
47
|
Playback.play(sounds, output, options)
|
42
48
|
end
|
43
49
|
|
@@ -18,6 +18,20 @@ module AudioPlayback
|
|
18
18
|
:name => "Direct to channel(s)"
|
19
19
|
},
|
20
20
|
|
21
|
+
:duration => {
|
22
|
+
:short => "-d",
|
23
|
+
:long => "--duration [seconds]",
|
24
|
+
:name => "Duration",
|
25
|
+
:type => String
|
26
|
+
},
|
27
|
+
|
28
|
+
:end_position => {
|
29
|
+
:short => "-e",
|
30
|
+
:long => "--end-position [seconds]",
|
31
|
+
:name => "End position",
|
32
|
+
:type => String
|
33
|
+
},
|
34
|
+
|
21
35
|
:latency => {
|
22
36
|
:short => "-l",
|
23
37
|
:long => "--latency [seconds]",
|
@@ -30,6 +44,18 @@ module AudioPlayback
|
|
30
44
|
:name => "List devices"
|
31
45
|
},
|
32
46
|
|
47
|
+
:loop => {
|
48
|
+
:long => "--loop",
|
49
|
+
:name => "Loop"
|
50
|
+
},
|
51
|
+
|
52
|
+
:seek => {
|
53
|
+
:short => "-s",
|
54
|
+
:long => "--seek [seconds]",
|
55
|
+
:name => "Seek",
|
56
|
+
:type => String
|
57
|
+
},
|
58
|
+
|
33
59
|
:logger => {
|
34
60
|
:short => "-v",
|
35
61
|
:long => "--verbose",
|
@@ -43,7 +69,7 @@ module AudioPlayback
|
|
43
69
|
:type => String,
|
44
70
|
:name => "Output device for playback"
|
45
71
|
}
|
46
|
-
}
|
72
|
+
}.freeze
|
47
73
|
|
48
74
|
end
|
49
75
|
|
@@ -21,7 +21,7 @@ module AudioPlayback
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Get a device by its ID
|
24
|
-
# @param [
|
24
|
+
# @param [Integer] id
|
25
25
|
# @return [Output]
|
26
26
|
def by_id(id)
|
27
27
|
outputs.find { |device| [device, device.id].include?(id) }
|
@@ -41,7 +41,7 @@ module AudioPlayback
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# Get system device info given a device ID
|
44
|
-
# @param [
|
44
|
+
# @param [Integer] id
|
45
45
|
# @return [FFI::PortAudio::API::PaDeviceInfo]
|
46
46
|
def device_info(id)
|
47
47
|
FFI::PortAudio::API.Pa_GetDeviceInfo(id)
|
@@ -50,7 +50,7 @@ module AudioPlayback
|
|
50
50
|
private
|
51
51
|
|
52
52
|
# Is the device with the given ID an output?
|
53
|
-
# @param [
|
53
|
+
# @param [Integer] id
|
54
54
|
# @return [Boolean]
|
55
55
|
def output?(id)
|
56
56
|
device_info(id)[:maxOutputChannels] > 0
|
@@ -42,7 +42,7 @@ module AudioPlayback
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# Select an output device by ID
|
45
|
-
# @param [
|
45
|
+
# @param [Integer] id
|
46
46
|
# @return [Output]
|
47
47
|
def self.by_id(id)
|
48
48
|
Device.by_id(id)
|
@@ -55,7 +55,7 @@ module AudioPlayback
|
|
55
55
|
Device.by_name(name)
|
56
56
|
end
|
57
57
|
|
58
|
-
# @param [
|
58
|
+
# @param [Integer] id
|
59
59
|
# @param [Hash] options
|
60
60
|
# @option options [Float] :latency Device latency in seconds
|
61
61
|
def initialize(id, options = {})
|
@@ -71,13 +71,13 @@ module AudioPlayback
|
|
71
71
|
end
|
72
72
|
|
73
73
|
# Number of channels the device supports
|
74
|
-
# @return [
|
74
|
+
# @return [Integer]
|
75
75
|
def num_channels
|
76
76
|
@resource[:channelCount]
|
77
77
|
end
|
78
78
|
|
79
79
|
# ID of the device
|
80
|
-
# @return [
|
80
|
+
# @return [Integer]
|
81
81
|
def id
|
82
82
|
@resource[:device]
|
83
83
|
end
|
@@ -91,7 +91,7 @@ module AudioPlayback
|
|
91
91
|
end
|
92
92
|
|
93
93
|
# Populate the output
|
94
|
-
# @param [
|
94
|
+
# @param [Integer] id
|
95
95
|
# @param [Hash] options
|
96
96
|
# @option options [Float] :latency
|
97
97
|
# @return [FFI::PortAudio::API::PaStreamParameters]
|
@@ -54,10 +54,10 @@ module AudioPlayback
|
|
54
54
|
def block
|
55
55
|
begin
|
56
56
|
while active?
|
57
|
-
sleep(0.
|
57
|
+
sleep(0.001)
|
58
58
|
end
|
59
59
|
while FFI::PortAudio::API.Pa_IsStreamActive(@stream.read_pointer) != :paNoError
|
60
|
-
sleep(1)
|
60
|
+
sleep(0.1)
|
61
61
|
end
|
62
62
|
rescue SystemExit, Interrupt
|
63
63
|
# Control-C
|
@@ -74,7 +74,7 @@ module AudioPlayback
|
|
74
74
|
# @param [Playback] playback
|
75
75
|
# @return [Boolean]
|
76
76
|
def open_stream(playback)
|
77
|
-
@userdata
|
77
|
+
@userdata ||= playback.data.to_pointer
|
78
78
|
FFI::PortAudio::API.Pa_OpenStream(@stream, @input, @output, @freq, @frames, @flags, @method, @userdata)
|
79
79
|
true
|
80
80
|
end
|
@@ -112,15 +112,23 @@ module AudioPlayback
|
|
112
112
|
# @param [Playback] playback
|
113
113
|
# @return [Boolean]
|
114
114
|
def open_playback(playback)
|
115
|
-
|
116
|
-
@frames = playback.buffer_size
|
117
|
-
@flags = FFI::PortAudio::API::NoFlag
|
118
|
-
@stream = FFI::Buffer.new(:pointer)
|
119
|
-
@method = method(:process)
|
115
|
+
populate_stream_playback(playback)
|
120
116
|
open_stream(playback)
|
121
117
|
true
|
122
118
|
end
|
123
119
|
|
120
|
+
# Initialize the stream's playback properties
|
121
|
+
# @param [Playback] playback
|
122
|
+
# @return [Boolean]
|
123
|
+
def populate_stream_playback(playback)
|
124
|
+
@freq ||= playback.sample_rate.to_i
|
125
|
+
@frames ||= playback.buffer_size
|
126
|
+
@flags ||= FFI::PortAudio::API::NoFlag
|
127
|
+
@stream ||= FFI::Buffer.new(:pointer)
|
128
|
+
@method ||= method(:process)
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
124
132
|
# Report about the stream
|
125
133
|
# @param [Playback] playback
|
126
134
|
# @param [IO] logger
|
@@ -135,41 +143,58 @@ module AudioPlayback
|
|
135
143
|
#puts "Entering callback at #{Time.now.to_f}"
|
136
144
|
counter = user_data.get_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE).to_i
|
137
145
|
#puts "Frame: #{counter}"
|
138
|
-
|
139
|
-
#puts "Sample size: #{
|
146
|
+
audio_data_size = user_data.get_float32(Playback::METADATA.index(:size) * Playback::FRAME_SIZE).to_i
|
147
|
+
#puts "Sample size: #{audio_data_size}"
|
140
148
|
num_channels = user_data.get_float32(Playback::METADATA.index(:num_channels) * Playback::FRAME_SIZE).to_i
|
141
149
|
#puts "Num Channels: #{num_channels}"
|
150
|
+
start_frame = user_data.get_float32(Playback::METADATA.index(:start_frame) * Playback::FRAME_SIZE).to_i
|
151
|
+
#puts "Start point: #{start_frame}"
|
152
|
+
end_frame = user_data.get_float32(Playback::METADATA.index(:end_frame) * Playback::FRAME_SIZE).to_i
|
153
|
+
#puts "Duration: #{duration}"
|
154
|
+
is_looping = user_data.get_float32(Playback::METADATA.index(:is_looping) * Playback::FRAME_SIZE).to_i > 0
|
155
|
+
#puts "Is looping: #{is_looping}"
|
156
|
+
end_frame = [end_frame, audio_data_size].min
|
142
157
|
is_eof = false
|
143
|
-
|
144
|
-
|
145
|
-
|
158
|
+
end_window = end_frame - frames_per_buffer
|
159
|
+
if counter >= end_window
|
160
|
+
if counter == end_frame
|
161
|
+
is_eof = true
|
162
|
+
elsif counter < end_frame
|
163
|
+
buffer_size = end_frame.divmod(frames_per_buffer).last
|
146
164
|
#puts "Truncated buffer size: #{buffer_size}"
|
147
165
|
difference = frames_per_buffer - buffer_size
|
148
166
|
#puts "Adding #{difference} frames of null audio"
|
149
167
|
extra_data = [0] * difference * num_channels
|
150
168
|
is_eof = true
|
151
169
|
else
|
170
|
+
# p "Aborting (counter: #{counter}, end_frame: #{end_frame})"
|
152
171
|
return :paAbort
|
153
172
|
end
|
154
173
|
end
|
155
174
|
buffer_size ||= frames_per_buffer
|
156
175
|
#puts "Size per buffer per channel: #{frames_per_buffer}"
|
157
|
-
offset = ((counter * num_channels) + Playback::METADATA.count) * Playback::FRAME_SIZE
|
176
|
+
offset = (((counter + start_frame) * num_channels) + Playback::METADATA.count) * Playback::FRAME_SIZE
|
158
177
|
#puts "Starting at location: #{offset}"
|
159
178
|
data = user_data.get_array_of_float32(offset, buffer_size * num_channels)
|
160
179
|
data += extra_data unless extra_data.nil?
|
161
180
|
#puts "This buffer size: #{data.size}"
|
162
181
|
#puts "Writing to output"
|
163
182
|
output.write_array_of_float(data)
|
164
|
-
counter
|
165
|
-
user_data.put_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE, counter.to_f) # update counter
|
183
|
+
next_counter = counter + frames_per_buffer
|
166
184
|
if is_eof
|
167
|
-
|
168
|
-
|
169
|
-
|
185
|
+
if is_looping
|
186
|
+
#puts "Looping to beginning"
|
187
|
+
next_counter = start_frame
|
188
|
+
:paContinue
|
189
|
+
else
|
190
|
+
#puts "Marking eof"
|
191
|
+
user_data.put_float32(Playback::METADATA.index(:is_eof) * Playback::FRAME_SIZE, 1.0) # mark eof
|
192
|
+
:paComplete
|
193
|
+
end
|
170
194
|
else
|
171
195
|
:paContinue
|
172
196
|
end
|
197
|
+
user_data.put_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE, next_counter.to_f) # update counter
|
173
198
|
#puts "Exiting callback at #{Time.now.to_f}"
|
174
199
|
result
|
175
200
|
end
|
@@ -13,23 +13,41 @@ module AudioPlayback
|
|
13
13
|
|
14
14
|
FRAME_SIZE = FFI::TYPE_FLOAT32.size
|
15
15
|
|
16
|
-
METADATA = [:size, :num_channels, :pointer, :is_eof].freeze
|
16
|
+
METADATA = [:size, :num_channels, :start_frame, :end_frame, :is_looping, :pointer, :is_eof].freeze
|
17
|
+
|
18
|
+
class InvalidChannels < RuntimeError
|
19
|
+
end
|
20
|
+
|
21
|
+
class InvalidTruncation < RuntimeError
|
22
|
+
end
|
17
23
|
|
18
24
|
# Action of playing back an audio file
|
19
25
|
class Action
|
20
26
|
|
21
27
|
extend Forwardable
|
22
28
|
|
23
|
-
attr_reader :buffer_size,
|
29
|
+
attr_reader :buffer_size,
|
30
|
+
:channels,
|
31
|
+
:data,
|
32
|
+
:num_channels,
|
33
|
+
:output,
|
34
|
+
:sounds,
|
35
|
+
:stream,
|
36
|
+
:truncate
|
37
|
+
|
24
38
|
def_delegators :@sounds, :audio_files
|
25
|
-
def_delegators :@data, :reset
|
39
|
+
def_delegators :@data, :reset, :size
|
26
40
|
|
27
41
|
# @param [Array<Sound>, Sound] sounds
|
28
42
|
# @param [Output] output
|
29
43
|
# @param [Hash] options
|
30
|
-
# @option options [
|
31
|
-
# @option options [Array<
|
44
|
+
# @option options [Integer] :buffer_size
|
45
|
+
# @option options [Array<Integer>, Integer] :channels (or: :channel)
|
46
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
47
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
48
|
+
# @option options [Boolean] :is_looping Whether to loop audio
|
32
49
|
# @option options [IO] :logger
|
50
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
33
51
|
# @option options [Stream] :stream
|
34
52
|
def initialize(sounds, output, options = {})
|
35
53
|
@sounds = Array(sounds)
|
@@ -41,17 +59,11 @@ module AudioPlayback
|
|
41
59
|
end
|
42
60
|
|
43
61
|
# Sample rate of the playback sound
|
44
|
-
# @return [
|
62
|
+
# @return [Integer]
|
45
63
|
def sample_rate
|
46
64
|
@sounds.last.sample_rate
|
47
65
|
end
|
48
66
|
|
49
|
-
# Size of the playback sound
|
50
|
-
# @return [Fixnum]
|
51
|
-
def size
|
52
|
-
@sounds.map(&:size).max
|
53
|
-
end
|
54
|
-
|
55
67
|
# Start playback
|
56
68
|
# @return [Playback]
|
57
69
|
def start
|
@@ -66,6 +78,13 @@ module AudioPlayback
|
|
66
78
|
@stream.block
|
67
79
|
end
|
68
80
|
|
81
|
+
# Should playback be truncated?
|
82
|
+
# eg :start 3 seconds, :duration 1 second
|
83
|
+
# @return [Boolean]
|
84
|
+
def truncate?
|
85
|
+
!@truncate.nil? && !@truncate.values.empty?
|
86
|
+
end
|
87
|
+
|
69
88
|
# Log a report about playback
|
70
89
|
# @param [IO] logger
|
71
90
|
# @return [Boolean]
|
@@ -80,9 +99,9 @@ module AudioPlayback
|
|
80
99
|
end
|
81
100
|
|
82
101
|
# Total size of the playback's sound frames in bytes
|
83
|
-
# @return [
|
102
|
+
# @return [Integer]
|
84
103
|
def data_size
|
85
|
-
frames =
|
104
|
+
frames = size * @num_channels
|
86
105
|
frames * FRAME_SIZE.size
|
87
106
|
end
|
88
107
|
|
@@ -92,21 +111,28 @@ module AudioPlayback
|
|
92
111
|
!@channels.nil?
|
93
112
|
end
|
94
113
|
|
114
|
+
# Is audio looping ?
|
115
|
+
# @return [Boolean]
|
116
|
+
def looping?
|
117
|
+
@is_looping
|
118
|
+
end
|
119
|
+
|
95
120
|
private
|
96
121
|
|
97
122
|
# Are the requested channels available in the current environment?
|
98
|
-
# @param [Array<
|
123
|
+
# @param [Array<Integer>] channels
|
99
124
|
# @return [Boolean]
|
100
125
|
def validate_requested_channels(channels)
|
101
126
|
if channels.count > @output.num_channels
|
102
|
-
|
127
|
+
message = "Only #{@output.num_channels} channels available on #{@output.name} output"
|
128
|
+
raise(InvalidChannels.new(message))
|
103
129
|
false
|
104
130
|
end
|
105
131
|
true
|
106
132
|
end
|
107
133
|
|
108
134
|
# Validate and populate the variables containing information about the requested channels
|
109
|
-
# @param [
|
135
|
+
# @param [Integer, Array<Integer>] request Channel(s)
|
110
136
|
# @return [Boolean]
|
111
137
|
def populate_requested_channels(request)
|
112
138
|
request = Array(request)
|
@@ -122,7 +148,7 @@ module AudioPlayback
|
|
122
148
|
|
123
149
|
# Populate the playback channels
|
124
150
|
# @param [Hash] options
|
125
|
-
# @option options [
|
151
|
+
# @option options [Integer, Array<Integer>] :channels (or: :channel)
|
126
152
|
# @return [Boolean]
|
127
153
|
def populate_channels(options = {})
|
128
154
|
request = options[:channels] || options[:channel]
|
@@ -134,12 +160,92 @@ module AudioPlayback
|
|
134
160
|
end
|
135
161
|
end
|
136
162
|
|
163
|
+
# Populate the truncation parameters. Converts the seconds based Position arguments
|
164
|
+
# to number of frames
|
165
|
+
# @param [Position, nil] seek Start at given time position in seconds
|
166
|
+
# @param [Position, nil] duration Play for given time in seconds
|
167
|
+
# @param [Position, nil] end_position Stop at given time position in seconds (will use duration arg if both are included)
|
168
|
+
# @return [Hash]
|
169
|
+
def populate_truncation(seek, duration, end_position)
|
170
|
+
@truncate = {}
|
171
|
+
end_position = if duration.nil?
|
172
|
+
end_position
|
173
|
+
elsif seek.nil?
|
174
|
+
duration || end_position
|
175
|
+
else
|
176
|
+
duration + seek || end_position
|
177
|
+
end
|
178
|
+
unless seek.nil?
|
179
|
+
@truncate[:start_frame] = number_of_seconds_to_number_of_frames(seek)
|
180
|
+
end
|
181
|
+
unless end_position.nil?
|
182
|
+
@truncate[:end_frame] = number_of_seconds_to_number_of_frames(end_position)
|
183
|
+
end
|
184
|
+
@truncate
|
185
|
+
end
|
186
|
+
|
187
|
+
# Convert number of seconds to number of sample frames given the sample rate
|
188
|
+
# @param [Numeric] num_seconds
|
189
|
+
# @return [Integer]
|
190
|
+
def number_of_seconds_to_number_of_frames(num_seconds)
|
191
|
+
(num_seconds * sample_rate).to_i
|
192
|
+
end
|
193
|
+
|
194
|
+
# Are the options for truncation valid? eg is the :end_position option after the
|
195
|
+
# :seek option?
|
196
|
+
# @param [Hash] options
|
197
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
198
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
199
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
200
|
+
# @return [Boolean]
|
201
|
+
def truncate_valid?(options)
|
202
|
+
options[:end_position].nil? || options[:seek].nil? ||
|
203
|
+
options[:end_position] > options[:seek]
|
204
|
+
end
|
205
|
+
|
206
|
+
# Has truncation been requested in the constructor options?
|
207
|
+
# @param [Hash] options
|
208
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
209
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
210
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
211
|
+
# @return [Boolean]
|
212
|
+
def truncate_requested?(options)
|
213
|
+
!options[:seek].nil? || !options[:duration].nil? || !options[:end_position].nil?
|
214
|
+
end
|
215
|
+
|
216
|
+
# Populate Position objects using the the truncation parameters.
|
217
|
+
# @param [Hash] options
|
218
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
219
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
220
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
221
|
+
# @return [Array<Position>]
|
222
|
+
def truncate_options_as_positions(options = {})
|
223
|
+
seek = Position.new(options[:seek]) unless options[:seek].nil?
|
224
|
+
duration = Position.new(options[:duration]) unless options[:duration].nil?
|
225
|
+
end_position = Position.new(options[:end_position]) unless options[:end_position].nil?
|
226
|
+
[seek, duration, end_position]
|
227
|
+
end
|
228
|
+
|
137
229
|
# Populate the playback action
|
138
230
|
# @param [Hash] options
|
139
|
-
# @option options [
|
231
|
+
# @option options [Integer, Array<Integer>] :channels (or: :channel)
|
232
|
+
# @option options [Numeric] :duration Play for given time in seconds
|
233
|
+
# @option options [Numeric] :end_position Stop at given time position in seconds (will use :duration if both are included)
|
234
|
+
# @option options [Boolean] :is_looping Whether to loop audio
|
235
|
+
# @option options [Numeric] :seek Start at given time position in seconds
|
140
236
|
# @return [Playback::Action]
|
141
237
|
def populate(options = {})
|
142
238
|
populate_channels(options)
|
239
|
+
if truncate_requested?(options)
|
240
|
+
if truncate_valid?(options)
|
241
|
+
seek, duration, end_position = *truncate_options_as_positions(options)
|
242
|
+
populate_truncation(seek, duration, end_position)
|
243
|
+
else
|
244
|
+
message = "Truncation options are not valid"
|
245
|
+
raise(InvalidTruncation.new(message))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
@is_looping = !!options[:is_looping]
|
143
249
|
@data = StreamData.new(self)
|
144
250
|
self
|
145
251
|
end
|
@@ -155,8 +261,8 @@ module AudioPlayback
|
|
155
261
|
# @param [Sound] sound
|
156
262
|
# @param [Output] output
|
157
263
|
# @param [Hash] options
|
158
|
-
# @option options [
|
159
|
-
# @option options [Array<
|
264
|
+
# @option options [Integer] :buffer_size
|
265
|
+
# @option options [Array<Integer>, Integer] :channels (or: :channel)
|
160
266
|
# @option options [IO] :logger
|
161
267
|
# @option options [Stream] :stream
|
162
268
|
# @return [Playback]
|