win32-sound 0.6.0 → 0.6.1
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGES +78 -73
- data/MANIFEST +10 -10
- data/README +57 -57
- data/Rakefile +33 -36
- data/certs/djberg96_pub.pem +21 -0
- data/examples/example_win32_sound.rb +56 -56
- data/lib/win32/sound.rb +350 -350
- data/lib/win32/windows/functions.rb +41 -41
- data/lib/win32/windows/structs.rb +78 -78
- data/test/test_win32_sound.rb +141 -141
- data/win32-sound.gemspec +26 -25
- metadata +44 -16
- metadata.gz.sig +3 -0
- data/.gemtest +0 -0
data/lib/win32/sound.rb
CHANGED
@@ -1,350 +1,350 @@
|
|
1
|
-
require_relative 'windows/functions'
|
2
|
-
require_relative 'windows/structs'
|
3
|
-
|
4
|
-
# The Win32 module serves as a namespace only.
|
5
|
-
module Win32
|
6
|
-
|
7
|
-
# The Sound class encapsulates various methods for playing sound as well
|
8
|
-
# as querying or configuring sound related properties.
|
9
|
-
class Sound
|
10
|
-
include Windows::SoundStructs
|
11
|
-
extend Windows::SoundFunctions
|
12
|
-
|
13
|
-
# The version of the win32-sound library
|
14
|
-
VERSION = '0.6.
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
LOW_FREQUENCY = 37
|
19
|
-
HIGH_FREQUENCY = 32767
|
20
|
-
MAX_VOLUME = 0xFFFF
|
21
|
-
WAVE_FORMAT_PCM = 1 # Waveform-audio data is PCM.
|
22
|
-
WAVE_MAPPER = -1 # Used by waveOutOpen. The function selects
|
23
|
-
# a waveform-audio output device capable of
|
24
|
-
# playing the given format.
|
25
|
-
|
26
|
-
public
|
27
|
-
|
28
|
-
SYNC = 0x00000000 # play synchronously (default)
|
29
|
-
ASYNC = 0x00000001 # play asynchronously
|
30
|
-
NODEFAULT = 0x00000002 # silence (!default) if sound not found
|
31
|
-
MEMORY = 0x00000004 # pszSound points to a memory file
|
32
|
-
LOOP = 0x00000008 # loop the sound until next sndPlaySound
|
33
|
-
NOSTOP = 0x00000010 # don't stop any currently playing sound
|
34
|
-
NOWAIT = 8192 # don't wait if the driver is busy
|
35
|
-
ALIAS = 65536 # name is a registry alias
|
36
|
-
ALIAS_ID = 1114112 # alias is a predefined ID
|
37
|
-
FILENAME = 131072 # name is file name
|
38
|
-
RESOURCE = 262148 # name is resource name or atom
|
39
|
-
PURGE = 0x00000040 # purge non-static events for task
|
40
|
-
APPLICATION = 0x00000080 # look for app specific association
|
41
|
-
|
42
|
-
# Plays a frequency for a specified duration at a given volume.
|
43
|
-
# Defaults are 440Hz, 1 second, full volume.
|
44
|
-
#
|
45
|
-
# The result is a single channel, 44100Hz sampled, 16 bit sine wave.
|
46
|
-
# If multiple instances are plays in simultaneous threads, they
|
47
|
-
# will be started and played at the same time.
|
48
|
-
#
|
49
|
-
# ex.: threads = []
|
50
|
-
# [440, 660].each do |freq|
|
51
|
-
# threads << Thread.new { Win32::Sound.play_freq(freq) }
|
52
|
-
# end
|
53
|
-
# threads.each { |th| th.join }
|
54
|
-
#
|
55
|
-
# The first frequency in this array (440) will wait until the
|
56
|
-
# thread for 660 finished calculating its PCM array and they
|
57
|
-
# will both start streaming at the same time.
|
58
|
-
#
|
59
|
-
# If immediate_playback is set to false, the thread will calculate
|
60
|
-
# all pending PCM arrays and wait to be woken up again. This
|
61
|
-
# if useful for time-sensitive playback of notes in succession.
|
62
|
-
#
|
63
|
-
def self.play_freq(frequency = 440, duration = 1000, volume = 1, immediate_playback = true)
|
64
|
-
|
65
|
-
if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
|
66
|
-
raise ArgumentError, 'invalid frequency'
|
67
|
-
end
|
68
|
-
|
69
|
-
if duration < 0 || duration > 5000
|
70
|
-
raise ArgumentError, 'invalid duration'
|
71
|
-
end
|
72
|
-
|
73
|
-
if volume.abs > 1
|
74
|
-
warn("WARNING: Volume greater than 1 will cause audio clipping.")
|
75
|
-
end
|
76
|
-
|
77
|
-
stream(immediate_playback) do |wfx|
|
78
|
-
data = generate_pcm_integer_array_for_freq(frequency, duration, volume)
|
79
|
-
data_buffer = FFI::MemoryPointer.new(:int, data.size)
|
80
|
-
data_buffer.write_array_of_int data
|
81
|
-
buffer_length = wfx[:nAvgBytesPerSec]*duration/1000
|
82
|
-
WAVEHDR.new(data_buffer, buffer_length)
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns an array of all the available sound devices; their names contain
|
88
|
-
# the type of the device and a zero-based ID number. Possible return values
|
89
|
-
# are WAVEOUT, WAVEIN, MIDIOUT, MIDIIN, AUX or MIXER.
|
90
|
-
#
|
91
|
-
def self.devices
|
92
|
-
devs = []
|
93
|
-
|
94
|
-
begin
|
95
|
-
0.upto(waveOutGetNumDevs()){ |i| devs << "WAVEOUT#{i}" }
|
96
|
-
0.upto(waveInGetNumDevs()){ |i| devs << "WAVEIN#{i}" }
|
97
|
-
0.upto(midiOutGetNumDevs()){ |i| devs << "MIDIOUT#{i}" }
|
98
|
-
0.upto(midiInGetNumDevs()){ |i| devs << "MIDIIN#{i}" }
|
99
|
-
0.upto(auxGetNumDevs()){ |i| devs << "AUX#{i}" }
|
100
|
-
0.upto(mixerGetNumDevs()){ |i| devs << "MIXER#{i}" }
|
101
|
-
rescue Exception
|
102
|
-
raise SystemCallError, FFI.errno, "GetNumDevs"
|
103
|
-
end
|
104
|
-
|
105
|
-
devs
|
106
|
-
end
|
107
|
-
|
108
|
-
# Generates simple tones on the speaker. The function is synchronous; it
|
109
|
-
# does not return control to its caller until the sound finishes.
|
110
|
-
#
|
111
|
-
# The frequency (in Hertz) must be between 37 and 32767.
|
112
|
-
# The duration is in milliseconds.
|
113
|
-
#
|
114
|
-
def self.beep(frequency, duration)
|
115
|
-
if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
|
116
|
-
raise ArgumentError, 'invalid frequency'
|
117
|
-
end
|
118
|
-
|
119
|
-
if 0 == Beep(frequency, duration)
|
120
|
-
raise SystemCallError, FFI.errno, "Beep"
|
121
|
-
end
|
122
|
-
|
123
|
-
self
|
124
|
-
end
|
125
|
-
|
126
|
-
# Stops any currently playing waveform sound. If +purge+ is set to
|
127
|
-
# true, then *all* sounds are stopped. The default is false.
|
128
|
-
#
|
129
|
-
def self.stop(purge = false)
|
130
|
-
if purge && purge != 0
|
131
|
-
flags = PURGE
|
132
|
-
else
|
133
|
-
flags = 0
|
134
|
-
end
|
135
|
-
|
136
|
-
unless PlaySound(nil, 0, flags)
|
137
|
-
raise SystemCallError, FFI.errno, "PlaySound"
|
138
|
-
end
|
139
|
-
|
140
|
-
self
|
141
|
-
end
|
142
|
-
|
143
|
-
# Plays the specified sound. The sound can be a wave file or a system
|
144
|
-
# sound, when used in conjunction with the ALIAS flag.
|
145
|
-
#
|
146
|
-
# Valid flags:
|
147
|
-
#
|
148
|
-
# Sound::ALIAS
|
149
|
-
# The sound parameter is a system-event alias in the registry or the
|
150
|
-
# WIN.INI file. If the registry contains no such name, it plays the
|
151
|
-
# system default sound unless the NODEFAULT value is also specified.
|
152
|
-
# Do not use with FILENAME.
|
153
|
-
#
|
154
|
-
# Sound::APPLICATION
|
155
|
-
# The sound is played using an application-specific association.
|
156
|
-
#
|
157
|
-
# Sound::ASYNC
|
158
|
-
# The sound is played asynchronously and the function returns
|
159
|
-
# immediately after beginning the sound.
|
160
|
-
#
|
161
|
-
# Sound::FILENAME
|
162
|
-
# The sound parameter is the name of a WAV file. Do not use with
|
163
|
-
# ALIAS.
|
164
|
-
#
|
165
|
-
# Sound::LOOP
|
166
|
-
# The sound plays repeatedly until Sound.stop() is called. You must
|
167
|
-
# also specify the ASYNC flag to loop sounds.
|
168
|
-
#
|
169
|
-
# Sound::MEMORY
|
170
|
-
# The sound points to an image of a waveform sound in memory.
|
171
|
-
#
|
172
|
-
# Sound::NODEFAULT
|
173
|
-
# If the sound cannot be found, the function returns silently without
|
174
|
-
# playing the default sound.
|
175
|
-
#
|
176
|
-
# Sound::NOSTOP
|
177
|
-
# If a sound is currently playing, the function immediately returns
|
178
|
-
# false without playing the requested sound.
|
179
|
-
#
|
180
|
-
# Sound::NOWAIT
|
181
|
-
# If the driver is busy, return immediately without playing the sound.
|
182
|
-
#
|
183
|
-
# Sound::PURGE
|
184
|
-
# Stop playing all instances of the specified sound.
|
185
|
-
#
|
186
|
-
# Sound::SYNC
|
187
|
-
# The sound is played synchronously and the function does not return
|
188
|
-
# until the sound ends.
|
189
|
-
#
|
190
|
-
# Examples:
|
191
|
-
#
|
192
|
-
# require 'win32/sound'
|
193
|
-
# include Win32
|
194
|
-
#
|
195
|
-
# # Play a wave file once
|
196
|
-
# Sound.play('some_file.wav')
|
197
|
-
#
|
198
|
-
# # Play a wave file in an asynchronous loop for 2 seconds
|
199
|
-
# Sound.play('some_file.wav', Sound::ASYNC | Sound::LOOP)
|
200
|
-
# sleep 2
|
201
|
-
# Sound.stop
|
202
|
-
#
|
203
|
-
def self.play(sound, flags = 0)
|
204
|
-
unless PlaySound(sound, 0, flags)
|
205
|
-
raise SystemCallError, FFI.errno, "PlaySound"
|
206
|
-
end
|
207
|
-
|
208
|
-
self
|
209
|
-
end
|
210
|
-
|
211
|
-
# Sets the volume for the left and right channel. If the +right_channel+
|
212
|
-
# is omitted, the volume is set for *both* channels.
|
213
|
-
#
|
214
|
-
# You may optionally pass a single Integer rather than an Array, in which
|
215
|
-
# case it is assumed you are setting both channels to the same value.
|
216
|
-
#
|
217
|
-
def self.set_wave_volume(left_channel, right_channel = nil)
|
218
|
-
right_channel ||= left_channel
|
219
|
-
|
220
|
-
lvolume = left_channel > MAX_VOLUME ? MAX_VOLUME : left_channel
|
221
|
-
rvolume = right_channel > MAX_VOLUME ? MAX_VOLUME : right_channel
|
222
|
-
|
223
|
-
volume = lvolume | rvolume << 16
|
224
|
-
|
225
|
-
if waveOutSetVolume(-1, volume) != 0
|
226
|
-
raise SystemCallError, FFI.errno, "waveOutSetVolume"
|
227
|
-
end
|
228
|
-
|
229
|
-
self
|
230
|
-
end
|
231
|
-
|
232
|
-
# Returns a 2-element array that contains the volume for the left channel
|
233
|
-
# and right channel, respectively.
|
234
|
-
#
|
235
|
-
def self.wave_volume
|
236
|
-
ptr = FFI::MemoryPointer.new(:ulong)
|
237
|
-
|
238
|
-
if waveOutGetVolume(-1, ptr) != 0
|
239
|
-
raise SystemCallError, FFI.errno, "waveOutGetVolume"
|
240
|
-
end
|
241
|
-
|
242
|
-
volume = ptr.read_long
|
243
|
-
|
244
|
-
[low_word(volume), high_word(volume)]
|
245
|
-
end
|
246
|
-
|
247
|
-
class << self
|
248
|
-
alias get_wave_volume wave_volume
|
249
|
-
end
|
250
|
-
|
251
|
-
private
|
252
|
-
|
253
|
-
def self.low_word(num)
|
254
|
-
num & 0xFFFF
|
255
|
-
end
|
256
|
-
|
257
|
-
def self.high_word(num)
|
258
|
-
num >> 16
|
259
|
-
end
|
260
|
-
|
261
|
-
# Sets up a ready-made waveOut stream to push a PCM integer array to.
|
262
|
-
# It expects a block to be associated with the method call to which
|
263
|
-
# it will yield an instance of WAVEFORMATEX that the block uses
|
264
|
-
# to prepare a WAVEHDR to return to the function.
|
265
|
-
#
|
266
|
-
# The WAVEHDR can contain either a self-made PCM integer array
|
267
|
-
# or an array from a wav file or some other audio file converted
|
268
|
-
# to PCM.
|
269
|
-
#
|
270
|
-
# This function will take the entire PCM array and create one
|
271
|
-
# giant buffer, so it is not intended for audio streams larger
|
272
|
-
# than 5 seconds.
|
273
|
-
#
|
274
|
-
# In order to play larger audio files, you will have to use the waveOut
|
275
|
-
# functions and structs to set up a double buffer to incrementally
|
276
|
-
# push PCM data to.
|
277
|
-
#
|
278
|
-
def self.stream(immediate_playback)
|
279
|
-
hWaveOut = HWAVEOUT.new
|
280
|
-
wfx = WAVEFORMATEX.new
|
281
|
-
|
282
|
-
wfx[:wFormatTag] = WAVE_FORMAT_PCM
|
283
|
-
wfx[:nChannels] = 1
|
284
|
-
wfx[:nSamplesPerSec] = 44100
|
285
|
-
wfx[:wBitsPerSample] = 16
|
286
|
-
wfx[:cbSize] = 0
|
287
|
-
wfx[:nBlockAlign] = (wfx[:wBitsPerSample] >> 3) * wfx[:nChannels]
|
288
|
-
wfx[:nAvgBytesPerSec] = wfx[:nBlockAlign] * wfx[:nSamplesPerSec]
|
289
|
-
|
290
|
-
if ((error_code = waveOutOpen(hWaveOut.pointer, WAVE_MAPPER, wfx.pointer, 0, 0, 0)) != 0)
|
291
|
-
raise SystemCallError.new('waveOutOpen', FFI.errno)
|
292
|
-
end
|
293
|
-
|
294
|
-
header = yield(wfx)
|
295
|
-
|
296
|
-
if ((error_code = waveOutPrepareHeader(hWaveOut[:i], header.pointer, header.size)) != 0)
|
297
|
-
raise SystemCallError.new('waveOutPrepareHeader', FFI.errno)
|
298
|
-
end
|
299
|
-
|
300
|
-
unless immediate_playback
|
301
|
-
Thread.stop
|
302
|
-
Thread.current[:sleep_time] ||= 0
|
303
|
-
sleep Thread.current[:sleep_time]
|
304
|
-
end
|
305
|
-
Thread.pass
|
306
|
-
|
307
|
-
if (waveOutWrite(hWaveOut[:i], header.pointer, header.size) != 0)
|
308
|
-
raise SystemCallError.new('waveOutWrite', FFI.errno)
|
309
|
-
end
|
310
|
-
|
311
|
-
while (waveOutUnprepareHeader(hWaveOut[:i], header.pointer, header.size) == 33)
|
312
|
-
sleep 0.1
|
313
|
-
end
|
314
|
-
|
315
|
-
if ((error_code = waveOutClose(hWaveOut[:i])) != 0)
|
316
|
-
raise SystemCallError.new('waveOutClose', FFI.errno)
|
317
|
-
end
|
318
|
-
|
319
|
-
self
|
320
|
-
end
|
321
|
-
|
322
|
-
# Generates an array of PCM integers to play a particular frequency
|
323
|
-
# It also ramps up and down the volume in the first and last
|
324
|
-
# 200 milliseconds to prevent audio clicking.
|
325
|
-
#
|
326
|
-
def self.generate_pcm_integer_array_for_freq(freq, duration, volume)
|
327
|
-
data = []
|
328
|
-
ramp = 200.0
|
329
|
-
samples = (44100/2*duration/1000.0).floor
|
330
|
-
|
331
|
-
samples.times do |sample|
|
332
|
-
|
333
|
-
angle = (2.0*Math::PI*freq) * sample/samples * duration/1000
|
334
|
-
factor = Math.sin(angle)
|
335
|
-
x = 32768.0*factor*volume
|
336
|
-
|
337
|
-
if sample < ramp
|
338
|
-
x *= sample/ramp
|
339
|
-
end
|
340
|
-
if samples - sample < ramp
|
341
|
-
x *= (samples - sample)/ramp
|
342
|
-
end
|
343
|
-
|
344
|
-
data << x.floor
|
345
|
-
end
|
346
|
-
|
347
|
-
data
|
348
|
-
end
|
349
|
-
end # Sound
|
350
|
-
end # Win32
|
1
|
+
require_relative 'windows/functions'
|
2
|
+
require_relative 'windows/structs'
|
3
|
+
|
4
|
+
# The Win32 module serves as a namespace only.
|
5
|
+
module Win32
|
6
|
+
|
7
|
+
# The Sound class encapsulates various methods for playing sound as well
|
8
|
+
# as querying or configuring sound related properties.
|
9
|
+
class Sound
|
10
|
+
include Windows::SoundStructs
|
11
|
+
extend Windows::SoundFunctions
|
12
|
+
|
13
|
+
# The version of the win32-sound library
|
14
|
+
VERSION = '0.6.1'.freeze
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
LOW_FREQUENCY = 37
|
19
|
+
HIGH_FREQUENCY = 32767
|
20
|
+
MAX_VOLUME = 0xFFFF
|
21
|
+
WAVE_FORMAT_PCM = 1 # Waveform-audio data is PCM.
|
22
|
+
WAVE_MAPPER = -1 # Used by waveOutOpen. The function selects
|
23
|
+
# a waveform-audio output device capable of
|
24
|
+
# playing the given format.
|
25
|
+
|
26
|
+
public
|
27
|
+
|
28
|
+
SYNC = 0x00000000 # play synchronously (default)
|
29
|
+
ASYNC = 0x00000001 # play asynchronously
|
30
|
+
NODEFAULT = 0x00000002 # silence (!default) if sound not found
|
31
|
+
MEMORY = 0x00000004 # pszSound points to a memory file
|
32
|
+
LOOP = 0x00000008 # loop the sound until next sndPlaySound
|
33
|
+
NOSTOP = 0x00000010 # don't stop any currently playing sound
|
34
|
+
NOWAIT = 8192 # don't wait if the driver is busy
|
35
|
+
ALIAS = 65536 # name is a registry alias
|
36
|
+
ALIAS_ID = 1114112 # alias is a predefined ID
|
37
|
+
FILENAME = 131072 # name is file name
|
38
|
+
RESOURCE = 262148 # name is resource name or atom
|
39
|
+
PURGE = 0x00000040 # purge non-static events for task
|
40
|
+
APPLICATION = 0x00000080 # look for app specific association
|
41
|
+
|
42
|
+
# Plays a frequency for a specified duration at a given volume.
|
43
|
+
# Defaults are 440Hz, 1 second, full volume.
|
44
|
+
#
|
45
|
+
# The result is a single channel, 44100Hz sampled, 16 bit sine wave.
|
46
|
+
# If multiple instances are plays in simultaneous threads, they
|
47
|
+
# will be started and played at the same time.
|
48
|
+
#
|
49
|
+
# ex.: threads = []
|
50
|
+
# [440, 660].each do |freq|
|
51
|
+
# threads << Thread.new { Win32::Sound.play_freq(freq) }
|
52
|
+
# end
|
53
|
+
# threads.each { |th| th.join }
|
54
|
+
#
|
55
|
+
# The first frequency in this array (440) will wait until the
|
56
|
+
# thread for 660 finished calculating its PCM array and they
|
57
|
+
# will both start streaming at the same time.
|
58
|
+
#
|
59
|
+
# If immediate_playback is set to false, the thread will calculate
|
60
|
+
# all pending PCM arrays and wait to be woken up again. This
|
61
|
+
# if useful for time-sensitive playback of notes in succession.
|
62
|
+
#
|
63
|
+
def self.play_freq(frequency = 440, duration = 1000, volume = 1, immediate_playback = true)
|
64
|
+
|
65
|
+
if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
|
66
|
+
raise ArgumentError, 'invalid frequency'
|
67
|
+
end
|
68
|
+
|
69
|
+
if duration < 0 || duration > 5000
|
70
|
+
raise ArgumentError, 'invalid duration'
|
71
|
+
end
|
72
|
+
|
73
|
+
if volume.abs > 1
|
74
|
+
warn("WARNING: Volume greater than 1 will cause audio clipping.")
|
75
|
+
end
|
76
|
+
|
77
|
+
stream(immediate_playback) do |wfx|
|
78
|
+
data = generate_pcm_integer_array_for_freq(frequency, duration, volume)
|
79
|
+
data_buffer = FFI::MemoryPointer.new(:int, data.size)
|
80
|
+
data_buffer.write_array_of_int data
|
81
|
+
buffer_length = wfx[:nAvgBytesPerSec]*duration/1000
|
82
|
+
WAVEHDR.new(data_buffer, buffer_length)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns an array of all the available sound devices; their names contain
|
88
|
+
# the type of the device and a zero-based ID number. Possible return values
|
89
|
+
# are WAVEOUT, WAVEIN, MIDIOUT, MIDIIN, AUX or MIXER.
|
90
|
+
#
|
91
|
+
def self.devices
|
92
|
+
devs = []
|
93
|
+
|
94
|
+
begin
|
95
|
+
0.upto(waveOutGetNumDevs()){ |i| devs << "WAVEOUT#{i}" }
|
96
|
+
0.upto(waveInGetNumDevs()){ |i| devs << "WAVEIN#{i}" }
|
97
|
+
0.upto(midiOutGetNumDevs()){ |i| devs << "MIDIOUT#{i}" }
|
98
|
+
0.upto(midiInGetNumDevs()){ |i| devs << "MIDIIN#{i}" }
|
99
|
+
0.upto(auxGetNumDevs()){ |i| devs << "AUX#{i}" }
|
100
|
+
0.upto(mixerGetNumDevs()){ |i| devs << "MIXER#{i}" }
|
101
|
+
rescue Exception
|
102
|
+
raise SystemCallError, FFI.errno, "GetNumDevs"
|
103
|
+
end
|
104
|
+
|
105
|
+
devs
|
106
|
+
end
|
107
|
+
|
108
|
+
# Generates simple tones on the speaker. The function is synchronous; it
|
109
|
+
# does not return control to its caller until the sound finishes.
|
110
|
+
#
|
111
|
+
# The frequency (in Hertz) must be between 37 and 32767.
|
112
|
+
# The duration is in milliseconds.
|
113
|
+
#
|
114
|
+
def self.beep(frequency, duration)
|
115
|
+
if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
|
116
|
+
raise ArgumentError, 'invalid frequency'
|
117
|
+
end
|
118
|
+
|
119
|
+
if 0 == Beep(frequency, duration)
|
120
|
+
raise SystemCallError, FFI.errno, "Beep"
|
121
|
+
end
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# Stops any currently playing waveform sound. If +purge+ is set to
|
127
|
+
# true, then *all* sounds are stopped. The default is false.
|
128
|
+
#
|
129
|
+
def self.stop(purge = false)
|
130
|
+
if purge && purge != 0
|
131
|
+
flags = PURGE
|
132
|
+
else
|
133
|
+
flags = 0
|
134
|
+
end
|
135
|
+
|
136
|
+
unless PlaySound(nil, 0, flags)
|
137
|
+
raise SystemCallError, FFI.errno, "PlaySound"
|
138
|
+
end
|
139
|
+
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
# Plays the specified sound. The sound can be a wave file or a system
|
144
|
+
# sound, when used in conjunction with the ALIAS flag.
|
145
|
+
#
|
146
|
+
# Valid flags:
|
147
|
+
#
|
148
|
+
# Sound::ALIAS
|
149
|
+
# The sound parameter is a system-event alias in the registry or the
|
150
|
+
# WIN.INI file. If the registry contains no such name, it plays the
|
151
|
+
# system default sound unless the NODEFAULT value is also specified.
|
152
|
+
# Do not use with FILENAME.
|
153
|
+
#
|
154
|
+
# Sound::APPLICATION
|
155
|
+
# The sound is played using an application-specific association.
|
156
|
+
#
|
157
|
+
# Sound::ASYNC
|
158
|
+
# The sound is played asynchronously and the function returns
|
159
|
+
# immediately after beginning the sound.
|
160
|
+
#
|
161
|
+
# Sound::FILENAME
|
162
|
+
# The sound parameter is the name of a WAV file. Do not use with
|
163
|
+
# ALIAS.
|
164
|
+
#
|
165
|
+
# Sound::LOOP
|
166
|
+
# The sound plays repeatedly until Sound.stop() is called. You must
|
167
|
+
# also specify the ASYNC flag to loop sounds.
|
168
|
+
#
|
169
|
+
# Sound::MEMORY
|
170
|
+
# The sound points to an image of a waveform sound in memory.
|
171
|
+
#
|
172
|
+
# Sound::NODEFAULT
|
173
|
+
# If the sound cannot be found, the function returns silently without
|
174
|
+
# playing the default sound.
|
175
|
+
#
|
176
|
+
# Sound::NOSTOP
|
177
|
+
# If a sound is currently playing, the function immediately returns
|
178
|
+
# false without playing the requested sound.
|
179
|
+
#
|
180
|
+
# Sound::NOWAIT
|
181
|
+
# If the driver is busy, return immediately without playing the sound.
|
182
|
+
#
|
183
|
+
# Sound::PURGE
|
184
|
+
# Stop playing all instances of the specified sound.
|
185
|
+
#
|
186
|
+
# Sound::SYNC
|
187
|
+
# The sound is played synchronously and the function does not return
|
188
|
+
# until the sound ends.
|
189
|
+
#
|
190
|
+
# Examples:
|
191
|
+
#
|
192
|
+
# require 'win32/sound'
|
193
|
+
# include Win32
|
194
|
+
#
|
195
|
+
# # Play a wave file once
|
196
|
+
# Sound.play('some_file.wav')
|
197
|
+
#
|
198
|
+
# # Play a wave file in an asynchronous loop for 2 seconds
|
199
|
+
# Sound.play('some_file.wav', Sound::ASYNC | Sound::LOOP)
|
200
|
+
# sleep 2
|
201
|
+
# Sound.stop
|
202
|
+
#
|
203
|
+
def self.play(sound, flags = 0)
|
204
|
+
unless PlaySound(sound, 0, flags)
|
205
|
+
raise SystemCallError, FFI.errno, "PlaySound"
|
206
|
+
end
|
207
|
+
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
# Sets the volume for the left and right channel. If the +right_channel+
|
212
|
+
# is omitted, the volume is set for *both* channels.
|
213
|
+
#
|
214
|
+
# You may optionally pass a single Integer rather than an Array, in which
|
215
|
+
# case it is assumed you are setting both channels to the same value.
|
216
|
+
#
|
217
|
+
def self.set_wave_volume(left_channel, right_channel = nil)
|
218
|
+
right_channel ||= left_channel
|
219
|
+
|
220
|
+
lvolume = left_channel > MAX_VOLUME ? MAX_VOLUME : left_channel
|
221
|
+
rvolume = right_channel > MAX_VOLUME ? MAX_VOLUME : right_channel
|
222
|
+
|
223
|
+
volume = lvolume | rvolume << 16
|
224
|
+
|
225
|
+
if waveOutSetVolume(-1, volume) != 0
|
226
|
+
raise SystemCallError, FFI.errno, "waveOutSetVolume"
|
227
|
+
end
|
228
|
+
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns a 2-element array that contains the volume for the left channel
|
233
|
+
# and right channel, respectively.
|
234
|
+
#
|
235
|
+
def self.wave_volume
|
236
|
+
ptr = FFI::MemoryPointer.new(:ulong)
|
237
|
+
|
238
|
+
if waveOutGetVolume(-1, ptr) != 0
|
239
|
+
raise SystemCallError, FFI.errno, "waveOutGetVolume"
|
240
|
+
end
|
241
|
+
|
242
|
+
volume = ptr.read_long
|
243
|
+
|
244
|
+
[low_word(volume), high_word(volume)]
|
245
|
+
end
|
246
|
+
|
247
|
+
class << self
|
248
|
+
alias get_wave_volume wave_volume
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def self.low_word(num)
|
254
|
+
num & 0xFFFF
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.high_word(num)
|
258
|
+
num >> 16
|
259
|
+
end
|
260
|
+
|
261
|
+
# Sets up a ready-made waveOut stream to push a PCM integer array to.
|
262
|
+
# It expects a block to be associated with the method call to which
|
263
|
+
# it will yield an instance of WAVEFORMATEX that the block uses
|
264
|
+
# to prepare a WAVEHDR to return to the function.
|
265
|
+
#
|
266
|
+
# The WAVEHDR can contain either a self-made PCM integer array
|
267
|
+
# or an array from a wav file or some other audio file converted
|
268
|
+
# to PCM.
|
269
|
+
#
|
270
|
+
# This function will take the entire PCM array and create one
|
271
|
+
# giant buffer, so it is not intended for audio streams larger
|
272
|
+
# than 5 seconds.
|
273
|
+
#
|
274
|
+
# In order to play larger audio files, you will have to use the waveOut
|
275
|
+
# functions and structs to set up a double buffer to incrementally
|
276
|
+
# push PCM data to.
|
277
|
+
#
|
278
|
+
def self.stream(immediate_playback)
|
279
|
+
hWaveOut = HWAVEOUT.new
|
280
|
+
wfx = WAVEFORMATEX.new
|
281
|
+
|
282
|
+
wfx[:wFormatTag] = WAVE_FORMAT_PCM
|
283
|
+
wfx[:nChannels] = 1
|
284
|
+
wfx[:nSamplesPerSec] = 44100
|
285
|
+
wfx[:wBitsPerSample] = 16
|
286
|
+
wfx[:cbSize] = 0
|
287
|
+
wfx[:nBlockAlign] = (wfx[:wBitsPerSample] >> 3) * wfx[:nChannels]
|
288
|
+
wfx[:nAvgBytesPerSec] = wfx[:nBlockAlign] * wfx[:nSamplesPerSec]
|
289
|
+
|
290
|
+
if ((error_code = waveOutOpen(hWaveOut.pointer, WAVE_MAPPER, wfx.pointer, 0, 0, 0)) != 0)
|
291
|
+
raise SystemCallError.new('waveOutOpen', FFI.errno)
|
292
|
+
end
|
293
|
+
|
294
|
+
header = yield(wfx)
|
295
|
+
|
296
|
+
if ((error_code = waveOutPrepareHeader(hWaveOut[:i], header.pointer, header.size)) != 0)
|
297
|
+
raise SystemCallError.new('waveOutPrepareHeader', FFI.errno)
|
298
|
+
end
|
299
|
+
|
300
|
+
unless immediate_playback
|
301
|
+
Thread.stop
|
302
|
+
Thread.current[:sleep_time] ||= 0
|
303
|
+
sleep Thread.current[:sleep_time]
|
304
|
+
end
|
305
|
+
Thread.pass
|
306
|
+
|
307
|
+
if (waveOutWrite(hWaveOut[:i], header.pointer, header.size) != 0)
|
308
|
+
raise SystemCallError.new('waveOutWrite', FFI.errno)
|
309
|
+
end
|
310
|
+
|
311
|
+
while (waveOutUnprepareHeader(hWaveOut[:i], header.pointer, header.size) == 33)
|
312
|
+
sleep 0.1
|
313
|
+
end
|
314
|
+
|
315
|
+
if ((error_code = waveOutClose(hWaveOut[:i])) != 0)
|
316
|
+
raise SystemCallError.new('waveOutClose', FFI.errno)
|
317
|
+
end
|
318
|
+
|
319
|
+
self
|
320
|
+
end
|
321
|
+
|
322
|
+
# Generates an array of PCM integers to play a particular frequency
|
323
|
+
# It also ramps up and down the volume in the first and last
|
324
|
+
# 200 milliseconds to prevent audio clicking.
|
325
|
+
#
|
326
|
+
def self.generate_pcm_integer_array_for_freq(freq, duration, volume)
|
327
|
+
data = []
|
328
|
+
ramp = 200.0
|
329
|
+
samples = (44100/2*duration/1000.0).floor
|
330
|
+
|
331
|
+
samples.times do |sample|
|
332
|
+
|
333
|
+
angle = (2.0*Math::PI*freq) * sample/samples * duration/1000
|
334
|
+
factor = Math.sin(angle)
|
335
|
+
x = 32768.0*factor*volume
|
336
|
+
|
337
|
+
if sample < ramp
|
338
|
+
x *= sample/ramp
|
339
|
+
end
|
340
|
+
if samples - sample < ramp
|
341
|
+
x *= (samples - sample)/ramp
|
342
|
+
end
|
343
|
+
|
344
|
+
data << x.floor
|
345
|
+
end
|
346
|
+
|
347
|
+
data
|
348
|
+
end
|
349
|
+
end # Sound
|
350
|
+
end # Win32
|