| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- #!/usr/bin/env python3
- """
- Generate pre-computed biquad coefficient tables for TAS5825M 15-band EQ.
- Frequencies and Q values match the mrtoy-me/esphome-tas58xx reference
- implementation. Coefficients are 5.27 fixed-point, big-endian byte order.
- Output: C header file with static const tables.
- """
- import math
- import struct
- import sys
- # ─── Band definitions (matching reference) ───
- BANDS = [
- # (freq_hz, Q)
- ( 20.0, 2.0), # BQ1
- ( 31.5, 2.0), # BQ2
- ( 50.0, 1.5), # BQ3
- ( 80.0, 1.5), # BQ4
- ( 125.0, 1.0), # BQ5
- ( 200.0, 1.0), # BQ6
- ( 315.0, 0.9), # BQ7
- ( 500.0, 0.9), # BQ8
- ( 800.0, 0.8), # BQ9
- ( 1250.0, 0.8), # BQ10
- ( 2000.0, 0.7), # BQ11
- ( 3150.0, 0.7), # BQ12
- ( 5000.0, 0.6), # BQ13
- ( 8000.0, 0.6), # BQ14
- (16000.0, 0.5), # BQ15
- ]
- NUM_BANDS = len(BANDS)
- FS = 48000.0 # Sample rate used by the reference implementation
- MIN_GAIN = -15
- MAX_GAIN = 15
- NUM_GAINS = MAX_GAIN - MIN_GAIN + 1 # 31
- def calc_peaking_biquad(fc, gain_db, q, fs):
- """
- Compute peaking-EQ biquad coefficients (Audio EQ Cookbook).
- TI convention: H(z) = (b0 + b1·z⁻¹ + b2·z⁻²) / (1 − a1·z⁻¹ − a2·z⁻²)
- so stored a1/a2 are the *negated* textbook values.
- Returns list of 5 int32 values in 5.27 fixed-point.
- """
- if abs(gain_db) < 0.001:
- # At 0 dB gain, we still compute the proper biquad (not passthrough)
- # to match reference behavior. A=1 makes b0/a0=1 exactly but
- # the other coefficients encode the filter shape.
- pass
- w0 = 2.0 * math.pi * fc / fs
- A = 10.0 ** (gain_db / 40.0)
- sinw = math.sin(w0)
- cosw = math.cos(w0)
- alpha = sinw / (2.0 * q)
- b0 = 1.0 + alpha * A
- b1 = -2.0 * cosw
- b2 = 1.0 - alpha * A
- a0 = 1.0 + alpha / A
- a1_txt = -2.0 * cosw
- a2_txt = 1.0 - alpha / A
- inv_a0 = 1.0 / a0
- b0 *= inv_a0
- b1 *= inv_a0
- b2 *= inv_a0
- # TI format: negate textbook a1/a2
- a1_ti = -a1_txt * inv_a0
- a2_ti = -a2_txt * inv_a0
- # Convert to 5.27 fixed-point
- scale = float(1 << 27)
- coeffs = [
- int(round(b0 * scale)),
- int(round(b1 * scale)),
- int(round(b2 * scale)),
- int(round(a1_ti * scale)),
- int(round(a2_ti * scale)),
- ]
- return coeffs
- def int32_to_bytes(val):
- """Convert a signed 32-bit integer to 4 big-endian bytes."""
- # Ensure value is in signed 32-bit range
- val = val & 0xFFFFFFFF
- return struct.pack(">I", val)
- def format_biquad_bytes(coeffs):
- """Format 5 int32 coefficients as a C byte array initializer."""
- raw = b""
- for c in coeffs:
- raw += int32_to_bytes(c)
- hex_bytes = ", ".join(f"0x{b:02X}" for b in raw)
- return hex_bytes
- def main():
- out = sys.stdout
- out.write("""\
- #pragma once
- /**
- * Pre-computed biquad coefficient tables for TAS5825M 15-band parametric EQ.
- *
- * Generated by scripts/gen_eq_tables.py — DO NOT EDIT BY HAND.
- *
- * Band definitions match the mrtoy-me/esphome-tas58xx reference:
- * Band Freq (Hz) Q
- * 0 20 2.0
- * 1 31.5 2.0
- * 2 50 1.5
- * 3 80 1.5
- * 4 125 1.0
- * 5 200 1.0
- * 6 315 0.9
- * 7 500 0.9
- * 8 800 0.8
- * 9 1250 0.8
- * 10 2000 0.7
- * 11 3150 0.7
- * 12 5000 0.6
- * 13 8000 0.6
- * 14 16000 0.5
- *
- * Gain range: -15 dB to +15 dB in 1 dB steps (31 levels).
- * Sample rate: 48000 Hz.
- * Coefficient format: 5.27 signed fixed-point, big-endian byte order.
- *
- * Index mapping: eq_coeff_table[gain_dB + 15][band]
- * gain_dB = -15 → index 0
- * gain_dB = 0 → index 15
- * gain_dB = +15 → index 30
- */
- #include <stdint.h>
- #define EQ_GAIN_OFFSET 15 /* add to gain_dB to get table index */
- #define EQ_NUM_GAIN_STEPS 31
- #define EQ_COEFF_BYTES 20 /* 5 coefficients × 4 bytes */
- """)
- # ─── Biquad address table for TAS5825M (Book 0xAA) ───
- out.write("""\
- /* ─── TAS5825M EQ biquad address table (Book 0xAA) ───
- * Each entry: { page, sub_address }
- * From mrtoy-me/esphome-tas58xx reference for TAS5825M.
- */
- typedef struct {
- uint8_t page;
- uint8_t sub_addr;
- } eq_bq_addr_t;
- static const eq_bq_addr_t eq_left_addr[15] = {
- { 0x01, 0x30 }, // BQ1 Left - 20 Hz
- { 0x01, 0x44 }, // BQ2 Left - 31.5 Hz
- { 0x01, 0x58 }, // BQ3 Left - 50 Hz
- { 0x01, 0x6C }, // BQ4 Left - 80 Hz
- { 0x02, 0x08 }, // BQ5 Left - 125 Hz
- { 0x02, 0x1C }, // BQ6 Left - 200 Hz
- { 0x02, 0x30 }, // BQ7 Left - 315 Hz
- { 0x02, 0x44 }, // BQ8 Left - 500 Hz
- { 0x02, 0x58 }, // BQ9 Left - 800 Hz
- { 0x02, 0x6C }, // BQ10 Left - 1250 Hz
- { 0x03, 0x08 }, // BQ11 Left - 2000 Hz
- { 0x03, 0x1C }, // BQ12 Left - 3150 Hz
- { 0x03, 0x30 }, // BQ13 Left - 5000 Hz
- { 0x03, 0x44 }, // BQ14 Left - 8000 Hz
- { 0x03, 0x58 }, // BQ15 Left - 16000 Hz
- };
- static const eq_bq_addr_t eq_right_addr[15] = {
- { 0x03, 0x6C }, // BQ1 Right - 20 Hz
- { 0x04, 0x08 }, // BQ2 Right - 31.5 Hz
- { 0x04, 0x1C }, // BQ3 Right - 50 Hz
- { 0x04, 0x30 }, // BQ4 Right - 80 Hz
- { 0x04, 0x44 }, // BQ5 Right - 125 Hz
- { 0x04, 0x58 }, // BQ6 Right - 200 Hz
- { 0x04, 0x6C }, // BQ7 Right - 315 Hz
- { 0x05, 0x08 }, // BQ8 Right - 500 Hz
- { 0x05, 0x1C }, // BQ9 Right - 800 Hz
- { 0x05, 0x30 }, // BQ10 Right - 1250 Hz
- { 0x05, 0x44 }, // BQ11 Right - 2000 Hz
- { 0x05, 0x58 }, // BQ12 Right - 3150 Hz
- { 0x05, 0x6C }, // BQ13 Right - 5000 Hz
- { 0x06, 0x08 }, // BQ14 Right - 8000 Hz
- { 0x06, 0x1C }, // BQ15 Right - 16000 Hz
- };
- """)
- # ─── Pre-computed coefficient table ───
- out.write("""\
- /* ─── Pre-computed biquad coefficients ───
- * eq_coeff_table[gain_index][band] = 20-byte biquad (b0 b1 b2 a1 a2).
- * gain_index = gain_dB + EQ_GAIN_OFFSET (0..30 for -15..+15 dB).
- */
- typedef struct {
- uint8_t bytes[EQ_COEFF_BYTES];
- } eq_biquad_t;
- """)
- out.write(
- f"static const eq_biquad_t eq_coeff_table"
- f"[EQ_NUM_GAIN_STEPS][{NUM_BANDS}] = {{\n"
- )
- for gain_idx in range(NUM_GAINS):
- gain_db = gain_idx + MIN_GAIN
- out.write(f" /* gain = {gain_db:+d} dB (index {gain_idx}) */\n")
- out.write(" {\n")
- for band_idx, (freq, q) in enumerate(BANDS):
- coeffs = calc_peaking_biquad(freq, float(gain_db), q, FS)
- hex_str = format_biquad_bytes(coeffs)
- comment = f"BQ{band_idx+1:2d} {freq:7.1f} Hz Q={q}"
- out.write(f" {{ {{ {hex_str} }} }}, // {comment}\n")
- out.write(" },\n")
- out.write("};\n")
- out.write("\n/* End of generated data */\n")
- if __name__ == "__main__":
- main()
|