File src/remiaudio/drivers/portaudio.cr from the latest check-in


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
#### 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