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)