plaything 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +13 -1
- data/lib/plaything.rb +143 -66
- data/lib/plaything/objects/buffer.rb +4 -0
- data/lib/plaything/objects/source.rb +31 -0
- data/lib/plaything/openal.rb +8 -6
- data/lib/plaything/support/managed_pointer.rb +0 -7
- data/lib/plaything/support/type_class.rb +4 -11
- data/lib/plaything/version.rb +1 -1
- metadata +22 -33
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3b370e69436137efbd0bb9964231b74bae9863a7
|
4
|
+
data.tar.gz: e8d93ef8632e8191f2be628a58618f04723e5cf6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5026505b6b2650bbac2e3a644b10bdbbb78ff675a8c8b9d3c86ec9d33ab81ac5176b5596da5d117837546a99b6aadf4b455ff2630cdd53682c8a2e60cb4fb1bc
|
7
|
+
data.tar.gz: d5590baa1b903ea691266b1c38d9834ead39c9d669b01113f929055e730918afefbb4847ebbd73fd973b6e82016c4824662d9bd869dd4001107de3f11be488e0
|
data/README.md
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
# Plaything
|
2
2
|
|
3
|
-
|
3
|
+
> OpenAL is a cross-platform 3D audio API appropriate for use with gaming applications and many other types of audio applications.
|
4
|
+
|
5
|
+
Plaything is tiny API wrapper around OpenAL, and makes it easy to play raw (PCM) streaming audio through your speakers.
|
6
|
+
|
7
|
+
Plaything was initially written to support audio playback from the [spotify gem](http://rubygems.org/gems/spotify).
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
## Note about the OpenAL bindings
|
12
|
+
|
13
|
+
Plaything contains bindings to a subset of the OpenAL API, just enough to cover the necessary streaming functionality. With little further work, the OpenAL bindings could be extracted and further developed indepdendently of Plaything.
|
14
|
+
|
15
|
+
Additionally, the OpenAL streaming source is retrievable from Plaything, and allows you to modify parameters on the playback source, such as pitch, gain, and anything else OpenAL allows you to change.
|
4
16
|
|
5
17
|
## License
|
6
18
|
|
data/lib/plaything.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "monitor"
|
1
2
|
require "ffi"
|
2
3
|
require "plaything/version"
|
3
4
|
require "plaything/monkey_patches/ffi"
|
@@ -5,16 +6,33 @@ require "plaything/support"
|
|
5
6
|
require "plaything/objects"
|
6
7
|
require "plaything/openal"
|
7
8
|
|
9
|
+
# Plaything is tiny API wrapper around OpenAL, and makes it easy to play raw
|
10
|
+
# (PCM) streaming audio through your speakers.
|
11
|
+
#
|
12
|
+
# API consist of a few key methods available on the Plaything instance.
|
13
|
+
#
|
14
|
+
# - {#play}, {#pause}, {#stop} — controls source playback state. If the source
|
15
|
+
# runs out of audio to play, it will forcefully stop playback.
|
16
|
+
# - {#position}, can be used to retrieve playback position.
|
17
|
+
# - {#queue_size}, {#drops} — status information; should be used by the streaming
|
18
|
+
# source to improve playback experience.
|
19
|
+
# - {#format=} — allows you to change format, even during playback.
|
20
|
+
# - {#stream}, {#<<} — fills the audio buffers with PCM audio.
|
21
|
+
#
|
22
|
+
# Internally, Plaything will queue and unqueue buffers as they are played during
|
23
|
+
# streaming. When a sufficient amount of audio has been fed into plaything, the
|
24
|
+
# audio will be queued on the source and plaything can accept additional audio.
|
25
|
+
#
|
26
|
+
# Plaything is considered thread-safe.
|
8
27
|
class Plaything
|
9
28
|
Error = Class.new(StandardError)
|
29
|
+
Formats = {
|
30
|
+
[ :int16, 1 ] => :mono16,
|
31
|
+
[ :int16, 2 ] => :stereo16,
|
32
|
+
}
|
10
33
|
|
11
34
|
# Open the default output device and prepare it for playback.
|
12
|
-
|
13
|
-
# @param [Hash] options
|
14
|
-
# @option options [Symbol] sample_type (:int16)
|
15
|
-
# @option options [Integer] sample_rate (44100)
|
16
|
-
# @option options [Integer] channels (2)
|
17
|
-
def initialize(options = { sample_type: :int16, sample_rate: 44100, channels: 2 })
|
35
|
+
def initialize(format = { sample_rate: 44100, sample_type: :int16, channels: 2 })
|
18
36
|
@device = OpenAL.open_device(nil)
|
19
37
|
raise Error, "Failed to open device" if @device.null?
|
20
38
|
|
@@ -28,14 +46,6 @@ class Plaything
|
|
28
46
|
@source = OpenAL::Source.new(ptr.read_uint)
|
29
47
|
end
|
30
48
|
|
31
|
-
@sample_type = options.fetch(:sample_type)
|
32
|
-
@sample_rate = Integer(options.fetch(:sample_rate))
|
33
|
-
@channels = Integer(options.fetch(:channels))
|
34
|
-
|
35
|
-
@sample_format = { [ :int16, 2 ] => :stereo16, }.fetch([@sample_type, @channels]) do
|
36
|
-
raise TypeError, "unknown sample format for type [#{@sample_type}, #{@channels}]"
|
37
|
-
end
|
38
|
-
|
39
49
|
FFI::MemoryPointer.new(OpenAL::Buffer, 3) do |ptr|
|
40
50
|
OpenAL.gen_buffers(ptr.count, ptr)
|
41
51
|
@buffers = OpenAL::Buffer.extract(ptr, ptr.count)
|
@@ -45,103 +55,170 @@ class Plaything
|
|
45
55
|
@queued_buffers = []
|
46
56
|
@queued_frames = []
|
47
57
|
|
48
|
-
|
49
|
-
@buffer_size = @sample_rate * @channels * 1.0
|
50
|
-
# how many samples there are in each buffer, irrespective of channels
|
51
|
-
@buffer_length = @buffer_size / @channels
|
52
|
-
# buffer_duration = buffer_length / sample_rate
|
53
|
-
|
58
|
+
@drops = 0
|
54
59
|
@total_buffers_processed = 0
|
60
|
+
|
61
|
+
@monitor = Monitor.new
|
62
|
+
|
63
|
+
self.format = format
|
55
64
|
end
|
56
65
|
|
66
|
+
# @return [Plaything::OpenAL::Source] the back-end audio source.
|
67
|
+
attr_reader :source
|
68
|
+
|
57
69
|
# Start playback of queued audio.
|
58
70
|
#
|
59
71
|
# @note You must continue to supply audio, or playback will cease.
|
60
72
|
def play
|
61
|
-
|
73
|
+
synchronize { @source.play }
|
62
74
|
end
|
63
75
|
|
64
76
|
# Pause playback of queued audio. Playback will resume from current position when {#play} is called.
|
65
77
|
def pause
|
66
|
-
|
78
|
+
synchronize { @source.pause }
|
67
79
|
end
|
68
80
|
|
69
81
|
# Stop playback and clear any queued audio.
|
70
82
|
#
|
71
83
|
# @note All audio queues are completely cleared, and {#position} is reset.
|
72
84
|
def stop
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
85
|
+
synchronize do
|
86
|
+
@source.stop
|
87
|
+
@source.detach_buffers
|
88
|
+
@free_buffers.concat(@queued_buffers)
|
89
|
+
@queued_buffers.clear
|
90
|
+
@queued_frames.clear
|
91
|
+
@total_buffers_processed = 0
|
92
|
+
end
|
79
93
|
end
|
80
94
|
|
81
95
|
# @return [Rational] how many seconds of audio that has been played.
|
82
96
|
def position
|
83
|
-
|
97
|
+
synchronize do
|
98
|
+
total_samples_processed = @total_buffers_processed * @buffer_length
|
99
|
+
Rational(total_samples_processed + @source.sample_offset, @sample_rate)
|
100
|
+
end
|
84
101
|
end
|
85
102
|
|
86
103
|
# @return [Integer] total size of current play queue.
|
87
104
|
def queue_size
|
88
|
-
|
105
|
+
synchronize do
|
106
|
+
@source.buffers_queued * @buffer_length - @source.sample_offset
|
107
|
+
end
|
89
108
|
end
|
90
109
|
|
91
110
|
# @return [Integer] how many audio drops since last call to drops.
|
92
111
|
def drops
|
93
|
-
|
112
|
+
synchronize do
|
113
|
+
@drops.tap { @drops = 0 }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Hash] current audio format in the queues
|
118
|
+
def format
|
119
|
+
synchronize do
|
120
|
+
{
|
121
|
+
sample_rate: @sample_rate,
|
122
|
+
sample_type: @sample_type,
|
123
|
+
channels: @channels,
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Change the format.
|
129
|
+
#
|
130
|
+
# @note if there is any queued audio it will be cleared,
|
131
|
+
# and the playback will be stopped.
|
132
|
+
#
|
133
|
+
# @param [Hash] format
|
134
|
+
# @option format [Symbol] sample_type only :int16 available
|
135
|
+
# @option format [Integer] sample_rate
|
136
|
+
# @option format [Integer] channels 1 or 2
|
137
|
+
def format=(format)
|
138
|
+
synchronize do
|
139
|
+
if @source.playing?
|
140
|
+
stop
|
141
|
+
@drops += 1
|
142
|
+
end
|
143
|
+
|
144
|
+
@sample_type = format.fetch(:sample_type)
|
145
|
+
@sample_rate = Integer(format.fetch(:sample_rate))
|
146
|
+
@channels = Integer(format.fetch(:channels))
|
147
|
+
|
148
|
+
@sample_format = Formats.fetch([@sample_type, @channels]) do
|
149
|
+
raise TypeError, "unknown sample format for type [#{@sample_type}, #{@channels}]"
|
150
|
+
end
|
151
|
+
|
152
|
+
# 44100 int16s = 22050 frames = 0.5s (1 frame * 2 channels = 2 int16 = 1 sample = 1/44100 s)
|
153
|
+
@buffer_size = @sample_rate * @channels * 1.0
|
154
|
+
# how many samples there are in each buffer, irrespective of channels
|
155
|
+
@buffer_length = @buffer_size / @channels
|
156
|
+
# buffer_duration = buffer_length / sample_rate
|
157
|
+
end
|
94
158
|
end
|
95
159
|
|
96
160
|
# Queue audio frames for playback.
|
97
161
|
#
|
98
|
-
# @
|
162
|
+
# @note this method is here for backwards-compatibility,
|
163
|
+
# and does not support changing format automatically.
|
164
|
+
# You should use {#stream} instead.
|
165
|
+
#
|
166
|
+
# @param [Array<Integer>] array of interleaved audio samples.
|
167
|
+
# @return (see #stream)
|
99
168
|
def <<(frames)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
169
|
+
stream(frames, format)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Queue audio frames for playback.
|
173
|
+
#
|
174
|
+
# @param [Array<Integer>] array of interleaved audio samples.
|
175
|
+
# @param [Hash] format
|
176
|
+
# @option format [Symbol] :sample_type should be :int16
|
177
|
+
# @option format [Integer] :sample_rate
|
178
|
+
# @option format [Integer] :channels
|
179
|
+
# @return [Integer] number of frames consumed (consumed_samples / channels), a multiple of channels
|
180
|
+
def stream(frames, frame_format)
|
181
|
+
synchronize do
|
182
|
+
if @source.playing? and @source.buffers_processed > 0
|
183
|
+
FFI::MemoryPointer.new(OpenAL::Buffer, @source.buffers_processed) do |ptr|
|
184
|
+
OpenAL.source_unqueue_buffers(@source, ptr.count, ptr)
|
185
|
+
@total_buffers_processed += ptr.count
|
186
|
+
@free_buffers.concat OpenAL::Buffer.extract(ptr, ptr.count)
|
187
|
+
@queued_buffers.delete_if { |buffer| @free_buffers.include?(buffer) }
|
188
|
+
end
|
106
189
|
end
|
107
|
-
end
|
108
190
|
|
109
|
-
|
110
|
-
consumed_frames = frames.take(wanted_size)
|
111
|
-
@queued_frames.concat(consumed_frames)
|
191
|
+
self.format = frame_format if frame_format != format
|
112
192
|
|
113
|
-
|
114
|
-
|
193
|
+
wanted_size = (@buffer_size - @queued_frames.length).div(@channels) * @channels
|
194
|
+
consumed_frames = frames.take(wanted_size)
|
195
|
+
@queued_frames.concat(consumed_frames)
|
115
196
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
197
|
+
if @queued_frames.length >= @buffer_size and @free_buffers.any?
|
198
|
+
current_buffer = @free_buffers.shift
|
199
|
+
|
200
|
+
FFI::MemoryPointer.new(@sample_type, @queued_frames.length) do |frames|
|
201
|
+
frames.public_send(:"write_array_of_#{@sample_type}", @queued_frames)
|
202
|
+
# stereo16 = 2 int16s (1 frame) = 1 sample
|
203
|
+
OpenAL.buffer_data(current_buffer, @sample_format, frames, frames.size, @sample_rate)
|
204
|
+
@queued_frames.clear
|
205
|
+
end
|
206
|
+
|
207
|
+
FFI::MemoryPointer.new(OpenAL::Buffer, 1) do |buffers|
|
208
|
+
buffers.write_uint(current_buffer.to_native)
|
209
|
+
OpenAL.source_queue_buffers(@source, buffers.count, buffers)
|
210
|
+
end
|
122
211
|
|
123
|
-
|
124
|
-
buffers.write_uint(current_buffer.to_native)
|
125
|
-
OpenAL.source_queue_buffers(@source, buffers.count, buffers)
|
212
|
+
@queued_buffers.push(current_buffer)
|
126
213
|
end
|
127
214
|
|
128
|
-
@
|
215
|
+
consumed_frames.length / @channels
|
129
216
|
end
|
130
|
-
|
131
|
-
consumed_frames.length
|
132
217
|
end
|
133
218
|
|
134
219
|
protected
|
135
220
|
|
136
|
-
def
|
137
|
-
@
|
138
|
-
end
|
139
|
-
|
140
|
-
def buffers_processed
|
141
|
-
if not @source.stopped?
|
142
|
-
@source.get(:buffers_processed, Integer)
|
143
|
-
else
|
144
|
-
0
|
145
|
-
end
|
221
|
+
def synchronize
|
222
|
+
@monitor.synchronize { return yield }
|
146
223
|
end
|
147
224
|
end
|
@@ -3,6 +3,37 @@ class Plaything
|
|
3
3
|
class Source < TypeClass(FFI::Type::UINT)
|
4
4
|
include OpenAL::Paramable(:source)
|
5
5
|
|
6
|
+
# Start playback.
|
7
|
+
def play
|
8
|
+
OpenAL.source_play(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Pause playback.
|
12
|
+
def pause
|
13
|
+
OpenAL.source_pause(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Stop playback and rewind the source.
|
17
|
+
def stop
|
18
|
+
OpenAL.source_stop(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Integer] how many samples (/ channels) that have been played from the queued buffers
|
22
|
+
def sample_offset
|
23
|
+
get(:sample_offset, Integer)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Integer] number of queued buffers.
|
27
|
+
def buffers_queued
|
28
|
+
get(:buffers_queued, Integer)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @note returns {#buffers_queued} if source is not playing!
|
32
|
+
# @return [Integer] number of processed buffers.
|
33
|
+
def buffers_processed
|
34
|
+
get(:buffers_processed, Integer)
|
35
|
+
end
|
36
|
+
|
6
37
|
# Detach all queued or attached buffers.
|
7
38
|
#
|
8
39
|
# @note all buffers must be processed for this operation to succeed.
|
data/lib/plaything/openal.rb
CHANGED
@@ -5,6 +5,8 @@ class Plaything
|
|
5
5
|
ffi_lib ["openal", "/System/Library/Frameworks/OpenAL.framework/Versions/Current/OpenAL"]
|
6
6
|
|
7
7
|
typedef :pointer, :attributes
|
8
|
+
typedef :pointer, :source_array
|
9
|
+
typedef :pointer, :buffer_array
|
8
10
|
typedef :int, :sizei
|
9
11
|
|
10
12
|
# Errors
|
@@ -61,15 +63,15 @@ class Plaything
|
|
61
63
|
attach_function :alcMakeContextCurrent, [ Context ], :bool
|
62
64
|
|
63
65
|
# Sources
|
64
|
-
attach_function :alGenSources, [ :sizei, :
|
65
|
-
attach_function :alDeleteSources, [ :sizei, :
|
66
|
+
attach_function :alGenSources, [ :sizei, :source_array ], :void
|
67
|
+
attach_function :alDeleteSources, [ :sizei, :source_array ], :void
|
66
68
|
|
67
69
|
attach_function :alSourcePlay, [ Source ], :void
|
68
70
|
attach_function :alSourcePause, [ Source ], :void
|
69
71
|
attach_function :alSourceStop, [ Source ], :void
|
70
72
|
|
71
|
-
attach_function :alSourceQueueBuffers, [ Source, :sizei, :
|
72
|
-
attach_function :alSourceUnqueueBuffers, [ Source, :sizei, :
|
73
|
+
attach_function :alSourceQueueBuffers, [ Source, :sizei, :buffer_array ], :void
|
74
|
+
attach_function :alSourceUnqueueBuffers, [ Source, :sizei, :buffer_array ], :void
|
73
75
|
|
74
76
|
# Buffers
|
75
77
|
enum :format, [
|
@@ -78,8 +80,8 @@ class Plaything
|
|
78
80
|
:stereo8, 0x1102,
|
79
81
|
:stereo16, 0x1103,
|
80
82
|
]
|
81
|
-
attach_function :alGenBuffers, [ :sizei, :
|
82
|
-
attach_function :alDeleteBuffers, [ :sizei, :
|
83
|
+
attach_function :alGenBuffers, [ :sizei, :buffer_array ], :void
|
84
|
+
attach_function :alDeleteBuffers, [ :sizei, :buffer_array ], :void
|
83
85
|
|
84
86
|
attach_function :alBufferData, [ Buffer, :format, :pointer, :sizei, :sizei ], :void
|
85
87
|
|
@@ -13,13 +13,6 @@ class Plaything
|
|
13
13
|
rescue => e
|
14
14
|
warn "release for #{name} failed: #{e.message}."
|
15
15
|
end
|
16
|
-
|
17
|
-
def allocate(*args, &block)
|
18
|
-
pointer = FFI::MemoryPointer.new(*args)
|
19
|
-
yield pointer
|
20
|
-
pointer.autorelease = false
|
21
|
-
new(FFI::Pointer.new(pointer))
|
22
|
-
end
|
23
16
|
end
|
24
17
|
end
|
25
18
|
end
|
@@ -3,17 +3,16 @@ class Plaything
|
|
3
3
|
def self.TypeClass(type)
|
4
4
|
Class.new do
|
5
5
|
extend FFI::DataConverter
|
6
|
-
|
6
|
+
|
7
|
+
define_singleton_method(:type) do
|
8
|
+
type
|
9
|
+
end
|
7
10
|
|
8
11
|
class << self
|
9
12
|
def inherited(other)
|
10
13
|
other.native_type(type)
|
11
14
|
end
|
12
15
|
|
13
|
-
def type
|
14
|
-
@@type
|
15
|
-
end
|
16
|
-
|
17
16
|
def to_native(source, ctx)
|
18
17
|
source.value
|
19
18
|
end
|
@@ -25,12 +24,6 @@ class Plaything
|
|
25
24
|
def size
|
26
25
|
type.size
|
27
26
|
end
|
28
|
-
|
29
|
-
def extract(pointer, count)
|
30
|
-
pointer.read_array_of_type(self, :read_uint, count).map do |uint|
|
31
|
-
new(uint)
|
32
|
-
end
|
33
|
-
end
|
34
27
|
end
|
35
28
|
|
36
29
|
def initialize(value)
|
data/lib/plaything/version.rb
CHANGED
metadata
CHANGED
@@ -1,64 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plaything
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 1.0.0
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Kim Burgestrand
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-04-09 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
version_requirements: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ~>
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.1'
|
20
|
-
none: false
|
21
|
-
prerelease: false
|
22
14
|
name: ffi
|
23
15
|
requirement: !ruby/object:Gem::Requirement
|
24
16
|
requirements:
|
25
17
|
- - ~>
|
26
18
|
- !ruby/object:Gem::Version
|
27
19
|
version: '1.1'
|
28
|
-
none: false
|
29
20
|
type: :runtime
|
30
|
-
|
21
|
+
prerelease: false
|
31
22
|
version_requirements: !ruby/object:Gem::Requirement
|
32
23
|
requirements:
|
33
|
-
- -
|
24
|
+
- - ~>
|
34
25
|
- !ruby/object:Gem::Version
|
35
|
-
version: '
|
36
|
-
|
37
|
-
prerelease: false
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
38
28
|
name: rspec
|
39
29
|
requirement: !ruby/object:Gem::Requirement
|
40
30
|
requirements:
|
41
|
-
- -
|
31
|
+
- - '>='
|
42
32
|
- !ruby/object:Gem::Version
|
43
33
|
version: '0'
|
44
|
-
none: false
|
45
34
|
type: :development
|
46
|
-
|
35
|
+
prerelease: false
|
47
36
|
version_requirements: !ruby/object:Gem::Requirement
|
48
37
|
requirements:
|
49
|
-
- -
|
38
|
+
- - '>='
|
50
39
|
- !ruby/object:Gem::Version
|
51
40
|
version: '0'
|
52
|
-
|
53
|
-
prerelease: false
|
41
|
+
- !ruby/object:Gem::Dependency
|
54
42
|
name: rake
|
55
43
|
requirement: !ruby/object:Gem::Requirement
|
56
44
|
requirements:
|
57
|
-
- -
|
45
|
+
- - '>='
|
58
46
|
- !ruby/object:Gem::Version
|
59
47
|
version: '0'
|
60
|
-
none: false
|
61
48
|
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
62
55
|
description:
|
63
56
|
email:
|
64
57
|
- kim@burgestrand.se
|
@@ -90,30 +83,26 @@ files:
|
|
90
83
|
homepage: https://github.com/Burgestrand/plaything
|
91
84
|
licenses:
|
92
85
|
- MIT
|
86
|
+
metadata: {}
|
93
87
|
post_install_message:
|
94
88
|
rdoc_options: []
|
95
89
|
require_paths:
|
96
90
|
- lib
|
97
91
|
required_ruby_version: !ruby/object:Gem::Requirement
|
98
92
|
requirements:
|
99
|
-
- -
|
93
|
+
- - '>='
|
100
94
|
- !ruby/object:Gem::Version
|
101
95
|
version: '1.9'
|
102
|
-
none: false
|
103
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
97
|
requirements:
|
105
|
-
- -
|
98
|
+
- - '>='
|
106
99
|
- !ruby/object:Gem::Version
|
107
100
|
version: '0'
|
108
|
-
segments:
|
109
|
-
- 0
|
110
|
-
hash: 2964046639458012733
|
111
|
-
none: false
|
112
101
|
requirements: []
|
113
102
|
rubyforge_project:
|
114
|
-
rubygems_version:
|
103
|
+
rubygems_version: 2.0.3
|
115
104
|
signing_key:
|
116
|
-
specification_version:
|
105
|
+
specification_version: 4
|
117
106
|
summary: Blast raw PCM audio through your speakers using OpenAL.
|
118
107
|
test_files:
|
119
108
|
- spec/plaything_spec.rb
|