speakersafetyd/docs/audump.py

304 lines
9.8 KiB
Python
Raw Permalink Normal View History

import struct, sys, os.path, plistlib, pprint
LABELS = {
"spp3": {
0: {
0: "thermal protection enabled",
1: "displacement protection enabled",
2: "thermal/power control gain attack time (s)",
3: "thermal/power control gain release time (s)",
4: "ambient temperature",
5: "SafeTlim",
6: "SafeTlimTimeMin",
7: "SafeTlimOffset",
8: "LookaheadDelay_ms",
9: "peak attack time (s)",
10: "peak decay time (s)",
11: "feedback integration time",
12: "thermal gain (dB)",
13: "displacement gain (dB)",
14: "spk pwr averaging window time (s)",
15: "modeled speaker power",
16: "measured speaker power",
17: "power control gain",
18: "CPMS power control enabled",
19: "CPMS power control closed loop",
},
4: {
0: "temperature limit",
1: "hard temp limit headroom",
2: "T_sett_vc",
3: "T_sett_mg",
4: "tau_Tvc",
5: "tau_Tmg",
6: "ThermalFFSpeedupFactor",
7: "temperature",
8: "OL temperature",
9: "Reb_ref",
10: "Rshunt",
11: "Rampout",
12: "mt",
13: "ct",
14: "kt",
15: "ag",
16: "g_bw",
17: "Q_d",
18: "phi",
19: "x_lim",
20: "ThermalMeasurementMethod",
21: "pilot tone enabled",
22: "CL thermal feedback enabled",
23: "TlimErrDecayTime",
24: "TempSenseWindowTime",
25: "TempSenseSmoothingTau",
26: "a_t_inv",
27: "PilotAmplHi_dB",
28: "PilotAmplLo_dB",
29: "PilotUpperThres",
30: "PilotLowerThres",
31: "PilotDecayTime",
32: "PilotFreq",
33: "LPMLSPreGain",
34: "LPMLSPostGain",
35: "LPMLSLowerCorner",
36: "LPMLS pre clip level",
37: "mu_Re",
38: "mu_Le",
39: "mu mechanical (PU)",
40: "Max relative displacement",
41: "abs(Min relative displacement)",
42: "DisplacementProtectionType",
64: "thermal gain",
65: "displacement gain",
66: "power control gain",
67: "PilotDecayTimeStage2",
68: "PilotEnableThres",
},
},
"atsp": {
0: {
0: "Bypass",
40: "Gain link all audio channels",
1: "speakerType A: Amplifier sensitivity [V/Fs]",
2: "speakerType A: VoiceCoil: DC resistance [Ohms]",
3: "speakerType A: VoiceCoil: thermal resistance [C/Watt]",
4: "speakerType A: Voice Coil: thermal time constant [s]",
5: "speakerType A: Magnet: thermal resistance [C/Watt]",
6: "speakerType A: Magnet: thermal time constant [s]",
7: "speakerType A: Ambient temperature, [C]",
# The target temperature of the speakers
8: "speakerType A: Temperature limit [C]",
9: "speakerType A: Attack time (ms)",
10: "speakerType A: Release time (ms)",
11: "speakerType A: Temperature hard limit headroom [C]",
12: "speakerType A: Gain link",
13: "speakerType A: Audio channel assignment",
14: "speakerType B: Amplifier sensitivity [V/Fs",
15: "speakerType B: VoiceCoil: DC resistance [Ohms]",
16: "speakerType B: VoiceCoil: thermal resistance [C/Watt]",
17: "speakerType B: Voice Coil: thermal time constant [s]",
18: "speakerType B: Magnet: thermal resistance [C/Watt]",
19: "speakerType B: Magnet: thermal time constant [s]",
20: "speakerType B: Ambient temperature, [C]",
21: "speakerType B: Temperature limit [C]",
22: "speakerType B: Attack time (ms)",
23: "speakerType B: Release time (ms)",
24: "speakerType B: Temperature hard limit headroom [C]",
25: "speakerType B: Gain link",
26: "speakerType B: Audio channel assignment",
27: "speakerType C: Amplifier sensitivity [V/Fs]",
28: "speakerType C: VoiceCoil: DC resistance [Ohms]",
29: "speakerType C: VoiceCoil: thermal resistance [C/Watt]",
30: "speakerType C: Voice Coil: thermal time constant [s]",
31: "speakerType C: Magnet: thermal resistance [C/Watt]",
32: "speakerType C: Magnet: thermal time constant [s]",
33: "speakerType C: Ambient temperature, [C]",
34: "speakerType C: Temperature limit [C]",
35: "speakerType C: Attack time (ms)",
36: "speakerType C: Release time (ms)",
37: "speakerType C: Temperature hard limit headroom [C]",
38: "speakerType C: Gain link",
39: "speakerType C: Audio channel assignment",
}
}
}
"""
ATSP protection behavior:
Max gain reduction is 20dB.
"Temperature limit" is the target temperature
If temperature exceeds limit + "Temperature hard limit headroom",
protection goes into panic mode and triggers 20dB reduction.
For settings:
amp = 12 r = 4
rVc = 50 aVc = 2 rMg = 1 aMg = 1
Ta = 50 Tlim = 150 Theadroom = 5
We see this limiter behavior:
In Out
0 -9.7
-8 -9.7
-9 -9.6
-9.5 -9.7
-9.8 -9.9
-10 -10
In other words, it behaves like a hard limit / compressor with infinite ratio.
Theadroom has no influence on the gain reduction, it just affects stability
(temperature does exceed Tlim transiently, if the transient is > Theadroom
it panics). Too low a Theadroom leads to unstable behavior.
"""
def dump_audata(labels, data):
top = {}
while data:
hdr = data[:0xc]
data = data[0xc:]
typ, grp, cnt = struct.unpack(">III", hdr)
d = {}
for i in range(cnt):
blk = data[:0x8]
data = data[0x8:]
key, val = struct.unpack(">If", blk)
if typ in labels:
if key in labels[typ]:
key = labels[typ][key]
d[key] = val
top[(typ, grp)] = d
pprint.pprint(top, stream=sys.stderr)
return top
def process_spp3(e):
# Grab the plist file, which is mostly redundant but contains
# some details not in the au preset
for i in prop["Boxes"]:
if i["Name"] == e["displayname"]:
for p in i["Properties"]:
if p["Number"] == 64003:
path = os.path.join(base, "DSP", p["Path"].split("/DSP/")[1])
pl = plistlib.load(open(path, "rb"))
d = dump_audata(LABELS["spp3"], e["aupreset"]["data"])
spkrs = ""
channels = 0
gbl = d[(0, 0)]
for (typ, ch), p in sorted(d.items()):
if typ != 4:
continue
chp = pl["ChannelSpecificParams"][f"Channel{ch}"]
channels += 2
spkrs += f"""
[Speaker/{chp["SpeakerName"]}]
group = {chp["SpeakerGroup"]}
tr_coil = {p["T_sett_vc"]:.2f}
tr_magnet = {p["T_sett_mg"]:.2f}
tau_coil = {p["tau_Tvc"]:.2f}
tau_magnet = {p["tau_Tmg"]:.2f}
t_limit = {p["temperature limit"]:.1f}
t_headroom = {p["hard temp limit headroom"]:.1f}
z_nominal = {p["Reb_ref"]:.2f}
z_shunt = {p["Rshunt"]:.2f}
a_t_20c = {chp["CL"]["a_t_20C"]:.8f}
a_t_35c = {chp["CL"]["a_t_35C"]:.8f}
is_scale = 3.75
vs_scale = 14
is_chan = {2 * ch}
vs_chan = {2 * ch + 1}"""
print(f"""\
[Globals]
visense_pcm = 2
t_ambient = {gbl["ambient temperature"]}
t_hysteresis = 5.0
t_window = 20.0
channels = {channels}
period = 4096
link_gains = True
uclamp_max = 64
[Controls]
vsense = VSENSE Switch
isense = ISENSE Switch
amp_gain = Amp Gain Volume
volume = Speaker Volume{spkrs}""")
def process_atsp(e):
# print(e)
d = dump_audata(LABELS["atsp"], e["aupreset"]["data"])[(0,0)]
t_ambient = None
spkrs = ""
channels = 0
for gid, gn in enumerate("ABC"):
p = f"speakerType {gn}: "
ch = int(d[p + "Audio channel assignment"])
if not ch:
continue
if ch == 0xffff:
ch = 1
ambient = d[p + "Ambient temperature, [C]"]
assert t_ambient is None or t_ambient == ambient
t_ambient = ambient
for i in range(16):
if ch & (1 << i):
channels += 2
spkrs += f"""
[Speaker/{gn}_ch{i}]
group = {gid}
tr_coil = {d[p + "VoiceCoil: thermal resistance [C/Watt]"]:.2f}
tr_magnet = {d[p + "Magnet: thermal resistance [C/Watt]"]:.2f}
tau_coil = {d[p + "Voice Coil: thermal time constant [s]"]:.2f}
tau_magnet = {d[p + "Magnet: thermal time constant [s]"]:.2f}
t_limit = {d[p + "Temperature limit [C]"]:.1f}
t_headroom = {d[p + "Temperature hard limit headroom [C]"]:.1f}
z_nominal = {d[p + "VoiceCoil: DC resistance [Ohms]"]:.2f}
a_t_20c = 0.0037
a_t_35c = 0.0037
is_scale = 3.75
vs_scale = 14
is_chan = {2 * i}
vs_chan = {2 * i + 1}"""
print(f"""\
[Globals]
visense_pcm = 2
t_ambient = {t_ambient}
t_hysteresis = 5.0
t_window = 20.0
channels = {channels}
period = 4096
link_gains = {bool(d["Gain link all audio channels"])}
uclamp_max = 64
[Controls]
vsense = VSENSE Switch
isense = ISENSE Switch
amp_gain = Amp Gain Volume
volume = Speaker Volume{spkrs}""")
if __name__ == "__main__":
base = sys.argv[1]
au = plistlib.load(open(os.path.join(base, "DSP/Strips/builtin_speaker_out_general.austrip"), "rb"))
try:
prop = plistlib.load(open(os.path.join(base, "DSP/Strips/builtin_speaker_out_general.propstrip"), "rb"))
except:
prop = None
for s in au["strips"]:
for e in s["effects"]:
if e["unit"]["subtype"].to_bytes(4) == b"spp3":
process_spp3(e)
if e["unit"]["subtype"].to_bytes(4) == b"atsp":
process_atsp(e)