Browse Source
This codec is assembled for the MIMXRT1xxx_DEV boards and available for WM8960 breakout boards as well. The driver itself has been tested as working with the MIMXRT boards and a Sparkfun WM6890 breakout board. It implements the initialization, basic methods and some enhanced methods like 3D, ALC, soft-mute and deemphasis.pull/8476/head
robert-hh
3 years ago
committed by
Damien George
4 changed files with 1164 additions and 0 deletions
@ -0,0 +1,397 @@ |
|||
.. _wm8960: |
|||
|
|||
:mod:`WM8960` -- Driver for the WM8960 codec |
|||
============================================ |
|||
|
|||
This driver is used to control a WM8960 codec chip. It is a Python |
|||
translation of the C-Code provided by NXP/Freescale for their i.MX RT series of |
|||
MCUs. Very little has been added, and just a few API related names were changed |
|||
or added to cope with the naming style of MicroPython. |
|||
|
|||
The primary purpose of the driver is initialization and setting operation modes |
|||
of the codec. It does not do the audio data processing for the codec. That is |
|||
the task of a separate driver. |
|||
|
|||
The WM8960 supports an I2C interface, in addition to the audio interface. The |
|||
connection depends on the interface used and the number of devices in the |
|||
system. For the I2C interface, SCL and SDA have to be connected, and of course |
|||
GND and Vcc. The I2C default address is ``0x1A``. |
|||
|
|||
Constructor |
|||
----------- |
|||
|
|||
.. class:: WM8960(i2c, sample_rate, *, 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=WM8960_I2C_ADDR) |
|||
|
|||
Create a WM8960 driver object, initialize the device with default settings and return the |
|||
WM8960 object. |
|||
|
|||
Only the first two arguments are mandatory. All others are optional. The arguments are: |
|||
|
|||
- *i2c* is the I2C bus object. |
|||
- *sample_rate* is the audio sample rate. Acceptable values are 8000, |
|||
11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, 192000 |
|||
and 384000. Note that not every I2S hardware will support all values. |
|||
- *bits* is the number of bits per audio word. Acceptable value are 16, |
|||
20, 24, and 32. |
|||
- *swap* swaps the left & right channel, if set; see below for options. |
|||
- *route* Setting the audio path in the codec; see below for options. |
|||
- *left_input* sets the audio source for the left input channel; |
|||
see below for options. |
|||
- *right_input* sets the audio source for the right input channel; |
|||
see below for options. |
|||
- *play_source* sets the audio target for the output audio; |
|||
see below for options. |
|||
- *sysclk_source* controls whether the internal master clock called |
|||
"sysclk" is directly taken from the MCLK input or derived from it |
|||
using an internal PLL. It is usually not required to change this. |
|||
- *mclk_freq* sets the mclk frequency applied to the MCLK pin of the |
|||
codec. If not set, default values are used. |
|||
- *primary* lets the WM8960 act as primary or secondary device. The |
|||
default setting is ``False``. When set to ``False``, |
|||
*sample_rate* and *bits* are controlled by the MCU. |
|||
- *adc_sync* sets which input is used for the ADC sync signal. |
|||
The default is using the DACLRC pin. |
|||
- *protocol* sets the communication protocol. The default is I2S. |
|||
See below for all options. |
|||
- *i2c_address* sets the I2C address of the WM8960, with default ``0x1A``. |
|||
|
|||
If *mclk_freq* is not set the following default values are used: |
|||
|
|||
- sysclk_source == SYSCLK_PLL: 11.2896 MHz for sample rates of 44100, |
|||
22050 and 11015 Hz, and 12.288 Mhz for sample rates < 48000, otherwise |
|||
sample_rate * 256. |
|||
- sysclk_source == SYSCLK_MCLK: sample_rate * 256. |
|||
|
|||
If the MCLK signal is applied using, for example,. a separate oscillator, |
|||
it must be specified for proper operation. |
|||
|
|||
Tables of parameter constants |
|||
----------------------------- |
|||
|
|||
.. table:: **Swap Parameter** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
0 SWAP_NONE |
|||
1 SWAP_INPUT |
|||
2 SWAP_OUTPUT |
|||
===== ==== |
|||
|
|||
.. table:: **Protocol Parameter** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
2 BUS_I2S |
|||
1 BUS_LEFT_JUSTIFIED |
|||
0 BUS_RIGHT_JUSTIFIED |
|||
3 BUS_PCMA |
|||
19 BUS_PCMB |
|||
===== ==== |
|||
|
|||
.. table:: **Input Source Parameter** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ============ ==== |
|||
Value Name Type |
|||
===== ============ ==== |
|||
0 INPUT_CLOSED |
|||
1 INPUT_MIC1 Single ended |
|||
2 INPUT_MIC2 Differential |
|||
3 INPUT_MIC3 Differential |
|||
4 INPUT_LINE2 |
|||
5 INPUT_LINE3 |
|||
===== ============ ==== |
|||
|
|||
.. table:: **Route Parameter** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
0 ROUTE_BYPASS |
|||
1 ROUTE_PLAYBACK |
|||
2 ROUTE_PLAYBACK_RECORD |
|||
5 ROUTE_RECORD |
|||
===== ==== |
|||
|
|||
.. table:: **Master Clock Source Parameter** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
0 SYSCLK_MCLK |
|||
1 SYSCLK_PLL |
|||
===== ==== |
|||
|
|||
.. table:: **Module Names** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
0 MODULE_ADC |
|||
1 MODULE_DAC |
|||
2 MODULE_VREF |
|||
3 MODULE_HEADPHONE |
|||
4 MODULE_MIC_BIAS |
|||
5 MODULE_MIC |
|||
6 MODULE_LINE_IN |
|||
7 MODULE_LINE_OUT |
|||
8 MODULE_SPEAKER |
|||
9 MODULE_OMIX |
|||
10 MODULE_MONO_OUT |
|||
===== ==== |
|||
|
|||
.. table:: **Play Channel Names** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
1 PLAY_HEADPHONE_LEFT |
|||
2 PLAY_HEADPHONE_RIGHT |
|||
4 PLAY_SPEAKER_LEFT |
|||
8 PLAY_SPEAKER_RIGHT |
|||
===== ==== |
|||
|
|||
.. table:: **adc_sync Parameters** |
|||
:widths: auto |
|||
:align: left |
|||
|
|||
===== ==== |
|||
Value Name |
|||
===== ==== |
|||
0 SYNC_ADC |
|||
1 SYNC_DAC |
|||
===== ==== |
|||
|
|||
|
|||
Methods |
|||
------- |
|||
|
|||
In addition to initialization, the driver provides some useful methods for |
|||
controlling its operation: |
|||
|
|||
.. method:: WM8960.set_left_input(input_source) |
|||
|
|||
Specify the source for the left input. The input source names are listed above. |
|||
|
|||
.. method:: WM8960.set_right_input(input_source) |
|||
|
|||
Specify the source for the right input. The input source names are listed above. |
|||
|
|||
.. method:: WM8960.volume(module, volume_l=None, volume_r=None) |
|||
|
|||
Sets or gets the volume of a certain module. |
|||
|
|||
If no volume values are supplied, the actual volume tuple is returned. |
|||
|
|||
If one or two values are supplied, it sets the volume of a certain module. |
|||
If two values are provided, the first one is used for the left channel, |
|||
the second for the right channel. If only one value is supplied, it is used |
|||
for both channels. The value range is normalized to 0.0-100.0 with a |
|||
logarithmic scale. |
|||
|
|||
For a list of suitable modules and db/step, see the table below. |
|||
|
|||
.. table:: **Module Names and dB steps** |
|||
:widths: auto |
|||
:align: center |
|||
|
|||
======= ==== |
|||
dB/Step Name |
|||
======= ==== |
|||
1.28 MODULE_ADC |
|||
1.28 MODULE_DAC |
|||
0.8 MODULE_HEADPHONE |
|||
0.475 MODULE_LINE_IN |
|||
0.8 MODULE_SPEAKER |
|||
======= ==== |
|||
|
|||
.. method:: WM8960.mute(module, mute, soft=True, ramp=wm8960.MUTE_FAST) |
|||
|
|||
Mute or unmute the output. If *mute* is True, the output is muted, if ``False`` |
|||
it is unmuted. |
|||
|
|||
If *soft* is set as True, muting will happen as a soft transition. The time for |
|||
the transition is defined by *ramp*, which is either ``MUTE_FAST`` or ``MUTE_SLOW``. |
|||
|
|||
.. method:: WM8960.set_data_route(route) |
|||
|
|||
Set the audio data route. For the parameter value/names, see the table above. |
|||
|
|||
.. method:: WM8960.set_module(module, active) |
|||
|
|||
Enable or disable a module, with *active* being ``False`` or ``True``. For |
|||
the list of module names, see the table above. |
|||
|
|||
Note that enabling ``MODULE_MONO_OUT`` is different from the `WM8960.mono` |
|||
method. The first enables output 3, while the `WM8960.mono` method sends a |
|||
mono mix to the left and right output. |
|||
|
|||
.. method:: WM8960.enable_module(module) |
|||
|
|||
Enable a module. For the list of module names, see the table above. |
|||
|
|||
.. method:: WM8960.disable_module(module) |
|||
|
|||
Disable a module. For the list of module names, see the table above. |
|||
|
|||
.. method:: WM8960.expand_3d(level) |
|||
|
|||
Enable Stereo 3D exansion. *level* is a number between 0 and 15. |
|||
A value of 0 disables the expansion. |
|||
|
|||
.. method:: WM8960.mono(active) |
|||
|
|||
If *active* is ``True``, a Mono mix is sent to the left and right output |
|||
channel. This is different from enabling the ``MODULE_MONO_MIX``, which |
|||
enables output 3. |
|||
|
|||
.. method:: WM8960.alc_mode(channel, mode=ALC_MODE) |
|||
|
|||
Enables or disables ALC mode. Parameters are: |
|||
|
|||
- *channel* enables and sets the channel for ALC. The parameter values are: |
|||
|
|||
- ALC_OFF: Switch ALC off |
|||
- ALS_RIGHT: Use the right input channel |
|||
- ALC_LEFT: Use the left input channel |
|||
- ALC_STEREO: Use both input channels. |
|||
|
|||
- *mode* sets the ALC mode. Input values are: |
|||
|
|||
- ALC_MODE: act as ALC |
|||
- ALC_LIMITER: act as limiter. |
|||
|
|||
.. method:: WM8960.alc_gain(target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78) |
|||
|
|||
Set the target level, highest and lowest gain levels and the noise gate as dB level. |
|||
Permitted ranges are: |
|||
|
|||
- *target*: -22.5 to -1.5 dB |
|||
- *max_gain*: -12 to 30 dB |
|||
- *min_gain*: -17 to 25 dB |
|||
- *noise_gate*: -78 to -30 dB |
|||
|
|||
Excess values are limited to the permitted ranges. A value of -78 or less |
|||
for *noise_gate* disables the noise gate function. |
|||
|
|||
.. method:: WM8960.alc_time(attack=24, decay=192, hold=0) |
|||
|
|||
Set the dynamic characteristic of ALC. The times are given as millisecond |
|||
values. Permitted ranges are: |
|||
|
|||
- *attack*: 6 to 6140 |
|||
- *decay*: 24 to 24580 |
|||
- *hold*: 0 to 43000 |
|||
|
|||
Excess values are limited within the permitted ranges. |
|||
|
|||
.. method:: WM8960.deemphasis(active) |
|||
|
|||
Enables or disables a deemphasis filter for playback, with *active* being |
|||
``False`` or ``True``. This filter is applied only for sample rates of |
|||
32000, 44100 and 48000. For other sample rates, the filter setting |
|||
is silently ignored. |
|||
|
|||
.. method:: WM8960.deinit() |
|||
|
|||
Disable all modules. |
|||
|
|||
|
|||
Examples |
|||
-------- |
|||
|
|||
Run WM8960 in secondary mode (default):: |
|||
|
|||
# Micro_python WM8960 Codec driver |
|||
# |
|||
# Setting the driver to Slave mode using the default settings |
|||
# |
|||
from machine import Pin, I2C |
|||
import wm8960 |
|||
i2c = I2C(0) |
|||
wm=wm8960.WM8960(i2c, 32000, left_input=wm8960.INPUT_MIC1) |
|||
wm.set_volume(wm8960.MODULE_HEADPHONE, 100) |
|||
|
|||
|
|||
Run WM8960 in primary mode:: |
|||
|
|||
# Micro_python WM8960 Codec driver |
|||
# |
|||
# Setting the driver to Master mode using specific audio format settings |
|||
# |
|||
from machine import Pin, I2C |
|||
import wm8960 |
|||
|
|||
i2c = I2C(0) |
|||
wm=wm8960.WM8960(i2c, 44100, primary=True, bits=16) |
|||
|
|||
|
|||
Run WM8960 on a MIMXRT10xx_DEV board in secondary mode (default):: |
|||
|
|||
# Micro_python WM8960 Codec driver |
|||
# |
|||
# Setting the driver to Slave mode using the default settings |
|||
# swap the input channels such that a MIMXRT Dev board mic, which |
|||
# is connected to the right input, is assigned to the left audio channel. |
|||
# |
|||
from machine import Pin, I2C |
|||
import wm8960 |
|||
i2c = I2C(0) |
|||
wm=wm8960.WM8960(i2c, sample_rate=16_000, |
|||
adc_sync=wm8960.SYNC_DAC, |
|||
swap=wm8960.SWAP_INPUT, |
|||
sysclk_source=wm8960.SYSCLK_MCLK) |
|||
|
|||
|
|||
Record with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default):: |
|||
|
|||
# Micro_python WM8960 Codec driver |
|||
# |
|||
# The breakout board uses a fixed 24MHz MCLK. Therefore the internal |
|||
# PLL must be used as sysclk, which is the master audio clock. |
|||
# The Sparkfun board has the WS pins for RX and TX connected on the |
|||
# board. Therefore adc_sync must be set to sync_adc, to configure |
|||
# it's ADCLRC pin as input. |
|||
# |
|||
from machine import Pin, I2C |
|||
import wm8960 |
|||
i2c = I2C(0) |
|||
wm=wm8960.WM8960(i2c, sample_rate=16_000, |
|||
adc_sync=wm8960.SYNC_ADC, |
|||
sysclk_source=wm8960.SYSCLK_PLL, |
|||
mclk_freq=24_000_000, |
|||
left_input=wm8960.INPUT_MIC1, |
|||
right_input=wm8960.INPUT_CLOSED) |
|||
|
|||
|
|||
Play with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default):: |
|||
|
|||
# The breakout board uses a fixed 24MHz MCLK. Therefore the internal |
|||
# PLL must be used as sysclk, which is the master audio clock. |
|||
# The Sparkfun board has the WS pins for RX and TX connected on the |
|||
# board. Therefore adc_sync must be set to sync_adc, to configure |
|||
# it's ADCLRC pin as input. |
|||
|
|||
from machine import I2C |
|||
i2c=I2C(0) |
|||
import wm8960 |
|||
wm=wm8960.WM8960(i2c, sample_rate=44_100, |
|||
adc_sync=wm8960.SYNC_ADC, |
|||
sysclk_source=wm8960.SYSCLK_PLL, |
|||
mclk_freq=24_000_000) |
|||
wm.set_volume(wm8960.MODULE_HEADPHONE, 100) |
@ -0,0 +1,753 @@ |
|||
# |
|||
# 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) |
Loading…
Reference in new issue