Login
Artifact [1972172a38]
Login

Artifact 1972172a38b8c6598df4ae0f38c653a6b0ae73b190060861aa769fbeed538224:


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