#### Nuked OPM
#### Copyright (C) 2020 Nuke.YKT
#### Crystal port Copyright (C) 2023 Remilia Scarlet
####
#### This file is part of Nuked OPM.
####
#### Nuked OPM is free software: you can redistribute it and/or modify it under
#### the terms of the GNU Lesser General Public License as published by the Free
#### Software Foundation, either version 2.1 of the License, or (at your option)
#### any later version.
####
#### Nuked OPM 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 Lesser General Public
#### License for more details.
####
#### You should have received a copy of the GNU Lesser General Public License
#### along with Nuked OPM. If not, see <https://www.gnu.org/licenses/>.
####
#### Nuked OPM emulator.
#### Thanks:
#### siliconpr0n.org(digshadow, John McMaster):
#### YM2151 and other FM chip decaps and die shots.
####
#### Based on version: 0.9 beta
####
#### Yamaha YM2151 sound chip emulator internal interface.
####
module Yuno::Chips
class YM2151 < AbstractChip
private class YM2151Nuked < Yuno::AbstractEmulator
###
### Constants
###
RSM_FRAC = 10
OPN_WRITEBUF_SIZE = 2048
OPN_WRITEBUF_DELAY = 36
EG_NUM_ATTACK = 0_u8
EG_NUM_DECAY = 1_u8
EG_NUM_SUSTAIN = 2_u8
EG_NUM_RELEASE = 3_u8
LOG_SIN_ROM = [
0x859u16, 0x6c3u16, 0x607u16, 0x58bu16, 0x52eu16, 0x4e4u16, 0x4a6u16, 0x471u16,
0x443u16, 0x41au16, 0x3f5u16, 0x3d3u16, 0x3b5u16, 0x398u16, 0x37eu16, 0x365u16,
0x34eu16, 0x339u16, 0x324u16, 0x311u16, 0x2ffu16, 0x2edu16, 0x2dcu16, 0x2cdu16,
0x2bdu16, 0x2afu16, 0x2a0u16, 0x293u16, 0x286u16, 0x279u16, 0x26du16, 0x261u16,
0x256u16, 0x24bu16, 0x240u16, 0x236u16, 0x22cu16, 0x222u16, 0x218u16, 0x20fu16,
0x206u16, 0x1fdu16, 0x1f5u16, 0x1ecu16, 0x1e4u16, 0x1dcu16, 0x1d4u16, 0x1cdu16,
0x1c5u16, 0x1beu16, 0x1b7u16, 0x1b0u16, 0x1a9u16, 0x1a2u16, 0x19bu16, 0x195u16,
0x18fu16, 0x188u16, 0x182u16, 0x17cu16, 0x177u16, 0x171u16, 0x16bu16, 0x166u16,
0x160u16, 0x15bu16, 0x155u16, 0x150u16, 0x14bu16, 0x146u16, 0x141u16, 0x13cu16,
0x137u16, 0x133u16, 0x12eu16, 0x129u16, 0x125u16, 0x121u16, 0x11cu16, 0x118u16,
0x114u16, 0x10fu16, 0x10bu16, 0x107u16, 0x103u16, 0x0ffu16, 0x0fbu16, 0x0f8u16,
0x0f4u16, 0x0f0u16, 0x0ecu16, 0x0e9u16, 0x0e5u16, 0x0e2u16, 0x0deu16, 0x0dbu16,
0x0d7u16, 0x0d4u16, 0x0d1u16, 0x0cdu16, 0x0cau16, 0x0c7u16, 0x0c4u16, 0x0c1u16,
0x0beu16, 0x0bbu16, 0x0b8u16, 0x0b5u16, 0x0b2u16, 0x0afu16, 0x0acu16, 0x0a9u16,
0x0a7u16, 0x0a4u16, 0x0a1u16, 0x09fu16, 0x09cu16, 0x099u16, 0x097u16, 0x094u16,
0x092u16, 0x08fu16, 0x08du16, 0x08au16, 0x088u16, 0x086u16, 0x083u16, 0x081u16,
0x07fu16, 0x07du16, 0x07au16, 0x078u16, 0x076u16, 0x074u16, 0x072u16, 0x070u16,
0x06eu16, 0x06cu16, 0x06au16, 0x068u16, 0x066u16, 0x064u16, 0x062u16, 0x060u16,
0x05eu16, 0x05cu16, 0x05bu16, 0x059u16, 0x057u16, 0x055u16, 0x053u16, 0x052u16,
0x050u16, 0x04eu16, 0x04du16, 0x04bu16, 0x04au16, 0x048u16, 0x046u16, 0x045u16,
0x043u16, 0x042u16, 0x040u16, 0x03fu16, 0x03eu16, 0x03cu16, 0x03bu16, 0x039u16,
0x038u16, 0x037u16, 0x035u16, 0x034u16, 0x033u16, 0x031u16, 0x030u16, 0x02fu16,
0x02eu16, 0x02du16, 0x02bu16, 0x02au16, 0x029u16, 0x028u16, 0x027u16, 0x026u16,
0x025u16, 0x024u16, 0x023u16, 0x022u16, 0x021u16, 0x020u16, 0x01fu16, 0x01eu16,
0x01du16, 0x01cu16, 0x01bu16, 0x01au16, 0x019u16, 0x018u16, 0x017u16, 0x017u16,
0x016u16, 0x015u16, 0x014u16, 0x014u16, 0x013u16, 0x012u16, 0x011u16, 0x011u16,
0x010u16, 0x00fu16, 0x00fu16, 0x00eu16, 0x00du16, 0x00du16, 0x00cu16, 0x00cu16,
0x00bu16, 0x00au16, 0x00au16, 0x009u16, 0x009u16, 0x008u16, 0x008u16, 0x007u16,
0x007u16, 0x007u16, 0x006u16, 0x006u16, 0x005u16, 0x005u16, 0x005u16, 0x004u16,
0x004u16, 0x004u16, 0x003u16, 0x003u16, 0x003u16, 0x002u16, 0x002u16, 0x002u16,
0x002u16, 0x001u16, 0x001u16, 0x001u16, 0x001u16, 0x001u16, 0x001u16, 0x001u16,
0x000u16, 0x000u16, 0x000u16, 0x000u16, 0x000u16, 0x000u16, 0x000u16, 0x000u16
]
EXP_ROM = [
0x7FAu16, 0x7F5u16, 0x7EFu16, 0x7EAu16, 0x7E4u16, 0x7DFu16, 0x7DAu16, 0x7D4u16,
0x7CFu16, 0x7C9u16, 0x7C4u16, 0x7BFu16, 0x7B9u16, 0x7B4u16, 0x7AEu16, 0x7A9u16,
0x7A4u16, 0x79Fu16, 0x799u16, 0x794u16, 0x78Fu16, 0x78Au16, 0x784u16, 0x77Fu16,
0x77Au16, 0x775u16, 0x770u16, 0x76Au16, 0x765u16, 0x760u16, 0x75Bu16, 0x756u16,
0x751u16, 0x74Cu16, 0x747u16, 0x742u16, 0x73Du16, 0x738u16, 0x733u16, 0x72Eu16,
0x729u16, 0x724u16, 0x71Fu16, 0x71Au16, 0x715u16, 0x710u16, 0x70Bu16, 0x706u16,
0x702u16, 0x6FDu16, 0x6F8u16, 0x6F3u16, 0x6EEu16, 0x6E9u16, 0x6E5u16, 0x6E0u16,
0x6DBu16, 0x6D6u16, 0x6D2u16, 0x6CDu16, 0x6C8u16, 0x6C4u16, 0x6BFu16, 0x6BAu16,
0x6B5u16, 0x6B1u16, 0x6ACu16, 0x6A8u16, 0x6A3u16, 0x69Eu16, 0x69Au16, 0x695u16,
0x691u16, 0x68Cu16, 0x688u16, 0x683u16, 0x67Fu16, 0x67Au16, 0x676u16, 0x671u16,
0x66Du16, 0x668u16, 0x664u16, 0x65Fu16, 0x65Bu16, 0x657u16, 0x652u16, 0x64Eu16,
0x649u16, 0x645u16, 0x641u16, 0x63Cu16, 0x638u16, 0x634u16, 0x630u16, 0x62Bu16,
0x627u16, 0x623u16, 0x61Eu16, 0x61Au16, 0x616u16, 0x612u16, 0x60Eu16, 0x609u16,
0x605u16, 0x601u16, 0x5FDu16, 0x5F9u16, 0x5F5u16, 0x5F0u16, 0x5ECu16, 0x5E8u16,
0x5E4u16, 0x5E0u16, 0x5DCu16, 0x5D8u16, 0x5D4u16, 0x5D0u16, 0x5CCu16, 0x5C8u16,
0x5C4u16, 0x5C0u16, 0x5BCu16, 0x5B8u16, 0x5B4u16, 0x5B0u16, 0x5ACu16, 0x5A8u16,
0x5A4u16, 0x5A0u16, 0x59Cu16, 0x599u16, 0x595u16, 0x591u16, 0x58Du16, 0x589u16,
0x585u16, 0x581u16, 0x57Eu16, 0x57Au16, 0x576u16, 0x572u16, 0x56Fu16, 0x56Bu16,
0x567u16, 0x563u16, 0x560u16, 0x55Cu16, 0x558u16, 0x554u16, 0x551u16, 0x54Du16,
0x549u16, 0x546u16, 0x542u16, 0x53Eu16, 0x53Bu16, 0x537u16, 0x534u16, 0x530u16,
0x52Cu16, 0x529u16, 0x525u16, 0x522u16, 0x51Eu16, 0x51Bu16, 0x517u16, 0x514u16,
0x510u16, 0x50Cu16, 0x509u16, 0x506u16, 0x502u16, 0x4FFu16, 0x4FBu16, 0x4F8u16,
0x4F4u16, 0x4F1u16, 0x4EDu16, 0x4EAu16, 0x4E7u16, 0x4E3u16, 0x4E0u16, 0x4DCu16,
0x4D9u16, 0x4D6u16, 0x4D2u16, 0x4CFu16, 0x4CCu16, 0x4C8u16, 0x4C5u16, 0x4C2u16,
0x4BEu16, 0x4BBu16, 0x4B8u16, 0x4B5u16, 0x4B1u16, 0x4AEu16, 0x4ABu16, 0x4A8u16,
0x4A4u16, 0x4A1u16, 0x49Eu16, 0x49Bu16, 0x498u16, 0x494u16, 0x491u16, 0x48Eu16,
0x48Bu16, 0x488u16, 0x485u16, 0x482u16, 0x47Eu16, 0x47Bu16, 0x478u16, 0x475u16,
0x472u16, 0x46Fu16, 0x46Cu16, 0x469u16, 0x466u16, 0x463u16, 0x460u16, 0x45Du16,
0x45Au16, 0x457u16, 0x454u16, 0x451u16, 0x44Eu16, 0x44Bu16, 0x448u16, 0x445u16,
0x442u16, 0x43Fu16, 0x43Cu16, 0x439u16, 0x436u16, 0x433u16, 0x430u16, 0x42Du16,
0x42Au16, 0x428u16, 0x425u16, 0x422u16, 0x41Fu16, 0x41Cu16, 0x419u16, 0x416u16,
0x414u16, 0x411u16, 0x40Eu16, 0x40Bu16, 0x408u16, 0x406u16, 0x403u16, 0x400u16
]
EG_STEP_HI = [
[ 0u32, 0u32, 0u32, 0u32 ],
[ 1u32, 0u32, 0u32, 0u32 ],
[ 1u32, 0u32, 1u32, 0u32 ],
[ 1u32, 1u32, 1u32, 0u32 ]
]
PG_DETUNE = [ 16u32, 17u32, 19u32, 20u32, 22u32, 24u32, 27u32, 29u32 ]
record FreqTable, baseFreq : Int32, approxType : Int32, slope : Int32
PG_FREQ_TABLE = [
FreqTable.new(baseFreq: 1299, approxType: 1, slope: 19),
FreqTable.new(baseFreq: 1318, approxType: 1, slope: 19),
FreqTable.new(baseFreq: 1337, approxType: 1, slope: 19),
FreqTable.new(baseFreq: 1356, approxType: 1, slope: 20),
FreqTable.new(baseFreq: 1376, approxType: 1, slope: 20),
FreqTable.new(baseFreq: 1396, approxType: 1, slope: 20),
FreqTable.new(baseFreq: 1416, approxType: 1, slope: 21),
FreqTable.new(baseFreq: 1437, approxType: 1, slope: 20),
FreqTable.new(baseFreq: 1458, approxType: 1, slope: 21),
FreqTable.new(baseFreq: 1479, approxType: 1, slope: 21),
FreqTable.new(baseFreq: 1501, approxType: 1, slope: 22),
FreqTable.new(baseFreq: 1523, approxType: 1, slope: 22),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 1545, approxType: 1, slope: 22),
FreqTable.new(baseFreq: 1567, approxType: 1, slope: 22),
FreqTable.new(baseFreq: 1590, approxType: 1, slope: 23),
FreqTable.new(baseFreq: 1613, approxType: 1, slope: 23),
FreqTable.new(baseFreq: 1637, approxType: 1, slope: 23),
FreqTable.new(baseFreq: 1660, approxType: 1, slope: 24),
FreqTable.new(baseFreq: 1685, approxType: 1, slope: 24),
FreqTable.new(baseFreq: 1709, approxType: 1, slope: 24),
FreqTable.new(baseFreq: 1734, approxType: 1, slope: 25),
FreqTable.new(baseFreq: 1759, approxType: 1, slope: 25),
FreqTable.new(baseFreq: 1785, approxType: 1, slope: 26),
FreqTable.new(baseFreq: 1811, approxType: 1, slope: 26),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 1837, approxType: 1, slope: 26),
FreqTable.new(baseFreq: 1864, approxType: 1, slope: 27),
FreqTable.new(baseFreq: 1891, approxType: 1, slope: 27),
FreqTable.new(baseFreq: 1918, approxType: 1, slope: 28),
FreqTable.new(baseFreq: 1946, approxType: 1, slope: 28),
FreqTable.new(baseFreq: 1975, approxType: 1, slope: 28),
FreqTable.new(baseFreq: 2003, approxType: 1, slope: 29),
FreqTable.new(baseFreq: 2032, approxType: 1, slope: 30),
FreqTable.new(baseFreq: 2062, approxType: 1, slope: 30),
FreqTable.new(baseFreq: 2092, approxType: 1, slope: 30),
FreqTable.new(baseFreq: 2122, approxType: 1, slope: 31),
FreqTable.new(baseFreq: 2153, approxType: 1, slope: 31),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 2185, approxType: 1, slope: 31),
FreqTable.new(baseFreq: 2216, approxType: 0, slope: 31),
FreqTable.new(baseFreq: 2249, approxType: 0, slope: 31),
FreqTable.new(baseFreq: 2281, approxType: 0, slope: 31),
FreqTable.new(baseFreq: 2315, approxType: 0, slope: 31),
FreqTable.new(baseFreq: 2348, approxType: 0, slope: 31),
FreqTable.new(baseFreq: 2382, approxType: 0, slope: 30),
FreqTable.new(baseFreq: 2417, approxType: 0, slope: 30),
FreqTable.new(baseFreq: 2452, approxType: 0, slope: 30),
FreqTable.new(baseFreq: 2488, approxType: 0, slope: 30),
FreqTable.new(baseFreq: 2524, approxType: 0, slope: 30),
FreqTable.new(baseFreq: 2561, approxType: 0, slope: 30),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16),
FreqTable.new(baseFreq: 0, approxType: 0, slope: 16)
]
FM_ALGORITHM = [
[
[ 1u32, 1u32, 1u32, 1u32, 1u32, 1u32, 1u32, 1u32 ], # M1_0
[ 1u32, 1u32, 1u32, 1u32, 1u32, 1u32, 1u32, 1u32 ], # M1_1
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # C1
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # Last operator
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # Last operator
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 1u32 ] # Out
],
[
[ 0u32, 1u32, 0u32, 0u32, 0u32, 1u32, 0u32, 0u32 ], # M1_0
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # M1_1
[ 1u32, 1u32, 1u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # C1
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # Last operator
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # Last operator
[ 0u32, 0u32, 0u32, 0u32, 0u32, 1u32, 1u32, 1u32 ] # Out
],
[
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # M1_0
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # M1_1
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # C1
[ 1u32, 0u32, 0u32, 1u32, 1u32, 1u32, 1u32, 0u32 ], # Last operator
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # Last operator
[ 0u32, 0u32, 0u32, 0u32, 1u32, 1u32, 1u32, 1u32 ] # Out
],
[
[ 0u32, 0u32, 1u32, 0u32, 0u32, 1u32, 0u32, 0u32 ], # M1_0
[ 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # M1_1
[ 0u32, 0u32, 0u32, 1u32, 0u32, 0u32, 0u32, 0u32 ], # C1
[ 1u32, 1u32, 0u32, 1u32, 1u32, 0u32, 0u32, 0u32 ], # Last operator
[ 0u32, 0u32, 1u32, 0u32, 0u32, 0u32, 0u32, 0u32 ], # Last operator
[ 1u32, 1u32, 1u32, 1u32, 1u32, 1u32, 1u32, 1u32 ] # Out
]
]
LFO_COUNTER2_TABLE = [
0x0000u16, 0x4000u16, 0x6000u16, 0x7000u16,
0x7800u16, 0x7C00u16, 0x7E00u16, 0x7F00u16,
0x7F80u16, 0x7FC0u16, 0x7FE0u16, 0x7FF0u16,
0x7FF8u16, 0x7FFCu16, 0x7FFEu16, 0x7FFFu16
]
###
### Internal classes
###
class WriteBuf
property time : UInt64 = 0
property port : UInt8 = 0
property data : UInt8 = 0
def initialize
end
end
###
### Fields
###
@cycles : UInt32 = 0
@ic : UInt8 = 0
@ic2 : UInt8 = 0
# IO
@writeData : UInt8 = 0
@writeA : UInt8 = 0
@writeAEn : UInt8 = 0
@writeD : UInt8 = 0
@writeDEn : UInt8 = 0
@writeBusy : UInt8 = 0
@writeBusyCount : UInt8 = 0
@modeAddress : UInt8 = 0
@ioCt1 : UInt8 = 0
@ioCt2 : UInt8 = 0
# LFO
@lfoAmLock : UInt8 = 0
@lfoPmLock : UInt8 = 0
@lfoCounter1 : UInt8 = 0
@lfoCounter1Of1 : UInt8 = 0
@lfoCounter1Of2 : UInt8 = 0
@lfoCounter2 : UInt16 = 0
@lfoCounter2Load : UInt8 = 0
@lfoCounter2Of : UInt8 = 0
@lfoCounter2OfLock : UInt8 = 0
@lfoCounter2OfLock2 : UInt8 = 0
@lfoCounter3Clock : UInt8 = 0
@lfoCounter3 : UInt16 = 0
@lfoCounter3Step : UInt8 = 0
@lfoFrqUpdate : UInt8 = 0
@lfoClock : UInt8 = 0
@lfoClockLock : UInt8 = 0
@lfoClockTest : UInt8 = 0
@lfoTest : UInt8 = 0
@lfoVal : UInt32 = 0
@lfoValCarry : UInt8 = 0
@lfoOut1 : UInt32 = 0
@lfoOut2 : UInt32 = 0
@lfoOut2B : UInt32 = 0
@lfoMultCarry : UInt8 = 0
@lfoTrigSign : UInt8 = 0
@lfoSawSign : UInt8 = 0
@lfoBitCounter : UInt8 = 0
# Env Gen
@egState : Array(UInt8) = Array(UInt8).new(32, 0u8)
@egLevel : Array(UInt16) = Array(UInt16).new(32, 0u16)
@egRate : Array(UInt8) = Array(UInt8).new(32, 0u8)
@egSl : Array(UInt8) = Array(UInt8).new(2, 0u8)
@egTl : Array(UInt8) = Array(UInt8).new(3, 0u8)
@egZr : Array(UInt8) = Array(UInt8).new(2, 0u8)
@egTimerShiftLock : UInt8 = 0
@egTimerLock : UInt8 = 0
@egIncHi : UInt8 = 0
@egShift : UInt8 = 0
@egClock : UInt8 = 0
@egClockCount : UInt8 = 0
@egClockQuotinent : UInt8 = 0
@egInc : UInt8 = 0
@egRateMax : Array(UInt8) = Array(UInt8).new(2, 0u8)
@egInstantAttack : UInt8 = 0
@egIncLinear : UInt8 = 0
@egIncAttack : UInt8 = 0
@egMute : UInt8 = 0
@egOutTemp : Array(UInt16) = Array(UInt16).new(2, 0u16)
@egOut : Array(UInt16) = Array(UInt16).new(2, 0u16)
@egAm : UInt8 = 0
@egAms : Array(UInt8) = Array(UInt8).new(2, 0u8)
@egTimerCarry : UInt8 = 0
@egTimer : UInt32 = 0
@egTimer2 : UInt32 = 0
@egTimerBStop : UInt8 = 0
@egSerial : UInt32 = 0
@egSerialBit : UInt8 = 0
@egTest : UInt8 = 0
# Phase Gen
@pgFnum : Array(UInt16) = Array(UInt16).new(32, 0u16)
@pgKCode : Array(UInt8) = Array(UInt8).new(32, 0u8)
@pgInc : Array(UInt32) = Array(UInt32).new(32, 0u32)
@pgPhase : Array(UInt32) = Array(UInt32).new(32, 0u32)
@pgReset : Array(UInt8) = Array(UInt8).new(32, 0u8)
@pgResetLatch : Array(UInt8) = Array(UInt8).new(32, 0u8)
@pgSerial : UInt32 = 0
# Operator
@opPhaseIn : UInt16 = 0
@opModIn : UInt16 = 0
@opPhase : UInt16 = 0
@opLogSin : Array(UInt16) = Array(UInt16).new(3, 0u16)
@opAtten : UInt16 = 0
@opExp : Array(UInt16) = Array(UInt16).new(2, 0u16)
@opPow : Array(UInt8) = Array(UInt8).new(2, 0u8)
@opSign : UInt32 = 0
@opOut : Array(Int16) = Array(Int16).new(6, 0i16)
@opConnect : UInt32 = 0
@opCounter : UInt8 = 0
@opFbUpdate : UInt8 = 0
@opFbShift : UInt8 = 0
@opC1Update : UInt8 = 0
@opModTable : Array(UInt8) = Array(UInt8).new(5, 0u8)
@opM1 : Array(Array(Int16)) = Array(Array(Int16)).new(8) { |_| Array(Int16).new(2, 0) }
@opC1 : Array(Int16) = Array(Int16).new(8, 0i16)
@opMod : Array(Int16) = Array(Int16).new(3, 0i16)
@opFb : Array(Int16) = Array(Int16).new(2, 0i16)
@opMixL : UInt8 = 0
@opMixR : UInt8 = 0
# Mixer
@mix : Array(Int32) = Array(Int32).new(2, 0)
@mix2 : Array(Int32) = Array(Int32).new(2, 0)
@mixOp : Int32 = 0
@mixSerial : Array(UInt32) = Array(UInt32).new(2, 0)
@mixBits : UInt32 = 0
@mixTopBitsLock : UInt32 = 0
@mixSignLock : UInt8 = 0
@mixSignLock2 : UInt8 = 0
@mixExpLock : UInt8 = 0
@mixClampLow : Array(UInt8) = Array(UInt8).new(2, 0)
@mixClampHigh : Array(UInt8) = Array(UInt8).new(2, 0)
@mixOutBit : UInt8 = 0
# Output
@smpSo : UInt8 = 0
@smpSh1 : UInt8 = 0
@smpSh2 : UInt8 = 0
# Noise
@noiseLfsr : UInt32 = 0
@noiseTimer : UInt32 = 0
@noiseTimerOf : UInt8 = 0
@noiseUpdate : UInt8 = 0
@noiseTemp : UInt8 = 0
# Register set
@modeTest : Array(UInt8) = Array(UInt8).new(8, 0)
@modeKonOperator : Array(UInt8) = Array(UInt8).new(4, 0)
@modeKonChannel : UInt8 = 0
@regAddress : UInt8 = 0
@regAddressReady : UInt8 = 0
@regData : UInt8 = 0
@regDataReady : UInt8 = 0
@chRl : Array(UInt8) = Array(UInt8).new(8, 0)
@chFb : Array(UInt8) = Array(UInt8).new(8, 0)
@chConnect : Array(UInt8) = Array(UInt8).new(8, 0)
@chKc : Array(UInt8) = Array(UInt8).new(8, 0)
@chKf : Array(UInt8) = Array(UInt8).new(8, 0)
@chPms : Array(UInt8) = Array(UInt8).new(8, 0)
@chAms : Array(UInt8) = Array(UInt8).new(8, 0)
@slDt1 : Array(UInt8) = Array(UInt8).new(32, 0)
@slMul : Array(UInt8) = Array(UInt8).new(32, 0)
@slTl : Array(UInt8) = Array(UInt8).new(32, 0)
@slKs : Array(UInt8) = Array(UInt8).new(32, 0)
@slAr : Array(UInt8) = Array(UInt8).new(32, 0)
@slAmE : Array(UInt8) = Array(UInt8).new(32, 0)
@slD1r : Array(UInt8) = Array(UInt8).new(32, 0)
@slDt2 : Array(UInt8) = Array(UInt8).new(32, 0)
@slD2r : Array(UInt8) = Array(UInt8).new(32, 0)
@slD1l : Array(UInt8) = Array(UInt8).new(32, 0)
@slRr : Array(UInt8) = Array(UInt8).new(32, 0)
@noiseEn : UInt8 = 0
@noiseFreq : UInt8 = 0
# Timer
@timerAReg : UInt16 = 0
@timerBReg : UInt8 = 0
@timerATemp : UInt8 = 0
@timerADoReset : UInt8 = 0
@timerADoLoad : UInt8 = 0
@timerAInc : UInt8 = 0
@timerAVal : UInt16 = 0
@timerAOf : UInt8 = 0
@timerALoad : UInt8 = 0
@timerAStatus : UInt8 = 0
@timerBSub : UInt8 = 0
@timerBSubOf : UInt8 = 0
@timerBInc : UInt8 = 0
@timerBVal : UInt16 = 0
@timerBOf : UInt8 = 0
@timerBDoReset : UInt8 = 0
@timerBDoLoad : UInt8 = 0
@timerBTemp : UInt8 = 0
@timerBStatus : UInt8 = 0
@timerIrq : UInt8 = 0
@lfoFreqHi : UInt8 = 0
@lfoFreqLo : UInt8 = 0
@lfoPmd : UInt8 = 0
@lfoAmd : UInt8 = 0
@lfoWave : UInt8 = 0
@timerIrqA : UInt8 = 0
@timerIrqB : UInt8 = 0
@timerLoadA : UInt8 = 0
@timerLoadB : UInt8 = 0
@timerResetA : UInt8 = 0
@timerResetB : UInt8 = 0
@modeCsm : UInt8 = 0
@ncActive : UInt8 = 0
@ncActiveLock : UInt8 = 0
@ncSign : UInt8 = 0
@ncSignLock : UInt8 = 0
@ncSignLock2 : UInt8 = 0
@ncBit : UInt8 = 0
@ncOut : UInt16 = 0
@opMix : Int16 = 0
@konCsm : UInt8 = 0
@konCsmLock : UInt8 = 0
@konDo : UInt8 = 0
@konChanMatch : UInt8 = 0
@kon : Array(UInt8) = Array(UInt8).new(32, 0)
@kon2 : Array(UInt8) = Array(UInt8).new(32, 0)
@modeKon : Array(UInt8) = Array(UInt8).new(32, 0)
# DAC
@dacOsh1 : UInt8 = 0
@dacOsh2 : UInt8 = 0
@dacBits : UInt16 = 0
@dacOutput : Array(Int32) = Array(Int32).new(2, 0)
@mute : Array(UInt32) = Array(UInt32).new(8, 0)
protected property rateRatio : Int32 = 0
@sampleCount : Int32 = 0
@oldSamples : Array(Int32) = Array(Int32).new(2, 0)
@samples : Array(Int32) = Array(Int32).new(2, 0)
@writebufSampleCount : UInt64 = 0
@writebufCur : UInt32 = 0
@writebufLast : UInt32 = 0
@writebufLastTime : UInt64 = 0
@writebuf : Array(WriteBuf)
@updateBuffer : Array(Int32) = Array(Int32).new(2, 0)
@writeBuffer : Array(Int32) = Array(Int32).new(2, 0)
@generatedBuffer : Array(Int32) = Array(Int32).new(2, 0)
###
### Methods
###
def initialize
@writebuf = Array(WriteBuf).new(OPN_WRITEBUF_SIZE) do |_|
WriteBuf.new
end
end
def update(outputs : OutputBuffers, samples : UInt32) : Nil
outL = outputs[0]
outR = outputs[1]
# Check once, then we can avoid bounds checks below.
if outL.size < samples || outR.size < samples
raise "YM2151 update: Bad number of samples (expected #{samples}, " \
"but buffers have #{outL.size} and #{outR.size})"
end
samples.times do |i|
generateResampled
outL.put!(i, @updateBuffer.get!(0))
outR.put!(i, @updateBuffer.get!(1))
end
end
def self.reset(chip : YM2151Nuked, rate : UInt32, clock : UInt32) : YM2151Nuked
ratio : UInt32 = chip.rateRatio.to_u32!
ret = YM2151Nuked.new
ret.setIC(1u8)
(32 * 64).times do |_|
ret.clock(nil)
end
ret.setIC(0u8)
if rate != 0
ret.rateRatio = (((64u64 * rate.to_u64!) << RSM_FRAC) / clock).to_u32!.to_i32!
puts "New rate ratio: #{ret.rateRatio} (#{rate}, #{clock})"
else
ret.rateRatio = ratio.to_i32!
puts "New ratio: #{ret.rateRatio} (#{rate}, #{clock})"
end
ret
end
def writeBuffered(port : UInt32, data : UInt8) : Nil
time1 : UInt64 = 0
time2 : UInt64 = 0
skip : UInt64 = 0
if (@writebuf[@writebufLast].port & 0x02) != 0
write(@writebuf[@writebufLast].port & 0x01,
@writebuf[@writebufLast].data)
@writebuf[@writebufLast].port &= 0x01
@writebufCur = (@writebufLast + 1) % OPN_WRITEBUF_SIZE
skip = @writebuf[@writebufLast].time - @writebufSampleCount
@writebufSampleCount = @writebuf[@writebufLast].time
skip.times do |_|
clock(@writeBuffer)
end
end
@writebuf[@writebufLast].port = ((port & 0x01) | 0x02).to_u8!
@writebuf[@writebufLast].data = data
time1 = @writebufLastTime + OPN_WRITEBUF_DELAY
time2 = @writebufSampleCount
time1 = time2 if time1 < time2
@writebuf[@writebufLast].time = time1
@writebufLastTime = time1
@writebufLast = (@writebufLast + 1) % OPN_WRITEBUF_SIZE
end
def muteMask=(mask : UInt32) : Nil
7.times do |i|
@mute[i] = ((mask >> i) & 0x01).to_u8!
end
end
###
### Private Methods
###
@[AlwaysInline]
private def kcToFNum(kcode : Int32) : Int32
kcodeH : Int32 = (kcode >> 4) & 63
kcodeL : Int32 = kcode & 15
slope : Int32 = 0
sum : Int32 = 0
if PG_FREQ_TABLE.get!(kcodeH).approxType != 0
4.times do |i|
if Yuno.bitflag?(kcodeL, (1 << i))
sum += (PG_FREQ_TABLE.get!(kcodeH).slope >> (3 - i))
end
end
else
slope = PG_FREQ_TABLE.get!(kcodeH).slope | 1
sum += (slope >> 3) + 2 if Yuno.bitflag?(kcodeL, 1)
sum += 8 if Yuno.bitflag?(kcodeL, 2)
sum += (slope >> 1) if Yuno.bitflag?(kcodeL, 4)
if Yuno.bitflag?(kcodeL, 8)
sum += slope
sum += 1
end
if (kcodeL & 12) == 12 && (PG_FREQ_TABLE.get!(kcodeH).slope & 1) == 0
sum += 4
end
end
PG_FREQ_TABLE.get!(kcodeH).baseFreq + (sum >> 1)
end
@[AlwaysInline]
private def lfoApplyPms(lfo : Int32, pms : Int32) : Int32
top : Int32 = (lfo >> 4) & 7
top >>= 1 if pms != 7
t : Int32 = if (top & 6) == 6 || ((top & 3) == 3 && pms >= 6)
1
else
0
end
output : Int32 = top + ((top >> 2) & 1) + t
output = output * 2 + ((lfo >> 4) & 1)
output >>= 1 if pms == 7
output &= 15
output = (lfo & 15) + output * 16
case pms
when 0 then 0
when 1 then (output >> 5) & 3
when 2 then (output >> 4) & 7
when 3 then (output >> 3) & 15
when 4 then (output >> 2) & 31
when 5 then (output >> 1) & 63
when 6 then (output & 255) << 1
when 7 then (output & 255) << 2
else 0
end
end
private def calcKCode(kcf : Int32, lfo : Int32, lfoSign : Int32, dt : Int32) : Int32
t2 : Int32 = 0
t3 : Int32 = 0
b0 : Int32 = 0
b1 : Int32 = 0
b2 : Int32 = 0
b3 : Int32 = 0
w2 : Int32 = 0
w3 : Int32 = 0
w4 : Int32 = 0
w6 : Int32 = 0
overflow1 : Int32 = 0
overflow2 : Int32 = 0
negOverflow : Int32 = 0
sum : Int32 = 0
cr : Int32 = 0
lfo = ~lfo if lfoSign == 0
sum = (kcf & 8191) + (lfo & 8191) + (lfoSign == 0 ? 1 : 0)
cr = ((kcf & 255) + (lfo & 255) + (lfoSign == 0 ? 1 : 0)) >> 8
overflow1 = 1 if Yuno.bitflag?(sum, (1 << 13))
sum &= 8191
sum += 64 if lfoSign != 0 && ((((sum >> 6) & 3) == 3) || cr != 0)
if lfoSign == 0 && cr == 0
sum += (-64) & 8191
negOverflow = 1
end
overflow2 = 1 if Yuno.bitflag?(sum, (1 << 13))
sum &= 8191
sum = 0 if (lfoSign == 0 && overflow1 == 0) || (negOverflow != 0 && overflow2 == 0)
sum = 8127 if lfoSign != 0 && (overflow1 != 0 || overflow2 != 0)
t2 = sum & 63
t2 += 20 if dt == 2
t2 += 32 if dt == 2 || dt == 3
b0 = (t2 >> 6) & 1
b1 = (dt == 2 ? 1 : 0)
b2 = ((sum >> 6) & 1)
b3 = ((sum >> 7) & 1)
w2 = (b0 != 0 && b1 != 0 && b2 != 0) ? 1 : 0
w3 = (b0 != 0 && b3 != 0) ? 1 : 0
w4 = (w2 == 0 && w3 == 0) ? 1 : 0
w6 = (b0 != 0 && w2 == 0 && w3 == 0) || (b3 != 0 && b0 == 0 && b1 != 0) ? 1 : 0
t2 &= 63
t3 = (sum >> 6) + w6 + b1 + ((w2 != 0 || w3 != 0) ? 1 : 0) * 2 +
((dt == 3) ? 1 : 0) * 4 + ((dt != 0) ? 1 : 0) * 8
if Yuno.bitflag?(t3, 128)
t2 = 63
t3 = 126
end
t3 * 64 + t2
end
@[AlwaysInline]
private def phaseCalcFNumBlock : Nil
slot : UInt32 = (@cycles + 7) & 31
channel : UInt32 = slot & 7
kcf : UInt32 = (@chKc.get!(channel).to_u32! << 6) + @chKf.get!(channel).to_u32!
lfo : UInt32 = (@lfoPmd != 0 ? @lfoPmLock.to_u32! : 0u32)
pms : UInt32 = @chPms.get!(channel).to_u32!
dt : UInt32 = @slDt2.get!(slot).to_u32!
lfoPm : Int32 = lfoApplyPms((lfo & 127).to_i32!, pms.to_i32!)
kcode : UInt32 = calcKCode(kcf.to_i32!, lfoPm, (Yuno.bitflag?(lfo, 0x80) && pms != 0 ? 0 : 1), dt.to_i32!).to_u32!
fnum : UInt32 = kcToFNum(kcode.to_i32!).to_u32!
kcodeH : UInt32 = kcode >> 8
@pgFnum[slot] = fnum.to_u16!
@pgKCode[slot] = kcodeH.to_u8!
end
@[AlwaysInline]
private def phaseCalcIncrement : Nil
slot : UInt32 = @cycles
dt : UInt32 = @slDt1.get!(slot).to_u32!
dtL : UInt32 = dt & 3
detune : UInt32 = 0
multi : UInt32 = @slMul.get!(slot).to_u32!
kcode : UInt32 = @pgKCode.get!(slot).to_u32!
fnum : UInt32 = @pgFnum.get!(slot).to_u32!
block : UInt32 = kcode >> 2
basefreq : UInt32 = (fnum << block) >> 2
note : UInt32 = 0
sum : UInt32 = 0
sumH : UInt32 = 0
sumL : UInt32 = 0
inc : UInt32 = 0
# Apply detune
if dtL != 0
kcode = 0x1C if kcode > 0x1C
block = kcode >> 2
note = kcode & 0x03
sum = block + 9 + ((dtL == 3 ? 1 : 0) | (dtL & 0x02))
sumH = sum >> 1
sumL = sum & 0x01
detune = PG_DETUNE[(sumL << 2) | note] >> (9 - sumH)
end
if (dt & 0x04) != 0
basefreq = basefreq &- detune
else
basefreq = basefreq &+ detune
end
basefreq &= 0x1FFFF
if multi != 0
inc = basefreq * multi
else
inc = basefreq >> 1
end
inc &= 0xFFFFF
@pgInc[slot] = inc
end
@[AlwaysInline]
private def phaseGenerate : Nil
slot = (@cycles + 27) & 31
@pgResetLatch[slot] = @pgReset.get!(slot)
slot = (@cycles + 25) & 31
# Mask increment
@pgInc[slot] = 0 if @pgResetLatch.get!(slot) != 0
# Phase step
slot = (@cycles + 24) & 31
@pgPhase[slot] = 0 if @pgResetLatch.get!(slot) != 0 || @modeTest.get!(3) != 0
@pgPhase[slot] += @pgInc.get!(slot)
@pgPhase[slot] &= 0xFFFFF
end
@[AlwaysInline]
private def phaseDebug : Nil
@pgSerial >>= 1
if @cycles == 5
@pgSerial |= (@pgPhase.get!(29) & 0x3FF)
end
end
@[AlwaysInline]
private def keyOn1 : Nil
cycles : UInt32 = (@cycles + 1) & 31
@konChanmatch = 0
if @modeKonChannel + 24 == cycles
@konChanmatch = 1
end
end
@[AlwaysInline]
private def keyOn2 : Nil
if @konChanmatch != 0
slot : UInt32 = (@cycles + 8) & 31
@modeKon[(slot + 0) & 31] = @modeKonOperator.get!(0)
@modeKon[(slot + 8) & 31] = @modeKonOperator.get!(2)
@modeKon[(slot + 16) & 31] = @modeKonOperator.get!(1)
@modeKon[(slot + 24) & 31] = @modeKonOperator.get!(3)
end
end
@[AlwaysInline]
private def envelopePhase1 : Nil
slot : UInt32 = (@cycles + 2) & 31
kon : UInt32 = (@modeKon.get!(slot) | @konCsm).to_u32!
@egState.put!(slot, EG_NUM_ATTACK) if @kon.get!(slot) == 0 && kon != 0
@kon2.put!(slot, @kon.get!(slot))
@kon.put!(slot, kon.to_u8!)
end
@[AlwaysInline]
private def envelopePhase2 : Nil
slot : UInt32 = @cycles
chan : UInt32 = slot & 7
rate : UInt8 = case @egState.get!(slot)
when EG_NUM_ATTACK then @slAr.get!(slot)
when EG_NUM_DECAY then @slD1r.get!(slot)
when EG_NUM_SUSTAIN then @slD2r.get!(slot)
when EG_NUM_RELEASE then (@slRr.get!(slot).to_i32! * 2 + 1).to_u8!
else 0u8
end
rate = 31 if @ic != 0
zr : UInt8 = (rate == 0 ? 1u8 : 0u8)
ksv : UInt8 = @pgKCode.get!(slot) >> (@slKs.get!(slot) ^ 3)
ksv &= ~3u8 if @slKs.get!(slot) == 0 && zr != 0
rate = rate &* 2 &+ ksv
rate = 63 if Yuno.bitflag?(rate, 64)
@egTl[2] = @egTl.get!(1)
@egTl[1] = @egTl.get!(0)
@egTl[0] = @slTl.get!(slot)
@egSl[1] = @egSl.get!(0)
@egSl[0] = @slD1l.get!(slot)
@egSl[0] = 31 if @slD1l.get!(slot) == 15
@egZr[1] = @egZr.get!(0)
@egZr[0] = zr
@egRate[1] = @egRate.get!(0)
@egRate[0] = rate
@egRateMax[1] = @egRateMax.get!(0)
@egRateMax[0] = ((rate >> 1) == 31 ? 1u8 : 0u8)
ams : UInt8 = (@slAmE.get!(slot) != 0 ? @chAms.get!(chan) : 0u8)
@egAm = case ams
when 0 then 0u8
when 1 then @lfoAmLock
when 2 then @lfoAmLock << 1
when 3 then @lfoAmLock << 2
else 0u8
end
end
@[AlwaysInline]
private def envelopePhase3 : Nil
slot : UInt32 = (@cycles + 31) & 31
@egShift = (@egTimerShiftLock + (@egRate.get!(0) >> 2)) & 15
@egIncHi = EG_STEP_HI.get!(@egRate.get!(0) & 3).get!(@egTimerLock & 3).to_u8!
@egOutTemp[1] = @egOutTemp.get!(0)
@egOutTemp[0] = @egLevel.get!(slot) + @egAm
@egOutTemp[0] = 1023 if Yuno.bitflag?(@egOutTemp.get!(0), 1024)
end
@[AlwaysInline]
private def envelopePhase4 : Nil
slot : UInt32 = (@cycles + 30) & 31
inc : UInt8 = 0
kon : UInt8 = 0
egOff : UInt8 = 0
egZero : UInt8 = 0
slReach : UInt8 = 0
if Yuno.bitflag?(@egClock, 2)
if @egRate.get!(1) >= 48
inc = @egIncHi + (@egRate.get!(1) >> 2) - 11
inc = 4 if inc > 4
end
elsif @egZr.get!(1) == 0
case @egShift
when 12 then inc = (@egRate.get!(1) != 0 ? 1u8 : 0u8)
when 13 then inc = ((@egRate.get!(1) >> 1) & 1).to_u8!
when 14 then inc = (@egRate.get!(1) & 1).to_u8!
end
end
@egInc = inc
kon = (@kon.get!(slot) != 0 && @kon2.get!(slot) == 0) ? 1u8 : 0u8
@pgReset.put!(slot, kon)
@egInstantAttack = (@egRateMax.get!(1) != 0 && (kon != 0 || @egRateMax.get!(1) == 0)) ? 1u8 : 0u8
egOff = (@egLevel.get!(slot) & 0x3F0) == 0x3F0 ? 1u8 : 0u8
slReach = ((@egLevel.get!(slot) >> 5) == @egSl.get!(1)) ? 1u8 : 0u8
egZero = @egLevel.get!(slot) == 0 ? 1u8 : 0u8
@egMute = (egOff != 0 && @egState.get!(slot) != EG_NUM_ATTACK && kon == 0) ? 1u8 : 0u8
@egIncLinear = 0
if kon == 0 && egOff == 0
case @egState.get!(slot)
when EG_NUM_DECAY
@egIncLinear = 1 if slReach == 0
when EG_NUM_SUSTAIN, EG_NUM_RELEASE
@egIncLinear = 1
end
end
if @egState.get!(slot) == EG_NUM_ATTACK && @egRateMax.get!(1) == 0 && @kon.get!(slot) != 0 && egZero == 0
@egIncAttack = 1u8
else
@egIncAttack = 0u8
end
# Update state
if kon != 0
@egState.put!(slot, EG_NUM_ATTACK)
elsif @kon.get!(slot) == 0
@egState.put!(slot, EG_NUM_RELEASE)
else
case @egState.get!(slot)
when EG_NUM_ATTACK
if egZero != 0
@egState.put!(slot, EG_NUM_DECAY)
end
when EG_NUM_DECAY
if egOff != 0
@egState.put!(slot, EG_NUM_RELEASE)
elsif slReach != 0
@egState.put!(slot, EG_NUM_SUSTAIN)
end
when EG_NUM_SUSTAIN
if egOff != 0
@egState.put!(slot, EG_NUM_RELEASE)
end
end
end
if @ic != 0
@egState.put!(slot, EG_NUM_RELEASE)
end
end
@[AlwaysInline]
private def envelopePhase5 : Nil
slot : UInt32 = (@cycles + 29) & 31
level : UInt32 = @egLevel.get!(slot).to_u32!
step : UInt32 = 0
level = 0 if @egInstantAttack != 0
level = 0x3FF if @egMute != 0 || @ic != 0
if @egInc != 0
if @egIncLinear != 0
step |= (1u32 << (@egInc - 1))
end
if @egIncAttack != 0
step |= (~(@egLevel.get!(slot).to_u32!) << @egInc) >> 5
end
end
level = level &+ step
@egLevel[slot] = level.to_u16!
@egOut[0] = @egOutTemp.get!(1) + (@egTl.get!(2).to_u16! << 3)
@egOut[0] = 1023 if Yuno.bitflag?(@egOut.get!(0), 1024)
@egOut[0] = 0 if @egTest != 0
@egTest = @modeTest.get!(5)
end
@[AlwaysInline]
private def envelopePhase6 : Nil
@egSerialBit = ((@egSerial >> 9) & 1).to_u8!
if @cycles == 3
@egSerial = (@egOut.get!(0) ^ 1023).to_u32!
else
@egSerial <<= 1
end
@egOut.put!(1, @egOut.get!(0))
end
@[AlwaysInline]
private def envelopeClock : Nil
@egClock <<= 1
if Yuno.bitflag?(@egClockCount, 2) || @modeTest.get!(0) != 0
@egClock |= 1
end
if @ic != 0 || (@cycles == 31 && Yuno.bitflag?(@egClockCount, 2))
@egClockCount = 0
elsif @cycles == 31
@egClockCount += 1
end
end
@[AlwaysInline]
private def envelopeTimer : Nil
cycle : UInt32 = (@cycles + 31) & 15
inc : UInt8 = if ((@cycles + 31) & 31) < 16 && Yuno.bitflag?(@egClock, 1) && (cycle == 0 || @egTimerCarry != 0)
1u8
else
0u8
end
timerBit : UInt8 = ((@egTimer >> cycle) & 1).to_u8!
sum : UInt8 = timerBit + inc
sum0 : UInt8 = (Yuno.bitflag?(sum, 1) && @ic == 0) ? 1u8 : 0u8
@egTimerCarry = sum >> 1
@egTimer = (@egTimer & (~(1u32 << cycle))) | (sum0.to_u32! << cycle)
cycle2 : UInt32 = (@cycles + 30) & 15
@egTimer2 <<= 1
if Yuno.bitflag?(@egTimer, (1u32 << cycle2)) && @egTimerBStop == 0
@egTimer2 |= 1
end
if Yuno.bitflag?(@egTimer, (1u32 << cycle2))
@egTimerBStop = 1
end
if cycle == 0 || @ic2 != 0
@egTimerBStop = 0
end
if @cycles == 1 && Yuno.bitflag?(@egClock, 1)
@egTimerShiftLock = 0
if Yuno.bitflag?(@egTimer2, (8 + 32 + 128 + 512 + 2048 + 8192 + 32768))
@egTimerShiftLock |= 1
end
if Yuno.bitflag?(@egTimer2, (4 + 32 + 64 + 512 + 1024 + 8192 + 16384))
@egTimerShiftLock |= 2
end
if Yuno.bitflag?(@egTimer2, (4 + 8 + 16 + 512 + 1024 + 2048 + 4096))
@egTimerShiftLock |= 4
end
if Yuno.bitflag?(@egTimer2, (4 + 8 + 16 + 32 + 64 + 128 + 256))
@egTimerShiftLock |= 8
end
@egTimerLock = @egTimer.to_u8!
end
end
@[AlwaysInline]
private def operatorPhase1 : Nil
slot : UInt32 = @cycles
mod : Int16 = @opMod.get!(2)
@opPhaseIn = (@pgPhase.get!(slot) >> 10).to_u16!
if (@opFbShift & 8) != 0
mod = if @opFb.get!(1) == 0
0i16
else
mod >> (9 - @opFb.get!(1))
end
end
@opModIn = mod.to_u16!
end
@[AlwaysInline]
private def operatorPhase2 : Nil
@opPhase = ((@opPhaseIn.to_i32! + @opModIn.to_i32!) & 1023).to_u16!
end
@[AlwaysInline]
private def operatorPhase3 : Nil
phase : UInt16 = @opPhase & 255
phase ^= 255 if (@opPhase & 256) != 0
@opLogSin.put!(0, LOG_SIN_ROM.get!(phase))
@opSign <<= 1
@opSign |= (@opPhase >> 9) & 1
end
@[AlwaysInline]
private def operatorPhase4 : Nil
@opLogSin.put!(1, @opLogSin.get!(0))
end
@[AlwaysInline]
private def operatorPhase5 : Nil
@opLogSin.put!(2, @opLogSin.get!(1))
end
@[AlwaysInline]
private def operatorPhase6 : Nil
@opAtten = (@opLogSin.get!(2).to_u32! + (@egOut.get!(1).to_u32! << 2)).to_u16!
@opAtten = 4095 if Yuno.bitflag?(@opAtten, 4096)
end
@[AlwaysInline]
private def operatorPhase7 : Nil
@opExp.put!(0, EXP_ROM.get!(@opAtten & 255))
@opPow.put!(0, (@opAtten >> 8).to_u8!)
end
@[AlwaysInline]
private def operatorPhase8 : Nil
@opExp.put!(1, @opExp.get!(0))
@opPow.put!(1, @opPow.get!(0))
end
@[AlwaysInline]
private def operatorPhase9 : Nil
output : Int16 = ((@opExp.get!(1).to_i32! << 2) >> @opPow.get!(1)).to_i16!
output = -output if Yuno.bitflag?(@opSign, 32)
@opOut.put!(0, output)
end
@[AlwaysInline]
private def operatorPhase10 : Nil
@opOut.put!(1, @opOut.get!(0))
end
@[AlwaysInline]
private def operatorPhase11 : Nil
@opOut.put!(2, @opOut.get!(1))
end
@[AlwaysInline]
private def operatorPhase12 : Nil
@opOut.put!(3, @opOut.get!(2))
end
@[AlwaysInline]
private def operatorPhase13 : Nil
slot : UInt32 = (@cycles + 20) & 31
@opOut.put!(4, @opOut.get!(3))
@opConnect = @chConnect.get!(slot & 7).to_u32!
end
@[AlwaysInline]
private def operatorPhase14 : Nil
slot : UInt32 = (@cycles + 19) & 31
channel : UInt32 = slot & 7
@opMix = @opOut.get!(4)
@opOut.put!(5, @opOut.get!(4))
@opFbUpdate = (@opCounter == 0) ? 1u8 : 0u8
@opC1Update = (@opCounter == 2) ? 1u8 : 0u8
@opFbShift <<= 1
@opFbShift |= ((@opCounter == 2) ? 1 : 0)
@opModTable[0] = FM_ALGORITHM[(@opCounter + 2) & 3][0][@opConnect].to_u8!
@opModTable[1] = FM_ALGORITHM[(@opCounter + 2) & 3][1][@opConnect].to_u8!
@opModTable[2] = FM_ALGORITHM[(@opCounter + 2) & 3][2][@opConnect].to_u8!
@opModTable[3] = FM_ALGORITHM[(@opCounter + 2) & 3][3][@opConnect].to_u8!
@opModTable[4] = FM_ALGORITHM[(@opCounter + 2) & 3][4][@opConnect].to_u8!
@opMixL = (FM_ALGORITHM[@opCounter][5][@opConnect] != 0 && (@chRl[channel] & 1) != 0) ? 1u8 : 0u8
@opMixR = (FM_ALGORITHM[@opCounter][5][@opConnect] != 0 && (@chRl[channel] & 2) != 0) ? 1u8 : 0u8
if @mute.get!(channel) != 0
@opMixL = 0
@opMixR = 0
end
end
@[AlwaysInline]
private def operatorPhase15 : Nil
slot : UInt32 = (@cycles + 18) & 31
mod : Int16 = 0
mod1 : Int32 = 0
mod2 : Int32 = 0
if @opModTable.get!(0) != 0
mod2 |= @opM1.get!(slot & 7).get!(0)
end
if @opModTable.get!(1) != 0
mod1 |= @opM1.get!(slot & 7).get!(1)
end
if @opModTable.get!(2) != 0
mod1 |= @opC1.get!(slot & 7)
end
if @opModTable.get!(3) != 0
mod2 |= @opOut.get!(5)
end
if @opModTable.get!(4) != 0
mod1 |= @opOut.get!(5)
end
mod = ((mod1 &+ mod2) >> 1).to_i16!
@opMod.put!(0, mod)
if @opFbUpdate != 0
@opM1[slot & 7][1] = @opM1.get!(slot & 7).get!(0)
@opM1[slot & 7][0] = @opOut.get!(5)
end
if @opC1Update != 0
@opC1[slot & 7] = @opOut.get!(5)
end
end
@[AlwaysInline]
private def operatorPhase16 : Nil
slot : UInt32 = (@cycles + 17) & 31
# Hack
@opMod.put!(2, @opMod.get!(1))
@opFb.put!(1, @opFb.get!(0))
@opMod.put!(1, @opMod.get!(0))
@opFb.put!(0, @chFb.get!(slot & 7))
end
@[AlwaysInline]
private def operatorCounter : Nil
@opCounter += 1 if (@cycles & 7) == 4
@opCounter = 0 if @cycles == 12
end
private def mixer2 : Nil
cycles : UInt32 = (@cycles + 30) & 31
top : UInt8 = 0
ex : UInt8 = 0
bit : UInt8 = if cycles < 16
bit = (@mixSerial.get!(0) & 1).to_u8!
else
bit = (@mixSerial.get!(1) & 1).to_u8!
end
if (@cycles & 15) == 1
@mixSignLock = bit ^ 1
@mixTopBitsLock = (@mixBits >> 15) & 63
end
@mixBits >>= 1
@mixBits |= bit.to_u32! << 20
if (@cycles & 15) == 10
top = @mixTopBitsLock.to_u8!
top ^= 63 if @mixSignLock != 0
ex = case
when (top & 32) != 0 then 7u8
when (top & 16) != 0 then 6u8
when (top & 8) != 0 then 5u8
when (top & 4) != 0 then 4u8
when (top & 2) != 0 then 3u8
when (top & 1) != 0 then 2u8
else 1u8
end
@mixSignLock2 = @mixSignLock
@mixExpLock = ex
end
@mixOutBit <<= 1
case (@cycles + 1) & 15
when 0
@mixOutBit |= @mixSignLock2 ^ 1
when 1
@mixOutBit |= @mixExpLock & 1
when 2
@mixOutBit |= (@mixExpLock >> 1) & 1
when 3
@mixOutBit |= (@mixExpLock >> 2) & 1
else
if @mixExpLock != 0
@mixOutBit |= (@mixBits >> (@mixExpLock - 1)) & 1
end
end
end
@[AlwaysInline]
private def opmOutput : Nil
slot : UInt32 = (@cycles + 27) & 31
@smpSo = Yuno.bitflag?(@mixOutBit, 4) ? 1u8 : 0u8
@smpSh1 = ((slot & 24) == 8 && @ic == 0) ? 1u8 : 0u8
@smpSh2 = ((slot & 24) == 24 && @ic == 0) ? 1u8 : 0u8
end
private def opmDac : Nil
exp : Int32 = 0
mant : Int32 = 0
#puts @dacBits
if @dacOsh1 != 0 && @smpSh1 == 0
exp = ((@dacBits >> 10) & 7).to_i32!
mant = (@dacBits & 1023).to_i32!
mant -= 512
@dacOutput[1] = (((mant.to_i64! << exp)) >> 1).to_i32!
#puts " 1: Exp: #{exp}, Mant: #{mant}, Res: #{@dacOutput[1]}"
end
if @dacOsh2 != 0 && @smpSh2 == 0
exp = ((@dacBits >> 10) & 7).to_i32!
mant = (@dacBits & 1023).to_i32!
mant -= 512
@dacOutput[0] = (((mant.to_i64! << exp)) >> 1).to_i32!
#puts " 1: Exp: #{exp}, Mant: #{mant}, Res: #{@dacOutput[0]}"
end
@dacBits >>= 1
@dacBits |= (@smpSo.to_u32! << 12)
@dacOsh1 = @smpSh1
@dacOsh2 = @smpSh2
end
private def mixer : Nil
slot : UInt32 = (@cycles + 18) & 31
# Right channel
@mixSerial.put!(1, @mixSerial.get!(1) >> 1)
if @cycles == 13
@mixSerial[1] |= (@mix.get!(1).to_u32! & 1023) << 4
end
if @cycles == 14
@mixSerial[1] |= ((@mix2.get!(1).to_u32! >> 10) & 31) << 13
@mixSerial[1] |= (((@mix2.get!(1).to_u32! >> 17) & 1) ^ 1) << 18
@mixClampLow.put!(1, 0u8)
@mixClampHigh.put!(1, 0u8)
case (@mix2.get!(1) >> 15) & 7
when 1, 2, 3 then @mixClampHigh.put!(1, 1u8)
when 4, 5, 6 then @mixClampLow.put!(1, 1u8)
end
end
@mixSerial[1] &= ~2u32 if @mixClampLow.get!(1) != 0
@mixSerial[1] |= 2 if @mixClampHigh.get!(1) != 0
# Left channel
@mixSerial.put!(0, @mixSerial.get!(0) >> 1)
if @cycles == 29
@mixSerial[0] |= (@mix.get!(0).to_u32! & 1023) << 4
end
if @cycles == 30
@mixSerial[0] |= ((@mix2.get!(0).to_u32! >> 10) & 31) << 13
@mixSerial[0] |= (((@mix2.get!(0).to_u32! >> 17) & 1) ^ 1) << 18
@mixClampLow.put!(0, 0u8)
@mixClampHigh.put!(0, 0u8)
case (@mix2.get!(0) >> 15) & 7
when 1, 2, 3 then @mixClampHigh.put!(0, 1u8)
when 4, 5, 6 then @mixClampLow.put!(0, 1u8)
end
end
@mixSerial[0] &= ~2u32 if @mixClampLow.get!(0) != 0
@mixSerial[0] |= 2 if @mixClampHigh.get!(0) != 0
@mix2.put!(0, @mix.get!(0))
@mix2.put!(1, @mix.get!(1))
@mix.put!(1, 0) if @cycles == 13
@mix.put!(0, 0) if @cycles == 29
@mix.incf!(0, @opMix * @opMixL)
@mix.incf!(1, @opMix * @opMixR)
end
@[AlwaysInline]
private def opmNoise : Nil
w1 : UInt8 = (@ic == 0 && @noiseUpdate == 0) ? 1u8 : 0u8
xr : UInt8 = (((@noiseLfsr >> 2) & 1) ^ @noiseTemp).to_u8!
w2t : UInt8 = ((@noiseLfsr & 0xFFFF) == 0xFFFF && @noiseTemp == 0) ? 1u8 : 0u8
w2 : UInt8 = (w2t == 0 && xr == 0) ? 1u8 : 0u8
w3 : UInt8 = (@ic == 0 && w1 == 0 && w2 == 0) ? 1u8 : 0u8
w4 : UInt8 = (((@noiseLfsr & 1) == 0 || w1 == 0) && w3 == 0) ? 1u8 : 0u8
if w1 == 0
@noiseTemp = (@noiseLfsr & 1) == 0 ? 1u8 : 0u8
end
@noiseLfsr >>= 1
@noiseLfsr |= w4.to_u32! << 15
end
@[AlwaysInline]
private def noiseTimer : Nil
timer = @noiseTimer
@noiseUpdate = @noiseTimerOf
if (@cycles & 15) == 15
timer += 1
timer &= 31
end
if @ic != 0 || (@noiseTimerOf != 0 && (@cycles & 15) == 15)
timer = 0
end
@noiseTimerOf = (@noiseTimer == (@noiseFreq ^ 31)) ? 1u8 : 0u8
@noiseTimer = timer.to_u32!
end
@[AlwaysInline]
private def doTimerA : Nil
value : UInt16 = @timerAVal
value += @timerAInc
@timerAOf = ((value >> 10) & 1).to_u8!
value = 0 if @timerADoReset != 0
value = @timerAReg if @timerADoLoad != 0
@timerAVal = value & 1023
end
@[AlwaysInline]
private def doTimerA2 : Nil
@timerALoad = @timerLoadA if @cycles == 1
@timerAInc = (@modeTest.get!(2) != 0 || (@timerALoad != 0 && @cycles == 0)) ? 1u8 : 0u8
@timerADoLoad = (@timerAOf != 0 || (@timerALoad != 0 && @timerATemp != 0)) ? 1u8 : 0u8
@timerADoReset = @timerATemp
@timerATemp = @timerALoad == 0 ? 1u8 : 0u8
if @timerResetA != 0 || @ic != 0
@timerAStatus = 0
else
@timerAStatus |= (@timerIrqA != 0 && @timerAOf != 0) ? 1u8 : 0u8
end
@timerResetA = 0
end
@[AlwaysInline]
private def doTimerB : Nil
value : UInt16 = @timerBVal
value += @timerBInc
@timerBOf = ((value >> 8) & 1).to_u8!
value = 0 if @timerBDoReset != 0
value = @timerBReg.to_u16! if @timerBDoLoad != 0
@timerBVal = value & 255
@timerBSub += 1 if @cycles == 0
@timerBSubOf = (@timerBSub >> 4) & 1
@timerBSub &= 15
@timerBSub = 0 if @ic != 0
end
@[AlwaysInline]
private def doTimerB2 : Nil
@timerBInc = (@modeTest.get!(2) != 0 || (@timerLoadB != 0 && @timerBSubOf != 0)) ? 1u8 : 0u8
@timerBDoLoad = (@timerBOf != 0 || (@timerLoadB != 0 && @timerBTemp != 0)) ? 1u8 : 0u8
@timerBDoReset = @timerBTemp
@timerBTemp = @timerLoadB == 0 ? 1u8 : 0u8
if @timerResetB != 0 || @ic != 0
@timerBStatus = 0
else
@timerBStatus |= (@timerIrqB != 0 && @timerBOf != 0) ? 1u8 : 0u8
end
@timerResetB = 0
end
@[AlwaysInline]
private def doTimerIRQ : Nil
@timerIrq = (@timerAStatus != 0 || @timerBStatus != 0) ? 1u8 : 0u8
end
private def doLfoMult : Nil
ampmSel : UInt8 = ((@lfoBitCounter & 8) != 0) ? 1u8 : 0u8
dp : UInt8 = (ampmSel != 0) ? @lfoPmd : @lfoAmd
@lfoOut2B = @lfoOut2
bit : UInt8 = case @lfoBitCounter & 7
when 0
((dp & 64) != 0 && (@lfoOut1 & 64) == 0) ? 1u8 : 0u8
when 1
((dp & 32) != 0 && (@lfoOut1 & 32) == 0) ? 1u8 : 0u8
when 2
((dp & 16) != 0 && (@lfoOut1 & 16) == 0) ? 1u8 : 0u8
when 3
((dp & 8) != 0 && (@lfoOut1 & 8) == 0) ? 1u8 : 0u8
when 4
((dp & 4) != 0 && (@lfoOut1 & 4) == 0) ? 1u8 : 0u8
when 5
((dp & 2) != 0 && (@lfoOut1 & 2) == 0) ? 1u8 : 0u8
when 6
((dp & 1) != 0 && (@lfoOut1 & 1) == 0) ? 1u8 : 0u8
else
0u8
end
b1 : UInt8 = (@lfoOut2 & 1) != 0 ? 1u8 : 0u8
b1 = 0 if (@lfoBitCounter & 7) == 0
b2 : UInt8 = @lfoMultCarry
b2 = 0 if (@cycles & 15) == 15
sum : UInt8 = bit + b1 + b2
@lfoOut2 >>= 1
@lfoOut2 |= (sum & 1).to_u32! << 15
@lfoMultCarry = sum >> 1
end
private def doLfo1 : Nil
counter2 : UInt16 = @lfoCounter2
ofOld : UInt8 = @lfoCounter2Of
lfoBit : UInt8 = 0
noise : UInt8 = 0
sum : UInt8 = 0
carry : UInt8 = 0
w : StaticArray(UInt8, 10) = StaticArray(UInt8, 10).new(0u8)
lfoPmSign : UInt8 = 0
ampmSel : UInt8 = (@lfoBitCounter & 8) != 0 ? 1u8 : 0u8
counter2 += ((@lfoCounter1Of1 & 2) != 0 || @modeTest.get!(3) != 0) ? 1u8 : 0u8
@lfoCounter2Of = ((counter2 >> 15) & 1).to_u8!
counter2 = 0 if @ic != 0
counter2 = LFO_COUNTER2_TABLE.get!(@lfoFreqHi) if @lfoCounter2Load != 0
@lfoCounter2 = counter2 & 32767
@lfoCounter2Load = (@lfoFrqUpdate != 0 || ofOld != 0) ? 1u8 : 0u8
@lfoFrqUpdate = 0
@lfoCounter1 += 1 if (@cycles & 15) == 12
@lfoCounter1Of1 <<= 1
@lfoCounter1Of1 |= (@lfoCounter1 >> 4) & 1
@lfoCounter1 &= 15
@lfoCounter1 = 0 if @ic != 0
@lfoCounter2OfLock2 = @lfoCounter2OfLock if (@cycles & 15) == 5
@lfoCounter3 = @lfoCounter3 &+ @lfoCounter3Clock
@lfoCounter3 = 0 if @ic != 0
@lfoCounter3Clock = ((@cycles & 15) == 13 && @lfoCounter2OfLock2 != 0) ? 1u8 : 0u8
if (@cycles & 15) == 15
@lfoTrigSign = (@lfoVal & 0x80) != 0 ? 1u8 : 0u8
@lfoSawSign = (@lfoVal & 0x100) != 0 ? 1u8 : 0u8
end
lfoPmSign = @lfoWave == 2 ? @lfoTrigSign : @lfoSawSign
w[5] = if ampmSel != 0
@lfoSawSign
else
if @lfoWave != 2 || @lfoTrigSign != 0
1u8
else
0u8
end
end
w[1] = (@lfoClock == 0 || @lfoWave == 3 || (@cycles & 15) != 15) ? 1u8 : 0u8
w[2] = (@lfoWave == 2 && w[1] == 0) ? 1u8 : 0u8
w[4] = (@lfoClockLock != 0 && @lfoWave == 3) ? 1u8 : 0u8
w[3] = (@ic == 0 && @modeTest.get!(1) == 0 && w[4] == 0 && (@lfoVal & 0x8000) != 0) ? 1u8 : 0u8
w[7] = (((@cycles + 1) & 15) < 8) ? 1u8 : 0u8
w[6] = w[5] ^ w[3]
w[9] = if ampmSel != 0
if (@cycles & 15) == 6
1u8
else
0u8
end
else
if @lfoSawSign == 0
1u8
else
0u8
end
end
w[8] = @lfoWave == 1 ? w[9] : w[6]
w[8] &= w[7]
@lfoOut1 <<= 1
@lfoOut1 |= (w[8] == 0 ? 1 : 0)
carry = (w[1] == 0 || ((@cycles & 15) != 15 && @lfoValCarry != 0 && @lfoWave != 3)) ? 1u8 : 0u8
sum = carry + w[2] + w[3]
noise = (@lfoClockLock != 0 && (@noiseLfsr & 1) != 0) ? 1u8 : 0u8
lfoBit = sum & 1
lfoBit |= (@lfoWave == 3 && noise != 0) ? 1 : 0
@lfoValCarry = sum >> 1
@lfoVal <<= 1
@lfoVal |= lfoBit
if (@cycles & 15) == 15 && (@lfoBitCounter & 7) == 7
if ampmSel != 0
@lfoPmLock = ((@lfoOut2B >> 8) & 255).to_u8!
@lfoPmLock ^= (lfoPmSign.to_u32! << 7).to_u8!
else
@lfoAmLock = ((@lfoOut2B >> 8) & 255).to_u8!
end
end
@lfoBitCounter += 1 if (@cycles & 15) == 14
@lfoBitCounter = 0 if (@cycles & 15) != 12 && @lfoCounter1Of2 != 0
@lfoCounter1Of2 = @lfoCounter1 == 2 ? 1u8 : 0u8
end
@[AlwaysInline]
private def doLfo2 : Nil
@lfoClockTest = @lfoClock
@lfoClock = (@lfoCounter2Of != 0 || @lfoTest != 0 || @lfoCounter3Step != 0) ? 1u8 : 0u8
if (@cycles & 15) == 14
@lfoCounter2OfLock = @lfoCounter2Of
@lfoClockLock = @lfoClock
end
@lfoCounter3Step = 0
if @lfoCounter3Clock != 0
case
when (@lfoCounter3 & 1) == 0
@lfoCounter3Step = (@lfoFreqLo & 8) != 0 ? 1u8 : 0u8
when (@lfoCounter3 & 2) == 0
@lfoCounter3Step = (@lfoFreqLo & 4) != 0 ? 1u8 : 0u8
when (@lfoCounter3 & 4) == 0
@lfoCounter3Step = (@lfoFreqLo & 2) != 0 ? 1u8 : 0u8
when (@lfoCounter3 & 8) == 0
@lfoCounter3Step = (@lfoFreqLo & 1) != 0 ? 1u8 : 0u8
end
end
@lfoTest = @modeTest.get!(2)
end
@[AlwaysInline]
private def opmCsm : Nil
@konCsm = @konCsmLock
if @cycles == 1
@konCsmLock = (@timerADoLoad != 0 && @modeCsm != 0) ? 1u8 : 0u8
end
end
@[AlwaysInline]
private def noiseChannel : Nil
@ncActive |= @egSerialBit & 1
@ncActive = 0 if @cycles == 13
@ncOut <<= 1
@ncOut |= @ncSign ^ @egSerialBit
@ncSign = @ncSignLock == 0 ? 1u8 : 0u8
if @cycles == 12
@ncActiveLock = @ncActive
@ncSignLock2 = (@ncActiveLock != 0 && @ncSignLock == 0) ? 1u8 : 0u8
@ncSignLock = (@noiseLfsr & 1).to_u8!
if @noiseEn != 0
if @ncSignLock2 != 0
@opMix = (((@ncOut & ~1u16).to_u32! << 2) | -4089.to_u32!).to_i16!
else
@opMix = ((@ncOut & ~1u16).to_u32! << 2).to_i16!
end
end
end
end
@[AlwaysInline]
private def doIo : Nil
# Busy
@writeBusyCount += @writeBusy
@writeBusy = if (@writeBusyCount >> 5) == 0 && @writeBusy != 0 && @ic == 0
1u8 | @writeDEn
else
0u8 | @writeDEn
end
@writeBusyCount &= 0x1F
@writeBusyCount = 0 if @ic != 0
# Write signal check
@writeAEn = @writeA
@writeDEn = @writeD
@writeA = 0
@writeD = 0
end
private def doRegWrite : Nil
channel : UInt32 = @cycles & 7
slot : UInt32 = @cycles
# Register write
if @regDataReady != 0
# Channel
if (@regAddress & 0xE7) == (0x20 | channel)
case @regAddress & 0x18
when 0x00 # RL, FB, CONNECT
@chRl[channel] = @regData >> 6
@chFb[channel] = (@regData >> 3) & 0x07
@chConnect[channel] = @regData & 0x07
when 0x08 # KC
@chKc[channel] = @regData & 0x7F
when 0x10 # KF
@chKf[channel] = @regData >> 2
when 0x18 # PMS, AMS
@chPms[channel] = (@regData >> 4) & 0x07
@chAms[channel] = @regData & 0x03
end
end
# Slot
if (@regAddress & 0x1F) == slot
case @regAddress & 0xE0
when 0x40 # DT1, MUL
@slDt1[slot] = (@regData >> 4) & 0x07
@slMul[slot] = @regData & 0x0F
when 0x60 # TL
@slTl[slot] = @regData & 0x7F
when 0x80 # KS, AR
@slKs[slot] = @regData >> 6
@slAr[slot] = @regData & 0x1F
when 0xA0 # AMS-EN, D1R
@slAmE[slot] = @regData >> 7
@slD1r[slot] = @regData & 0x1F
when 0xC0 # DT2, D2R
@slDt2[slot] = @regData >> 6
@slD2r[slot] = @regData & 0x1F
when 0xE0 # D1L, RR
@slD1l[slot] = @regData >> 4
@slRr[slot] = @regData & 0x0F
end
end
end
# Mode write
if @writeDEn != 0
case @modeAddress
when 0x01
8.times do |i|
@modeTest[i] = (@writeData >> i) & 0x01
end
when 0x08
4.times do |i|
@modeKonOperator[i] = (@writeData >> (i + 3)) & 0x01
end
@modeKonChannel = @writeData & 0x07
when 0x0F
@noiseEn = @writeData >> 7
@noiseFreq = @writeData & 0x1F
when 0x10
@timerAReg &= 0x03
@timerAReg |= (@writeData.to_u16! << 2)
when 0x11
@timerAReg &= 0x3FC
@timerAReg |= @writeData & 0x03
when 0x12
@timerBReg = @writeData
when 0x14
@modeCsm = (@writeData >> 7) & 1
@timerIrqB = (@writeData >> 3) & 1
@timerIrqA = (@writeData >> 2) & 1
@timerResetB = (@writeData >> 3) & 1
@timerResetA = (@writeData >> 2) & 1
@timerLoadB = (@writeData >> 1) & 1
@timerLoadA = @writeData & 1
when 0x18
@lfoFreqHi = @writeData >> 4
@lfoFreqLo = @writeData & 0x0F
@lfoFrqUpdate = 1
when 0x19
if (@writeData& 0x80) != 0
@lfoPmd = @writeData & 0x7F
else
@lfoAmd = @writeData
end
when 0x1B
@lfoWave = @writeData & 0x03
@ioCt1 = @writeData >> 7
@ioCt2 = (@writeData >> 6) & 0x01
end
end
# Register data write
@regDataReady = (@regDataReady != 0 && @writeAEn == 0) ? 1u8 : 0u8
if @regAddressReady != 0 && @writeDEn != 0
@regData = @writeData
@regDataReady = 1
end
# Register address write
@regAddressReady = (@regAddressReady != 0 && @writeAEn == 0) ? 1u8 : 0u8
if @writeAEn != 0 && (@writeData & 0xE0) != 0
@regAddress = @writeData
@regAddressReady = 1
end
if @writeAEn != 0
@modeAddress = @writeData
end
end
@[AlwaysInline]
private def doIc : Nil
if @ic != 0
channel : UInt32 = @cycles & 7
slot : UInt32 = @cycles
@chRl.put!(channel, 0u8)
@chFb.put!(channel, 0u8)
@chConnect.put!(channel, 0u8)
@chKc.put!(channel, 0u8)
@chKf.put!(channel, 0u8)
@chPms.put!(channel, 0u8)
@chAms.put!(channel, 0u8)
@slDt1.put!(slot, 0u8)
@slMul.put!(slot, 0u8)
@slTl.put!(slot, 0u8)
@slKs.put!(slot, 0u8)
@slAr.put!(slot, 0u8)
@slAmE.put!(slot, 0u8)
@slD1r.put!(slot, 0u8)
@slDt2.put!(slot, 0u8)
@slD2r.put!(slot, 0u8)
@slD1l.put!(slot, 0u8)
@slRr.put!(slot, 0u8)
@timerAReg = 0
@timerBReg = 0
@timerIrqA = 0
@timerIrqB = 0
@timerLoadA = 0
@timerLoadB = 0
@modeCsm = 0
@modeTest.fill(0u8)
@noiseEn = 0
@noiseFreq = 0
@modeKonChannel = 0
@modeKonOperator.fill(0u8)
@modeKon.put!((slot + 8) & 31, 0u8)
@lfoPmd = 0
@lfoAmd = 0
@lfoWave = 0
@lfoFreqHi = 0
@lfoFreqLo = 0
@ioCt1 = 0
@ioCt2 = 0
@regAddress = 0
@regData = 0
end
@ic2 = @ic
end
protected def clock(output : Array(Int32)?) : Nil
mixer2
mixer
operatorPhase16
operatorPhase15
operatorPhase14
operatorPhase13
operatorPhase12
operatorPhase11
operatorPhase10
operatorPhase9
operatorPhase8
operatorPhase7
operatorPhase6
operatorPhase5
operatorPhase4
operatorPhase3
operatorPhase2
operatorPhase1
operatorCounter
envelopeTimer
envelopePhase6
envelopePhase5
envelopePhase4
envelopePhase3
envelopePhase2
envelopePhase1
phaseDebug
phaseGenerate
phaseCalcIncrement
phaseCalcFNumBlock
doTimerIRQ
doTimerA
doTimerB
doLfoMult
doLfo1
opmNoise
keyOn2
doRegWrite
envelopeClock
noiseTimer
keyOn1
doIo
doTimerA2
doTimerB2
doLfo2
opmCsm
noiseChannel
opmOutput
opmDac
doIc
unless output.nil?
output.put!(0, @dacOutput.get!(0))
output.put!(1, @dacOutput.get!(1))
end
@cycles = (@cycles + 1) & 31
end
@[AlwaysInline]
private def write(port : UInt32, data : UInt8) : Nil
@writeData = data
return if @ic != 0
if (port & 0x01) != 0
@writeD = 1
else
@writeA = 1
end
end
@[AlwaysInline]
private def read(port : UInt32) : UInt8
if @modeTest.get!(6) != 0
testData : UInt16 = (@opOut.get!(5) | ((@egSerialBit ^ 1).to_u32! << 14) | ((@pgSerial & 1) << 15)).to_u16!
if @modeTest.get!(7) != 0
(testData & 255).to_u8!
else
(testData >> 8).to_u8!
end
else
(@writeBusy << 7) | (@timerBStatus << 1) | @timerAStatus
end
end
@[AlwaysInline]
private def readIRQ(port : UInt32) : UInt8
@timerIrq
end
@[AlwaysInline]
private def readCT1 : UInt8
@ioCt1
end
@[AlwaysInline]
private def readCT2 : UInt8
if @modeTest.get!(3) != 0
@lfoClockTest
else
@ioCt2
end
end
@[AlwaysInline]
protected def setIC(ic : UInt8) : Nil
if @ic != ic
@ic = ic
@cycles = 0 if ic == 0
end
end
@[AlwaysInline]
private def generateResampled : Nil
while @sampleCount >= @rateRatio
@oldSamples.put!(0, @samples.get!(0))
@oldSamples.put!(1, @samples.get!(1))
@samples.put!(0, 0)
@samples.put!(1, 0)
32.times do |i|
clock(@generatedBuffer)
if i == 0
@samples.incf!(0, @generatedBuffer.get!(0))
@samples.incf!(1, @generatedBuffer.get!(1))
end
while @writebuf[@writebufCur].time <= @writebufSampleCount
break if (@writebuf[@writebufCur].port & 0x02) == 0
@writebuf[@writebufCur].port &= 0x01
write(@writebuf[@writebufCur].port,
@writebuf[@writebufCur].data)
@writebufCur = (@writebufCur + 1) % OPN_WRITEBUF_SIZE
end
@writebufSampleCount += 1
end
@sampleCount -= @rateRatio
end
@updateBuffer.put!(0, ((@oldSamples.get!(0) &* (@rateRatio &- @sampleCount) &+ @samples.get!(0) &* @sampleCount) / @rateRatio).to_i32!)
@updateBuffer.put!(1, ((@oldSamples.get!(1) &* (@rateRatio &- @sampleCount) &+ @samples.get!(1) &* @sampleCount) / @rateRatio).to_i32!)
@sampleCount += (1 << RSM_FRAC)
end
end
end
end