Login
Artifact [0dd32b2417]
Login

Artifact 0dd32b24177fa2f8ad1b579ed3a7ff53f8c071b355217aa1be394eef8fb3bce9:


#### YunoSynth
#### Copyright (C) 2023 Remilia Scarlet <remilia@posteo.jp>
#### Portions 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-c140-mame"

####
#### Namco C140 sound chip emulator interface
####

module Yuno::Chips
  # C140 sound chip emulator.
  class C140 < Yuno::AbstractChip
    CHIP_ID = 0x1C_u32

    @clockFromHeader : UInt32 = 0
    @samplingMode : UInt8 = 0
    @chipSampleRate : UInt32 = 0
    @chip : C140Mame? = nil
    @subtype : UInt8 = 0

    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::C140) ||  vgm.header.c140Clock
      @clockFromHeader &= 0x3FFFFFFF
      @playerSampleRate = newPlayerSampleRate
      @subtype = vgm.header.c140ChipType
      chipCount = (vgm.header.c140Clock & 0x40000000) != 0 ? 2 : 1
      initFields(vgm, emuCore, playbackSampleRate, newSamplingMode, chipCount, flags)
    end

    protected def initialize
    end

    protected def initFields(vgm : VgmFile, emuCore : Symbol?, playbackSampleRate : UInt32, newSamplingMode : UInt8,
                             chipCount : Int32, flags : ChipFlags?) : Nil
      @volume = vgm.getChipVolume(self, ChipType::C140, chipCount)

      @core = emuCore || C140.defaultEmuCore
      case @core
      when :mame
        @samplingMode = newSamplingMode
        @chipSampleRate = @clockFromHeader <= 0 ? playbackSampleRate : @clockFromHeader

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

    def type : ChipType
      ChipType::C140
    end

    def name : String
      case @subtype
      when 0, 1 then "Namco C140"
      when 2 then "Namco C219 ASIC"
      else "Namco C140 derivative"
      end
    end

    def shortName : String
      case @subtype
      when 0, 1 then "C140"
      when 2 then "C219"
      else "C140"
      end
    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
      case @core
      when :mame
        rate = if clock < 1000000
                 clock
               else
                 clock.tdiv(384)
               end

        srate = if (Yuno.bitflag?(@samplingMode, 0x01) && rate < @chipSampleRate) || @samplingMode == 0x02
                  @chipSampleRate
                else
                  rate
                end

        raise "Cannot start C140" if rate >= 0x1000000 # Limit to 16 MHz sample rate (32 MB buffer)

        @chip = C140Mame.new(rate, srate, C140Mame::BankingType.from_value(@subtype))
        @sampleRate = @chip.not_nil!.rate # Update sample rate
        @sampleRate

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

    def getClock : UInt32
      @clockFromHeader
    end

    def getStartFlags(vgm : VgmFile) : ChipFlags?
      nil
    end

    def update(chipIndex : UInt8, outputs : OutputBuffers, samples : Int) : Nil
      @chip.not_nil!.update(outputs, samples.to_u32!)
    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)
    end

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

    def writeDac(port : Int, command : Int, data : Int) : Nil
      write(0, (port.to_i32! << 8) | command, data)
    end

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

    def getVolModifier : UInt32
      @volume.to_u32
    end

    def baseVolume : UInt16
      0x100_u16
    end

    @[AlwaysInline]
    def writeRom(romSize : Int32, dataStart : Int32, dataLength : Int32, romData : Slice(UInt8)) : Nil
      @chip.not_nil!.writeRom(romSize, dataStart, dataLength, romData)
    end

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