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