#
# Driver class for the WM8960 Codec to be used e.g. with MIMXRT_1xxx Boards.
# Derived from the NXP SDK drivers.
#
# Copyright (c) 2015, Freescale Semiconductor, Inc., (C-Code)
# Copyright 2016-2021 NXP, (C-Code)
# All rights reserved.
#
# Translated to MicroPython by Robert Hammelrath, 2022
#
# SPDX-License-Identifier: BSD-3-Clause
#

import array
from micropython import const

# Define the register addresses of WM8960.
_LINVOL = const(0x0)
_RINVOL = const(0x1)
_LOUT1 = const(0x2)
_ROUT1 = const(0x3)
_CLOCK1 = const(0x4)
_DACCTL1 = const(0x5)
_DACCTL2 = const(0x6)
_IFACE1 = const(0x7)
_CLOCK2 = const(0x8)
_IFACE2 = const(0x9)
_LDAC = const(0xA)
_RDAC = const(0xB)
_RESET = const(0xF)
_3D = const(0x10)
_ALC1 = const(0x11)
_ALC2 = const(0x12)
_ALC3 = const(0x13)
_NOISEG = const(0x14)
_LADC = const(0x15)
_RADC = const(0x16)
_ADDCTL1 = const(0x17)
# Register _ADDCTL2 = const(0x18)
_POWER1 = const(0x19)
_POWER2 = const(0x1A)
_ADDCTL3 = const(0x1B)
# Register _APOP1 = const(0x1C)
# Register _APOP2 = const(0x1D)
_LINPATH = const(0x20)
_RINPATH = const(0x21)
_LOUTMIX = const(0x22)
_ROUTMIX = const(0x25)
_MONOMIX1 = const(0x26)
_MONOMIX2 = const(0x27)
_LOUT2 = const(0x28)
_ROUT2 = const(0x29)
_MONO = const(0x2A)
_INBMIX1 = const(0x2B)
_INBMIX2 = const(0x2C)
_BYPASS1 = const(0x2D)
_BYPASS2 = const(0x2E)
_POWER3 = const(0x2F)
_ADDCTL4 = const(0x30)
_CLASSD1 = const(0x31)
# Register _CLASSD3 = const(0x33)
_PLL1 = const(0x34)
_PLL2 = const(0x35)
_PLL3 = const(0x36)
_PLL4 = const(0x37)

# WM8960 PLLN range */
_PLL_N_MIN_VALUE = const(6)
_PLL_N_MAX_VALUE = const(12)

# WM8960 CLOCK2 bits
_CLOCK2_BCLK_DIV_MASK = const(0x0F)
_CLOCK2_DCLK_DIV_MASK = const(0x1C0)
_CLOCK2_DCLK_DIV_SHIFT = const(0x06)

# Register _IFACE1
_IFACE1_FORMAT_MASK = const(0x03)
_IFACE1_WL_MASK = const(0x0C)
_IFACE1_WL_SHIFT = const(0x02)
_IFACE1_LRP_MASK = const(0x10)
_IFACE1_MS_MASK = const(0x40)
_IFACE1_DLRSWAP_MASK = const(0x20)
_IFACE1_ALRSWAP_MASK = const(0x100)

# Register _POWER1
_POWER1_VREF_MASK = const(0x40)
_POWER1_VREF_SHIFT = const(0x06)
_POWER1_AINL_MASK = const(0x20)
_POWER1_AINR_MASK = const(0x10)
_POWER1_ADCL_MASK = const(0x08)
_POWER1_ADCR_MASK = const(0x0)
_POWER1_MICB_MASK = const(0x02)
_POWER1_MICB_SHIFT = const(0x01)

# Register _POWER2
_POWER2_DACL_MASK = const(0x100)
_POWER2_DACR_MASK = const(0x80)
_POWER2_LOUT1_MASK = const(0x40)
_POWER2_ROUT1_MASK = const(0x20)
_POWER2_SPKL_MASK = const(0x10)
_POWER2_SPKR_MASK = const(0x08)
_POWER3_LMIC_MASK = const(0x20)
_POWER3_RMIC_MASK = const(0x10)
_POWER3_LOMIX_MASK = const(0x08)
_POWER3_ROMIX_MASK = const(0x04)

# Register _DACCTL1 .. 3
_DACCTL1_MONOMIX_MASK = const(0x10)
_DACCTL1_MONOMIX_SHIFT = const(0x4)
_DACCTL1_DACMU_MASK = const(0x08)
_DACCTL1_DEEM_MASK = const(0x06)
_DACCTL1_DEEM_SHIFT = const(0x01)
_DACCTL2_DACSMM_MASK = const(0x08)
_DACCTL2_DACMR_MASK = const(0x04)
_DACCTL3_ALCSR_MASK = const(0x07)

# _WM8060_ALC1 .. 3
_ALC_CHANNEL_MASK = const(0x180)
_ALC_CHANNEL_SHIFT = const(0x7)
_ALC_MODE_MASK = const(0x100)
_ALC_MODE_SHIFT = const(0x8)
_ALC_GAIN_MASK = const(0x70)
_ALC_GAIN_SHIFT = const(0x4)
_ALC_TARGET_MASK = const(0x0F)
_ALC_ATTACK_MASK = const(0x0F)
_ALC_DECAY_MASK = const(0xF0)
_ALC_DECAY_SHIFT = const(4)
_ALC_HOLD_MASK = const(0xF)

# Register _NOISEG
_NOISEG_LEVEL_SHIFT = const(3)

_I2C_ADDR = const(0x1A)

# WM8960 maximum volume values
_MAX_VOLUME_ADC = const(0xFF)
_MAX_VOLUME_DAC = const(0xFF)
_MAX_VOLUME_HEADPHONE = const(0x7F)
_MAX_VOLUME_LINEIN = const(0x3F)
_MAX_VOLUME_SPEAKER = const(0x7F)

# Config symbol names
# Modules
MODULE_ADC = const(0)  # ADC module in WM8960
MODULE_DAC = const(1)  # DAC module in WM8960
MODULE_VREF = const(2)  # VREF module
MODULE_HEADPHONE = const(3)  # Headphone
MODULE_MIC_BIAS = const(4)  # Mic bias
MODULE_MIC = const(5)  # Input Mic
MODULE_LINE_IN = const(6)  # Analog in PGA
MODULE_LINE_OUT = const(7)  # Line out module
MODULE_SPEAKER = const(8)  # Speaker module
MODULE_OMIX = const(9)  # Output mixer
MODULE_MONO_OUT = const(10)  # Mono mix

# Route
ROUTE_BYPASS = const(0)  # LINEIN->Headphone.
ROUTE_PLAYBACK = const(1)  #  I2SIN->DAC->Headphone.
ROUTE_PLAYBACK_RECORD = const(2)  # I2SIN->DAC->Headphone, LINEIN->ADC->I2SOUT.
ROUTE_RECORD = const(5)  # LINEIN->ADC->I2SOUT.

# Input
INPUT_CLOSED = const(0)  # Input device is closed
INPUT_MIC1 = const(1)  # Input as single ended mic, only use L/RINPUT1
INPUT_MIC2 = const(2)  # Input as diff. mic, use L/RINPUT1 and L/RINPUT2
INPUT_MIC3 = const(3)  # Input as diff. mic, use L/RINPUT1 and L/RINPUT3
INPUT_LINE2 = const(4)  # Input as line input, only use L/RINPUT2
INPUT_LINE3 = const(5)  # Input as line input, only use L/RINPUT3

# ADC sync input
SYNC_ADC = const(0)  # Use ADCLRC pin for ADC sync
SYNC_DAC = const(1)  # used DACLRC pin for ADC sync

# Protocol type
BUS_I2S = const(2)  # I2S type
BUS_LEFT_JUSTIFIED = const(1)  # Left justified mode
BUS_RIGHT_JUSTIFIED = const(0)  # Right justified mode
BUS_PCMA = const(3)  # PCM A mode
BUS_PCMB = const(3 | (1 << 4))  # PCM B mode

# Channel swap
SWAP_NONE = const(0)
SWAP_INPUT = const(1)
SWAP_OUTPUT = const(2)

# Mute settings
MUTE_FAST = const(0)
MUTE_SLOW = const(1)

# ALC settings
ALC_OFF = const(0)
ALC_RIGHT = const(1)
ALC_LEFT = const(2)
ALC_STEREO = const(3)
ALC_MODE = const(0)  # ALC mode
ALC_LIMITER = const(1)  # Limiter mode

# Clock Source
SYSCLK_MCLK = const(0)  # sysclk source from external MCLK
SYSCLK_PLL = const(1)  # sysclk source from internal PLL


class Regs:
    # register cache of 56 register. Since registers cannot be read back, they are
    # kept in the table for modification
    # fmt: off
    cache = array.array("H", (
        0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000,
        0x000a, 0x01c0, 0x0000, 0x00ff, 0x00ff, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x007b, 0x0100, 0x0032, 0x0000,
        0x00c3, 0x00c3, 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0100, 0x0050,
        0x0050, 0x0050, 0x0050, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000, 0x0002,
        0x0037, 0x004d, 0x0080, 0x0008, 0x0031, 0x0026, 0x00e9
    ))
    # fmt: on

    def __init__(self, i2c, i2c_address=_I2C_ADDR):
        self.value_buffer = bytearray(2)
        self.i2c = i2c
        self.i2c_address = i2c_address

    def __getitem__(self, reg):
        return self.cache[reg]

    def __setitem__(self, reg, value):
        if type(reg) is tuple:
            if type(value) is tuple:
                self[reg[0]] = value[0]
                self[reg[1]] = value[1]
            else:
                self[reg[0]] = value
                self[reg[1]] = value
        else:
            if type(value) is tuple:
                val = (self.cache[reg] & (~value[0] & 0xFFFF)) | value[1]
            else:
                val = value
            self.cache[reg] = val
            self.value_buffer[0] = (reg << 1) | ((val >> 8) & 0x01)
            self.value_buffer[1] = val & 0xFF
            self.i2c.writeto(self.i2c_address, self.value_buffer)


class WM8960:
    _bit_clock_divider_table = {
        2: 0,
        3: 1,
        4: 2,
        6: 3,
        8: 4,
        11: 5,
        12: 6,
        16: 7,
        22: 8,
        24: 9,
        32: 10,
        44: 11,
        48: 12,
    }

    _dac_divider_table = {
        1.0 * 256: 0b000,
        1.5 * 256: 0b001,
        2 * 256: 0b010,
        3 * 256: 0b011,
        4 * 256: 0b100,
        5.5 * 256: 0b101,
        6 * 256: 0b110,
    }

    _audio_word_length_table = {
        16: 0b00,
        20: 0b01,
        24: 0b10,
        32: 0b11,
    }

    _alc_sample_rate_table = {
        48000: 0,
        44100: 0,
        32000: 1,
        24000: 2,
        22050: 2,
        16000: 3,
        12000: 4,
        11025: 4,
        8000: 5,
    }

    _volume_config_table = {
        MODULE_ADC: (_MAX_VOLUME_ADC, _LADC, 0x100),
        MODULE_DAC: (_MAX_VOLUME_DAC, _LDAC, 0x100),
        MODULE_HEADPHONE: (_MAX_VOLUME_HEADPHONE, _LOUT1, 0x180),
        MODULE_LINE_IN: (_MAX_VOLUME_LINEIN, _LINVOL, 0x140),
        MODULE_SPEAKER: (_MAX_VOLUME_SPEAKER, _LOUT2, 0x180),
    }

    _input_config_table = {
        INPUT_CLOSED: None,
        INPUT_MIC1: (0x138, 0x117),
        INPUT_MIC2: (0x178, 0x117),
        INPUT_MIC3: (0x1B8, 0x117),
        INPUT_LINE2: (0, 0xE),
        INPUT_LINE3: (0, 0x70),
    }

    def __init__(
        self,
        i2c,
        sample_rate=16000,
        bits=16,
        swap=SWAP_NONE,
        route=ROUTE_PLAYBACK_RECORD,
        left_input=INPUT_MIC3,
        right_input=INPUT_MIC2,
        sysclk_source=SYSCLK_MCLK,
        mclk_freq=None,
        primary=False,
        adc_sync=SYNC_DAC,
        protocol=BUS_I2S,
        i2c_address=_I2C_ADDR,
    ):
        self.regs = regs = Regs(i2c, i2c_address)
        self.sample_rate = sample_rate

        # check parameter consistency and set the sysclk value
        if sysclk_source == SYSCLK_PLL:
            if sample_rate in (11025, 22050, 44100):
                sysclk = 11289600
            else:
                sysclk = 12288000
            if sysclk < sample_rate * 256:
                sysclk = sample_rate * 256
            if mclk_freq is None:
                mclk_freq = sysclk
        else:  # sysclk_source == SYSCLK_MCLK
            if mclk_freq is None:
                mclk_freq = sample_rate * 256
            sysclk = mclk_freq

        regs[_RESET] = 0x00
        # VMID=50K, Enable VREF, AINL, AINR, ADCL and ADCR
        # I2S_IN (bit 0), I2S_OUT (bit 1), DAP (bit 4), DAC (bit 5), ADC (bit 6) are powered on
        regs[_POWER1] = 0xFE
        # Enable DACL, DACR, LOUT1, ROUT1, PLL down, SPKL, SPKR
        regs[_POWER2] = 0x1F8
        # Enable left and right channel input PGA, left and right output mixer
        regs[_POWER3] = 0x3C

        if adc_sync == SYNC_ADC:
            # ADC and DAC use different Frame Clock Pins
            regs[_IFACE2] = 0x00  # ADCLRC 0x00:Input 0x40:output.
        else:
            # ADC and DAC use the same Frame Clock Pin
            regs[_IFACE2] = 0x40  # ADCLRC 0x00:Input 0x40:output.
        self.set_data_route(route)
        self.set_protocol(protocol)

        if sysclk_source == SYSCLK_PLL:
            self.set_internal_pll_config(mclk_freq, sysclk)
        if primary:
            self.set_master_clock(sysclk, sample_rate, bits)
            # set master bit.
            self.regs[_IFACE1] = (0, _IFACE1_MS_MASK)

        self.set_speaker_clock(sysclk)

        # swap channels
        if swap & SWAP_INPUT:
            regs[_IFACE1] = (0, _IFACE1_ALRSWAP_MASK)
        if swap & SWAP_OUTPUT:
            regs[_IFACE1] = (0, _IFACE1_DLRSWAP_MASK)

        self.set_left_input(left_input)
        self.set_right_input(right_input)

        regs[_ADDCTL1] = 0x0C0
        regs[_ADDCTL4] = 0x60  # Set GPIO1 to 0.

        regs[_BYPASS1] = regs[_BYPASS2] = 0x0
        # ADC volume, 0dB
        regs[_LADC, _RADC] = 0x1C3
        # Digital DAC volume, 0dB
        regs[_LDAC, _RDAC] = 0x1FF
        # Headphone volume, LOUT1 and ROUT1, 0dB
        regs[_LOUT1, _ROUT1] = 0x16F
        # speaker volume 6dB
        regs[_LOUT2, _ROUT2] = 0x1FF
        # enable class D output
        regs[_CLASSD1] = 0xF7
        # Unmute DAC.
        regs[_DACCTL1] = 0x0000
        # Input PGA volume 0 dB
        regs[_LINVOL, _RINVOL] = 0x117

        self.config_data_format(sysclk, sample_rate, bits)

    def deinit(self):
        self.set_module(MODULE_ADC, False)
        self.set_module(MODULE_DAC, False)
        self.set_module(MODULE_VREF, False)
        self.set_module(MODULE_LINE_IN, False)
        self.set_module(MODULE_LINE_OUT, False)
        self.set_module(MODULE_SPEAKER, False)

    def set_internal_pll_config(self, input_mclk, output_clk):
        regs = self.regs
        pllF2 = output_clk * 4
        pll_prescale = 0
        sysclk_div = 1
        frac_mode = 0

        # disable PLL power
        regs[_POWER2] = (1, 0)
        regs[_CLOCK1] = (7, 0)

        pllN = pllF2 // input_mclk
        if pllN < _PLL_N_MIN_VALUE:
            input_mclk //= 2
            pll_prescale = 1
            pllN = pllF2 // input_mclk
            if pllN < _PLL_N_MIN_VALUE:
                sysclk_div = 2
                pllF2 *= 2
                pllN = pllF2 // input_mclk

        if (pllN < _PLL_N_MIN_VALUE) or (pllN > _PLL_N_MAX_VALUE):
            raise ValueError("Invalid MCLK vs. sysclk ratio")

        pllK = ((pllF2 % input_mclk) * (1 << 24)) // input_mclk
        if pllK != 0:
            frac_mode = 1

        regs[_PLL1] = (frac_mode << 5) | (pll_prescale << 4) | (pllN & 0x0F)
        regs[_PLL2] = (pllK >> 16) & 0xFF
        regs[_PLL3] = (pllK >> 8) & 0xFF
        regs[_PLL4] = pllK & 0xFF
        # enable PLL power
        regs[_POWER2] = (1, 1)
        regs[_CLOCK1] = (7, ((0 if sysclk_div == 1 else sysclk_div) << 1) | 1)

    def set_master_clock(self, sysclk, sample_rate, bit_width):
        bit_clock_divider = (sysclk * 2) // (sample_rate * bit_width * 2)
        try:
            reg_divider = self._bit_clock_divider_table[bit_clock_divider]
        except:
            raise ValueError("Invalid ratio of sysclk sample rate and bits")
        # configure the master bit clock divider
        self.regs[_CLOCK2] = (_CLOCK2_BCLK_DIV_MASK, reg_divider)

    def set_speaker_clock(self, sysclk):
        speaker_divider_table = (1.5, 2, 3, 4, 6, 8, 12, 16)
        for val in range(8):
            divider = speaker_divider_table[val]
            f = sysclk / divider
            if 500_000 < f < 1_000_000:
                break
        else:
            val = 7
        self.regs[_CLOCK2] = (
            _CLOCK2_DCLK_DIV_MASK,
            val << _CLOCK2_DCLK_DIV_SHIFT,
        )

    def set_module(self, module, is_enabled):
        is_enabled = 1 if is_enabled else 0
        regs = self.regs

        if module == MODULE_ADC:
            regs[_POWER1] = (
                _POWER1_ADCL_MASK | _POWER1_ADCR_MASK,
                (_POWER1_ADCL_MASK | _POWER1_ADCR_MASK) * is_enabled,
            )

        elif module == MODULE_DAC:
            regs[_POWER2] = (
                _POWER2_DACL_MASK | _POWER2_DACR_MASK,
                (_POWER2_DACL_MASK | _POWER2_DACR_MASK) * is_enabled,
            )

        elif module == MODULE_VREF:
            regs[_POWER1] = (
                _POWER1_VREF_MASK,
                (is_enabled << _POWER1_VREF_SHIFT),
            )

        elif module == MODULE_LINE_IN:
            regs[_POWER1] = (
                _POWER1_AINL_MASK | _POWER1_AINR_MASK,
                (_POWER1_AINL_MASK | _POWER1_AINR_MASK) * is_enabled,
            )
            regs[_POWER3] = (
                _POWER3_LMIC_MASK | _POWER3_RMIC_MASK,
                (_POWER3_LMIC_MASK | _POWER3_RMIC_MASK) * is_enabled,
            )

        elif module == MODULE_LINE_OUT:
            regs[_POWER2] = (
                _POWER2_LOUT1_MASK | _POWER2_ROUT1_MASK,
                (_POWER2_LOUT1_MASK | _POWER2_ROUT1_MASK) * is_enabled,
            )

        elif module == MODULE_MIC_BIAS:
            regs[_POWER1] = (
                _POWER1_MICB_MASK,
                (is_enabled << _POWER1_MICB_SHIFT),
            )

        elif module == MODULE_SPEAKER:
            regs[_POWER2] = (
                _POWER2_SPKL_MASK | _POWER2_SPKR_MASK,
                (_POWER2_SPKL_MASK | _POWER2_SPKR_MASK) * is_enabled,
            )
            regs[_CLASSD1] = 0xF7

        elif module == MODULE_OMIX:
            regs[_POWER3] = (
                _POWER3_LOMIX_MASK | _POWER3_ROMIX_MASK,
                (_POWER3_LOMIX_MASK | _POWER3_ROMIX_MASK) * is_enabled,
            )

        elif module == MODULE_MONO_OUT:
            regs[_MONOMIX1] = regs[_MONOMIX2] = is_enabled << 7
            regs[_MONO] = is_enabled << 6

        else:
            raise ValueError("Invalid module")

    def enable_module(self, module):
        self.set_module(module, True)

    def disable_module(self, module):
        self.set_module(module, False)

    def set_data_route(self, route):
        regs = self.regs
        if route == ROUTE_BYPASS:
            # Bypass means from line-in to HP
            # Left LINPUT3 to left output mixer, LINPUT3 left output mixer volume = 0dB
            # Right RINPUT3 to right output mixer, RINPUT3 right output mixer volume = 0dB
            regs[_LOUTMIX, _ROUTMIX] = 0x80

        elif route == ROUTE_PLAYBACK:
            # Data route I2S_IN-> DAC-> HP
            #
            # Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB
            # Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB
            regs[_LOUTMIX, _ROUTMIX] = 0x100
            regs[_POWER3] = 0x0C
            # Set power for DAC
            self.set_module(MODULE_DAC, True)
            self.set_module(MODULE_OMIX, True)
            self.set_module(MODULE_LINE_OUT, True)

        elif route == ROUTE_PLAYBACK_RECORD:
            #
            # Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB
            # Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB
            regs[_LOUTMIX, _ROUTMIX] = 0x100
            regs[_POWER3] = 0x3C
            self.set_module(MODULE_DAC, True)
            self.set_module(MODULE_ADC, True)
            self.set_module(MODULE_LINE_IN, True)
            self.set_module(MODULE_OMIX, True)
            self.set_module(MODULE_LINE_OUT, True)

        elif route == ROUTE_RECORD:
            # LINE_IN->ADC->I2S_OUT
            # Left and right input boost, LIN3BOOST and RIN3BOOST = 0dB
            regs[_POWER3] = 0x30
            # Power up ADC and AIN
            self.set_module(MODULE_LINE_IN, True)
            self.set_module(MODULE_ADC, True)

        else:
            raise ValueError("Invalid route")

    def set_left_input(self, input):
        if input not in self._input_config_table:
            raise ValueError("Invalid input")

        input = self._input_config_table[input]

        regs = self.regs
        if input is None:
            regs[_POWER1] = (_POWER1_AINL_MASK | _POWER1_ADCL_MASK, 0)
        elif input[0] == 0:
            regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCL_MASK)
            regs[_INBMIX1] = input
        else:
            regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCL_MASK | _POWER1_MICB_MASK)
            regs[_LINPATH] = input[0]
            regs[_LINVOL] = input[1]

    def set_right_input(self, input):
        if input not in self._input_config_table:
            raise ValueError("Invalid input name")

        input = self._input_config_table[input]

        regs = self.regs
        if input is None:
            regs[_POWER1] = (_POWER1_AINR_MASK | _POWER1_ADCR_MASK, 0)
        elif input[0] == 0:
            regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCR_MASK)
            regs[_INBMIX2] = input
        else:
            regs[_POWER1] = (0, _POWER1_AINR_MASK | _POWER1_ADCR_MASK | _POWER1_MICB_MASK)
            regs[_RINPATH] = input[0]
            regs[_RINVOL] = input[1]

    def set_protocol(self, protocol):
        self.regs[_IFACE1] = (
            _IFACE1_FORMAT_MASK | _IFACE1_LRP_MASK,
            protocol,
        )

    def config_data_format(self, sysclk, sample_rate, bits):
        #  Compute sample rate divider, dac and adc are the same sample rate
        try:
            divider = self._dac_divider_table[sysclk // sample_rate]
            wl = self._audio_word_length_table[bits]
        except:
            raise ValueError("Invalid ratio sysclk/sample_rate or invalid bit length")

        self.regs[_CLOCK1] = (0x1F8, divider << 6 | divider << 3)
        self.regs[_IFACE1] = (_IFACE1_WL_MASK, wl << _IFACE1_WL_SHIFT)

    def volume(self, module, volume_l=None, volume_r=None):
        if module not in self._volume_config_table:
            raise ValueError("Invalid module")

        if volume_l is None:  # get volume
            vol_max, regnum, _ = self._volume_config_table[module]
            return (
                int((self.regs[regnum] & vol_max) * 100 / vol_max + 0.5),
                int((self.regs[regnum + 1] & vol_max) * 100 / vol_max + 0.5),
            )
        else:  # set volume
            if volume_r is None:
                volume_r = volume_l

            if not ((0 <= volume_l <= 100) and (0 <= volume_r <= 100)):
                raise ValueError("Invalid value for volume")
            elif module not in self._volume_config_table:
                raise ValueError("Invalid module")

            vol_max, regnum, flags = self._volume_config_table[module]
            self.regs[regnum] = int(volume_l * vol_max / 100 + 0.5) | flags
            self.regs[regnum + 1] = int(volume_r * vol_max / 100 + 0.5) | flags

    def mute(self, enable, soft=True, ramp=MUTE_FAST):
        enable = _DACCTL1_DACMU_MASK if enable else 0
        soft = _DACCTL2_DACSMM_MASK if soft else 0
        ramp = _DACCTL2_DACMR_MASK if ramp == MUTE_SLOW else 0
        self.regs[_DACCTL1] = (_DACCTL1_DACMU_MASK, enable)
        self.regs[_DACCTL2] = (
            _DACCTL2_DACSMM_MASK | _DACCTL2_DACMR_MASK,
            soft | ramp,
        )

    def expand_3d(self, depth=0):
        depth &= 0x0F
        cutoff = 0 if self.sample_rate >= 32000 else 0b1100000
        self.regs[_3D] = cutoff | depth << 1 | (1 if depth > 0 else 0)

    def mono(self, enable):
        enable = 1 if enable else 0
        self.regs[_DACCTL1] = (
            _DACCTL1_MONOMIX_MASK,
            enable << _DACCTL1_MONOMIX_SHIFT,
        )

    def alc_mode(self, channel, mode=ALC_MODE):
        if mode != ALC_MODE:
            mode = ALC_LIMITER
        channel &= 3
        self.regs[_ALC1] = (
            _ALC_CHANNEL_MASK,
            channel << _ALC_CHANNEL_SHIFT,
        )
        self.regs[_ALC3] = (_ALC_MODE_MASK, mode << _ALC_MODE_SHIFT)
        try:
            rate = self._alc_sample_rate_table[self.sample_rate]
        except:
            rate = 0
        self.regs[_ADDCTL3] = (_DACCTL3_ALCSR_MASK, rate)

    def alc_gain(self, target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78):
        def limit(value, minval, maxval):
            value = int(value)
            if value < minval:
                value = minval
            if value > maxval:
                value = maxval
            return value

        target = limit((16 + (target * 2) // 3), 0, 15)
        max_gain = limit((max_gain + 12) // 6, 0, 7)
        min_gain = limit((min_gain * 4 + 69) // 24, 0, 7)
        noise_gate = limit((noise_gate * 2 + 153) // 3, -1, 31)
        self.regs[_ALC1] = (
            _ALC_GAIN_MASK | _ALC_TARGET_MASK,
            (max_gain << _ALC_GAIN_SHIFT) | target,
        )
        self.regs[_ALC2] = (_ALC_GAIN_MASK, (min_gain << _ALC_GAIN_SHIFT))
        if noise_gate >= 0:
            self.regs[_NOISEG] = noise_gate << _NOISEG_LEVEL_SHIFT | 1
        else:
            self.regs[_NOISEG] = 0

    def alc_time(self, attack=24, decay=192, hold=0):
        def logb(value, limit):
            value = int(value)
            lb = 0
            while value > 1:
                value >>= 1
                lb += 1
            if lb > limit:
                lb = limit
            return lb

        attack = logb(attack / 6, 7)
        decay = logb(decay / 24, 7)
        hold = logb((hold * 3) / 8, 15)
        self.regs[_ALC2] = (_ALC_HOLD_MASK, hold)
        self.regs[_ALC3] = (
            _ALC_DECAY_MASK | _ALC_ATTACK_MASK,
            (decay << _ALC_DECAY_SHIFT) | attack,
        )

    def deemphasis(self, enable):
        deem_table = (32000, 44100, 48000)
        enable = not not enable
        if enable and self.sample_rate in deem_table:
            val = deem_table.index(self.sample_rate) + 1
        else:
            val = 0
        self.regs[_DACCTL1] = (_DACCTL1_DEEM_MASK, val << _DACCTL1_DEEM_SHIFT)
