gen_eq_tables.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #!/usr/bin/env python3
  2. """
  3. Generate pre-computed biquad coefficient tables for TAS5825M 15-band EQ.
  4. Frequencies and Q values match the mrtoy-me/esphome-tas58xx reference
  5. implementation. Coefficients are 5.27 fixed-point, big-endian byte order.
  6. Output: C header file with static const tables.
  7. """
  8. import math
  9. import struct
  10. import sys
  11. # ─── Band definitions (matching reference) ───
  12. BANDS = [
  13. # (freq_hz, Q)
  14. ( 20.0, 2.0), # BQ1
  15. ( 31.5, 2.0), # BQ2
  16. ( 50.0, 1.5), # BQ3
  17. ( 80.0, 1.5), # BQ4
  18. ( 125.0, 1.0), # BQ5
  19. ( 200.0, 1.0), # BQ6
  20. ( 315.0, 0.9), # BQ7
  21. ( 500.0, 0.9), # BQ8
  22. ( 800.0, 0.8), # BQ9
  23. ( 1250.0, 0.8), # BQ10
  24. ( 2000.0, 0.7), # BQ11
  25. ( 3150.0, 0.7), # BQ12
  26. ( 5000.0, 0.6), # BQ13
  27. ( 8000.0, 0.6), # BQ14
  28. (16000.0, 0.5), # BQ15
  29. ]
  30. NUM_BANDS = len(BANDS)
  31. FS = 48000.0 # Sample rate used by the reference implementation
  32. MIN_GAIN = -15
  33. MAX_GAIN = 15
  34. NUM_GAINS = MAX_GAIN - MIN_GAIN + 1 # 31
  35. def calc_peaking_biquad(fc, gain_db, q, fs):
  36. """
  37. Compute peaking-EQ biquad coefficients (Audio EQ Cookbook).
  38. TI convention: H(z) = (b0 + b1·z⁻¹ + b2·z⁻²) / (1 − a1·z⁻¹ − a2·z⁻²)
  39. so stored a1/a2 are the *negated* textbook values.
  40. Returns list of 5 int32 values in 5.27 fixed-point.
  41. """
  42. if abs(gain_db) < 0.001:
  43. # At 0 dB gain, we still compute the proper biquad (not passthrough)
  44. # to match reference behavior. A=1 makes b0/a0=1 exactly but
  45. # the other coefficients encode the filter shape.
  46. pass
  47. w0 = 2.0 * math.pi * fc / fs
  48. A = 10.0 ** (gain_db / 40.0)
  49. sinw = math.sin(w0)
  50. cosw = math.cos(w0)
  51. alpha = sinw / (2.0 * q)
  52. b0 = 1.0 + alpha * A
  53. b1 = -2.0 * cosw
  54. b2 = 1.0 - alpha * A
  55. a0 = 1.0 + alpha / A
  56. a1_txt = -2.0 * cosw
  57. a2_txt = 1.0 - alpha / A
  58. inv_a0 = 1.0 / a0
  59. b0 *= inv_a0
  60. b1 *= inv_a0
  61. b2 *= inv_a0
  62. # TI format: negate textbook a1/a2
  63. a1_ti = -a1_txt * inv_a0
  64. a2_ti = -a2_txt * inv_a0
  65. # Convert to 5.27 fixed-point
  66. scale = float(1 << 27)
  67. coeffs = [
  68. int(round(b0 * scale)),
  69. int(round(b1 * scale)),
  70. int(round(b2 * scale)),
  71. int(round(a1_ti * scale)),
  72. int(round(a2_ti * scale)),
  73. ]
  74. return coeffs
  75. def int32_to_bytes(val):
  76. """Convert a signed 32-bit integer to 4 big-endian bytes."""
  77. # Ensure value is in signed 32-bit range
  78. val = val & 0xFFFFFFFF
  79. return struct.pack(">I", val)
  80. def format_biquad_bytes(coeffs):
  81. """Format 5 int32 coefficients as a C byte array initializer."""
  82. raw = b""
  83. for c in coeffs:
  84. raw += int32_to_bytes(c)
  85. hex_bytes = ", ".join(f"0x{b:02X}" for b in raw)
  86. return hex_bytes
  87. def main():
  88. out = sys.stdout
  89. out.write("""\
  90. #pragma once
  91. /**
  92. * Pre-computed biquad coefficient tables for TAS5825M 15-band parametric EQ.
  93. *
  94. * Generated by scripts/gen_eq_tables.py — DO NOT EDIT BY HAND.
  95. *
  96. * Band definitions match the mrtoy-me/esphome-tas58xx reference:
  97. * Band Freq (Hz) Q
  98. * 0 20 2.0
  99. * 1 31.5 2.0
  100. * 2 50 1.5
  101. * 3 80 1.5
  102. * 4 125 1.0
  103. * 5 200 1.0
  104. * 6 315 0.9
  105. * 7 500 0.9
  106. * 8 800 0.8
  107. * 9 1250 0.8
  108. * 10 2000 0.7
  109. * 11 3150 0.7
  110. * 12 5000 0.6
  111. * 13 8000 0.6
  112. * 14 16000 0.5
  113. *
  114. * Gain range: -15 dB to +15 dB in 1 dB steps (31 levels).
  115. * Sample rate: 48000 Hz.
  116. * Coefficient format: 5.27 signed fixed-point, big-endian byte order.
  117. *
  118. * Index mapping: eq_coeff_table[gain_dB + 15][band]
  119. * gain_dB = -15 → index 0
  120. * gain_dB = 0 → index 15
  121. * gain_dB = +15 → index 30
  122. */
  123. #include <stdint.h>
  124. #define EQ_GAIN_OFFSET 15 /* add to gain_dB to get table index */
  125. #define EQ_NUM_GAIN_STEPS 31
  126. #define EQ_COEFF_BYTES 20 /* 5 coefficients × 4 bytes */
  127. """)
  128. # ─── Biquad address table for TAS5825M (Book 0xAA) ───
  129. out.write("""\
  130. /* ─── TAS5825M EQ biquad address table (Book 0xAA) ───
  131. * Each entry: { page, sub_address }
  132. * From mrtoy-me/esphome-tas58xx reference for TAS5825M.
  133. */
  134. typedef struct {
  135. uint8_t page;
  136. uint8_t sub_addr;
  137. } eq_bq_addr_t;
  138. static const eq_bq_addr_t eq_left_addr[15] = {
  139. { 0x01, 0x30 }, // BQ1 Left - 20 Hz
  140. { 0x01, 0x44 }, // BQ2 Left - 31.5 Hz
  141. { 0x01, 0x58 }, // BQ3 Left - 50 Hz
  142. { 0x01, 0x6C }, // BQ4 Left - 80 Hz
  143. { 0x02, 0x08 }, // BQ5 Left - 125 Hz
  144. { 0x02, 0x1C }, // BQ6 Left - 200 Hz
  145. { 0x02, 0x30 }, // BQ7 Left - 315 Hz
  146. { 0x02, 0x44 }, // BQ8 Left - 500 Hz
  147. { 0x02, 0x58 }, // BQ9 Left - 800 Hz
  148. { 0x02, 0x6C }, // BQ10 Left - 1250 Hz
  149. { 0x03, 0x08 }, // BQ11 Left - 2000 Hz
  150. { 0x03, 0x1C }, // BQ12 Left - 3150 Hz
  151. { 0x03, 0x30 }, // BQ13 Left - 5000 Hz
  152. { 0x03, 0x44 }, // BQ14 Left - 8000 Hz
  153. { 0x03, 0x58 }, // BQ15 Left - 16000 Hz
  154. };
  155. static const eq_bq_addr_t eq_right_addr[15] = {
  156. { 0x03, 0x6C }, // BQ1 Right - 20 Hz
  157. { 0x04, 0x08 }, // BQ2 Right - 31.5 Hz
  158. { 0x04, 0x1C }, // BQ3 Right - 50 Hz
  159. { 0x04, 0x30 }, // BQ4 Right - 80 Hz
  160. { 0x04, 0x44 }, // BQ5 Right - 125 Hz
  161. { 0x04, 0x58 }, // BQ6 Right - 200 Hz
  162. { 0x04, 0x6C }, // BQ7 Right - 315 Hz
  163. { 0x05, 0x08 }, // BQ8 Right - 500 Hz
  164. { 0x05, 0x1C }, // BQ9 Right - 800 Hz
  165. { 0x05, 0x30 }, // BQ10 Right - 1250 Hz
  166. { 0x05, 0x44 }, // BQ11 Right - 2000 Hz
  167. { 0x05, 0x58 }, // BQ12 Right - 3150 Hz
  168. { 0x05, 0x6C }, // BQ13 Right - 5000 Hz
  169. { 0x06, 0x08 }, // BQ14 Right - 8000 Hz
  170. { 0x06, 0x1C }, // BQ15 Right - 16000 Hz
  171. };
  172. """)
  173. # ─── Pre-computed coefficient table ───
  174. out.write("""\
  175. /* ─── Pre-computed biquad coefficients ───
  176. * eq_coeff_table[gain_index][band] = 20-byte biquad (b0 b1 b2 a1 a2).
  177. * gain_index = gain_dB + EQ_GAIN_OFFSET (0..30 for -15..+15 dB).
  178. */
  179. typedef struct {
  180. uint8_t bytes[EQ_COEFF_BYTES];
  181. } eq_biquad_t;
  182. """)
  183. out.write(
  184. f"static const eq_biquad_t eq_coeff_table"
  185. f"[EQ_NUM_GAIN_STEPS][{NUM_BANDS}] = {{\n"
  186. )
  187. for gain_idx in range(NUM_GAINS):
  188. gain_db = gain_idx + MIN_GAIN
  189. out.write(f" /* gain = {gain_db:+d} dB (index {gain_idx}) */\n")
  190. out.write(" {\n")
  191. for band_idx, (freq, q) in enumerate(BANDS):
  192. coeffs = calc_peaking_biquad(freq, float(gain_db), q, FS)
  193. hex_str = format_biquad_bytes(coeffs)
  194. comment = f"BQ{band_idx+1:2d} {freq:7.1f} Hz Q={q}"
  195. out.write(f" {{ {{ {hex_str} }} }}, // {comment}\n")
  196. out.write(" },\n")
  197. out.write("};\n")
  198. out.write("\n/* End of generated data */\n")
  199. if __name__ == "__main__":
  200. main()