Login
Artifact [72c3a8c167]
Login

Artifact 72c3a8c1670ced5e8525cc3cb64d1336b7ab9317c8a34b05524e58b68cbc88ae:


#### YunoSynth
#### Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp>
#### Based on VGMPlay, Copyright (C) Valley Bell
####
#### 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 "./emu-opn-mame/ym2203-mame"
require "./chip-ay8910"

####
#### Yamaha YM2203 sound chip emulator interface
####

module Yuno::Chips
  # YM2203 sound chip emulator.
  class YM2203 < Yuno::AbstractChip
    CHIP_ID = 0x06_u32

    @clockFromHeader : UInt32 = 0
    @chip : YM2203Mame? = nil
    @samplingMode : UInt8 = 0
    @chipSampleRate : UInt32 = 0
    @lastReg : UInt8 = 0
    @ayClock : UInt32 = 0

    @psgIntf : OPNMame::SsgCallbacks = OPNMame::SsgCallbacks.new

    def initialize(chipNum : Int32, absChipCount : Int32, vgm : VgmFile, playbackSampleRate : UInt32,
                   newSamplingMode : UInt8, newPlayerSampleRate : UInt32, *, emuCore : Symbol? = nil,
                   flags : ChipFlags? = nil)
      @clockFromHeader = vgm.getAlternateChipClock(chipNum, Yuno::ChipType::Ym2203) || vgm.header.ym2203Clock
      @clockFromHeader &= 0x3FFFFFFF
      @playerSampleRate = newPlayerSampleRate
      chipCount = (vgm.header.ym2203Clock & 0x40000000) != 0 ? 2 : 1
      initFields(vgm, emuCore, playbackSampleRate, newSamplingMode, chipCount)
    end

    protected def initialize
    end

    protected def initFields(vgm : VgmFile, emuCore : Symbol?, playbackSampleRate : UInt32, newSamplingMode : UInt8,
                             chipCount : Int32) : Nil
      @volume = vgm.getChipVolume(self, ChipType::Ym2203, chipCount)
      @core = emuCore || YM2203.defaultEmuCore
      case @core
      when :mame
        @samplingMode = newSamplingMode
        @chipSampleRate = @clockFromHeader <= 0 ? playbackSampleRate : @clockFromHeader

        # The YM2203 has an internal YM2149.
        # KLUDGE pass 0 as the current chip number
        paired = Ay8910.new(0, 0, vgm, playbackSampleRate, newSamplingMode, @playerSampleRate,
                             # We always want the Emu2149 core.
                             emuCore: :emu2149)
        self.isPaired = true
        paired.updateFn = ->self.updatePaired(UInt8, OutputBuffers, UInt32)
        paired.volume = vgm.getChipVolume(paired, ChipType::Ym2203, chipCount, ChipType::Ay8910)

        @psgIntf = OPNMame::SsgCallbacks.new(paired,
                                             ->self.psgClock=(UInt32), ->self.psgWrite(Int32, Int32),
                                             ->self.psgRead, ->self.psgReset)
        @paired = paired

      else raise YunoError.new("Unsupported emulation core for YM2203: #{@core}")
      end
    end

    def type : ChipType
      ChipType::Ym2203
    end

    def name : String
      "Yamaha YM2203"
    end

    def shortName : String
      "YM2203"
    end

    def id : UInt32
      CHIP_ID
    end

    def emuCore : Symbol
      @core
    end

    def self.defaultEmuCore : Symbol
      :mame
    end

    def start(chipIndex : UInt8, clock : UInt32, flags : ChipFlags? = nil) : UInt32
      # Update sample rate
      @sampleRate = clock.tdiv(72)
      if (@samplingMode == 0x01 && @sampleRate < @chipSampleRate) || @samplingMode == 0x02
        @sampleRate = @chipSampleRate
      end

      case @core
      when :mame
        @chip = YM2203Mame.new(->self.updateRequest, self, clock, @sampleRate, nil, nil, @psgIntf)
        #@chip.not_nil!.setSampleRateChangeCB(->AbstractChip.changeChipSampleRate(AbstractChip, UInt32),
        #                                     @paired.not_nil!)
      else raise YunoError.new("Unsupported emulation core for YM2203: #{@core}")
      end

      ayClock = clock.tdiv(2)
      ayRate = ayClock.tdiv(8)
      if (@samplingMode == 0x01 && @sampleRate < @chipSampleRate) || @samplingMode == 0x02
        ayRate = @chipSampleRate
      end

      @paired.as(Ay8910).startYm(ayClock, ayRate, flags.as?(Ay8910::Ay8910Flags))
      @sampleRate
    end

    def getClock : UInt32
      @clockFromHeader
    end

    def getStartFlags(vgm : VgmFile) : ChipFlags?
      Ay8910::Ay8910Flags.new(vgm.header.ay8910Flags)
    end

    def update(chipIndex : UInt8, outputs : OutputBuffers, samples : Int) : Nil
      @chip.not_nil!.update(outputs, samples.to_u32!)
    end

    def updatePaired(chipIndex : UInt8, outputs : OutputBuffers, samples : Int) : Nil
      psgUpdate(chipIndex, outputs, samples)
    end

    def reset(chipIndex : UInt8) : Nil
      @chip.not_nil!.reset
    end

    def read(chipIndex : UInt8, offset : Int) : UInt8|UInt16|UInt32
      @chip.not_nil!.read(offset & 1)
    end

    def write(chipIndex : UInt8, offset : Int, data : Int, port : UInt8 = 0) : Nil
      @chip.not_nil!.write(offset & 1, data.to_u8!)
    end

    def writeDac(port : Int, command : Int, data : Int) : Nil
      write(0, 0x00, command)
      write(0, 0x01, data)
    end

    @[AlwaysInline]
    def statusPortRead(chipIndex : UInt8, offset : Int) : UInt8
      read(chipIndex, 0).to_u8!
    end

    @[AlwaysInline]
    def readPortRead(chipIndex : UInt8, offset : Int) : UInt8
      read(chipIndex, 1).to_u8!
    end

    @[AlwaysInline]
    def controlPortWrite(chipIndex : UInt8, offset : Int, data : UInt8) : Nil
      write(chipIndex, 0, data)
    end

    @[AlwaysInline]
    def writePortWrite(chipIndex : UInt8, offset : Int, data : UInt8) : Nil
      write(chipIndex, 1, data)
    end

    def setMuteMask(chipIndex : UInt8, mask : UInt32) : Nil
      @chip.not_nil!.muteMask = mask
    end

    def getVolModifier : UInt32
      @volume.to_u32 + @paired.not_nil!.volume
    end

    def baseVolume : UInt16
      0x100_u16
    end

    @[AlwaysInline]
    def updateRequest : Nil
      @chip.not_nil!.update(Yuno::FAKE_BUF, 0)
    end

    @[AlwaysInline]
    def psgClock=(value : UInt32) : Nil
      @paired.not_nil!.as(Ay8910).clock = value
    end

    @[AlwaysInline]
    def psgWrite(address : Int, data : Int)
      @paired.not_nil!.as(Ay8910).writeIO(address, data)
    end

    @[AlwaysInline]
    def psgRead : UInt8|UInt16|UInt32
      @paired.not_nil!.as(Ay8910).readIO
    end

    @[AlwaysInline]
    def psgReset : Nil
      @paired.not_nil!.as(Ay8910).reset(1)
    end

    @[AlwaysInline]
    def psgUpdate(chipIndex : UInt8, outputs : OutputBuffers, samples : Int) : Nil
      @paired.not_nil!.update(1, outputs, samples.to_u32!)
    end

    @[AlwaysInline]
    def psgStereoMask=(mask : UInt32) : Nil
      @paired.not_nil!.chip.as(Ay8910::Ay8910Emu2149).stereoMask = mask
    end

    # The internal interface for the chip.
    @[AlwaysInline]
    def chip : AbstractEmulator
      @chip.not_nil!
    end
  end
end