micropython/drivers/codec/wm8960.py

754 lines
23 KiB
Python

#
# 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 not input in self._input_config_table.keys():
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 not input in self._input_config_table.keys():
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 not module in self._volume_config_table.keys():
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 not module in self._volume_config_table.keys():
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 = _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)