require 'ffi' module Win32 WAVE_FORMAT_PCM = 1 WAVE_MAPPER = -1 class HWAVEOUT < FFI::Struct layout :i, :int end class WAVEHDR < FFI::Struct def initialize(dwBufferLength, dwBytesRecorded, dwLoops, dwFlags, lpData) self[:dwBufferLength] = dwBufferLength self[:dwBytesRecorded] = dwBytesRecorded self[:dwLoops] = dwLoops self[:dwFlags] = dwFlags self[:lpData] = lpData end layout :lpData, :pointer, :dwBufferLength, :ulong, :dwBytesRecorded, :ulong, :dwUser, :ulong, :dwFlags, :ulong, :dwLoops, :ulong, :lpNext, :pointer, :reserved, :ulong end class WAVEFORMATEX < FFI::Struct def initialize(nSamplesPerSec, wBitsPerSample, nChannels, cbSize) self[:nSamplesPerSec] = nSamplesPerSec self[:wBitsPerSample] = wBitsPerSample self[:nChannels] = nChannels self[:cbSize] = cbSize self[:wFormatTag] = WAVE_FORMAT_PCM self[:nBlockAlign] = (self[:wBitsPerSample] >> 3) * self[:nChannels] self[:nAvgBytesPerSec] = self[:nBlockAlign] * self[:nSamplesPerSec] end layout :wFormatTag, :ushort, :nChannels, :ushort, :nSamplesPerSec, :ulong, :nAvgBytesPerSec, :ulong, :nBlockAlign, :ushort, :wBitsPerSample, :ushort, :cbSize, :ushort end class Sound extend FFI::Library private typedef :uintptr_t, :hwaveout typedef :uint, :mmresult typedef :ulong, :dword ffi_lib :winmm attach_function :waveOutOpen, [:pointer, :uint, :pointer, :dword, :dword, :dword], :mmresult attach_function :waveOutPrepareHeader, [:hwaveout, :pointer, :uint], :mmresult attach_function :waveOutWrite, [:hwaveout, :pointer, :uint], :mmresult attach_function :waveOutUnprepareHeader, [:hwaveout, :pointer, :uint], :mmresult attach_function :waveOutClose, [:hwaveout], :mmresult ffi_lib FFI::Library::LIBC attach_function :malloc, [:size_t], :pointer attach_function :calloc, [:size_t], :pointer attach_function :realloc, [:pointer, :size_t], :pointer attach_function :free, [:pointer], :void attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer def self.fill_data data = [] period = 1.0/440.0 ramp = 1500.0 time = Time.now 44100.times do |sample| t = sample/44100.0 angle = (2.0*Math::PI/period) * t angle2 = 1.5*angle factor = 0.5*Math.sin(angle) + 0.5 factor2 = 0.5*Math.sin(angle2) + 0.5 x = 0.5*32768.0*(factor + factor2) if sample < ramp x *= sample/ramp end if 44100 - sample < ramp x *= (44100 - sample)/ramp end data << x.floor end puts "This took #{Time.now - time} milliseconds to compute" data end public def self.play hWaveOut = HWAVEOUT.new wfx = WAVEFORMATEX.new(44100, 16, 2, 0) if ((error_code = waveOutOpen(hWaveOut.pointer, WAVE_MAPPER, wfx.pointer, 0, 0, 0)) != 0) raise SystemCallError, FFI.errno, "waveOutOpen: #{error_code}" end data = fill_data data_buffer = malloc(data.first.size * data.size) data_buffer.write_array_of_int data header = WAVEHDR.new(4*44100, 0, 1, (4 | 8), data_buffer) if ((error_code = waveOutPrepareHeader(hWaveOut[:i], header.pointer, header.size)) != 0) raise SystemCallError, FFI.errno, "waveOutPrepareHeader: #{error_code}" end if ((error_code = waveOutWrite(hWaveOut[:i], header.pointer, header.size)) != 0) raise SystemCallError, FFI.errno, "waveOutWrite: #{error_code}" end while (waveOutUnprepareHeader(hWaveOut[:i], header.pointer, header.size) == 33) sleep 0.1 end if ((error_code = waveOutClose(hWaveOut[:i])) != 0) raise SystemCallError, FFI.errno, "waveOutClose: #{error_code}" end self end end end Win32::Sound.play
Saturday, March 8, 2014
Holy crap I've done it
I've done it. I can now play two arbitrary frequencies through FFI using native library calls. And all in 155 lines of code. Also, it takes 76 milliseconds to calculate 1 second of PCM audio from scratch on my system.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment