Class: Plaything

Inherits:
Object
  • Object
show all
Defined in:
lib/plaything.rb,
lib/plaything/openal.rb,
lib/plaything/version.rb,
lib/plaything/objects/buffer.rb,
lib/plaything/objects/device.rb,
lib/plaything/objects/source.rb,
lib/plaything/objects/context.rb,
lib/plaything/support/paramable.rb,
lib/plaything/support/type_class.rb,
lib/plaything/support/managed_pointer.rb

Defined Under Namespace

Modules: OpenAL

Constant Summary collapse

Error =
Class.new(StandardError)
VERSION =
"1.0.0"

Instance Method Summary collapse

Constructor Details

#initialize(options = { sample_type: :int16, sample_rate: 44100, channels: 2 }) ⇒ Plaything

Open the default output device and prepare it for playback.

Options Hash (options):

  • sample_type (Symbol) — default: :int16
  • sample_rate (Integer) — default: 44100
  • channels (Integer) — default: 2

Raises:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/plaything.rb', line 17

def initialize(options = { sample_type: :int16, sample_rate: 44100, channels: 2 })
  @device  = OpenAL.open_device(nil)
  raise Error, "Failed to open device" if @device.null?

  @context = OpenAL.create_context(@device, nil)
  OpenAL.make_context_current(@context)
  OpenAL.distance_model(:none)
  OpenAL.listenerf(:gain, 1.0)

  FFI::MemoryPointer.new(OpenAL::Source, 1) do |ptr|
    OpenAL.gen_sources(ptr.count, ptr)
    @source = OpenAL::Source.new(ptr.read_uint)
  end

  @sample_type = options.fetch(:sample_type)
  @sample_rate = Integer(options.fetch(:sample_rate))
  @channels    = Integer(options.fetch(:channels))

  @sample_format = { [ :int16, 2 ] => :stereo16, }.fetch([@sample_type, @channels]) do
    raise TypeError, "unknown sample format for type [#{@sample_type}, #{@channels}]"
  end

  FFI::MemoryPointer.new(OpenAL::Buffer, 3) do |ptr|
    OpenAL.gen_buffers(ptr.count, ptr)
    @buffers = OpenAL::Buffer.extract(ptr, ptr.count)
  end

  @free_buffers = @buffers.clone
  @queued_buffers = []
  @queued_frames = []

  # 44100 int16s = 22050 frames = 0.5s (1 frame * 2 channels = 2 int16 = 1 sample = 1/44100 s)
  @buffer_size  = @sample_rate * @channels * 1.0
  # how many samples there are in each buffer, irrespective of channels
  @buffer_length = @buffer_size / @channels
  # buffer_duration = buffer_length / sample_rate

  @total_buffers_processed = 0
end

Instance Method Details

#<<(frames) ⇒ Object

Queue audio frames for playback.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/plaything.rb', line 99

def <<(frames)
  if buffers_processed > 0
    FFI::MemoryPointer.new(OpenAL::Buffer, buffers_processed) do |ptr|
      OpenAL.source_unqueue_buffers(@source, ptr.count, ptr)
      @total_buffers_processed += ptr.count
      @free_buffers.concat OpenAL::Buffer.extract(ptr, ptr.count)
      @queued_buffers.delete_if { |buffer| @free_buffers.include?(buffer) }
    end
  end

  wanted_size = (@buffer_size - @queued_frames.length).div(@channels) * @channels
  consumed_frames = frames.take(wanted_size)
  @queued_frames.concat(consumed_frames)

  if @queued_frames.length >= @buffer_size and @free_buffers.any?
    current_buffer = @free_buffers.shift

    FFI::MemoryPointer.new(@sample_type, @queued_frames.length) do |frames|
      frames.public_send(:"write_array_of_#{@sample_type}", @queued_frames)
      # stereo16 = 2 int16s (1 frame) = 1 sample
      OpenAL.buffer_data(current_buffer, @sample_format, frames, frames.size, @sample_rate)
      @queued_frames.clear
    end

    FFI::MemoryPointer.new(OpenAL::Buffer, 1) do |buffers|
      buffers.write_uint(current_buffer.to_native)
      OpenAL.source_queue_buffers(@source, buffers.count, buffers)
    end

    @queued_buffers.push(current_buffer)
  end

  consumed_frames.length
end

#dropsInteger



92
93
94
# File 'lib/plaything.rb', line 92

def drops
  0
end

#pauseObject

Pause playback of queued audio. Playback will resume from current position when #play is called.



65
66
67
# File 'lib/plaything.rb', line 65

def pause
  OpenAL.source_pause(@source)
end

#playObject

Note:

You must continue to supply audio, or playback will cease.

Start playback of queued audio.



60
61
62
# File 'lib/plaything.rb', line 60

def play
  OpenAL.source_play(@source)
end

#positionRational



82
83
84
# File 'lib/plaything.rb', line 82

def position
  Rational(@total_buffers_processed * @buffer_length + sample_offset, @sample_rate)
end

#queue_sizeInteger



87
88
89
# File 'lib/plaything.rb', line 87

def queue_size
  @source.get(:buffers_queued, Integer) * @buffer_length - sample_offset
end

#stopObject

Note:

All audio queues are completely cleared, and #position is reset.

Stop playback and clear any queued audio.



72
73
74
75
76
77
78
79
# File 'lib/plaything.rb', line 72

def stop
  OpenAL.source_stop(@source)
  @source.detach_buffers
  @free_buffers.concat(@queued_buffers)
  @queued_buffers.clear
  @queued_frames.clear
  @total_buffers_processed = 0
end