Artifact b3789276f8fd9d17565c30d447d3d89afb45d5ac2105f35a16f1ecfdecf1bbc9:

  • File src/remiaudio/drivers/portaudio.cr — part of check-in [e4bff7e605] at 2024-09-05 04:10:27 on branch trunk — Merge stuff from RemiSound and RemiPortAudio into RemiAudio. (user: alexa size: 3117)

#### RemiAudio
#### Copyright (C) 2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify it
#### under the terms of the GNU Affero General Public License as published by
#### the Free Software Foundation, either version 3 of the License, or (at your
#### option) any later version.
####
#### This program is distributed in the hope that it will be useful, but WITHOUT
#### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
#### FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
#### License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program.  If not, see <https://www.gnu.org/licenses/>.
require "./port-bindings"

####
#### PortAudio Device
####

module RemiAudio::Drivers::PortAudio
  # The `PortDevice` class is used to playback audio using PortAudio.
  #
  # PortAudio supports the output of Float32 audio natively, so the
  # `#bitDepth` field is ignored by this class and is always set to `32`.
  class PortDevice < AudioDevice
    @stream : PaStream?

    # Creates a new `PortDevice` instance.
    #
    # PortAudio supports the output of Float32 audio natively, so the
    # `#bitDepth` field is ignored by this class and is always set to `32`.
    def initialize(newSampleRate : Int, newBitDepth : Int, newChannels : Int)
      @sampleRate = newSampleRate.to_u32.as(UInt32)
      @bitDepth = 32u8 # newBitDepth is ignored by PortDevice
      @channels = newChannels.to_u8.as(UInt8)
      @expectedBufferSize = @bufferSize * @channels
      @outputSize = @expectedBufferSize * sizeof(Float32)
    end

    # :inherit:
    def start : Nil
      raise AudioDeviceError.new("Attempted to start a device twice") if @started
      PortAudio.init
      @stream = PaStream.openDefaultStream(nil, 2,
                                           SampleFormat::Float32,
                                           @sampleRate,
                                           @bufferSize)
      @stream.not_nil!.start
      @started = true
    end

    # :inherit:
    def stop : Nil
      @stream.try &.stop
      @stream.try &.close
      PortAudio.terminate if @started
      @started = false
    end

    # Plays back the audio in *buf* by sending it to the underlying backend.
    #
    # You MUST ALWAYS pass the correct buffer size to `#writeBuffer`, as defined
    # by the value of `#bufferSize` multiplied by the number of `#channels`.
    #
    # Note: if `-Dremiaudio_wd40` is used at compile time, then the size of
    # *buf* is not checked for a valid size, and it is not checked if the
    # device has been started.
    def writeBuffer(buf : Array(Float32)|Slice(Float32)) : Nil
      {% unless flag?(:remiaudio_wd40) %}
        raise AudioDeviceError.new("Device not started") unless @started
        unless buf.size == @expectedBufferSize
          raise AudioDeviceError.new("Buffer was the incorrect size: #{buf.size} != #{@expectedBufferSize}")
        end
      {% end %}
      @stream.not_nil! << buf
    end
  end
end