class Win32::Sound

The Sound class encapsulates various methods for playing sound as well as querying or configuring sound related properties.

Constants

ALIAS
ALIAS_ID
APPLICATION
ASYNC
FILENAME
HIGH_FREQUENCY
LOOP
LOW_FREQUENCY
MAX_VOLUME
MEMORY
NODEFAULT
NOSTOP
NOWAIT
PURGE
RESOURCE
SYNC

playing the given format.

VERSION

The version of the win32-sound library

WAVE_FORMAT_PCM
WAVE_MAPPER

Public Class Methods

beep(frequency, duration) click to toggle source

Generates simple tones on the speaker. The function is synchronous; it does not return control to its caller until the sound finishes.

The frequency (in Hertz) must be between 37 and 32767. The duration is in milliseconds.

# File lib/win32/sound.rb, line 114
def self.beep(frequency, duration)
  if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
    raise ArgumentError, 'invalid frequency'
  end

  if 0 == Beep(frequency, duration)
    raise SystemCallError, FFI.errno, "Beep"
  end

  self
end
devices() click to toggle source

Returns an array of all the available sound devices; their names contain the type of the device and a zero-based ID number. Possible return values are WAVEOUT, WAVEIN, MIDIOUT, MIDIIN, AUX or MIXER.

# File lib/win32/sound.rb, line 91
def self.devices
  devs = []

  begin
    0.upto(waveOutGetNumDevs()){ |i| devs << "WAVEOUT#{i}" }
    0.upto(waveInGetNumDevs()){ |i| devs << "WAVEIN#{i}" }
    0.upto(midiOutGetNumDevs()){ |i| devs << "MIDIOUT#{i}" }
    0.upto(midiInGetNumDevs()){ |i| devs << "MIDIIN#{i}" }
    0.upto(auxGetNumDevs()){ |i| devs << "AUX#{i}" }
    0.upto(mixerGetNumDevs()){ |i| devs << "MIXER#{i}" }
  rescue Exception
    raise SystemCallError, FFI.errno, "GetNumDevs"
  end

  devs
end
get_wave_volume()
Alias for: wave_volume
play(sound, flags = 0) click to toggle source

Plays the specified sound. The sound can be a wave file or a system sound, when used in conjunction with the ALIAS flag.

Valid flags:

Sound::ALIAS

The sound parameter is a system-event alias in the registry or the
WIN.INI file. If the registry contains no such name, it plays the
system default sound unless the NODEFAULT value is also specified.
Do not use with FILENAME.

Sound::APPLICATION

The sound is played using an application-specific association.

Sound::ASYNC

The sound is played asynchronously and the function returns
immediately after beginning the sound.

Sound::FILENAME

The sound parameter is the name of a WAV file.  Do not use with
ALIAS.

Sound::LOOP

The sound plays repeatedly until Sound.stop() is called. You must
also specify the ASYNC flag to loop sounds.

Sound::MEMORY

The sound points to an image of a waveform sound in memory.

Sound::NODEFAULT

If the sound cannot be found, the function returns silently without
playing the default sound.

Sound::NOSTOP

If a sound is currently playing, the function immediately returns
false without playing the requested sound.

Sound::NOWAIT

If the driver is busy, return immediately without playing the sound.

Sound::PURGE

Stop playing all instances of the specified sound.

Sound::SYNC

The sound is played synchronously and the function does not return
until the sound ends.

Examples:

require 'win32/sound'
include Win32

# Play a wave file once
Sound.play('some_file.wav')

# Play a wave file in an asynchronous loop for 2 seconds
Sound.play('some_file.wav', Sound::ASYNC | Sound::LOOP)
sleep 2
Sound.stop
# File lib/win32/sound.rb, line 203
def self.play(sound, flags = 0)
  unless PlaySound(sound, 0, flags)
    raise SystemCallError, FFI.errno, "PlaySound"
  end

  self
end
play_freq(frequency = 440, duration = 1000, volume = 1, immediate_playback = true) click to toggle source

Plays a frequency for a specified duration at a given volume. Defaults are 440Hz, 1 second, full volume.

The result is a single channel, 44100Hz sampled, 16 bit sine wave. If multiple instances are plays in simultaneous threads, they will be started and played at the same time.

ex.: threads = []

[440, 660].each do |freq|
  threads << Thread.new { Win32::Sound.play_freq(freq) }
end
threads.each { |th| th.join }

The first frequency in this array (440) will wait until the thread for 660 finished calculating its PCM array and they will both start streaming at the same time.

If immediate_playback is set to false, the thread will calculate all pending PCM arrays and wait to be woken up again. This if useful for time-sensitive playback of notes in succession.

# File lib/win32/sound.rb, line 63
def self.play_freq(frequency = 440, duration = 1000, volume = 1, immediate_playback = true)

  if frequency > HIGH_FREQUENCY || frequency < LOW_FREQUENCY
    raise ArgumentError, 'invalid frequency'
  end

  if duration < 0 || duration > 5000
    raise ArgumentError, 'invalid duration'
  end

  if volume.abs > 1
    warn("WARNING: Volume greater than 1 will cause audio clipping.")
  end

  stream(immediate_playback) do |wfx|
    data = generate_pcm_integer_array_for_freq(frequency, duration, volume)
    data_buffer = FFI::MemoryPointer.new(:int, data.size)
    data_buffer.write_array_of_int data
    buffer_length = wfx[:nAvgBytesPerSec]*duration/1000
    WAVEHDR.new(data_buffer, buffer_length)
  end

end
set_wave_volume(left_channel, right_channel = nil) click to toggle source

Sets the volume for the left and right channel. If the right_channel is omitted, the volume is set for both channels.

You may optionally pass a single Integer rather than an Array, in which case it is assumed you are setting both channels to the same value.

# File lib/win32/sound.rb, line 217
def self.set_wave_volume(left_channel, right_channel = nil)
  right_channel ||= left_channel

  lvolume = left_channel > MAX_VOLUME ? MAX_VOLUME : left_channel
  rvolume = right_channel > MAX_VOLUME ? MAX_VOLUME : right_channel

  volume = lvolume | rvolume << 16

  if waveOutSetVolume(-1, volume) != 0
    raise SystemCallError, FFI.errno, "waveOutSetVolume"
  end

  self
end
stop(purge = false) click to toggle source

Stops any currently playing waveform sound. If purge is set to true, then all sounds are stopped. The default is false.

# File lib/win32/sound.rb, line 129
def self.stop(purge = false)
  if purge && purge != 0
    flags = PURGE
  else
    flags = 0
  end

  unless PlaySound(nil, 0, flags)
    raise SystemCallError, FFI.errno, "PlaySound"
  end

  self
end
wave_volume() click to toggle source

Returns a 2-element array that contains the volume for the left channel and right channel, respectively.

# File lib/win32/sound.rb, line 235
def self.wave_volume
  ptr = FFI::MemoryPointer.new(:ulong)

  if waveOutGetVolume(-1, ptr) != 0
    raise SystemCallError, FFI.errno, "waveOutGetVolume"
  end

  volume = ptr.read_long

  [low_word(volume), high_word(volume)]
end
Also aliased as: get_wave_volume

Private Class Methods

generate_pcm_integer_array_for_freq(freq, duration, volume) click to toggle source

Generates an array of PCM integers to play a particular frequency It also ramps up and down the volume in the first and last 200 milliseconds to prevent audio clicking.

# File lib/win32/sound.rb, line 326
def self.generate_pcm_integer_array_for_freq(freq, duration, volume)
  data = []
  ramp = 200.0
  samples = (44100/2*duration/1000.0).floor

  samples.times do |sample|

    angle = (2.0*Math::PI*freq) * sample/samples * duration/1000
    factor = Math.sin(angle)
    x = 32768.0*factor*volume

    if sample < ramp
      x *= sample/ramp
    end
    if samples - sample < ramp
      x *= (samples - sample)/ramp
    end

    data << x.floor
  end

  data
end
high_word(num) click to toggle source
# File lib/win32/sound.rb, line 257
def self.high_word(num)
  num >> 16
end
low_word(num) click to toggle source
# File lib/win32/sound.rb, line 253
def self.low_word(num)
  num & 0xFFFF
end
stream(immediate_playback) { |wfx| ... } click to toggle source

Sets up a ready-made waveOut stream to push a PCM integer array to. It expects a block to be associated with the method call to which it will yield an instance of WAVEFORMATEX that the block uses to prepare a WAVEHDR to return to the function.

The WAVEHDR can contain either a self-made PCM integer array or an array from a wav file or some other audio file converted to PCM.

This function will take the entire PCM array and create one giant buffer, so it is not intended for audio streams larger than 5 seconds.

In order to play larger audio files, you will have to use the waveOut functions and structs to set up a double buffer to incrementally push PCM data to.

# File lib/win32/sound.rb, line 278
def self.stream(immediate_playback)
  hWaveOut = HWAVEOUT.new
  wfx = WAVEFORMATEX.new

  wfx[:wFormatTag] = WAVE_FORMAT_PCM
  wfx[:nChannels] = 1
  wfx[:nSamplesPerSec] = 44100
  wfx[:wBitsPerSample] = 16
  wfx[:cbSize] = 0
  wfx[:nBlockAlign] = (wfx[:wBitsPerSample] >> 3) * wfx[:nChannels]
  wfx[:nAvgBytesPerSec] = wfx[:nBlockAlign] * wfx[:nSamplesPerSec]

  if ((error_code = waveOutOpen(hWaveOut.pointer, WAVE_MAPPER, wfx.pointer, 0, 0, 0)) != 0)
    raise SystemCallError.new('waveOutOpen', FFI.errno)
  end

  header = yield(wfx)

  if ((error_code = waveOutPrepareHeader(hWaveOut[:i], header.pointer, header.size)) != 0)
    raise SystemCallError.new('waveOutPrepareHeader', FFI.errno)
  end

  unless immediate_playback
    Thread.stop
    Thread.current[:sleep_time] ||= 0
    sleep Thread.current[:sleep_time]
  end
  Thread.pass

  if (waveOutWrite(hWaveOut[:i], header.pointer, header.size) != 0)
    raise SystemCallError.new('waveOutWrite', FFI.errno)
  end

  while (waveOutUnprepareHeader(hWaveOut[:i], header.pointer, header.size) == 33)
    sleep 0.1
  end

  if ((error_code = waveOutClose(hWaveOut[:i])) != 0)
    raise SystemCallError.new('waveOutClose', FFI.errno)
  end

  self
end