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.
@@ -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.0'
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