Login
Artifact [f83f45f05f]
Login

Artifact f83f45f05ff138bf968cac6634fbe51fec674244922e5c75165a05955236d3d2:


#### Software implementation of Yamaha FM sound generator
####
#### Copyright Jarek Burczynski (bujar at mame dot net)
#### Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development
#### Copyright (C) 2023 Remilia Scarlet
require "math"
require "./opn-mame-constants"

####
#### Common Yamaha OPN Mame Routines
####

module Yuno::Chips
  # :nodoc:
  module OPNMame
    ###
    ### Aliases
    ###

    alias FmTimerHandler = Proc(AbstractChip?, Int32, Int32, Int32, Nil)
    alias FmIrqHandler = Proc(AbstractChip?, Int32, Nil)

    ###
    ### Internal classes
    ###

    class SsgCallbacks
      property! chip : Yuno::Chips::Ay8910?
      property! setClock : Proc(UInt32, Nil)?
      property! write : Proc(Int32, Int32, Nil)?
      property! read : Proc(UInt8)?
      property! reset : Proc(Nil)?

      def initialize
      end

      def initialize(@chip, @setClock, @write, @read, @reset)
      end
    end

    class FmSlot
      property dt   : Array(Int32) = Array(Int32).new(0, 0) # Detune
      property ksr  : UInt8  = 0 # Key scale rate
      property ar   : UInt32 = 0 # Attack rate
      property d1r  : UInt32 = 0 # Decay rate
      property d2r  : UInt32 = 0 # Sustain rate
      property rr   : UInt32 = 0 # Release rate
      property ksr3 : UInt8  = 0 # Key scale rate
      property mul  : UInt32 = 0 # Multiple

      #
      # Phase generator
      #

      property phase : UInt32 = 0 # Phase counter
      property incr  : Int32  = 0 # Phase step

      #
      # Envelope generator
      #

      property state  : UInt8  = 0 # Phase type
      property tl     : UInt32 = 0 # Total level
      property volume : Int32  = 0 # Envelope counter
      property sl     : UInt32 = 0 # Sustain level
      property volOut : UInt32 = 0 # Current output from EG circuit (without AM from LFO)

      property egShAr   : UInt8 = 0 # Attack state
      property egSelAr  : UInt8 = 0 # Attack state
      property egShD1r  : UInt8 = 0 # Decay state
      property egSelD1r : UInt8 = 0 # Decay state
      property egShD2r  : UInt8 = 0 # Sustain state
      property egSelD2r : UInt8 = 0 # Sustain state
      property egShRr   : UInt8 = 0 # Release state
      property egSelRr  : UInt8 = 0 # Release state

      property ssg  : UInt8 = 0 # SSG-EG waveform
      property ssgn : UInt8 = 0 #SSG_EG negated output

      property key : UInt32 = 0 # 0 - last key was off, 1 - key on

      #
      # LFO
      #

      property amMask : UInt32 = 0 # AM enable flag

      def initialize
      end
    end

    class FmCh
      property slot : Array(FmSlot) # Four SLOTs (operators)

      property algo : UInt8 = 0 # Algorithm
      property fb   : UInt8 = 0 # Feedback shift
      property op1Out : Array(Int32) = [0, 0] # OP1 output for feedback

      property connect1 : Pointer(Int32) = Pointer(Int32).malloc(1, 0) # SLOT1 output pointer
      property connect2 : Pointer(Int32) = Pointer(Int32).malloc(1, 0) # SLOT1 output pointer
      property connect3 : Pointer(Int32) = Pointer(Int32).malloc(1, 0) # SLOT1 output pointer
      property connect4 : Pointer(Int32) = Pointer(Int32).malloc(1, 0) # SLOT1 output pointer

      property memConnect : Pointer(Int32) = Pointer(Int32).malloc(1, 0) # Where to put the delayed sample (MEM)
      property memValue : Int32 = 0 # Delayed sample (MEM) value

      property pms : Int32 = 0 # Channel PMS
      property ams : UInt8 = 0 # Channel AMS

      property fc : UInt32 = 0 # fnum, blk: adjusted to sample rate
      property kcode : UInt8 = 0 # Key code
      property blockFnum : UInt32 = 0 # Current block/fnum value for this slot

      property muted : UInt8 = 0

      def initialize
        @slot = Array(FmSlot).new(4) { |_| FmSlot.new }
      end

      @[AlwaysInline]
      def memConnectAddr
        pointerof(@memConnect)
      end

      {% begin %}
        {% for i in 1..4 %}
          @[AlwaysInline]
          protected def connect{{i}}Addr
            pointerof(@connect{{i}})
          end

          @[AlwaysInline]
          def slot{{i}} : FmSlot
            @slot.unsafe_fetch(SLOT{{i}})
          end
        {% end %}
      {% end %}
    end

    class FmSt
      property! param : AbstractChip?
      property clock : UInt32 = 0 # Main clock rate in hertz
      property rate : UInt32 = 0 # Sampling rate in hertz
      property freqBase : Float64 = 0.0 # Frequency base
      property timerPrescaler : Int32 = 0 # Timer pre-scaler

      property address : UInt8 = 0 # Address register
      property irq : UInt8 = 0 # Interrupt level
      property irqMask : UInt8 = 0 # IRQ mask
      property status : UInt8 = 0 # Status flag
      property mode : UInt32 = 0 # Mode CSM/3SLOT
      property prescalerSel : UInt8 = 0 # Pre-scaler selector
      property fnh : UInt8 = 0 # Frequency latch
      property ta  : Int32 = 0 # Timer A
      property tac : Int32 = 0 # Timer A counter
      property tb  : UInt8 = 0 # Timer B
      property tbc : Int32 = 0 # Timer B counter

      property dtTab : Array(Array(Int32)) # Detune table

      property timerHandler : FmTimerHandler?
      property irqHandler : FmIrqHandler?

      property ssg : SsgCallbacks = SsgCallbacks.new

      def initialize
        @dtTab = Array(Array(Int32)).new(8) do |_|
          Array(Int32).new(32, 0i32)
        end
      end
    end

    class Fm3Slot
      property fc : Array(UInt32) = [0u32, 0u32, 0u32] # fnum3, block3: Calculated
      property fnh : UInt8 = 0 # Freq3 latch
      property kcode : Array(UInt8) = Array(UInt8).new(3, 0u8) # Key code
      property blockFnum : Array(UInt32) = [0u32, 0u32, 0u32] # Current fnum value for this slot

      def initialize
      end
    end

    # OPN/A/B common state.
    class FmOpn
      property type : UInt8 = 0 # Chip type
      property st : FmSt = FmSt.new # General state
      property sl3 : Fm3Slot = Fm3Slot.new # 3-slot mode state
      getter pch : Slice(FmCh)
      property pan : Array(UInt32) = Array(UInt32).new(6 * 2, 0u32) # FM channel output masks (0xFFFFFFFF = enable)

      property egCount : UInt32 = 0 # Global envelope generator counter
      property egTimer : UInt32 = 0 # Global envelope generator works at frequency = chipclock / 64 / 3
      property egTimerAdd : UInt32 = 0 # Step of egTimer
      property egTimerOverflow : UInt32 = 0 # Envelope generator timer overflows every 3 samples (on real chip)

      # there are 2048 FNUMs that can be generated using FNUM/BLK registers,
      # but LFO works with one more bit of a precision so we really need 4096
      # elements.

      property fnTable : Array(UInt32) = Array(UInt32).new(4096, 0u32) # fnumber -> increment counter
      property fnMax : UInt32 = 0 # Maximal phase increment (used for phase overflow)

      #
      # LFO
      #

      property lfoAm : UInt32 = 0 # Runtime LFO calculations helper
      property lfoPm : Int32 = 0 # Runtime LFO calculations helper

      property lfoCount : UInt32 = 0
      property lfoInc : UInt32 = 0

      property lfoFreq : Array(UInt32) = Array(UInt32).new(8, 0u32) # LFO freq table

      property m2 : Int32 = 0 # Phase modulation inpuyt for operators 2, 3, and 4
      property c1 : Int32 = 0 # Phase modulation inpuyt for operators 2, 3, and 4
      property c2 : Int32 = 0 # Phase modulation inpuyt for operators 2, 3, and 4
      property mem : Int32 = 0 # One sample delay memory

      property outFm : Array(Int32) = [0, 0, 0, 0, 0, 0, 0, 0] # Outputs of working channels

      # Channel output NONE, LEFT, RIGHT, or CENTER for YM2608/YM2610 ADPCM
      property outAdpcm : Array(Int32) = [0, 0, 0, 0]

      # Channel output NONE, LEFT, RIGHT, or CENTER for YM2608/YM2610 DELTA
      property outDelta : Array(Int32) = [0, 0, 0, 0]

      def initialize(@pch : Slice(FmCh))
      end

      #@[AlwaysInline]
      def m2Addr : Pointer(Int32)
        pointerof(@m2)
      end

      #@[AlwaysInline]
      def c1Addr : Pointer(Int32)
        pointerof(@c1)
      end

      #@[AlwaysInline]
      def c2Addr : Pointer(Int32)
        pointerof(@c2)
      end

      #@[AlwaysInline]
      def memAddr : Pointer(Int32)
        pointerof(@mem)
      end
    end

    ###
    ### Methods and Macros
    ###

    private macro opnChan(n)
      ({{n}} & 3)
    end

    private macro opnSlot(n)
      (({{n}} >> 2) & 3)
    end

    @[AlwaysInline]
    protected def statusSet(st : FmSt, flag : Int) : Nil
      st.status |= flag
      if st.irq == 0 && Yuno.bitflag?(st.status, st.irqMask)
        st.irq = 1
        st.irqHandler.try &.call(st.param, 1)
      end
    end

    @[AlwaysInline]
    protected def statusReset(st : FmSt, flag : Int) : Nil
      st.status &= ~flag
      if st.irq != 0 && !Yuno.bitflag?(st.status, st.irqMask)
        st.irq = 0
        st.irqHandler.try &.call(st.param, 0)
      end
    end

    @[AlwaysInline]
    protected def irqMaskSet(st : FmSt, flag : Int) : Nil
      st.irqMask = flag.to_u8!
      statusSet(st, 0)
      statusReset(st, 0)
    end

    @[AlwaysInline]
    protected def setTimers(st : FmSt, n : AbstractChip?, v : Int) : Nil
      # b7 = CSM MODE
      # b6 = 3 slot mode
      # b5 = reset b
      # b4 = reset a
      # b3 = timer enable b
      # b2 = timer enable a
      # b1 = load b
      # b0 = load a
      st.mode = v.to_u32!

      # Reset timer B flag
      statusReset(st, 0x02) if Yuno.bitflag?(v, 0x20)

      # Reset timer A flag
      statusReset(st, 0x01) if Yuno.bitflag?(v, 0x10)

      # Load B
      if Yuno.bitflag?(v, 0x02)
        if st.tbc == 0
          st.tbc = (256 - st.tb.to_i32!) << 4
          st.timerHandler.try &.call(n, 1, st.tbc * st.timerPrescaler, st.clock.to_i32!)
        end
      else
        # Stop timer B
        unless st.tbc == 0
          st.tbc = 0
          st.timerHandler.try &.call(n, 1, 0, st.clock.to_i32!)
        end
      end

      # Load A
      if Yuno.bitflag?(v, 0x01)
        if st.tac == 0
          st.tac = 1024 - st.ta
          st.timerHandler.try &.call(n, 0, st.tac * st.timerPrescaler, st.clock.to_i32!)
        end
      else
        # Stop timer A
        unless st.tac == 0
          st.tac = 0
          st.timerHandler.try &.call(n, 0, 0, st.clock.to_i32!)
        end
      end
    end

    @[AlwaysInline]
    protected def timerAOver(st : FmSt) : Nil
      # Set status (if enabled)
      statusSet(st, 0x01) if Yuno.bitflag?(st.mode, 0x04)

      # Clear and reload the counter
      st.tac = 1024 - st.ta
      st.timerHandler.try &.call(st.param, 0, st.tac * st.timerPrescaler, st.clock.to_i32!)
    end

    @[AlwaysInline]
    protected def timerBOver(st : FmSt) : Nil
      # Set status (if enabled)
      statusSet(st, 0x02) if Yuno.bitflag?(st.mode, 0x08)

      # Clear and reload the counter
      st.tbc = (256 - st.tb.to_i32!) << 4
      st.timerHandler.try &.call(st.param, 1, st.tbc * st.timerPrescaler, st.clock.to_i32!)
    end

    @[AlwaysInline]
    protected def internalTimerA(opn : FmOpn, st : FmSt, csmCh : FmCh) : Nil
      if st.tac != 0 && st.timerHandler.nil?
        st.tac -= (st.freqBase * 4096).to_i32!
        if st.tac <= 0
          timerAOver(st)

          # CSM mode total level latch and auto key on
          csmKeyControl(csmCh) if Yuno.bitflag?(st.mode, 0x80)
        end
      end
    end

    @[AlwaysInline]
    protected def internalTimerB(opn : FmOpn, st : FmSt, step : Int) : Nil
      if st.tbc != 0 && st.timerHandler.nil?
        st.tbc -= (st.freqBase * 4096 * step).to_i32!
        if st.tbc <= 0
          timerBOver(st)
        end
      end
    end

    @[AlwaysInline]
    protected def keyOn(ch : FmCh, s : Int) : Nil
      slot = ch.slot[s]
      if slot.key == 0
        slot.key = 1
        slot.phase = 0 # Restart phase generator
        slot.ssgn = (slot.ssg & 4) >> 1
        slot.state = EG_ATT
      end
    end

    @[AlwaysInline]
    protected def keyOff(ch : FmCh, s : Int) : Nil
      slot = ch.slot[s]
      if slot.key != 0
        slot.key = 0
        slot.state = EG_REL if slot.state > EG_REL # Phase -> Release
      end
    end

    protected def setupConnection(opn : FmOpn, ch : FmCh, chNum : Int) : Nil
      # Remi: The algorithms are basically represented as a topology of pointer
      # connections, where an operator (FmCh) has pointers to some sample
      # placeholders in the FmOpn instance, or to another operator's pointer.

      # Pointer to the carrier value.
      carrier : Pointer(Int32) = opn.outFm.to_unsafe + chNum

      # These are just here to keep the code below a bit easier to read.
      om1 : Pointer(Pointer(Int32)) = ch.connect1Addr
      om2 : Pointer(Pointer(Int32)) = ch.connect3Addr
      oc1 : Pointer(Pointer(Int32)) = ch.connect2Addr
      memc : Pointer(Pointer(Int32)) = ch.memConnectAddr

      # Set up the topology according to the selected algorithm.
      case ch.algo
      when 0
        # M1---C1---MEM---M2---C2---OUT
        om1.value = opn.c1Addr
        oc1.value = opn.memAddr
        om2.value = opn.c2Addr
        memc.value = opn.m2Addr

      when 1
        # M1------+-MEM---M2---C2---OUT/
        #      C1-+
        om1.value = opn.memAddr
        oc1.value = opn.memAddr
        om2.value = opn.c2Addr
        memc.value = opn.m2Addr

      when 2
        # M1-----------------+-C2---OUT
        #      C1---MEM---M2-+
        om1.value = opn.c2Addr
        oc1.value = opn.memAddr
        om2.value = opn.c2Addr
        memc.value = opn.m2Addr

      when 3
        # M1---C1---MEM------+-C2---OUT
        #                 M2-+
        om1.value = opn.c1Addr
        oc1.value = opn.memAddr
        om2.value = opn.c2Addr
        memc.value = opn.c2Addr

      when 4
        # M1---C1-+-OUT
        # M2---C2-+
        # MEM: not used
        om1.value = opn.c1Addr
        oc1.value = carrier
        om2.value = opn.c2Addr
        memc.value = opn.memAddr # Store it somewhere where it will not be used

      when 5
        #    +----C1----+
        # M1-+-MEM---M2-+-OUT
        #    +----C2----+
        om1.value = Pointer(Int32).null # Special mark
        oc1.value = carrier
        om2.value = carrier
        memc.value = opn.m2Addr

      when 6
        # M1---C1-+
        #      M2-+-OUT
        #      C2-+
        # MEM: not used
        om1.value = opn.c1Addr
        oc1.value = carrier
        om2.value = carrier
        memc.value = opn.memAddr # Store it somewhere where it will not be used

      when 7
        # M1-+
        # C1-+-OUT
        # M2-+
        # C2-+
        # MEM: not used
        om1.value = carrier
        oc1.value = carrier
        om2.value = carrier
        memc.value = opn.memAddr # Store it somewhere where it will not be used
      end

      ch.connect4 = carrier
    end

    # Sets the detune amount and multiple.
    @[AlwaysInline]
    protected def setDetMul(st : FmSt, ch : FmCh, slot : FmSlot, v : Int) : Nil
      slot.mul = if Yuno.bitflag?(v, 0x0F)
                   (v & 0x0F).to_u32! * 2u32
                 else
                   1u32
                 end
      slot.dt = st.dtTab[(v >> 4) & 7]
      ch.slot1.incr = -1
    end

    # Sets the total level.
    @[AlwaysInline]
    protected def setTl(ch : FmCh, slot : FmSlot, v : Int) : Nil
      slot.tl = (v.to_u32! & 0x7F) << (ENV_BITS - 7) # 7-bit TL
    end

    # Sets the attack rate and key scaling.
    @[AlwaysInline]
    protected def setArKsr(ch : FmCh, slot : FmSlot, v : Int) : Nil
      oldKsr : UInt8 = slot.ksr

      slot.ar = if Yuno.bitflag?(v, 0x1F)
                  32u32 + ((v.to_u32! & 0x1F) << 1)
                else
                  0u32
                end

      slot.ksr = 3u8 - (v >> 6).to_u8!
      if slot.ksr != oldKsr
        ch.slot1.incr = -1
      end

      # Refresh attack rate
      if (slot.ar + slot.ksr3.to_u32!) < (32 + 62)
        slot.egShAr = EG_RATE_SHIFT[slot.ar + slot.ksr3.to_u32!].to_u8!
        slot.egSelAr = EG_RATE_SELECT[slot.ar + slot.ksr3.to_u32!]
      else
        slot.egShAr = 0
        slot.egSelAr = (17 * RATE_STEPS).to_u8!
      end
    end

    # Sets the decay rate
    @[AlwaysInline]
    protected def setDr(slot : FmSlot, v : Int) : Nil
      slot.d1r = (Yuno.bitflag?(v, 0x1F) ? 32u32 + ((v.to_u32! & 0x1F) << 1) : 0u32)
      slot.egShD1r = EG_RATE_SHIFT[slot.d1r + slot.ksr3.to_u32!].to_u8!
      slot.egSelD1r = EG_RATE_SELECT[slot.d1r + slot.ksr3.to_u32!]
    end

    # Sets the sustain "rate".
    @[AlwaysInline]
    protected def setSr(slot : FmSlot, v : Int) : Nil
      slot.d2r = Yuno.bitflag?(v, 0x1F) ? 32u32 + ((v.to_u32! & 0x1F) << 1) : 0u32
      slot.egShD2r = EG_RATE_SHIFT[slot.d2r + slot.ksr3.to_u32!].to_u8!
      slot.egSelD2r = EG_RATE_SELECT[slot.d2r + slot.ksr3.to_u32!]
    end

    # Sets the release rate.
    @[AlwaysInline]
    protected def setSlRr(slot : FmSlot, v : Int) : Nil
      slot.sl = SL_TABLE[v >> 4]
      slot.rr = 34u32 + ((v.to_u32! & 0x0F) << 2)
      slot.egShRr = EG_RATE_SHIFT[slot.rr + slot.ksr3.to_u32!].to_u8!
      slot.egSelRr = EG_RATE_SELECT[slot.rr + slot.ksr3.to_u32!]
    end

    @[AlwaysInline]
    protected def opCalc(phase : UInt32, env : UInt32, pm : Int32) : Int32
      x : UInt32 = (env.to_u64!.unsafe_shl(3) &+
                    @sinTab.unsafe_fetch((((phase & INV_FREQ_MASK) &+
                                           pm.unsafe_shl(15)).unsafe_shr(FREQ_SH)) & SIN_MASK)).to_u32!

      if x >= TL_TAB_LEN
        0
      else
        @tlTab.unsafe_fetch(x)
      end
    end

    # 2715293376
    # 284
    # 0
    @[AlwaysInline]
    protected def opCalc1(phase : UInt32, env : UInt32, pm : Int32) : Int32
      x : UInt32 = (env.to_u64!.unsafe_shl(3) &+
                    @sinTab.unsafe_fetch((((phase & INV_FREQ_MASK) &+ pm).unsafe_shr(FREQ_SH)) & SIN_MASK)).to_u32!

      if x >= TL_TAB_LEN
        0
      else
        @tlTab.unsafe_fetch(x)
      end
    end

    # Advances the LFO to the next sample.
    #@[AlwaysInline]
    protected def advanceLfo(opn : FmOpn) : Nil
      pos : UInt8 = 0

      # Is the LFO enabled?
      if opn.lfoInc != 0
        # LFO is enabled.

        opn.lfoCount += opn.lfoInc
        pos = ((opn.lfoCount >> LFO_SH) & 127).to_u8!

        # Update AM when LFO output changes.
        opn.lfoAm = if pos < 64
                      (pos & 63).to_u32! * 2u32
                    else
                      (126 - ((pos & 63).to_i32! * 2)).to_u32!
                    end

        # PM works with 4 times slower clock.
        pos >>= 2
        opn.lfoPm = pos
      else
        # LFO is disabled.
        opn.lfoAm = 0u32
        opn.lfoPm = 0u32
      end
    end

    protected def advanceEgChannel(opn : FmOpn, slots : Array(FmSlot)) : Nil
      swapFlag : UInt32 = 0
      output : UInt32 = 0
      slotPos : Int32 = 0

      # Four operators per channel
      i : UInt8 = 4u8
      loop do
        slot : FmSlot = slots[slotPos]

        # Reset SSG-EG swap flag
        swapFlag = 0u32

        case slot.state
        when EG_ATT # Attack phase
          unless Yuno.bitflag?(opn.egCount, (1 << slot.egShAr) - 1)
            slot.volume += (~(slot.volume) * (EG_INC[slot.egSelAr + ((opn.egCount >> slot.egShAr) & 7)])) >> 4
            if slot.volume <= MIN_ATT_INDEX
              slot.volume = MIN_ATT_INDEX
              slot.state = EG_DEC
            end
          end

        when EG_DEC # Decay phase
          if Yuno.bitflag?(slot.ssg, 0x08) # SSG EG type envelope selected
            unless Yuno.bitflag?(opn.egCount, (1 << slot.egShD1r) - 1)
              slot.volume += 4 * EG_INC[slot.egSelD1r + ((opn.egCount >> slot.egShD1r) & 7)]
              if slot.volume >= slot.sl.to_i32!
                slot.state = EG_SUS
              end
            end
          else
            unless Yuno.bitflag?(opn.egCount, (1 << slot.egShD1r) - 1)
              slot.volume += EG_INC[slot.egSelD1r + ((opn.egCount >> slot.egShD1r) & 7)]
              if slot.volume >= slot.sl.to_i32!
                slot.state = EG_SUS
              end
            end
          end

        when EG_SUS # Sustain phase
          if Yuno.bitflag?(slot.ssg, 0x08) # SSG EG type envelope selected
            unless Yuno.bitflag?(opn.egCount, (1 << slot.egShD2r) - 1)
              slot.volume += 4 * EG_INC[slot.egSelD2r + ((opn.egCount >> slot.egShD2r) & 7)]

              if slot.volume >= ENV_QUIET
                slot.volume = MAX_ATT_INDEX

                if Yuno.bitflag?(slot.ssg, 0x01) # bit 0 = hold
                  if Yuno.bitflag?(slot.ssgn, 1) # Have we swapped once?
                    # Do nothing, just hold current level.
                  else
                    # Swap.
                    swapFlag = ((slot.ssg & 0x02) | 1).to_u32! # bit 1 = alternate
                  end
                else
                  # Same as key on operation.

                  # Restart of the phase generator should be here.
                  slot.phase = 0

                  # Phase -> Attack
                  slot.volume = 511
                  slot.state = EG_ATT

                  swapFlag = (slot.ssg & 0x02).to_u32! # bit 1 = alternate
                end
              end
            end
          else
            unless Yuno.bitflag?(opn.egCount, (1 << slot.egShD2r) - 1)
              slot.volume += EG_INC[slot.egSelD2r + ((opn.egCount >> slot.egShD2r) & 7)]
              if slot.volume >= MAX_ATT_INDEX
                slot.volume = MAX_ATT_INDEX
                # do not change SLOT->state (verified on real chip)
              end
            end
          end

        when EG_REL # Release phase
          unless Yuno.bitflag?(opn.egCount, (1 << slot.egShRr) - 1)
            # SSG-EG affects release phase also (Nemesis)
            slot.volume += EG_INC[slot.egSelRr + ((opn.egCount >> slot.egShRr) & 7)]
            if slot.volume >= MAX_ATT_INDEX
              slot.volume = MAX_ATT_INDEX
              slot.state = EG_OFF
            end
          end
        end # case slot.state

        output = slot.volume.to_u32!

        # negate output (changes come from alternate bit, init comes from attack bit)
        if Yuno.bitflag?(slot.ssg, 0x08) && Yuno.bitflag?(slot.ssgn, 2) && (slot.state > EG_REL)
          output ^= MAX_ATT_INDEX
        end

        # We need to store the result here because we are going to change ssgn
        # in next instruction.
        slot.volOut = output + slot.tl

        # Reverse slot inversion flag
        slot.ssgn ^= swapFlag.to_u8!

        slotPos += 1
        i -= 1
        break if i == 0
      end # Loop do
    end

    #@[AlwaysInline]
    protected def updatePhaseLfoSlot(opn : FmOpn, slot : FmSlot, pms : Int32, blockFnum : UInt32) : Nil
      fnumLfo : UInt32 = ((blockFnum & 0x7F0) >> 4) * 32u32 * 8u32
      lfoFnTableIndexOffset : Int32 = @lfoPmTable[fnumLfo + pms + opn.lfoPm]

      if lfoFnTableIndexOffset != 0
        # LFO phase modulation is active.
        blockFnum = (blockFnum * 2u32) + lfoFnTableIndexOffset.to_u32!
        blk : UInt8 = ((blockFnum & 0x7000) >> 12).to_u8!
        fn : UInt32 = blockFnum & 0xFFF

        # Keyscale code
        kc : Int32 = (blk.to_i32! << 2) | OPN_FK_TABLE[fn >> 8]

        # Phase increment counter
        fc : Int32 = (opn.fnTable[fn] >> (7 - blk.to_i32!)).to_i32! + slot.dt[kc]

        # Detects frequency overflow (credits to Nemesis)
        fc += opn.fnMax if fc < 0

        # Update phase
        slot.phase += (fc.to_u32! * slot.mul) >> 1
      else
        # LFO phase modulation is not active.
        slot.phase = slot.phase &+ slot.incr
      end
    end

    @[AlwaysInline]
    protected def updatePhaseLfoChannel(opn : FmOpn, ch : FmCh) : Nil
      blockFnum : UInt32 = ch.blockFnum
      fnumLfo : UInt32 = ((blockFnum & 0x7F0) >> 4) * 32u32 * 8u32
      lfoFnTableIndexOffset : Int32 = @lfoPmTable[fnumLfo + ch.pms + opn.lfoPm]

      if lfoFnTableIndexOffset != 0
        # LFO phase modulation is active.
        blockFnum = (blockFnum * 2u32) + lfoFnTableIndexOffset.to_u32!
        blk : UInt8 = ((blockFnum & 0x7000) >> 12).to_u8!
        fn : UInt32 = blockFnum & 0xFFF

        # Keyscale code
        kc : Int32 = (blk.to_i32! << 2) | OPN_FK_TABLE[fn >> 8]

        # Phase increment counter
        fc : Int32 = (opn.fnTable[fn] >> (7 - blk.to_i32!)).to_i32!

        # Detects frequency overflow (credits to Nemesis)
        finc : Int32 = fc + ch.slot1.dt[kc]

        finc += opn.fnMax if finc < 0
        ch.slot1.phase += ((finc * ch.slot1.mul) >> 1).to_u32!

        finc = fc + ch.slot2.dt[kc]
        finc += opn.fnMax if finc < 0
        ch.slot2.phase += ((finc * ch.slot2.mul) >> 1).to_u32!

        finc = fc + ch.slot3.dt[kc]
        finc += opn.fnMax if finc < 0
        ch.slot3.phase += ((finc * ch.slot3.mul) >> 1).to_u32!

        finc = fc + ch.slot4.dt[kc]
        finc += opn.fnMax if finc < 0
        ch.slot4.phase += ((finc * ch.slot4.mul) >> 1).to_u32!
      else
        # LFO phase modulation is not active.
        {% begin %}
          {% for i in 1..4 %}
            ch.slot{{i}}.phase = ch.slot{{i}}.phase &+ ch.slot{{i}}.incr
          {% end %}
        {% end %}
      end
    end

    @[AlwaysInline]
    protected def volumeCalc(op : FmSlot, am : UInt32) : UInt32
      op.volOut + (am & op.amMask)
    end

    @[AlwaysInline]
    private def chanCalc(opn : FmOpn, ch : FmCh, chNum : UInt32) : Nil
      return if ch.muted != 0

      am : UInt32 = opn.lfoAm >> ch.ams
      opn.m2 = 0
      opn.c1 = 0
      opn.c2 = 0
      opn.mem = 0
      ch.memConnect.value = ch.memValue # Restored delayed sample (MEM) value to m2 or c2

      egOut : UInt32 = volumeCalc(ch.slot1, am)
      output : Int32 = ch.op1Out.unsafe_fetch(0) + ch.op1Out.unsafe_fetch(1)
      ch.op1Out.unsafe_put(0, ch.op1Out.unsafe_fetch(1))

      if ch.connect1.null?
        # Algorithm 5
        opn.mem = ch.op1Out.unsafe_fetch(0)
        opn.c1 = ch.op1Out.unsafe_fetch(0)
        opn.c2 = ch.op1Out.unsafe_fetch(0)
      else
        # Other algorithms
        ch.connect1.value += ch.op1Out.unsafe_fetch(0)
      end

      ch.op1Out.unsafe_put(1, 0)

      # Slot 1
      if egOut < ENV_QUIET
        output = 0 if ch.fb == 0
        ch.op1Out.unsafe_put(1, opCalc1(ch.slot1.phase, egOut, (output << ch.fb)))
      end

      # Slot 3
      egOut = volumeCalc(ch.slot3, am)
      if egOut < ENV_QUIET
        ch.connect3.value += opCalc(ch.slot3.phase, egOut, opn.m2)
      end

      # Slot 2
      egOut = volumeCalc(ch.slot2, am)
      if egOut < ENV_QUIET
        ch.connect2.value += opCalc(ch.slot2.phase, egOut, opn.c1)
      end

      # Slot 4
      egOut = volumeCalc(ch.slot4, am)
      if egOut < ENV_QUIET
        ch.connect4.value += opCalc(ch.slot4.phase, egOut, opn.c2)
      end

      # Store current MEM
      ch.memValue = opn.mem

      # Update phase counters *after* output calculations.
      if ch.pms != 0
        # Add support for 3-slot mode.
        if Yuno.bitflag?(opn.st.mode, 0xC0) && chNum == 2
          updatePhaseLfoSlot(opn, ch.slot1, ch.pms, opn.sl3.blockFnum.unsafe_fetch(1))
          updatePhaseLfoSlot(opn, ch.slot2, ch.pms, opn.sl3.blockFnum.unsafe_fetch(2))
          updatePhaseLfoSlot(opn, ch.slot3, ch.pms, opn.sl3.blockFnum.unsafe_fetch(0))
          updatePhaseLfoSlot(opn, ch.slot4, ch.pms, ch.blockFnum)
        else
          updatePhaseLfoChannel(opn, ch)
        end
      else
        # No LFO phase modulation.
        {% begin %}
          {% for i in 1..4 %}
            ch.slot{{i}}.phase = ch.slot{{i}}.phase &+ ch.slot{{i}}.incr
          {% end %}
        {% end %}
      end
    end

    # Updates the phase increment and envelope generator.
    @[AlwaysInline]
    protected def refreshFcEgSlot(opn : FmOpn, slot : FmSlot, fc : Int32, kc : Int)
      ksr : Int32 = kc.to_i32! >> slot.ksr
      fc += slot.dt[kc]

      # Detects frequency overflow (credits to Nemesis)
      fc += opn.fnMax if fc < 0

      # (frequency) phase increment counter
      slot.incr = ((fc &* slot.mul) >> 1).to_i32!

      if slot.ksr3 != ksr
        slot.ksr3 = ksr.to_u8!

        # Calculate envelope generator rates
        if (slot.ar + slot.ksr3.to_u32!) < (32 + 62)
          slot.egShAr = EG_RATE_SHIFT[slot.ar + slot.ksr3.to_u32!].to_u8!
          slot.egSelAr = EG_RATE_SELECT[slot.ar + slot.ksr3.to_u32!]
        else
          slot.egShAr = 0
          slot.egSelAr = (17 * RATE_STEPS).to_u8!
        end

        slot.egShD1r = EG_RATE_SHIFT[slot.d1r + slot.ksr3.to_u32!].to_u8!
        slot.egShD2r = EG_RATE_SHIFT[slot.d2r + slot.ksr3.to_u32!].to_u8!
        slot.egShRr  = EG_RATE_SHIFT[slot.rr  + slot.ksr3.to_u32!].to_u8!

        slot.egSelD1r = EG_RATE_SELECT[slot.d1r + slot.ksr3.to_u32!]
        slot.egSelD2r = EG_RATE_SELECT[slot.d2r + slot.ksr3.to_u32!]
        slot.egSelRr  = EG_RATE_SELECT[slot.rr  + slot.ksr3.to_u32!]
      end
    end

    # Updates phase increment counters.
    @[AlwaysInline]
    protected def refreshFcEgChan(opn : FmOpn, chan : FmCh) : Nil
      if chan.slot1.incr == -1
        fc : Int32 = chan.fc.to_i32!
        kc : Int32 = chan.kcode.to_i32!
        refreshFcEgSlot(opn, chan.slot1, fc, kc)
        refreshFcEgSlot(opn, chan.slot2, fc, kc)
        refreshFcEgSlot(opn, chan.slot3, fc, kc)
        refreshFcEgSlot(opn, chan.slot4, fc, kc)
      end
    end

    # Initialize time tables
    protected def initTimeTables(st : FmSt) : Nil
      rate : Float64 = 0.0

      # Detune table
      4.times do |d|
        32.times do |i|
          rate = DT_TAB[d * 32 + i].to_f64! * SIN_LEN * st.freqBase * (1 << FREQ_SH) / (1u32 << 20).to_f64!
          st.dtTab[d][i] = rate.to_i32!
          st.dtTab[d + 4][i] = -(st.dtTab[d][i])
        end
      end
    end

    protected def resetChannels(st : FmSt, ch : Slice(FmCh), num : Int) : Nil
      st.mode = 0 # Normal mode
      st.ta   = 0
      st.tac  = 0
      st.tb   = 0
      st.tbc  = 0

      num.times do |c|
        chan = ch[c]
        chan.memValue = 0
        chan.op1Out[0] = 0
        chan.op1Out[1] = 0
        chan.fc = 0

        4.times do |s|
          slot = chan.slot[s]
          slot.incr = -1
          slot.key = 0
          slot.phase = 0
          slot.ssg = 0
          slot.ssgn = 0
          slot.state = EG_OFF
          slot.volume = MAX_ATT_INDEX
          slot.volOut = MAX_ATT_INDEX.to_u32!
        end
      end
    end

    # Initializes generic tables.
    protected def initTables : Nil
      n : Int32 = 0
      o : Float64 = 0.0
      m : Float64 = 0.0

      TL_RES_LEN.times do |x|
        m = (1 << 16) / (2 ** ((x + 1) * (ENV_STEP / 4.0) / 8.0))
        m = m.floor

        # We never reach (1<<16) here due to the (x+1).  Result fits within 16
        # bits at maximum.

        n = m.to_i32! # 16 bits here
        n >>= 4       # 12 bits here
        n = if Yuno.bitflag?(n, 1) # Round to nearest
              (n >> 1) + 1
            else
              n >> 1
            end
                      # 11 bits here
        n <<= 2       # 13 bits here
        @tlTab[x * 2 + 0] = n
        @tlTab[x * 2 + 1] = -(@tlTab[x * 2 + 0])

        (1...13).each do |i|
          @tlTab[x * 2 + 0 + i * 2 * TL_RES_LEN] =   @tlTab[x * 2 + 0] >> i
          @tlTab[x * 2 + 1 + i * 2 * TL_RES_LEN] = -(@tlTab[x * 2 + 0 + i * 2 * TL_RES_LEN])
        end
      end

      SIN_LEN.times do |i|
        # Non-standard sinus
        m = Math.sin(((i * 2) + 1) * Math::PI / SIN_LEN) # Checked against real chip)

        # We never reach zero here due to ((i * 2) + 1)

        # Conver to decibels.
        o = if m > 0.0
              8 * Math.log(1.0 / m) / Math.log(2.0)
            else
              8 * Math.log(-1.0 / m) / Math.log(2.0)
            end

        o = o / (ENV_STEP / 4)

        n = (2.0 * o).to_i32!

        # Round to nearest
        n = if Yuno.bitflag?(n, 1)
              (n >> 1) + 1
            else
              n >> 1
            end

        @sinTab[i] = (n * 2 + (m >= 0.0 ? 0 : 1)).to_u32!
      end

      # Build LFO PM modulation table.
      8u32.times do |i|
        128u32.times do |fnum| # 7-bits meaningful fnum
          value : UInt8 = 0
          offsetDepth : UInt32 = i
          offsetFnumBit : UInt32 = 0

          8.times do |step|
            value = 0
            7u32.times do |bitTmp| # 7 bits
              if Yuno.bitflag?(fnum, (1u32 << bitTmp)) # Only if bit "bitTmp" is set.
                offsetFnumBit = bitTmp * 8
                value += LFO_PM_OUTPUT[offsetFnumBit + offsetDepth][step]
              end
            end

            @lfoPmTable[(fnum * 32 * 8) + (i * 32) +  step      +  0] = value.to_i32!
            @lfoPmTable[(fnum * 32 * 8) + (i * 32) + (step ^ 7) +  8] = value.to_i32!
            @lfoPmTable[(fnum * 32 * 8) + (i * 32) +  step      + 16] = -(value.to_i32!)
            @lfoPmTable[(fnum * 32 * 8) + (i * 32) + (step ^ 7) + 24] = -(value.to_i32!)
          end
        end
      end
    end

    # CSM key control
    #@[AlwaysInline]
    protected def csmKeyControl(ch : FmCh) : Nil
      # All key on, then off (only for operators which were off in the first place).
      {% begin %}
        {% for snum in [:SLOT1, :SLOT2, :SLOT3, :SLOT4] %}
          if ch.slot[{{snum.id}}].key == 0
            keyOn(ch, {{snum.id}})
            keyOff(ch, {{snum.id}})
          end
        {% end %}
      {% end %}
    end

    # Prescaler set (and make time tables)
    protected def opnSetPres(opn : FmOpn, pres : Int, timerPrescaler : Int32, ssgPres : Int) : Nil
      # Frequency base
      opn.st.freqBase = if opn.st.rate != 0
                          (opn.st.clock.to_f64! / opn.st.rate) / pres
                        else
                          0.0
                        end

      opn.egTimerAdd = ((1u32 << EG_SH) * opn.st.freqBase).to_u32!
      opn.egTimerOverflow = 3u32 * (1u32 << EG_SH)

      # Timer base time
      opn.st.timerPrescaler = timerPrescaler

      # SSG part prescaler set
      if ssgPres != 0
        opn.st.ssg.setClock.call((opn.st.clock * 2 / ssgPres).to_u32!)
      end

      # Make time tables
      initTimeTables(opn.st)

      # there are 2048 FNUMs that can be generated using FNUM/BLK registers, but
      # LFO works with one more bit of a precision so we really need 4096
      # elements.  Calculate fnumber -> increment counter table.
      4096.times do |i|
        # Freq table for octave 7.
        # OPN phase increment counter = 20 bit
        #
        # -10 because chip works with 10.10 fixed point, while we use 16.16.
        opn.fnTable[i] = (i.to_f64! * 32 * opn.st.freqBase * (1 << (FREQ_SH - 10))).to_u32!
      end

      # Maximal frequency is required for phase overflow calculation, register
      # size is 17 bits (Nemesis).
      opn.fnMax = (131072.0 * opn.st.freqBase * (1 << (FREQ_SH - 10))).to_u32!

      # LFO freq. table
      8.times do |i|
        # Amplitude modulation: 64 output levels (triangle waveform); 1 level
        # lasts for one of "lfo_samples_per_step" samples.
        #
        # Phase modulation: one entry from lfo_pm_output lasts for one of 4 *
        # "lfo_samples_per_step" samples.
        opn.lfoFreq[i] = ((1.0 / LFO_SAMPLES_PER_STEP[i]) * (1 << LFO_SH) * opn.st.freqBase).to_u32!
      end
    end

    # Write an OPN mode register (0x20 - 0x2F).
    #@[AlwaysInline]
    protected def opnWriteMode(opn : FmOpn, r : Int32, v : Int32) : Nil
      case r
      #when 0x21 # Test
      #  nil

      when 0x22 # LFO Freq (YM2608 / YM2610 / YM2610B / YM2612)
        if Yuno.bitflag?(opn.type, TYPE_LFOPAN)
          if Yuno.bitflag?(v, 8) # LFO enabled?
            opn.lfoInc = opn.lfoFreq[v & 7]
          else
            opn.lfoInc = 0
          end
        end

      when 0x24 # Timer A high 8
        opn.st.ta = (opn.st.ta & 0x03) | (v << 2)

      when 0x25 # Timer A low 2
        opn.st.ta = (opn.st.ta & 0x3FC) | (v & 3)

      when 0x26 # Timer B
        opn.st.tb = v.to_u8

      when 0x27 # Mode, timer control
        setTimers(opn.st, opn.st.param, v)

      when 0x28 # Key on/off
        c : UInt8 = v.to_u8! & 0x03
        return if c == 3
        c += 3 if Yuno.bitflag?(v, 0x04) && Yuno.bitflag?(opn.type, TYPE_6CH)
        ch = opn.pch[c]
        Yuno.bitflag?(v, 0x10) ? keyOn(ch, SLOT1) : keyOff(ch, SLOT1)
        Yuno.bitflag?(v, 0x20) ? keyOn(ch, SLOT2) : keyOff(ch, SLOT2)
        Yuno.bitflag?(v, 0x40) ? keyOn(ch, SLOT3) : keyOff(ch, SLOT3)
        Yuno.bitflag?(v, 0x80) ? keyOn(ch, SLOT4) : keyOff(ch, SLOT4)
      end
    end

    # Writes to an OPN register (0x30 - 0xFF)
    #@[AlwaysInline]
    protected def opnWriteReg(opn : FmOpn, r : Int32, v : Int32) : Nil
      c : UInt8 = opnChan(r).to_u8!
      return if c == 3 # 0xX3, 0xX7, 0xXB, 0xXF
      c += 3 if r >= 0x100

      ch = opn.pch[c]
      slot = ch.slot[opnSlot(r)]

      case r & 0xF0
      when 0x30 # DET, MUL
        setDetMul(opn.st, ch, slot, v)

      when 0x40 # TL
        setTl(ch, slot, v)

      when 0x50 # KS, AR
        setArKsr(ch, slot, v)

      when 0x60 # bit 7 = AM ENABLE, DR
        setDr(slot, v)

        if Yuno.bitflag?(opn.type, TYPE_LFOPAN) # YM2608/YM2610/YM2610B/YM2612
          slot.amMask = if Yuno.bitflag?(v, 0x80)
                          UInt32::MAX
                        else
                          0u32
                        end
        end

      when 0x70 # SR
        setSr(slot, v.to_i32!)

      when 0x80 # SL, RR
        setSlRr(slot, v.to_i32!)

      when 0x90 # SSG-EG
        slot.ssg = (v & 0x0F).to_u8!
        slot.ssgn = (v & 0x04).to_u8! >> 1 # bit 1 in ssgn = attack

        # SSG-EG envelope wshapes:
        #
        # E AtAlH
        # 1 0 0 0  \\\\
        #
        # 1 0 0 1  \___
        #
        # 1 0 1 0  \/\/
        #         ___
        # 1 0 1 1  \
        #
        # 1 1 0 0  ////
        #                 ___
        # 1 1 0 1  /
        #
        # 1 1 1 0  /\/\
        #
        # 1 1 1 1  /___
        #
        #
        # E = SSG-EG enable
        #
        #
        # The shapes are generated using Attack, Decay and Sustain phases.
        #
        # Each single character in the diagrams above represents this whole
        # sequence:
        #
        # - when KEY-ON = 1, normal Attack phase is generated (*without* any
        #   difference when compared to normal mode),
        #
        # - later, when envelope level reaches minimum level (max volume),
        #   the EG switches to Decay phase (which works with bigger steps
        #   when compared to normal mode - see below),
        #
        # - later when envelope level passes the SL level,
        #   the EG swithes to Sustain phase (which works with bigger steps
        #   when compared to normal mode - see below),
        #
        # - finally when envelope level reaches maximum level (min volume),
        #   the EG switches to Attack phase again (depends on actual waveform).
        #
        # Important is that when switch to Attack phase occurs, the phase counter
        # of that operator will be zeroed-out (as in normal KEY-ON) but not always.
        # (I havent found the rule for that - perhaps only when the output level is low)
        #
        # The difference (when compared to normal Envelope Generator mode) is
        # that the resolution in Decay and Sustain phases is 4 times lower;
        # this results in only 256 steps instead of normal 1024.
        # In other words:
        # when SSG-EG is disabled, the step inside of the EG is one,
        # when SSG-EG is enabled, the step is four (in Decay and Sustain phases).
        #
        # Times between the level changes are the same in both modes.
        #
        #
        # Important:
        # Decay 1 Level (so called SL) is compared to actual SSG-EG output, so
        # it is the same in both SSG and no-SSG modes, with this exception:
        #
        # when the SSG-EG is enabled and is generating raising levels
        # (when the EG output is inverted) the SL will be found at wrong level !!!
        # For example, when SL=02:
        #     0 -6 = -6dB in non-inverted EG output
        #     96-6 = -90dB in inverted EG output
        # Which means that EG compares its level to SL as usual, and that the
        # output is simply inverted afterall.
        #
        #
        # The Yamaha's manuals say that AR should be set to 0x1f (max speed).
        # That is not necessary, but then EG will be generating Attack phase.

      when 0xA0
        case opnSlot(r)
        when 0 # 0xA0 - 0xA2 : FNUM1
          fn : UInt32 = ((opn.st.fnh & 7).to_u32! << 8) + v
          blk : UInt8 = opn.st.fnh >> 3

          # Keyscale code
          ch.kcode = ((blk.to_u32! << 2) | OPN_FK_TABLE[fn >> 7]).to_u8!

          # Phase increment counter
          ch.fc = opn.fnTable[fn * 2] >> (7 - blk.to_i32!)

          # Store fnum in clear form for LFO PM calculations
          ch.blockFnum = (blk.to_u32! << 11) | fn

          ch.slot1.incr = -1

        when 1 # 0xA4 - 0xA6 : FNUM2, BLK
          opn.st.fnh = (v & 0x3F).to_u8!

        when 2 # 0xA8 - 0xAA : 3CH FNUM1
          if r < 0x100
            fn = ((opn.sl3.fnh.to_u32! & 7) << 8) + v
            blk = opn.sl3.fnh >> 3

            # Keyscale code
            opn.sl3.kcode[c] = ((blk.to_u32! << 2) | OPN_FK_TABLE[fn >> 7]).to_u8!

            # Phase increment counter
            opn.sl3.fc[c] = opn.fnTable[fn * 2] >> (7 - blk.to_i32!)

            # Store fnum in clear form for LFO PM calculations
            opn.sl3.blockFnum[c] = (blk.to_u32! << 11) | fn

            opn.pch[2].slot1.incr = -1
          end

        when 3 # 0xAC - 0xAE : #3CH FNUM2, BLK
          if r < 0x100
            opn.sl3.fnh = (v & 0x3F).to_u8!
          end
        end

      when 0xB0
        case opnSlot(r)
        when 0 # 0xB0 - 0xB2 : FB, ALGO
          feedback : UInt8 = ((v >> 3) & 7).to_u8!
          ch.algo = (v & 7).to_u8!
          ch.fb = if feedback != 0
                    feedback &+ 6
                  else
                    0u8
                  end
          setupConnection(opn, ch, c)

        when 1 # 0xB4 - 0xB6 : L, R, AMS, PMS (YM2612/YM2610B/YM2610/YM2608)
          if Yuno.bitflag?(opn.type, TYPE_LFOPAN)
            # b0-2 PMS
            ch.pms = (v.to_i32! & 7) * 32

            # b4-5 Ams
            ch.ams = LFO_AMS_DEPTH_SHIFT[(v >> 4) & 3]

            # PAN : b7 = L, B6 = R
            opn.pan[c * 2    ] = (Yuno.bitflag?(v, 0x80) ? UInt32::MAX : 0u32)
            opn.pan[c * 2 + 1] = (Yuno.bitflag?(v, 0x40) ? UInt32::MAX : 0u32)
          end
        end
      end
    end

    # prescaler circuit (best guess to verified chip behaviour)
    #
    #                +--------------+  +-sel2-+
    #                |              +--|in20  |
    #          +---+ |  +-sel1-+       |      |
    # M-CLK -+-|1/2|-+--|in10  | +---+ |   out|--INT_CLOCK
    #        | +---+    |   out|-|1/3|-|in21  |
    #        +----------|in11  | +---+ +------+
    #                   +------+
    #
    # reg.2d : sel2 = in21 (select sel2)
    # reg.2e : sel1 = in11 (select sel1)
    # reg.2f : sel1 = in10 , sel2 = in20 (clear selector)
    # reset  : sel1 = in11 , sel2 = in21 (clear both)
    protected def opnPrescalerWrite(opn : FmOpn, addr : Int32, preDivider : Int32) : Nil
      case addr
      when 0 # Reset
        opn.st.prescalerSel = 2u8
      when 0x2D # Divider sel : sel 1/1 for 1/3line
        opn.st.prescalerSel |= 0x02_u8
      when 0x2E # Divider sel : select 1/3line for output
        opn.st.prescalerSel |= 0x01_u8
      when 0x2F # Divider sel : Clear both selector to 1/2, 1/2
        opn.st.prescalerSel = 0u8
      end

      sel = opn.st.prescalerSel & 3
      opnSetPres(opn,
                 OPN_PRES[sel] * preDivider,
                 OPN_PRES[sel] * preDivider,
                 SSG_PRES[sel] * preDivider)
    end
  end
end