#### 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