Class: SFML::SoundRecorder

Inherits:
Object
  • Object
show all
Defined in:
lib/sfml/audio/sound_recorder.rb

Overview

Callback-based audio-capture base class. Subclass and override ‘#on_start` (initialise capture state, return `true` to begin), `#on_process_samples(samples, channels)` (called every audio chunk with an Array<Integer> of interleaved int16 PCM, return `true` to keep recording or `false` to stop), and `#on_stop` (release any resources).

The simpler “record to a SoundBuffer” path lives in SFML::SoundBufferRecorder — reach for SoundRecorder only when you need to stream samples somewhere else (file, socket, DSP pipeline).

class LevelMeter < SFML::SoundRecorder
  def on_start
    @peak = 0
    true
  end

  def on_process_samples(samples, _channels)
    @peak = [@peak, *samples.map(&:abs)].max
    true
  end

  def on_stop
    puts "Peak: #{@peak}"
  end
end

meter = LevelMeter.new
meter.start(sample_rate: 44_100)
sleep 2
meter.stop

CAVEATS

  • All three callbacks run on CSFML’s audio thread; heavy Ruby work on the audio thread will glitch the capture.

  • Always keep a reference to the SoundRecorder object — if the Ruby object is GC’d while CSFML is mid-capture, the process crashes.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSoundRecorder

—- Instance API ————————————————-

Raises:



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sfml/audio/sound_recorder.rb', line 63

def initialize
  # Strong refs so the GC doesn't disappear callbacks under CSFML.
  @start_cb = FFI::Function.new(:bool, [:pointer]) do |_user|
    on_start ? true : false
  end
  @process_cb = FFI::Function.new(:bool, [:pointer, :size_t, :pointer]) do |samples_ptr, count, _user|
    samples = count.zero? ? [] : samples_ptr.read_array_of_int16(count)
    on_process_samples(samples, channel_count) ? true : false
  end
  @stop_cb = FFI::Function.new(:void, [:pointer]) do |_user|
    on_stop
    nil
  end

  ptr = C::Audio.sfSoundRecorder_create(@start_cb, @process_cb, @stop_cb, nil)
  raise Error, "sfSoundRecorder_create returned NULL" if ptr.null?

  @handle = FFI::AutoPointer.new(ptr, C::Audio.method(:sfSoundRecorder_destroy))
end

Instance Attribute Details

#handleObject (readonly)

:nodoc:



141
142
143
# File 'lib/sfml/audio/sound_recorder.rb', line 141

def handle
  @handle
end

Class Method Details

.available?Boolean

—- Static helpers (work without a recorder instance) ————

Returns:

  • (Boolean)


42
43
44
# File 'lib/sfml/audio/sound_recorder.rb', line 42

def self.available?
  C::Audio.sfSoundRecorder_isAvailable
end

.default_deviceObject



46
47
48
# File 'lib/sfml/audio/sound_recorder.rb', line 46

def self.default_device
  C::Audio.sfSoundRecorder_getDefaultDevice
end

.devicesObject

All input devices the OS exposes to SFML, as an Array of String names. Pass any of them to SoundRecorder#device= or SoundBufferRecorder#device= to switch.



53
54
55
56
57
58
59
# File 'lib/sfml/audio/sound_recorder.rb', line 53

def self.devices
  count_buf = FFI::MemoryPointer.new(:size_t)
  array_ptr = C::Audio.sfSoundRecorder_getAvailableDevices(count_buf)
  n = count_buf.read(:size_t)
  return [] if array_ptr.null? || n.zero?
  array_ptr.read_array_of_pointer(n).map { |p| p.read_string }
end

Instance Method Details

#channel_countObject



117
# File 'lib/sfml/audio/sound_recorder.rb', line 117

def channel_count = C::Audio.sfSoundRecorder_getChannelCount(@handle)

#channel_count=(n) ⇒ Object



119
120
121
# File 'lib/sfml/audio/sound_recorder.rb', line 119

def channel_count=(n)
  C::Audio.sfSoundRecorder_setChannelCount(@handle, Integer(n))
end

#channel_mapObject

The channel layout the recorder is producing, as an Array of ‘sfSoundChannel` enum values (1 = Mono, 2 = FrontLeft, 3 = FrontRight, etc — see SoundBuffer::DEFAULT_CHANNEL_MAPS).



133
134
135
136
137
138
139
# File 'lib/sfml/audio/sound_recorder.rb', line 133

def channel_map
  count_buf = FFI::MemoryPointer.new(:size_t)
  ptr = C::Audio.sfSoundRecorder_getChannelMap(@handle, count_buf)
  n = count_buf.read(:size_t)
  return [] if ptr.null? || n.zero?
  ptr.read_array_of_int32(n)
end

#deviceObject



123
# File 'lib/sfml/audio/sound_recorder.rb', line 123

def device = C::Audio.sfSoundRecorder_getDevice(@handle)

#device=(name) ⇒ Object



125
126
127
128
# File 'lib/sfml/audio/sound_recorder.rb', line 125

def device=(name)
  C::Audio.sfSoundRecorder_setDevice(@handle, name.to_s) ||
    raise(Error, "sfSoundRecorder_setDevice failed for #{name.inspect}")
end

#on_process_samples(_samples, _channels) ⇒ Object

Called with each chunk of captured audio. ‘samples` is an Array<Integer> of interleaved int16 PCM (length = frames * channels). Return `true` to keep capturing or `false` to stop. Default raises so subclasses must implement it.

Raises:

  • (NoMethodError)


96
97
98
# File 'lib/sfml/audio/sound_recorder.rb', line 96

def on_process_samples(_samples, _channels)
  raise NoMethodError, "#{self.class} must override #on_process_samples"
end

#on_startObject

Called once before the capture starts. Return ‘true` to begin the capture or `false` to abort. Default does nothing and accepts the start.



88
89
90
# File 'lib/sfml/audio/sound_recorder.rb', line 88

def on_start
  true
end

#on_stopObject

Called once after the capture is done. Default is a no-op.



101
# File 'lib/sfml/audio/sound_recorder.rb', line 101

def on_stop; end

#sample_rateObject



116
# File 'lib/sfml/audio/sound_recorder.rb', line 116

def sample_rate   = C::Audio.sfSoundRecorder_getSampleRate(@handle)

#start(sample_rate: 44_100) ⇒ Object

—- Public recorder API —-



105
106
107
108
109
# File 'lib/sfml/audio/sound_recorder.rb', line 105

def start(sample_rate: 44_100)
  C::Audio.sfSoundRecorder_start(@handle, Integer(sample_rate)) ||
    raise(Error, "sfSoundRecorder_start failed (no input device or driver error)")
  self
end

#stopObject



111
112
113
114
# File 'lib/sfml/audio/sound_recorder.rb', line 111

def stop
  C::Audio.sfSoundRecorder_stop(@handle)
  self
end