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