win32-sound 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|