sound 0.0.9 → 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.
- checksums.yaml +8 -8
- data/examples/example.rb +1 -1
- data/lib/os/os.rb +20 -15
- data/lib/sound.rb +17 -19
- data/lib/sound/data.rb +0 -3
- data/lib/sound/device.rb +72 -33
- data/lib/sound/device_library.rb +19 -0
- data/lib/sound/device_library/alsa.rb +240 -0
- data/lib/sound/device_library/base.rb +44 -0
- data/lib/sound/device_library/mmlib.rb +237 -0
- data/lib/sound/format.rb +8 -29
- data/lib/sound/format_library.rb +15 -0
- data/lib/sound/format_library/alsa.rb +10 -0
- data/lib/sound/format_library/base.rb +16 -0
- data/lib/sound/format_library/mmlib.rb +61 -0
- metadata +14 -14
- data/lib/sound/alsa.rb +0 -211
- data/lib/sound/win32.rb +0 -150
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'sound/device_library'
|
2
|
+
|
3
|
+
module Sound
|
4
|
+
module Library
|
5
|
+
module Base
|
6
|
+
def Base.please_implement
|
7
|
+
raise NoMethodError, "Please implement ##{method} for your Library."
|
8
|
+
end
|
9
|
+
class Handle
|
10
|
+
def pointer
|
11
|
+
raise NoMethodError, "Please implement #{self.class}##{__method__} for your Library"
|
12
|
+
end
|
13
|
+
def id
|
14
|
+
raise NoMethodError, "Please implement #{self.class}##{__method__} for your Library"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
private
|
18
|
+
def open_device
|
19
|
+
Base.please_implement(__method__)
|
20
|
+
end
|
21
|
+
def prepare_buffer
|
22
|
+
Base.please_implement(__method__)
|
23
|
+
end
|
24
|
+
def write_to_device
|
25
|
+
Base.please_implement(__method__)
|
26
|
+
end
|
27
|
+
def unprepare_buffer
|
28
|
+
Base.please_implement(__method__)
|
29
|
+
end
|
30
|
+
def close_device
|
31
|
+
Base.please_implement(__method__)
|
32
|
+
end
|
33
|
+
def handle
|
34
|
+
Thread.current[:handle] ||= Handle.new
|
35
|
+
end
|
36
|
+
def data
|
37
|
+
Thread.current[:data] ||= Sound::Data.new
|
38
|
+
end
|
39
|
+
def data_buffer
|
40
|
+
Thread.current[:data_buffer] ||= FFI::MemoryPointer.new(:int, data.pcm_data.size).write_array_of_int data.pcm_data
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
#gem 'ffi', '=1.3.1'
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
module Sound
|
5
|
+
module DeviceLibrary
|
6
|
+
module MMLib
|
7
|
+
extend self
|
8
|
+
|
9
|
+
class Handle
|
10
|
+
def initialize
|
11
|
+
@handle = FFI::MemoryPointer.new(:pointer)
|
12
|
+
end
|
13
|
+
def pointer
|
14
|
+
@handle
|
15
|
+
end
|
16
|
+
def id
|
17
|
+
@handle.read_int
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
extend FFI::Library
|
22
|
+
|
23
|
+
typedef :ulong, :dword
|
24
|
+
typedef :uintptr_t, :hwaveout
|
25
|
+
typedef :uint, :mmresult
|
26
|
+
|
27
|
+
ffi_lib :winmm
|
28
|
+
ffi_convention :stdcall
|
29
|
+
|
30
|
+
callback :waveOutProc, [:hwaveout, :uint, :pointer, :ulong, :ulong], :void
|
31
|
+
attach_function :waveOutOpen, [:pointer, :uint, :pointer, :dword, :dword, :dword], :mmresult
|
32
|
+
attach_function :waveOutPrepareHeader, [:hwaveout, :pointer, :uint], :mmresult
|
33
|
+
attach_function :waveOutWrite, [:hwaveout, :pointer, :uint], :mmresult
|
34
|
+
attach_function :waveOutUnprepareHeader, [:hwaveout, :pointer, :uint], :mmresult
|
35
|
+
attach_function :waveOutClose, [:hwaveout], :mmresult
|
36
|
+
attach_function :midiOutOpen, [:pointer, :uint, :dword, :dword, :dword], :mmresult
|
37
|
+
attach_function :midiOutClose, [:uintptr_t], :mmresult
|
38
|
+
attach_function :midiOutShortMsg, [:uintptr_t, :ulong], :mmresult
|
39
|
+
|
40
|
+
WaveOutProc = Proc.new do |hwo, uMsg, dwInstance, dwParam1, dwParam2|
|
41
|
+
# explicit returns in this callback will result in an error
|
42
|
+
case uMsg
|
43
|
+
when WOM_OPEN
|
44
|
+
puts "haha"
|
45
|
+
sleep 3
|
46
|
+
when WOM_DONE
|
47
|
+
block_mutex.lock
|
48
|
+
dwInstance.write_int(dwInstance.read_int + 1)
|
49
|
+
block_mutex.unlock
|
50
|
+
when WOM_CLOSE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
WAVE_MAPPER = -1
|
55
|
+
DEFAULT_DEVICE_ID = WAVE_MAPPER
|
56
|
+
|
57
|
+
WOM_OPEN = 0x3BB
|
58
|
+
WOM_CLOSE = 0x3BC
|
59
|
+
WOM_DONE = 0x3BD
|
60
|
+
CALLBACK_FUNCTION = 0x30000
|
61
|
+
|
62
|
+
def play_midi_notes
|
63
|
+
handle = FFI::MemoryPointer.new(:pointer)
|
64
|
+
midiOutOpen(handle, -1, 0, 0, 0)
|
65
|
+
midiOutShortMsg(handle.read_int, 0x007F3C90)
|
66
|
+
sleep 0.3
|
67
|
+
midiOutShortMsg(handle.read_int, 0x007F4090)
|
68
|
+
sleep 0.3
|
69
|
+
midiOutShortMsg(handle.read_int, 0x007F4390)
|
70
|
+
sleep 2
|
71
|
+
midiOutShortMsg(handle.read_int, 0x00003C90)
|
72
|
+
midiOutShortMsg(handle.read_int, 0x00004090)
|
73
|
+
midiOutShortMsg(handle.read_int, 0x00004390)
|
74
|
+
midiOutClose(handle.read_int)
|
75
|
+
end
|
76
|
+
|
77
|
+
def play_with_multiple_buffers(buffer_count = 2)
|
78
|
+
|
79
|
+
data = Sound::Data.new.sine_wave(440, 200, 1)
|
80
|
+
free_blocks.write_int buffer_count
|
81
|
+
waveOutOpen(handle, id, data.format.pointer, WaveOutProc, free_blocks.address, CALLBACK_FUNCTION)
|
82
|
+
|
83
|
+
data = data.data
|
84
|
+
|
85
|
+
data1 = data[0...(data.length/2)]
|
86
|
+
data_buffer = FFI::MemoryPointer.new(:int, data1.size)
|
87
|
+
data_buffer.write_array_of_int data1
|
88
|
+
buffer_length = wfx[:nAvgBytesPerSec]*100/1000
|
89
|
+
header = WAVEHDR.new(data_buffer, buffer_length)
|
90
|
+
waveOutPrepareHeader(handle.id, header.pointer, header.size)
|
91
|
+
block_mutex.lock
|
92
|
+
free_blocks.write_int(free_blocks.read_int - 1)
|
93
|
+
block_mutex.unlock
|
94
|
+
|
95
|
+
|
96
|
+
data2 = data[(data.length/2)..-1]
|
97
|
+
data_buffer = FFI::MemoryPointer.new(:int, data2.size)
|
98
|
+
data_buffer.write_array_of_int data2
|
99
|
+
buffer_length = wfx[:nAvgBytesPerSec]*100/1000
|
100
|
+
header = WAVEHDR.new(data_buffer, buffer_length)
|
101
|
+
waveOutPrepareHeader(handle.id, header2.pointer, header2.size)
|
102
|
+
block_mutex.lock
|
103
|
+
free_blocks.write_int(free_blocks.read_int - 1)
|
104
|
+
block_mutex.unlock
|
105
|
+
|
106
|
+
waveOutWrite(handle.id, header.pointer, header.size)
|
107
|
+
waveOutWrite(handle.id, header2.pointer, header2.size)
|
108
|
+
|
109
|
+
until free_blocks.read_int == 2
|
110
|
+
sleep 0.01
|
111
|
+
end
|
112
|
+
|
113
|
+
waveOutUnprepareHeader(handle.id, header.pointer, header.size)
|
114
|
+
waveOutUnprepareHeader(handle.id, header2.pointer, header2.size)
|
115
|
+
|
116
|
+
waveOutClose(handle.id)
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
def open_device(device)
|
121
|
+
waveOutOpen(handle.pointer, device.id, data.format.pointer, 0, 0, 0)
|
122
|
+
end
|
123
|
+
|
124
|
+
def prepare_buffer
|
125
|
+
waveOutPrepareHeader(handle.id, header.pointer, header.size)
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_to_device
|
129
|
+
waveOutWrite(handle.id, header.pointer, header.size)
|
130
|
+
end
|
131
|
+
|
132
|
+
def unprepare_buffer
|
133
|
+
while waveOutUnprepareHeader(handle.id, header.pointer, header.size) == 33
|
134
|
+
sleep 0.001
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def close_device
|
139
|
+
waveOutClose(handle.id)
|
140
|
+
end
|
141
|
+
|
142
|
+
def handle
|
143
|
+
Thread.current[:handle] ||= Handle.new
|
144
|
+
end
|
145
|
+
|
146
|
+
def data
|
147
|
+
Thread.current[:data] ||= Sound::Data.new
|
148
|
+
end
|
149
|
+
|
150
|
+
def data_buffer
|
151
|
+
Thread.current[:data_buffer] ||= FFI::MemoryPointer.new(:int, data.pcm_data.size).write_array_of_int data.pcm_data
|
152
|
+
end
|
153
|
+
|
154
|
+
def header
|
155
|
+
Thread.current[:header] ||= WAVEHDR.new(data_buffer, buffer_length)
|
156
|
+
end
|
157
|
+
|
158
|
+
def buffer_length
|
159
|
+
Thread.current[:buffer_length] ||= data.format.avg_bps*data.duration/1000
|
160
|
+
end
|
161
|
+
|
162
|
+
def free_blocks
|
163
|
+
Thread.current[:free_blocks] ||= FFI::MemoryPointer.new(:ulong)
|
164
|
+
end
|
165
|
+
|
166
|
+
def block_mutex
|
167
|
+
Thread.current[:block_mutex] ||= Mutex.new
|
168
|
+
end
|
169
|
+
|
170
|
+
def pointer
|
171
|
+
self.wfx.pointer
|
172
|
+
end
|
173
|
+
|
174
|
+
#define WAVEHDR which is a header to a block of audio
|
175
|
+
#lpData is a pointer to the block of native memory that,
|
176
|
+
# in this case, is an integer array of PCM data
|
177
|
+
|
178
|
+
class WAVEHDR < FFI::Struct
|
179
|
+
|
180
|
+
# Initializes struct with sensible defaults for most commonly used
|
181
|
+
# values. While setting these manually is possible, please be
|
182
|
+
# sure you know what changes will result in, as an incorrectly
|
183
|
+
# set struct will result in unpredictable behavior.
|
184
|
+
#
|
185
|
+
def initialize(lpData, dwBufferLength, dwFlags = 0, dwLoops = 1)
|
186
|
+
self[:lpData] = lpData
|
187
|
+
self[:dwBufferLength] = dwBufferLength
|
188
|
+
self[:dwFlags] = dwFlags
|
189
|
+
self[:dwLoops] = dwLoops
|
190
|
+
end
|
191
|
+
|
192
|
+
layout(
|
193
|
+
:lpData, :pointer,
|
194
|
+
:dwBufferLength, :ulong,
|
195
|
+
:dwBytesRecorded, :ulong,
|
196
|
+
:dwUser, :ulong,
|
197
|
+
:dwFlags, :ulong,
|
198
|
+
:dwLoops, :ulong,
|
199
|
+
:lpNext, :pointer,
|
200
|
+
:reserved, :ulong
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Define WAVEFORMATEX which defines the format (PCM in this case)
|
205
|
+
# and various properties like sampling rate, number of channels, etc.
|
206
|
+
#
|
207
|
+
class WAVEFORMATEX < FFI::Struct
|
208
|
+
|
209
|
+
# Initializes struct with sensible defaults for most commonly used
|
210
|
+
# values. While setting these manually is possible, please be
|
211
|
+
# sure you know what changes will result in, as an incorrectly
|
212
|
+
# set struct will result in unpredictable behavior.
|
213
|
+
#
|
214
|
+
def initialize(nSamplesPerSec = 44100, wBitsPerSample = 16, nChannels = 1, cbSize = 0)
|
215
|
+
self[:wFormatTag] = WAVE_FORMAT_PCM
|
216
|
+
self[:nChannels] = nChannels
|
217
|
+
self[:nSamplesPerSec] = nSamplesPerSec
|
218
|
+
self[:wBitsPerSample] = wBitsPerSample
|
219
|
+
self[:cbSize] = cbSize
|
220
|
+
self[:nBlockAlign] = (self[:wBitsPerSample] >> 3) * self[:nChannels]
|
221
|
+
self[:nAvgBytesPerSec] = self[:nBlockAlign] * self[:nSamplesPerSec]
|
222
|
+
end
|
223
|
+
|
224
|
+
layout(
|
225
|
+
:wFormatTag, :ushort,
|
226
|
+
:nChannels, :ushort,
|
227
|
+
:nSamplesPerSec, :ulong,
|
228
|
+
:nAvgBytesPerSec, :ulong,
|
229
|
+
:nBlockAlign, :ushort,
|
230
|
+
:wBitsPerSample, :ushort,
|
231
|
+
:cbSize, :ushort
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
data/lib/sound/format.rb
CHANGED
@@ -1,38 +1,17 @@
|
|
1
|
-
|
1
|
+
require 'sound/format_library'
|
2
2
|
|
3
3
|
module Sound
|
4
|
-
|
5
|
-
WAVE_FORMAT_PCM = 1
|
6
4
|
|
7
5
|
class Format
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(
|
6
|
+
include FormatLibrary
|
7
|
+
attr_accessor :channels, :sample_rate, :bits_per_sample, :type
|
8
|
+
alias :bps :bits_per_sample
|
9
|
+
def initialize(type = FormatLibrary::DEFAULT_FORMAT)
|
12
10
|
@channels = 1
|
13
11
|
@sample_rate = 44100
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
@wfx[:wFormatTag] = format_type
|
18
|
-
@wfx[:nChannels] = channels
|
19
|
-
@wfx[:nSamplesPerSec] = sample_rate
|
20
|
-
@wfx[:wBitsPerSample] = bps
|
21
|
-
@wfx[:cbSize] = 0
|
22
|
-
@wfx[:nBlockAlign] = block_align
|
23
|
-
@wfx[:nAvgBytesPerSec] = avg_bps
|
24
|
-
end
|
25
|
-
end
|
26
|
-
def block_align
|
27
|
-
(bps >> 3) * channels
|
28
|
-
end
|
29
|
-
def avg_bps
|
30
|
-
block_align * sample_rate
|
31
|
-
end
|
32
|
-
def pointer
|
33
|
-
if OS.windows?
|
34
|
-
@wfx.pointer
|
35
|
-
end
|
12
|
+
@bits_per_sample = 16
|
13
|
+
@type = type
|
14
|
+
new_format
|
36
15
|
end
|
37
16
|
end
|
38
17
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Sound
|
4
|
+
module FormatLibrary
|
5
|
+
#extend Forwardable
|
6
|
+
DEFAULT_FORMAT = Sound.format_library::DEFAULT_FORMAT
|
7
|
+
include Sound.format_library
|
8
|
+
#duties = [
|
9
|
+
# :new_format,
|
10
|
+
# :pointer,
|
11
|
+
# :avg_bps
|
12
|
+
#]
|
13
|
+
#delegate duties => Sound.format_library
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'sound/format_library/base'
|
2
|
+
|
3
|
+
module Sound
|
4
|
+
module FormatLibrary
|
5
|
+
module MMLib
|
6
|
+
include FormatLibrary::Base
|
7
|
+
|
8
|
+
WAVE_FORMAT_PCM = 1
|
9
|
+
DEFAULT_FORMAT = WAVE_FORMAT_PCM
|
10
|
+
|
11
|
+
attr_accessor :wfx
|
12
|
+
|
13
|
+
def new_format
|
14
|
+
self.wfx = WAVEFORMATEX.new
|
15
|
+
self.wfx[:wFormatTag] = type
|
16
|
+
self.wfx[:nChannels] = channels
|
17
|
+
self.wfx[:nSamplesPerSec] = sample_rate
|
18
|
+
self.wfx[:wBitsPerSample] = bps
|
19
|
+
self.wfx[:cbSize] = 0
|
20
|
+
self.wfx[:nBlockAlign] = block_align
|
21
|
+
self.wfx[:nAvgBytesPerSec] = avg_bps
|
22
|
+
end
|
23
|
+
|
24
|
+
def pointer
|
25
|
+
self.wfx.pointer
|
26
|
+
end
|
27
|
+
|
28
|
+
# Define WAVEFORMATEX which defines the format (PCM in this case)
|
29
|
+
# and various properties like sampling rate, number of channels, etc.
|
30
|
+
#
|
31
|
+
class WAVEFORMATEX < FFI::Struct
|
32
|
+
|
33
|
+
# Initializes struct with sensible defaults for most commonly used
|
34
|
+
# values. While setting these manually is possible, please be
|
35
|
+
# sure you know what changes will result in, as an incorrectly
|
36
|
+
# set struct will result in unpredictable behavior.
|
37
|
+
#
|
38
|
+
def initialize(nSamplesPerSec = 44100, wBitsPerSample = 16, nChannels = 1, cbSize = 0)
|
39
|
+
self[:wFormatTag] = WAVE_FORMAT_PCM
|
40
|
+
self[:nChannels] = nChannels
|
41
|
+
self[:nSamplesPerSec] = nSamplesPerSec
|
42
|
+
self[:wBitsPerSample] = wBitsPerSample
|
43
|
+
self[:cbSize] = cbSize
|
44
|
+
self[:nBlockAlign] = (self[:wBitsPerSample] >> 3) * self[:nChannels]
|
45
|
+
self[:nAvgBytesPerSec] = self[:nBlockAlign] * self[:nSamplesPerSec]
|
46
|
+
end
|
47
|
+
|
48
|
+
layout(
|
49
|
+
:wFormatTag, :ushort,
|
50
|
+
:nChannels, :ushort,
|
51
|
+
:nSamplesPerSec, :ulong,
|
52
|
+
:nAvgBytesPerSec, :ulong,
|
53
|
+
:nBlockAlign, :ushort,
|
54
|
+
:wBitsPerSample, :ushort,
|
55
|
+
:cbSize, :ushort
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
CHANGED
@@ -1,35 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sound
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominic Muller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.9'
|
20
|
-
- - ! '>='
|
17
|
+
- - '='
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: 1.
|
19
|
+
version: 1.3.1
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.9'
|
30
|
-
- - ! '>='
|
24
|
+
- - '='
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.
|
26
|
+
version: 1.3.1
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: rspec
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -60,11 +54,17 @@ files:
|
|
60
54
|
- examples/example.rb
|
61
55
|
- lib/os/os.rb
|
62
56
|
- lib/sound.rb
|
63
|
-
- lib/sound/alsa.rb
|
64
57
|
- lib/sound/data.rb
|
65
58
|
- lib/sound/device.rb
|
59
|
+
- lib/sound/device_library.rb
|
60
|
+
- lib/sound/device_library/alsa.rb
|
61
|
+
- lib/sound/device_library/base.rb
|
62
|
+
- lib/sound/device_library/mmlib.rb
|
66
63
|
- lib/sound/format.rb
|
67
|
-
- lib/sound/
|
64
|
+
- lib/sound/format_library.rb
|
65
|
+
- lib/sound/format_library/alsa.rb
|
66
|
+
- lib/sound/format_library/base.rb
|
67
|
+
- lib/sound/format_library/mmlib.rb
|
68
68
|
homepage: https://github.com/RSMP/sound
|
69
69
|
licenses:
|
70
70
|
- MIT
|