From 7b9fc5ee84e102e5bbb683279e0baca882c7727b Mon Sep 17 00:00:00 2001 From: Ilkka Lehtoranta Date: Tue, 23 Jun 2026 14:02:50 +0300 Subject: [PATCH] Add SID fixture microscope reports --- .../coppermod-balanced-pal-6581.json | 1600 +++++++++++++++-- .../binaries/sid-adsr-gate-trace.sid | Bin 0 -> 332 bytes .../binaries/sid-combined-wave-edge-cases.sid | Bin 0 -> 377 bytes .../sid-filter-mode-resonance-sweep.sid | Bin 0 -> 2472 bytes .../binaries/sid-noise-edge-cases.sid | Bin 0 -> 332 bytes .../ConformanceFixtures/manifest.json | 32 + .../specs/sid-adsr-gate-trace.json | 37 + .../specs/sid-combined-wave-edge-cases.json | 42 + .../sid-filter-mode-resonance-sweep.json | 105 ++ .../specs/sid-noise-edge-cases.json | 37 + CopperMod.Sid/Properties/AssemblyInfo.cs | 1 + .../SidConformanceCommandTests.cs | 17 + CopperMod.Tools/SidConformance.cs | 26 +- CopperMod.Tools/SidConformanceDiagnostics.cs | 1077 +++++++++++ 14 files changed, 2854 insertions(+), 120 deletions(-) create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-adsr-gate-trace.sid create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-combined-wave-edge-cases.sid create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-filter-mode-resonance-sweep.sid create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-noise-edge-cases.sid create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-adsr-gate-trace.json create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-combined-wave-edge-cases.json create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-filter-mode-resonance-sweep.json create mode 100644 CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-noise-edge-cases.json create mode 100644 CopperMod.Tools/SidConformanceDiagnostics.cs diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/baselines/coppermod-balanced-pal-6581.json b/CopperMod.Sid.Tests/ConformanceFixtures/baselines/coppermod-balanced-pal-6581.json index 0732654..aa7ed78 100644 --- a/CopperMod.Sid.Tests/ConformanceFixtures/baselines/coppermod-balanced-pal-6581.json +++ b/CopperMod.Sid.Tests/ConformanceFixtures/baselines/coppermod-balanced-pal-6581.json @@ -253,6 +253,145 @@ } } }, + { + "id": "sid-adsr-gate-trace", + "outputs": { + "raw": { + "mean": 0.13266444312485837, + "acRms": 0.09765183578834657, + "peak": 0.49455294013023376, + "segments": [ + { + "name": "gate-pulse-1-on", + "mean": 0.19435304963994895, + "acRms": 0.16103452151479697, + "peak": 0.49455294013023376 + }, + { + "name": "gate-pulse-1-release", + "mean": 0.1293139243139236, + "acRms": 0.11091556348364287, + "peak": 0.36911478638648987 + }, + { + "name": "gate-pulse-2-on", + "mean": 0.13123001133596215, + "acRms": 0.04105945334551766, + "peak": 0.20847904682159424 + }, + { + "name": "gate-pulse-2-release", + "mean": 0.12926751521226645, + "acRms": 0.10797750256347899, + "peak": 0.37170299887657166 + }, + { + "name": "gate-pulse-3-on", + "mean": 0.13088499468285592, + "acRms": 0.039701255542141184, + "peak": 0.20856475830078125 + }, + { + "name": "gate-pulse-3-release", + "mean": 0.1290470884340195, + "acRms": 0.1077389480906023, + "peak": 0.3715035617351532 + }, + { + "name": "gate-pulse-4-on", + "mean": 0.1310014226551478, + "acRms": 0.039635639786530306, + "peak": 0.2063199281692505 + }, + { + "name": "gate-pulse-4-release", + "mean": 0.12900733177002835, + "acRms": 0.10778645786951001, + "peak": 0.3708292543888092 + }, + { + "name": "gate-pulse-5-on", + "mean": 0.13111146181278552, + "acRms": 0.03974971553547789, + "peak": 0.20497742295265198 + }, + { + "name": "gate-final-tail", + "mean": 0.13026514433141226, + "acRms": 0.08164150654685776, + "peak": 0.3715839385986328 + } + ] + }, + "player": { + "mean": 0.018471022548370814, + "acRms": 0.10746257619615393, + "peak": 0.5070149898529053, + "segments": [ + { + "name": "gate-pulse-1-on", + "mean": 0.1654823302042094, + "acRms": 0.17604768731513912, + "peak": 0.5070149898529053 + }, + { + "name": "gate-pulse-1-release", + "mean": 0.051440547495554906, + "acRms": 0.11600404411236381, + "peak": 0.3275153934955597 + }, + { + "name": "gate-pulse-2-on", + "mean": 0.0273571578269222, + "acRms": 0.042679007327142485, + "peak": 0.11155141890048981 + }, + { + "name": "gate-pulse-2-release", + "mean": 0.01369711616470776, + "acRms": 0.11217522239291708, + "peak": 0.2698541283607483 + }, + { + "name": "gate-pulse-3-on", + "mean": 0.008050869748672085, + "acRms": 0.041214950699098585, + "peak": 0.08993874490261078 + }, + { + "name": "gate-pulse-3-release", + "mean": 0.003252583356910795, + "acRms": 0.11191049195587303, + "peak": 0.2557527422904968 + }, + { + "name": "gate-pulse-4-on", + "mean": 0.0030292603858015354, + "acRms": 0.04117499289782989, + "peak": 0.08156854659318924 + }, + { + "name": "gate-pulse-4-release", + "mean": 0.0004963959200720088, + "acRms": 0.11196564878094947, + "peak": 0.25521013140678406 + }, + { + "name": "gate-pulse-5-on", + "mean": 0.0017925766567354156, + "acRms": 0.041295995552538226, + "peak": 0.0786069929599762 + }, + { + "name": "gate-final-tail", + "mean": 0.0005859186254061258, + "acRms": 0.0848036252530511, + "peak": 0.25638851523399353 + } + ] + } + } + }, { "id": "sidtest5-04-ring-modulation", "outputs": { @@ -415,150 +554,1081 @@ "peak": 0.4648406505584717 }, { - "name": "low-pass-high-mid", - "mean": -0.003133382183164536, - "acRms": 0.26222631637515564, - "peak": 0.46508729457855225 + "name": "low-pass-high-mid", + "mean": -0.003133382183164536, + "acRms": 0.26222631637515564, + "peak": 0.46508729457855225 + }, + { + "name": "low-pass-open", + "mean": -0.0016904647116360947, + "acRms": 0.24105152523686452, + "peak": 0.39723315834999084 + } + ] + } + } + }, + { + "id": "sidtest5-07-high-pass-filter", + "outputs": { + "raw": { + "mean": 0.07294690983522831, + "acRms": 0.11918503519125756, + "peak": 0.8000343441963196, + "segments": [ + { + "name": "high-pass-closed", + "mean": 0.07811470860404879, + "acRms": 0.20836973348779467, + "peak": 0.8000343441963196 + }, + { + "name": "high-pass-low-mid", + "mean": 0.07075090381362088, + "acRms": 0.10675823765338222, + "peak": 0.4957949221134186 + }, + { + "name": "high-pass-high-mid", + "mean": 0.07136095967263524, + "acRms": 0.04160726981519128, + "peak": 0.4697941839694977 + }, + { + "name": "high-pass-open", + "mean": 0.07156106725060833, + "acRms": 0.015423813662976264, + "peak": 0.27001622319221497 + } + ] + }, + "player": { + "mean": 0.00948530467499449, + "acRms": 0.12513269819576908, + "peak": 0.8266730904579163, + "segments": [ + { + "name": "high-pass-closed", + "mean": 0.03221610545883414, + "acRms": 0.2186383936777334, + "peak": 0.8266730904579163 + }, + { + "name": "high-pass-low-mid", + "mean": 0.0045420620839039605, + "acRms": 0.11022495514361196, + "peak": 0.4510822892189026 + }, + { + "name": "high-pass-high-mid", + "mean": 0.0009643335188815488, + "acRms": 0.04185608480391158, + "peak": 0.40951111912727356 + }, + { + "name": "high-pass-open", + "mean": 0.00021871763835831104, + "acRms": 0.015131005689331265, + "peak": 0.22152996063232422 + } + ] + } + } + }, + { + "id": "sidtest5-08-band-pass-filter", + "outputs": { + "raw": { + "mean": 0.13148955295220602, + "acRms": 0.06843846091556567, + "peak": 0.6519268155097961, + "segments": [ + { + "name": "band-pass-closed", + "mean": 0.1384043727085076, + "acRms": 0.11977089283112337, + "peak": 0.6519268155097961 + }, + { + "name": "band-pass-low-mid", + "mean": 0.12862729804504672, + "acRms": 0.05857439630378218, + "peak": 0.35244429111480713 + }, + { + "name": "band-pass-high-mid", + "mean": 0.12938821456987676, + "acRms": 0.02685028745197102, + "peak": 0.35210540890693665 + }, + { + "name": "band-pass-open", + "mean": 0.12953832648539296, + "acRms": 0.013194581412755613, + "peak": 0.30020609498023987 + } + ] + }, + "player": { + "mean": 0.017174183127040617, + "acRms": 0.0788383388522241, + "peak": 0.6699373126029968, + "segments": [ + { + "name": "band-pass-closed", + "mean": 0.05993469142361702, + "acRms": 0.13332206956386403, + "peak": 0.6699373126029968 + }, + { + "name": "band-pass-low-mid", + "mean": 0.007294064115855332, + "acRms": 0.06077462686587364, + "peak": 0.24617999792099 + }, + { + "name": "band-pass-high-mid", + "mean": 0.0012513660575286442, + "acRms": 0.02737932113335707, + "peak": 0.2332567572593689 + }, + { + "name": "band-pass-open", + "mean": 0.00021661091116147445, + "acRms": 0.01329679594370019, + "peak": 0.17338089644908905 + } + ] + } + } + }, + { + "id": "sid-filter-mode-resonance-sweep", + "outputs": { + "raw": { + "mean": -0.00930157732406718, + "acRms": 0.17986045978313175, + "peak": 0.6029611825942993, + "segments": [ + { + "name": "warmup-open", + "mean": -0.23784697385510248, + "acRms": 0.12076092023671156, + "peak": 0.46002060174942017 + }, + { + "name": "lp-res00-closed", + "mean": -0.21831572299667767, + "acRms": 0.06810863076454003, + "peak": 0.45662790536880493 + }, + { + "name": "lp-res00-low", + "mean": -0.2107031151898102, + "acRms": 0.07357675842095993, + "peak": 0.38423967361450195 + }, + { + "name": "lp-res00-mid", + "mean": -0.21650601165751546, + "acRms": 0.0951119439443895, + "peak": 0.4459287226200104 + }, + { + "name": "lp-res00-high", + "mean": -0.2272481613988955, + "acRms": 0.10793094850539124, + "peak": 0.4560789465904236 + }, + { + "name": "lp-res00-open", + "mean": -0.2253689513225734, + "acRms": 0.11308522592650673, + "peak": 0.46001720428466797 + }, + { + "name": "lp-res04-closed", + "mean": -0.2238791068334346, + "acRms": 0.06702061906105691, + "peak": 0.44737982749938965 + }, + { + "name": "lp-res04-low", + "mean": -0.22551742366049438, + "acRms": 0.07558399084237157, + "peak": 0.4260253310203552 + }, + { + "name": "lp-res04-mid", + "mean": -0.22342380319721794, + "acRms": 0.09967016880076966, + "peak": 0.4475485384464264 + }, + { + "name": "lp-res04-high", + "mean": -0.22345897557096478, + "acRms": 0.11092873354473758, + "peak": 0.4561486542224884 + }, + { + "name": "lp-res04-open", + "mean": -0.2121755417995385, + "acRms": 0.11474765368486749, + "peak": 0.45489323139190674 + }, + { + "name": "lp-res08-closed", + "mean": -0.2190422899240932, + "acRms": 0.07484814833433914, + "peak": 0.4508064389228821 + }, + { + "name": "lp-res08-low", + "mean": -0.21479896860069453, + "acRms": 0.09341153655626998, + "peak": 0.4466864764690399 + }, + { + "name": "lp-res08-mid", + "mean": -0.22174858969791053, + "acRms": 0.11425343167863503, + "peak": 0.45819148421287537 + }, + { + "name": "lp-res08-high", + "mean": -0.21596548991739029, + "acRms": 0.12051665795728526, + "peak": 0.46102192997932434 + }, + { + "name": "lp-res08-open", + "mean": -0.22843162274458942, + "acRms": 0.121186212140458, + "peak": 0.46220606565475464 + }, + { + "name": "lp-res12-closed", + "mean": -0.20967816556386526, + "acRms": 0.0863073214781748, + "peak": 0.43776383996009827 + }, + { + "name": "lp-res12-low", + "mean": -0.21432122513661764, + "acRms": 0.10755671633465222, + "peak": 0.4783633351325989 + }, + { + "name": "lp-res12-mid", + "mean": -0.22021467821856883, + "acRms": 0.1384442001967568, + "peak": 0.5084019303321838 + }, + { + "name": "lp-res12-high", + "mean": -0.21385583120877022, + "acRms": 0.13541267559064052, + "peak": 0.4971288740634918 + }, + { + "name": "lp-res12-open", + "mean": -0.2254918014010905, + "acRms": 0.12938880175560552, + "peak": 0.4759801924228668 + }, + { + "name": "lp-res15-closed", + "mean": -0.20489708162311016, + "acRms": 0.10203011366495954, + "peak": 0.4910593330860138 + }, + { + "name": "lp-res15-low", + "mean": -0.20669014967338056, + "acRms": 0.13483566393065782, + "peak": 0.5443249344825745 + }, + { + "name": "lp-res15-mid", + "mean": -0.20663084042340515, + "acRms": 0.18206422195526747, + "peak": 0.6029611825942993 + }, + { + "name": "lp-res15-high", + "mean": -0.21770139102352712, + "acRms": 0.16160361405409968, + "peak": 0.5903894305229187 + }, + { + "name": "lp-res15-open", + "mean": -0.21246381845262713, + "acRms": 0.1430669963667249, + "peak": 0.4945969879627228 + }, + { + "name": "bp-res00-closed", + "mean": 0.13962658131780092, + "acRms": 0.15379903820709637, + "peak": 0.5212051868438721 + }, + { + "name": "bp-res00-low", + "mean": 0.12951245200302866, + "acRms": 0.024699911717028355, + "peak": 0.19831794500350952 + }, + { + "name": "bp-res00-mid", + "mean": 0.12950320916457309, + "acRms": 0.024007967552160333, + "peak": 0.19862832129001617 + }, + { + "name": "bp-res00-high", + "mean": 0.12940701387770887, + "acRms": 0.019360636039184205, + "peak": 0.18735268712043762 + }, + { + "name": "bp-res00-open", + "mean": 0.12971367644560006, + "acRms": 0.015367966326018579, + "peak": 0.18363888561725616 + }, + { + "name": "bp-res04-closed", + "mean": 0.12997181956501055, + "acRms": 0.022901890515617727, + "peak": 0.20465734601020813 + }, + { + "name": "bp-res04-low", + "mean": 0.1295263483046761, + "acRms": 0.028595090081289742, + "peak": 0.2201651632785797 + }, + { + "name": "bp-res04-mid", + "mean": 0.12949304240626386, + "acRms": 0.027094715814044077, + "peak": 0.2163073718547821 + }, + { + "name": "bp-res04-high", + "mean": 0.12951647605126101, + "acRms": 0.021661066664181914, + "peak": 0.2018764466047287 + }, + { + "name": "bp-res04-open", + "mean": 0.12964255596355845, + "acRms": 0.01738833263579514, + "peak": 0.19953733682632446 + }, + { + "name": "bp-res08-closed", + "mean": 0.12920048467866663, + "acRms": 0.03149130661004767, + "peak": 0.2186935544013977 + }, + { + "name": "bp-res08-low", + "mean": 0.12949489060427166, + "acRms": 0.037994506354891364, + "peak": 0.242474764585495 + }, + { + "name": "bp-res08-mid", + "mean": 0.12908557966244796, + "acRms": 0.039828455077823226, + "peak": 0.2531009614467621 + }, + { + "name": "bp-res08-high", + "mean": 0.12926512857278188, + "acRms": 0.02778821688318326, + "peak": 0.22781305015087128 + }, + { + "name": "bp-res08-open", + "mean": 0.1293126759533253, + "acRms": 0.0196197930808509, + "peak": 0.20710265636444092 + }, + { + "name": "bp-res12-closed", + "mean": 0.12898627403291257, + "acRms": 0.04425583708109343, + "peak": 0.29710254073143005 + }, + { + "name": "bp-res12-low", + "mean": 0.12940922691159357, + "acRms": 0.054570823021434116, + "peak": 0.27703365683555603 + }, + { + "name": "bp-res12-mid", + "mean": 0.12855186123207304, + "acRms": 0.062288922075289056, + "peak": 0.3142721951007843 + }, + { + "name": "bp-res12-high", + "mean": 0.12942378002218094, + "acRms": 0.03640590614173925, + "peak": 0.27824196219444275 + }, + { + "name": "bp-res12-open", + "mean": 0.12932781669466445, + "acRms": 0.022607651671439966, + "peak": 0.22963345050811768 + }, + { + "name": "bp-res15-closed", + "mean": 0.12859592314729928, + "acRms": 0.05351727919296608, + "peak": 0.27606871724128723 + }, + { + "name": "bp-res15-low", + "mean": 0.1279898202569964, + "acRms": 0.07762758896598573, + "peak": 0.35055407881736755 + }, + { + "name": "bp-res15-mid", + "mean": 0.12711103330445642, + "acRms": 0.08957070949366974, + "peak": 0.3587515354156494 + }, + { + "name": "bp-res15-high", + "mean": 0.1287845980425522, + "acRms": 0.04620485907888172, + "peak": 0.3506345748901367 + }, + { + "name": "bp-res15-open", + "mean": 0.12904642468922703, + "acRms": 0.028910100041765183, + "peak": 0.25960734486579895 + }, + { + "name": "hp-res00-closed", + "mean": 0.056966240992830486, + "acRms": 0.13203652614043201, + "peak": 0.4273923337459564 + }, + { + "name": "hp-res00-low", + "mean": 0.07003468169953887, + "acRms": 0.08791096762382841, + "peak": 0.3848395347595215 + }, + { + "name": "hp-res00-mid", + "mean": 0.07104667335822039, + "acRms": 0.03652471292313181, + "peak": 0.3176570534706116 + }, + { + "name": "hp-res00-high", + "mean": 0.07131788084411204, + "acRms": 0.01614851516980348, + "peak": 0.17372143268585205 + }, + { + "name": "hp-res00-open", + "mean": 0.07162776838005003, + "acRms": 0.00894840640524695, + "peak": 0.12669189274311066 + }, + { + "name": "hp-res04-closed", + "mean": 0.06803772302644373, + "acRms": 0.1386967555970427, + "peak": 0.42949178814888 + }, + { + "name": "hp-res04-low", + "mean": 0.06981995928883862, + "acRms": 0.10177735252381928, + "peak": 0.39127448201179504 + }, + { + "name": "hp-res04-mid", + "mean": 0.07099148558897023, + "acRms": 0.0435969701661801, + "peak": 0.34368592500686646 + }, + { + "name": "hp-res04-high", + "mean": 0.07120894125220427, + "acRms": 0.020663725569893047, + "peak": 0.20886273682117462 + }, + { + "name": "hp-res04-open", + "mean": 0.07136478005623859, + "acRms": 0.010320097958243195, + "peak": 0.12026545405387878 + }, + { + "name": "hp-res08-closed", + "mean": 0.06788388919167056, + "acRms": 0.15082618285323576, + "peak": 0.4495895504951477 + }, + { + "name": "hp-res08-low", + "mean": 0.0686324535422121, + "acRms": 0.12327465602775287, + "peak": 0.4264175295829773 + }, + { + "name": "hp-res08-mid", + "mean": 0.0703970892566411, + "acRms": 0.06801930139445582, + "peak": 0.31494203209877014 + }, + { + "name": "hp-res08-high", + "mean": 0.0710289832953044, + "acRms": 0.027441500251407836, + "peak": 0.26742199063301086 + }, + { + "name": "hp-res08-open", + "mean": 0.07122580779606424, + "acRms": 0.014305646782320136, + "peak": 0.12287122011184692 + }, + { + "name": "hp-res12-closed", + "mean": 0.06832357657225303, + "acRms": 0.1764955720737506, + "peak": 0.4673401117324829 + }, + { + "name": "hp-res12-low", + "mean": 0.06777684322067457, + "acRms": 0.15159495999699868, + "peak": 0.4490926265716553 + }, + { + "name": "hp-res12-mid", + "mean": 0.06908753493835573, + "acRms": 0.1088644382401402, + "peak": 0.41042107343673706 + }, + { + "name": "hp-res12-high", + "mean": 0.0706858724811986, + "acRms": 0.03986862714237569, + "peak": 0.27855315804481506 + }, + { + "name": "hp-res12-open", + "mean": 0.07074700703000822, + "acRms": 0.023513894627338206, + "peak": 0.2506139874458313 + }, + { + "name": "hp-res15-closed", + "mean": 0.06686562879943381, + "acRms": 0.19828619336643277, + "peak": 0.5031586289405823 + }, + { + "name": "hp-res15-low", + "mean": 0.0671328226940078, + "acRms": 0.19698301299603008, + "peak": 0.5154279470443726 + }, + { + "name": "hp-res15-mid", + "mean": 0.06774637972243404, + "acRms": 0.16265845178643612, + "peak": 0.5028011202812195 + }, + { + "name": "hp-res15-high", + "mean": 0.06997541226520398, + "acRms": 0.04984025229204209, + "peak": 0.2351301908493042 + }, + { + "name": "hp-res15-open", + "mean": 0.07008279907393747, + "acRms": 0.03233951066895211, + "peak": 0.17989780008792877 + } + ] + }, + "player": { + "mean": 0.001934540594694629, + "acRms": 0.11004174968399436, + "peak": 0.7529124617576599, + "segments": [ + { + "name": "warmup-open", + "mean": -0.19317209092274779, + "acRms": 0.1328779978281837, + "peak": 0.45063820481300354 + }, + { + "name": "lp-res00-closed", + "mean": -0.10587692060985823, + "acRms": 0.07015955957974772, + "peak": 0.3781842291355133 + }, + { + "name": "lp-res00-low", + "mean": -0.05759188598875653, + "acRms": 0.07669304613312715, + "peak": 0.23385439813137054 + }, + { + "name": "lp-res00-mid", + "mean": -0.03917769027480063, + "acRms": 0.0991525568054398, + "peak": 0.2799208164215088 + }, + { + "name": "lp-res00-high", + "mean": -0.03407842405553083, + "acRms": 0.11167143602305898, + "peak": 0.2774783670902252 + }, + { + "name": "lp-res00-open", + "mean": -0.020092638635820333, + "acRms": 0.11670171382867803, + "peak": 0.26266148686408997 + }, + { + "name": "lp-res04-closed", + "mean": -0.008579319554958274, + "acRms": 0.06973361483031196, + "peak": 0.2446317821741104 + }, + { + "name": "lp-res04-low", + "mean": -0.00783562926909806, + "acRms": 0.07853632050019728, + "peak": 0.21741755306720734 + }, + { + "name": "lp-res04-mid", + "mean": -0.003568242408417809, + "acRms": 0.10332769116131219, + "peak": 0.25899210572242737 + }, + { + "name": "lp-res04-high", + "mean": -0.001140054628456255, + "acRms": 0.11472290234446929, + "peak": 0.265863299369812 + }, + { + "name": "lp-res04-open", + "mean": 0.00813395201421814, + "acRms": 0.11855751348209576, + "peak": 0.26387113332748413 + }, + { + "name": "lp-res08-closed", + "mean": -0.0015926242712465764, + "acRms": 0.07761783961425063, + "peak": 0.24954338371753693 + }, + { + "name": "lp-res08-low", + "mean": 0.0021912845891189172, + "acRms": 0.09709928407676066, + "peak": 0.23828043043613434 + }, + { + "name": "lp-res08-mid", + "mean": -0.0034264752714749293, + "acRms": 0.11827934207640425, + "peak": 0.271859735250473 + }, + { + "name": "lp-res08-high", + "mean": 0.0020567160265924952, + "acRms": 0.12442536912282255, + "peak": 0.26572543382644653 + }, + { + "name": "lp-res08-open", + "mean": -0.0076458960801839265, + "acRms": 0.1250849161169128, + "peak": 0.26420533657073975 + }, + { + "name": "lp-res12-closed", + "mean": 0.009175020524666403, + "acRms": 0.08968620402103097, + "peak": 0.2606659531593323 + }, + { + "name": "lp-res12-low", + "mean": 0.002733770732501423, + "acRms": 0.11172248762133787, + "peak": 0.3125600516796112 + }, + { + "name": "lp-res12-mid", + "mean": -0.004115962798899394, + "acRms": 0.14273207025085857, + "peak": 0.3331715762615204 + }, + { + "name": "lp-res12-high", + "mean": 0.00478592639305033, + "acRms": 0.13941762727036713, + "peak": 0.34156399965286255 + }, + { + "name": "lp-res12-open", + "mean": -0.006787493026046023, + "acRms": 0.13341652922838956, + "peak": 0.2885589301586151 + }, + { + "name": "lp-res15-closed", + "mean": 0.01150133239439381, + "acRms": 0.10600321256792897, + "peak": 0.28975075483322144 + }, + { + "name": "lp-res15-low", + "mean": 0.006114118008438001, + "acRms": 0.14000806129918814, + "peak": 0.3844009041786194 + }, + { + "name": "lp-res15-mid", + "mean": 0.0038460233779864616, + "acRms": 0.18740797271181303, + "peak": 0.4701629877090454 + }, + { + "name": "lp-res15-high", + "mean": -0.006252282512928057, + "acRms": 0.16635988342120125, + "peak": 0.44099268317222595 + }, + { + "name": "lp-res15-open", + "mean": -0.0012862247690871224, + "acRms": 0.14731779328168865, + "peak": 0.3602936267852783 + }, + { + "name": "bp-res00-closed", + "mean": 0.2902222518329735, + "acRms": 0.16311636675545876, + "peak": 0.7529124617576599 + }, + { + "name": "bp-res00-low", + "mean": 0.1690209358309706, + "acRms": 0.03493699354275893, + "peak": 0.26113367080688477 + }, + { + "name": "bp-res00-mid", + "mean": 0.1035443825162171, + "acRms": 0.028568647883824093, + "peak": 0.18977025151252747 + }, + { + "name": "bp-res00-high", + "mean": 0.06336118464345317, + "acRms": 0.021626902456829068, + "peak": 0.13389094173908234 + }, + { + "name": "bp-res00-open", + "mean": 0.03904112091263919, + "acRms": 0.016639407492128694, + "peak": 0.10384699702262878 + }, + { + "name": "bp-res04-closed", + "mean": 0.024089335534386514, + "acRms": 0.024053005513689975, + "peak": 0.10637576133012772 + }, + { + "name": "bp-res04-low", + "mean": 0.014418749599150812, + "acRms": 0.02960379215407153, + "peak": 0.10433653742074966 + }, + { + "name": "bp-res04-mid", + "mean": 0.008811518872184365, + "acRms": 0.027802736455105937, + "peak": 0.09517970681190491 + }, + { + "name": "bp-res04-high", + "mean": 0.0054426922713907465, + "acRms": 0.02212309268714848, + "peak": 0.08148440718650818 + }, + { + "name": "bp-res04-open", + "mean": 0.0034145075914412677, + "acRms": 0.017722542333944522, + "peak": 0.07434216886758804 + }, + { + "name": "bp-res08-closed", + "mean": 0.0017247320386448767, + "acRms": 0.03269702468326308, + "peak": 0.0944954976439476 + }, + { + "name": "bp-res08-low", + "mean": 0.001305496626907004, + "acRms": 0.03928093024164537, + "peak": 0.11713029444217682 + }, + { + "name": "bp-res08-mid", + "mean": 0.00047501364672939415, + "acRms": 0.04072713230368528, + "peak": 0.1274120956659317 + }, + { + "name": "bp-res08-high", + "mean": 0.00045105480959368234, + "acRms": 0.0283018888542713, + "peak": 0.11010225117206573 + }, + { + "name": "bp-res08-open", + "mean": 0.0003111221329371953, + "acRms": 0.019930956423017787, + "peak": 0.09245838969945908 + }, + { + "name": "bp-res12-closed", + "mean": -0.0001320554185761343, + "acRms": 0.045973555888087436, + "peak": 0.17455428838729858 + }, + { + "name": "bp-res12-low", + "mean": 0.00025612659516159256, + "acRms": 0.05648655311549015, + "peak": 0.16477499902248383 + }, + { + "name": "bp-res12-mid", + "mean": -0.0004804561375465255, + "acRms": 0.0636257832868098, + "peak": 0.19816899299621582 + }, + { + "name": "bp-res12-high", + "mean": 0.00044517245734516405, + "acRms": 0.036992234533742625, + "peak": 0.15878914296627045 + }, + { + "name": "bp-res12-open", + "mean": 0.00019479407219732774, + "acRms": 0.022807289993614945, + "peak": 0.1007782518863678 + }, + { + "name": "bp-res15-closed", + "mean": -0.0003408421061135098, + "acRms": 0.05560668532117644, + "peak": 0.17894239723682404 + }, + { + "name": "bp-res15-low", + "mean": -0.0008546559482763567, + "acRms": 0.08042008473841256, + "peak": 0.23003077507019043 + }, + { + "name": "bp-res15-mid", + "mean": -0.0012287652348681555, + "acRms": 0.09138812241310376, + "peak": 0.25745466351509094 + }, + { + "name": "bp-res15-high", + "mean": 0.0005942754182496814, + "acRms": 0.04689485752590376, + "peak": 0.23770369589328766 + }, + { + "name": "bp-res15-open", + "mean": 0.0006073165065325724, + "acRms": 0.02902808393417407, + "peak": 0.1339724361896515 + }, + { + "name": "hp-res00-closed", + "mean": -0.05749356381930271, + "acRms": 0.1366028898686643, + "peak": 0.47361716628074646 + }, + { + "name": "hp-res00-low", + "mean": -0.02593824248396509, + "acRms": 0.08980713549810272, + "peak": 0.3390732705593109 + }, + { + "name": "hp-res00-mid", + "mean": -0.01499005043656641, + "acRms": 0.037135071412435466, + "peak": 0.2273629903793335 + }, + { + "name": "hp-res00-high", + "mean": -0.008987681501517228, + "acRms": 0.016308793062261923, + "peak": 0.11708145588636398 + }, + { + "name": "hp-res00-open", + "mean": -0.00525487186342575, + "acRms": 0.008987284853811436, + "peak": 0.051507819443941116 + }, + { + "name": "hp-res04-closed", + "mean": -0.006172869635704122, + "acRms": 0.14235920283118736, + "peak": 0.36702385544776917 + }, + { + "name": "hp-res04-low", + "mean": -0.002324965151723922, + "acRms": 0.1039102958147609, + "peak": 0.32677099108695984 }, { - "name": "low-pass-open", - "mean": -0.0016904647116360947, - "acRms": 0.24105152523686452, - "peak": 0.39723315834999084 - } - ] - } - } - }, - { - "id": "sidtest5-07-high-pass-filter", - "outputs": { - "raw": { - "mean": 0.07294690983522831, - "acRms": 0.11918503519125756, - "peak": 0.8000343441963196, - "segments": [ + "name": "hp-res04-mid", + "mean": -0.00045103390354041847, + "acRms": 0.04422268378446349, + "peak": 0.27215248346328735 + }, { - "name": "high-pass-closed", - "mean": 0.07811470860404879, - "acRms": 0.20836973348779467, - "peak": 0.8000343441963196 + "name": "hp-res04-high", + "mean": -7.016955307740997E-05, + "acRms": 0.0207832703671684, + "peak": 0.13797713816165924 }, { - "name": "high-pass-low-mid", - "mean": 0.07075090381362088, - "acRms": 0.10675823765338222, - "peak": 0.4957949221134186 + "name": "hp-res04-open", + "mean": 4.874930646157812E-05, + "acRms": 0.010310861187294951, + "peak": 0.05146174132823944 }, { - "name": "high-pass-high-mid", - "mean": 0.07136095967263524, - "acRms": 0.04160726981519128, - "peak": 0.4697941839694977 + "name": "hp-res08-closed", + "mean": -0.0027814083609770347, + "acRms": 0.1552132903376593, + "peak": 0.4358021318912506 }, { - "name": "high-pass-open", - "mean": 0.07156106725060833, - "acRms": 0.015423813662976264, - "peak": 0.27001622319221497 - } - ] - }, - "player": { - "mean": 0.00948530467499449, - "acRms": 0.12513269819576908, - "peak": 0.8266730904579163, - "segments": [ + "name": "hp-res08-low", + "mean": -0.0011239865580238175, + "acRms": 0.12617523531729585, + "peak": 0.3665294051170349 + }, { - "name": "high-pass-closed", - "mean": 0.03221610545883414, - "acRms": 0.2186383936777334, - "peak": 0.8266730904579163 + "name": "hp-res08-mid", + "mean": 0.0007867146481203236, + "acRms": 0.0690364861549099, + "peak": 0.25457191467285156 }, { - "name": "high-pass-low-mid", - "mean": 0.0045420620839039605, - "acRms": 0.11022495514361196, - "peak": 0.4510822892189026 + "name": "hp-res08-high", + "mean": 0.0010104454700137606, + "acRms": 0.027538161053456363, + "peak": 0.21727079153060913 }, { - "name": "high-pass-high-mid", - "mean": 0.0009643335188815488, - "acRms": 0.04185608480391158, - "peak": 0.40951111912727356 + "name": "hp-res08-open", + "mean": 0.0007493452015744929, + "acRms": 0.01419621237569026, + "peak": 0.06213479861617088 }, { - "name": "high-pass-open", - "mean": 0.00021871763835831104, - "acRms": 0.015131005689331265, - "peak": 0.22152996063232422 - } - ] - } - } - }, - { - "id": "sidtest5-08-band-pass-filter", - "outputs": { - "raw": { - "mean": 0.13148955295220602, - "acRms": 0.06843846091556567, - "peak": 0.6519268155097961, - "segments": [ + "name": "hp-res12-closed", + "mean": -0.001892000968827132, + "acRms": 0.18196082893348386, + "peak": 0.490042507648468 + }, { - "name": "band-pass-closed", - "mean": 0.1384043727085076, - "acRms": 0.11977089283112337, - "peak": 0.6519268155097961 + "name": "hp-res12-low", + "mean": -0.001641640844036374, + "acRms": 0.15568413674093415, + "peak": 0.45688676834106445 }, { - "name": "band-pass-low-mid", - "mean": 0.12862729804504672, - "acRms": 0.05857439630378218, - "peak": 0.35244429111480713 + "name": "hp-res12-mid", + "mean": 7.183915729961478E-05, + "acRms": 0.11060021688803764, + "peak": 0.40079787373542786 }, { - "name": "band-pass-high-mid", - "mean": 0.12938821456987676, - "acRms": 0.02685028745197102, - "peak": 0.35210540890693665 + "name": "hp-res12-high", + "mean": 0.001323210830694974, + "acRms": 0.03983909704147699, + "peak": 0.21656057238578796 }, { - "name": "band-pass-open", - "mean": 0.12953832648539296, - "acRms": 0.013194581412755613, - "peak": 0.30020609498023987 - } - ] - }, - "player": { - "mean": 0.017174183127040617, - "acRms": 0.0788383388522241, - "peak": 0.6699373126029968, - "segments": [ + "name": "hp-res12-open", + "mean": 0.0008694313722072719, + "acRms": 0.02314291284678295, + "peak": 0.23412491381168365 + }, { - "name": "band-pass-closed", - "mean": 0.05993469142361702, - "acRms": 0.13332206956386403, - "peak": 0.6699373126029968 + "name": "hp-res15-closed", + "mean": -0.0026128405934062984, + "acRms": 0.20472133262667766, + "peak": 0.5219555497169495 }, { - "name": "band-pass-low-mid", - "mean": 0.007294064115855332, - "acRms": 0.06077462686587364, - "peak": 0.24617999792099 + "name": "hp-res15-low", + "mean": -0.0014605758361439156, + "acRms": 0.2025353340984737, + "peak": 0.5659340620040894 }, { - "name": "band-pass-high-mid", - "mean": 0.0012513660575286442, - "acRms": 0.02737932113335707, - "peak": 0.2332567572593689 + "name": "hp-res15-mid", + "mean": -0.00031384748904959755, + "acRms": 0.16529412360280624, + "peak": 0.4977010190486908 }, { - "name": "band-pass-open", - "mean": 0.00021661091116147445, - "acRms": 0.01329679594370019, - "peak": 0.17338089644908905 + "name": "hp-res15-high", + "mean": 0.001603184718967062, + "acRms": 0.0496100579657807, + "peak": 0.16312538087368011 + }, + { + "name": "hp-res15-open", + "mean": 0.0011120577087492872, + "acRms": 0.03137442604987544, + "peak": 0.12045009434223175 } ] } @@ -1900,6 +2970,308 @@ } } }, + { + "id": "sid-noise-edge-cases", + "outputs": { + "raw": { + "mean": 0.1454758429219989, + "acRms": 0.08327396676844842, + "peak": 0.49455294013023376, + "segments": [ + { + "name": "noise-reference", + "mean": 0.14577457908666838, + "acRms": 0.13932596470503616, + "peak": 0.49455294013023376 + }, + { + "name": "noise-test-reset", + "mean": 0.346143503943055, + "acRms": 0.08803383796124335, + "peak": 0.37475502490997314 + }, + { + "name": "noise-test-release", + "mean": 0.14403274568243415, + "acRms": 0.14159001374249688, + "peak": 0.37475502490997314 + }, + { + "name": "noise-triangle", + "mean": 0.13043643967772217, + "acRms": 0.03164660768130987, + "peak": 0.3676896393299103 + }, + { + "name": "noise-saw", + "mean": 0.1314508318901062, + "acRms": 0, + "peak": 0.1314508318901062 + }, + { + "name": "noise-pulse", + "mean": 0.1314508318901062, + "acRms": 0, + "peak": 0.1314508318901062 + }, + { + "name": "noise-triangle-saw", + "mean": 0.1314508318901062, + "acRms": 0, + "peak": 0.1314508318901062 + }, + { + "name": "noise-triangle-pulse", + "mean": 0.1314508318901062, + "acRms": 0, + "peak": 0.1314508318901062 + }, + { + "name": "noise-saw-pulse", + "mean": 0.1314508318901062, + "acRms": 0, + "peak": 0.1314508318901062 + }, + { + "name": "noise-all-waveforms", + "mean": 0.1314508318901062, + "acRms": 0, + "peak": 0.1314508318901062 + } + ] + }, + "player": { + "mean": 0.011004189075409773, + "acRms": 0.08899616682354651, + "peak": 0.5070149898529053, + "segments": [ + { + "name": "noise-reference", + "mean": 0.07963016218324377, + "acRms": 0.15286098357332661, + "peak": 0.5070149898529053 + }, + { + "name": "noise-test-reset", + "mean": 0.19353675386641953, + "acRms": 0.08003214874906042, + "peak": 0.2798336446285248 + }, + { + "name": "noise-test-release", + "mean": -0.048746886614635325, + "acRms": 0.1446755173917593, + "peak": 0.3748610019683838 + }, + { + "name": "noise-triangle", + "mean": -0.017093467266370985, + "acRms": 0.03365279579318584, + "peak": 0.2981013357639313 + }, + { + "name": "noise-saw", + "mean": -0.0043426416033071295, + "acRms": 0.0016156659380120876, + "peak": 0.0077806138433516026 + }, + { + "name": "noise-pulse", + "mean": -0.0011759340920965164, + "acRms": 0.0004373003375283437, + "peak": 0.002106610918417573 + }, + { + "name": "noise-triangle-saw", + "mean": -0.0003196117908828455, + "acRms": 0.00011780796311483224, + "peak": 0.0005709129036404192 + }, + { + "name": "noise-triangle-pulse", + "mean": -8.84164555718788E-05, + "acRms": 3.298442985153841E-05, + "peak": 0.00015565846115350723 + }, + { + "name": "noise-saw-pulse", + "mean": -4.55230510851834E-05, + "acRms": 0, + "peak": 4.55230510851834E-05 + }, + { + "name": "noise-all-waveforms", + "mean": -4.55230510851834E-05, + "acRms": 0, + "peak": 4.55230510851834E-05 + } + ] + } + } + }, + { + "id": "sid-combined-wave-edge-cases", + "outputs": { + "raw": { + "mean": -0.03955954971080112, + "acRms": 0.1453725972511764, + "peak": 0.49571022391319275, + "segments": [ + { + "name": "plain-triangle-saw", + "mean": 0.012939911530876931, + "acRms": 0.1019148335032195, + "peak": 0.49571022391319275 + }, + { + "name": "plain-triangle-pulse", + "mean": -0.2591232270672208, + "acRms": 0.09633699493383317, + "peak": 0.36468616127967834 + }, + { + "name": "plain-saw-pulse", + "mean": 0.03375720554565002, + "acRms": 0.07846453698205595, + "peak": 0.36422988772392273 + }, + { + "name": "plain-triangle-saw-pulse", + "mean": 0.06579189748202528, + "acRms": 0.0037579509891062786, + "peak": 0.07026486843824387 + }, + { + "name": "ring-triangle-saw", + "mean": -0.009577272689921302, + "acRms": 0.04278441197549018, + "peak": 0.127110555768013 + }, + { + "name": "ring-triangle-pulse", + "mean": -0.26369389479384986, + "acRms": 0.08341400351371306, + "peak": 0.34542569518089294 + }, + { + "name": "ring-saw-pulse", + "mean": 0.03716581378588065, + "acRms": 0.07013674461944512, + "peak": 0.3445316553115845 + }, + { + "name": "ring-triangle-saw-pulse", + "mean": 0.06591930071590468, + "acRms": 0.0035474916424374724, + "peak": 0.07022871822118759 + }, + { + "name": "sync-ring-triangle-saw", + "mean": -0.007299775004312418, + "acRms": 0.04528773579235709, + "peak": 0.12606346607208252 + }, + { + "name": "sync-ring-triangle-pulse", + "mean": -0.25708083457392755, + "acRms": 0.081227879696443, + "peak": 0.3447728455066681 + }, + { + "name": "sync-ring-saw-pulse", + "mean": 0.040418980653097, + "acRms": 0.06232100598960774, + "peak": 0.34473657608032227 + }, + { + "name": "sync-ring-triangle-saw-pulse", + "mean": 0.06606906003968713, + "acRms": 0.0032926194344138414, + "peak": 0.07012838125228882 + } + ] + }, + "player": { + "mean": 0.0028938754018666252, + "acRms": 0.12869768906913145, + "peak": 0.5089917778968811, + "segments": [ + { + "name": "plain-triangle-saw", + "mean": -0.0001470847713335388, + "acRms": 0.10543691553175126, + "peak": 0.5089917778968811 + }, + { + "name": "plain-triangle-pulse", + "mean": -0.1547711352716923, + "acRms": 0.10109218807038783, + "peak": 0.3679976463317871 + }, + { + "name": "plain-saw-pulse", + "mean": 0.13802072041450325, + "acRms": 0.07699586728701063, + "peak": 0.26197847723960876 + }, + { + "name": "plain-triangle-saw-pulse", + "mean": 0.05073546679632272, + "acRms": 0.017908864243644688, + "peak": 0.0876055434346199 + }, + { + "name": "ring-triangle-saw", + "mean": -0.03145909149943691, + "acRms": 0.04385595101445035, + "peak": 0.12694716453552246 + }, + { + "name": "ring-triangle-pulse", + "mean": -0.15875452182470534, + "acRms": 0.09064501284919818, + "peak": 0.35216638445854187 + }, + { + "name": "ring-saw-pulse", + "mean": 0.14006980784286802, + "acRms": 0.0723729224549231, + "peak": 0.26335713267326355 + }, + { + "name": "ring-triangle-saw-pulse", + "mean": 0.050325664425084446, + "acRms": 0.017978749380343283, + "peak": 0.08807207643985748 + }, + { + "name": "sync-ring-triangle-saw", + "mean": -0.02999793643067813, + "acRms": 0.04678045358393833, + "peak": 0.12360101193189621 + }, + { + "name": "sync-ring-triangle-pulse", + "mean": -0.15508113257696518, + "acRms": 0.09166790812342834, + "peak": 0.3599284589290619 + }, + { + "name": "sync-ring-saw-pulse", + "mean": 0.13715685362040705, + "acRms": 0.06798531931416088, + "peak": 0.256767600774765 + }, + { + "name": "sync-ring-triangle-saw-pulse", + "mean": 0.04862216282878459, + "acRms": 0.01754801450009332, + "peak": 0.08633670210838318 + } + ] + } + } + }, { "id": "sidtest5-12-master-volume-modulation", "outputs": { diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-adsr-gate-trace.sid b/CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-adsr-gate-trace.sid new file mode 100644 index 0000000000000000000000000000000000000000..797c4fd4bb520a4f971a6f60363342ba139dff00 GIT binary patch literal 332 zcmWFt_Hd074@MMj!+NN0;Cr1$V~~SA~!uM`u@%5DIY4FDOVY^36{{5k+u} z42;YWJS;2`kk}%Ll?-zkuACD1wUVKgX{BK=! z(Jh^dWO8(Ha1zH(=I9MV6UgVAq*F9=FmQ*UokB@lD?4iMH4j~GyXmR3F5h=HOAA+o zU#M&_9QLo&hE)T^S?qDDTL-$_Mg+@`>D0 ozLC$AUu;d0HvEDFkE7?&tLSF*B-)SOM7N{e=w^FCv3jFhrjn$l>u?M~C(OWQKW7^^YHYD($Nu1YB} z7&}HpN-4Ec`ld8>N+~5$M3m@{qWa#4diHn@-NX4j=W{QgbNn9XwzfCc8%bk#p~0iZ zNbumNp)u2uZYynSsZW=-rQ2JYQ_Xeh()P}Dx;6K;Q8&1G^QNJu!HwcdKUGy$RqJ+| zv2*KDKQzu8(SyPtp>ZG?8i5gowLv1{Dj9{dg1p>nMihPtW=3H)$dAHA;6>qrz>j*y zk}#(7G8;_f5d$V>68lWdCeE6eOI$QDpSW+rB;J~Ei0>wRVxEO!qTE6WvC2Xj(Pp8X z=(n(t7`CvOIBj7malt|*ao55!;fr)$#KT2m)Wap>s)sAYl!t4?M-MlMUmk7| zj*mOU5+C=7Iv*gqd^{w!_;^el^6`Wi@$rnf?BfOT$j2+Hj8EfA{UbI}u4;i=?haQqeN0X!(@c_@u5yQr9A> zXqi;BOisopbuE&*7D+|Rq@rc=YkX4IBB^VURJ2SgS|$^@WChMV6FlDZa2Ma!h3 zWil0?)U` zYmrp6Oe$I?6S=2siQhho7D@frBB^MZRJ2T{;*+`-NnMMiqGeLiGMSA}>RKdqEs~0s sNkz-#WPDQBBB^VURJ2SgS|-Jam7&$<{u^{HlDZa2Ma$%L{UO=&2bM+`6aWAK literal 0 HcmV?d00001 diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-noise-edge-cases.sid b/CopperMod.Sid.Tests/ConformanceFixtures/binaries/sid-noise-edge-cases.sid new file mode 100644 index 0000000000000000000000000000000000000000..934bd5b9310c694704e7cee72b0a45e8f0e9dcb4 GIT binary patch literal 332 zcmWFt_Hd074@MMj!+NKY!0)R|QuWcUJ{x$6(iBkQg#>&MzoPE%MDzK^8<} z8yOgxA#pL8A|Rnf5-S;-Ql^s(7npr&1x|7RsaAoLJV2^f;G_tUnksNo21v~nIH>}pmI|EI0a9xP zPMQFztpX=)fYe@rlP*B&sDRH|ft8KDELT8uCzOWqCqnrPq4Ys0y%9=ZgwhY8^hXH& Hj41&CE=y(n literal 0 HcmV?d00001 diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/manifest.json b/CopperMod.Sid.Tests/ConformanceFixtures/manifest.json index 999cd0c..0b592c9 100644 --- a/CopperMod.Sid.Tests/ConformanceFixtures/manifest.json +++ b/CopperMod.Sid.Tests/ConformanceFixtures/manifest.json @@ -26,6 +26,14 @@ "binary": "binaries/sidtest5-03-adsr-piano.sid", "sha256": "3048208fa7d7d0972c0a320e75e661e563b56becea80286292a28e3dadbddfaf" }, + { + "id": "sid-adsr-gate-trace", + "category": 301, + "name": "ADSR GATE TRACE", + "spec": "specs/sid-adsr-gate-trace.json", + "binary": "binaries/sid-adsr-gate-trace.sid", + "sha256": "22ebd49c1db76c3ebc1a683eda71897f3712fa6fa8b12401416d1e080ea23fbc" + }, { "id": "sidtest5-04-ring-modulation", "category": 4, @@ -66,6 +74,14 @@ "binary": "binaries/sidtest5-08-band-pass-filter.sid", "sha256": "d824999cdc72bca360ad26ebe0f344c1bd6d53d433a1544ba83d5411d561c376" }, + { + "id": "sid-filter-mode-resonance-sweep", + "category": 601, + "name": "FILTER MODE RESONANCE SWEEP", + "spec": "specs/sid-filter-mode-resonance-sweep.json", + "binary": "binaries/sid-filter-mode-resonance-sweep.sid", + "sha256": "765a076a20af09d87271184af31ca762677ed9893da6a258ee34af5998bfb9df" + }, { "id": "sidtest5-09-frequency-modulation", "category": 9, @@ -98,6 +114,22 @@ "binary": "binaries/sid-pulse-width-edge-cases.sid", "sha256": "b3c56d2e8d3e6f66d762659789ef22ef1e285d009e0c1bd7a60c06cfabb2e084" }, + { + "id": "sid-noise-edge-cases", + "category": 901, + "name": "NOISE EDGE CASES", + "spec": "specs/sid-noise-edge-cases.json", + "binary": "binaries/sid-noise-edge-cases.sid", + "sha256": "f6ac7d1f64e36050cc8d05b4817791ae85bc0b4c963adbad0d94d021a96da7cf" + }, + { + "id": "sid-combined-wave-edge-cases", + "category": 902, + "name": "COMBINED WAVE EDGE CASES", + "spec": "specs/sid-combined-wave-edge-cases.json", + "binary": "binaries/sid-combined-wave-edge-cases.sid", + "sha256": "fdcf01dd60d9b1b8bf3ec33282e33393ac685b13b1066420f8802653de6cc4b9" + }, { "id": "sidtest5-12-master-volume-modulation", "category": 12, diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-adsr-gate-trace.json b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-adsr-gate-trace.json new file mode 100644 index 0000000..c589862 --- /dev/null +++ b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-adsr-gate-trace.json @@ -0,0 +1,37 @@ +{ + "schema": 1, + "id": "sid-adsr-gate-trace", + "sidtestCategory": 301, + "name": "ADSR GATE TRACE", + "format": "psid", + "clock": "pal", + "chipModel": "mos6581", + "sampleRate": 48000, + "segmentRate": 50, + "seconds": 0.9, + "binary": "binaries/sid-adsr-gate-trace.sid", + "tags": [ "stress", "adsr", "piano", "trace" ], + "targetLayer": "digital", + "referenceAuthority": "sidplayfp", + "commonWrites": [ + { "address": "$d400", "value": "$31" }, + { "address": "$d401", "value": "$1c" }, + { "address": "$d402", "value": "$00" }, + { "address": "$d403", "value": "$08" }, + { "address": "$d405", "value": "$0d" }, + { "address": "$d406", "value": "$08" }, + { "address": "$d418", "value": "$0f" } + ], + "segments": [ + { "name": "gate-pulse-1-on", "frames": 2, "writes": [ { "address": "$d404", "value": "$21" } ] }, + { "name": "gate-pulse-1-release", "frames": 6, "writes": [ { "address": "$d404", "value": "$20" } ] }, + { "name": "gate-pulse-2-on", "frames": 2, "writes": [ { "address": "$d404", "value": "$21" } ] }, + { "name": "gate-pulse-2-release", "frames": 6, "writes": [ { "address": "$d404", "value": "$20" } ] }, + { "name": "gate-pulse-3-on", "frames": 2, "writes": [ { "address": "$d404", "value": "$21" } ] }, + { "name": "gate-pulse-3-release", "frames": 6, "writes": [ { "address": "$d404", "value": "$20" } ] }, + { "name": "gate-pulse-4-on", "frames": 2, "writes": [ { "address": "$d404", "value": "$21" } ] }, + { "name": "gate-pulse-4-release", "frames": 6, "writes": [ { "address": "$d404", "value": "$20" } ] }, + { "name": "gate-pulse-5-on", "frames": 2, "writes": [ { "address": "$d404", "value": "$21" } ] }, + { "name": "gate-final-tail", "frames": 11, "writes": [ { "address": "$d404", "value": "$20" } ] } + ] +} diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-combined-wave-edge-cases.json b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-combined-wave-edge-cases.json new file mode 100644 index 0000000..2244597 --- /dev/null +++ b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-combined-wave-edge-cases.json @@ -0,0 +1,42 @@ +{ + "schema": 1, + "id": "sid-combined-wave-edge-cases", + "sidtestCategory": 902, + "name": "COMBINED WAVE EDGE CASES", + "format": "psid", + "clock": "pal", + "chipModel": "mos6581", + "sampleRate": 48000, + "segmentRate": 50, + "seconds": 1.92, + "binary": "binaries/sid-combined-wave-edge-cases.sid", + "tags": [ "stress", "combined-wave", "ring", "sync", "edge" ], + "targetLayer": "mixed", + "referenceAuthority": "sidplayfp", + "commonWrites": [ + { "address": "$d400", "value": "$00" }, + { "address": "$d401", "value": "$24" }, + { "address": "$d402", "value": "$00" }, + { "address": "$d403", "value": "$08" }, + { "address": "$d405", "value": "$00" }, + { "address": "$d406", "value": "$f0" }, + { "address": "$d40e", "value": "$00" }, + { "address": "$d40f", "value": "$16" }, + { "address": "$d412", "value": "$20" }, + { "address": "$d418", "value": "$0f" } + ], + "segments": [ + { "name": "plain-triangle-saw", "frames": 8, "writes": [ { "address": "$d404", "value": "$31" } ] }, + { "name": "plain-triangle-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$51" } ] }, + { "name": "plain-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$61" } ] }, + { "name": "plain-triangle-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$71" } ] }, + { "name": "ring-triangle-saw", "frames": 8, "writes": [ { "address": "$d404", "value": "$35" } ] }, + { "name": "ring-triangle-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$55" } ] }, + { "name": "ring-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$65" } ] }, + { "name": "ring-triangle-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$75" } ] }, + { "name": "sync-ring-triangle-saw", "frames": 8, "writes": [ { "address": "$d404", "value": "$37" } ] }, + { "name": "sync-ring-triangle-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$57" } ] }, + { "name": "sync-ring-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$67" } ] }, + { "name": "sync-ring-triangle-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$77" } ] } + ] +} diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-filter-mode-resonance-sweep.json b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-filter-mode-resonance-sweep.json new file mode 100644 index 0000000..e66845a --- /dev/null +++ b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-filter-mode-resonance-sweep.json @@ -0,0 +1,105 @@ +{ + "schema": 1, + "id": "sid-filter-mode-resonance-sweep", + "sidtestCategory": 601, + "name": "FILTER MODE RESONANCE SWEEP", + "format": "psid", + "clock": "pal", + "chipModel": "mos6581", + "sampleRate": 48000, + "segmentRate": 50, + "seconds": 4.56, + "binary": "binaries/sid-filter-mode-resonance-sweep.sid", + "tags": [ "stress", "filter", "resonance", "sweep" ], + "targetLayer": "filter", + "referenceAuthority": "sidplayfp", + "commonWrites": [ + { "address": "$d400", "value": "$00" }, + { "address": "$d401", "value": "$40" }, + { "address": "$d402", "value": "$00" }, + { "address": "$d403", "value": "$08" }, + { "address": "$d405", "value": "$00" }, + { "address": "$d406", "value": "$f0" }, + { "address": "$d404", "value": "$81" }, + { "address": "$d417", "value": "$01" }, + { "address": "$d418", "value": "$1f" } + ], + "segments": [ + { "name": "warmup-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res00-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res00-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res00-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res00-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res00-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res04-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res04-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res04-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res04-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res04-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res08-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res08-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res08-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res08-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res08-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res12-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res12-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res12-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res12-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res12-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res15-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res15-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res15-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res15-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "lp-res15-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$1f" } ] }, + { "name": "bp-res00-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res00-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res00-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res00-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res00-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res04-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res04-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res04-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res04-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res04-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res08-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res08-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res08-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res08-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res08-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res12-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res12-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res12-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res12-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res12-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res15-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res15-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res15-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res15-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "bp-res15-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$2f" } ] }, + { "name": "hp-res00-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res00-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res00-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res00-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res00-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$01" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res04-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res04-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res04-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res04-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res04-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$41" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res08-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res08-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res08-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res08-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res08-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$81" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res12-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res12-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res12-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res12-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res12-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$c1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res15-closed", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$10" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res15-low", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$40" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res15-mid", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$80" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res15-high", "frames": 3, "writes": [ { "address": "$d415", "value": "$00" }, { "address": "$d416", "value": "$c0" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$4f" } ] }, + { "name": "hp-res15-open", "frames": 3, "writes": [ { "address": "$d415", "value": "$07" }, { "address": "$d416", "value": "$ff" }, { "address": "$d417", "value": "$f1" }, { "address": "$d418", "value": "$4f" } ] } + ] +} diff --git a/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-noise-edge-cases.json b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-noise-edge-cases.json new file mode 100644 index 0000000..1408ee7 --- /dev/null +++ b/CopperMod.Sid.Tests/ConformanceFixtures/specs/sid-noise-edge-cases.json @@ -0,0 +1,37 @@ +{ + "schema": 1, + "id": "sid-noise-edge-cases", + "sidtestCategory": 901, + "name": "NOISE EDGE CASES", + "format": "psid", + "clock": "pal", + "chipModel": "mos6581", + "sampleRate": 48000, + "segmentRate": 50, + "seconds": 1.52, + "binary": "binaries/sid-noise-edge-cases.sid", + "tags": [ "stress", "noise", "edge", "combined-wave" ], + "targetLayer": "mixed", + "referenceAuthority": "sidplayfp", + "commonWrites": [ + { "address": "$d400", "value": "$00" }, + { "address": "$d401", "value": "$40" }, + { "address": "$d402", "value": "$00" }, + { "address": "$d403", "value": "$08" }, + { "address": "$d405", "value": "$00" }, + { "address": "$d406", "value": "$f0" }, + { "address": "$d418", "value": "$0f" } + ], + "segments": [ + { "name": "noise-reference", "frames": 8, "writes": [ { "address": "$d404", "value": "$81" } ] }, + { "name": "noise-test-reset", "frames": 4, "writes": [ { "address": "$d404", "value": "$89" } ] }, + { "name": "noise-test-release", "frames": 8, "writes": [ { "address": "$d404", "value": "$81" } ] }, + { "name": "noise-triangle", "frames": 8, "writes": [ { "address": "$d404", "value": "$91" } ] }, + { "name": "noise-saw", "frames": 8, "writes": [ { "address": "$d404", "value": "$a1" } ] }, + { "name": "noise-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$c1" } ] }, + { "name": "noise-triangle-saw", "frames": 8, "writes": [ { "address": "$d404", "value": "$b1" } ] }, + { "name": "noise-triangle-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$d1" } ] }, + { "name": "noise-saw-pulse", "frames": 8, "writes": [ { "address": "$d404", "value": "$e1" } ] }, + { "name": "noise-all-waveforms", "frames": 8, "writes": [ { "address": "$d404", "value": "$f1" } ] } + ] +} diff --git a/CopperMod.Sid/Properties/AssemblyInfo.cs b/CopperMod.Sid/Properties/AssemblyInfo.cs index a928f0b..2cd9edf 100644 --- a/CopperMod.Sid/Properties/AssemblyInfo.cs +++ b/CopperMod.Sid/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("CopperMod.Sid.Tests")] +[assembly: InternalsVisibleTo("CopperMod.Tools")] [assembly: InternalsVisibleTo("CopperMod.Tools.Tests")] diff --git a/CopperMod.Tools.Tests/SidConformanceCommandTests.cs b/CopperMod.Tools.Tests/SidConformanceCommandTests.cs index 407da2d..35e1369 100644 --- a/CopperMod.Tools.Tests/SidConformanceCommandTests.cs +++ b/CopperMod.Tools.Tests/SidConformanceCommandTests.cs @@ -40,6 +40,10 @@ public void CompareSidConformanceRendersCandidatesAndReusesReferenceWavs() Assert.Contains("id,category,name,output,ref_ac,cand_ac,ac_ratio,diff,corr", stdout.ToString()); Assert.True(File.Exists(Path.Combine(outputDirectory, "conformance-comparison.csv"))); Assert.True(File.Exists(Path.Combine(outputDirectory, "conformance-segments.csv"))); + Assert.True(File.Exists(Path.Combine(outputDirectory, "conformance-adsr-trace.csv"))); + Assert.True(File.Exists(Path.Combine(outputDirectory, "conformance-adsr-pulses.csv"))); + Assert.True(File.Exists(Path.Combine(outputDirectory, "conformance-filter-bands.csv"))); + Assert.True(File.Exists(Path.Combine(outputDirectory, "conformance-waveform-edges.csv"))); Assert.True(File.Exists(Path.Combine(outputDirectory, "index.html"))); Assert.True(Directory.GetFiles(Path.Combine(outputDirectory, "waveforms"), "*-candidate.png").Length >= suite.Fixtures.Count); Assert.True(Directory.GetFiles(Path.Combine(outputDirectory, "waveforms"), "*-raw.png").Length >= suite.Fixtures.Count); @@ -48,6 +52,19 @@ public void CompareSidConformanceRendersCandidatesAndReusesReferenceWavs() Assert.Contains( "id,category,name,segment_index,segment,start_ms,end_ms,ref_mean,ref_ac,ref_peak,cand_player_mean,cand_player_ac,cand_player_peak,player_ratio,player_diff,player_corr,cand_raw_mean,cand_raw_ac,cand_raw_peak", File.ReadAllText(Path.Combine(outputDirectory, "conformance-segments.csv"))); + Assert.Contains( + "id,category,name,time_ms,frame,gate,alignment_offset_samples,ref_ac,cand_player_ac", + File.ReadAllText(Path.Combine(outputDirectory, "conformance-adsr-trace.csv"))); + Assert.Contains( + "id,category,name,segment_index,segment,start_ms,end_ms,ref_low,ref_mid,ref_high", + File.ReadAllText(Path.Combine(outputDirectory, "conformance-filter-bands.csv"))); + Assert.Contains( + "id,category,name,segment_index,segment,start_ms,end_ms,ref_ac,cand_player_ac", + File.ReadAllText(Path.Combine(outputDirectory, "conformance-waveform-edges.csv"))); + var index = File.ReadAllText(Path.Combine(outputDirectory, "index.html")); + Assert.Contains("conformance-adsr-trace.csv", index); + Assert.Contains("conformance-filter-bands.csv", index); + Assert.Contains("conformance-waveform-edges.csv", index); } [Fact] diff --git a/CopperMod.Tools/SidConformance.cs b/CopperMod.Tools/SidConformance.cs index 3c86dca..5b5a500 100644 --- a/CopperMod.Tools/SidConformance.cs +++ b/CopperMod.Tools/SidConformance.cs @@ -11,7 +11,7 @@ namespace CopperMod.Tools; -internal static class SidConformance +internal static partial class SidConformance { public const string CommandName = "compare-sid-conformance"; public const int DefaultSampleRate = 48000; @@ -53,6 +53,7 @@ public static void Run(string[] args, TextWriter output) output.WriteLine("id,category,name,output,ref_ac,cand_ac,ac_ratio,diff,corr"); var results = new List(); var segmentResults = new List(); + var diagnostics = new SidConformanceDiagnostics(); foreach (var fixture in suite.Fixtures) { var seconds = fixture.Spec.Seconds; @@ -116,7 +117,16 @@ public static void Run(string[] args, TextWriter output) Diff(referenceSamples, playerSamples, 0, 0, length), Correlation(referenceSamples, playerSamples, 0, 0, length)); results.Add(result); - segmentResults.AddRange(CompareSegments(fixture, sampleRate, referenceSamples, playerSamples, rawSamples)); + var fixtureSegments = CompareSegments(fixture, sampleRate, referenceSamples, playerSamples, rawSamples); + segmentResults.AddRange(fixtureSegments); + CollectFixtureDiagnostics( + fixture, + sampleRate, + referenceSamples, + playerSamples, + rawSamples, + fixtureSegments, + diagnostics); output.WriteLine(result.ToCsvLine()); WriteWaveformPng(Path.Combine(bitmapDirectory, fixture.Id + "-reference.png"), referenceSamples, sampleRate); } @@ -125,7 +135,8 @@ public static void Run(string[] args, TextWriter output) var segmentCsvPath = Path.Combine(outputDirectory, "conformance-segments.csv"); WriteComparisonCsv(csvPath, results); WriteSegmentComparisonCsv(segmentCsvPath, segmentResults); - WriteIndexHtml(Path.Combine(outputDirectory, "index.html"), suite.Fixtures, results, segmentResults); + WriteDiagnosticCsvs(outputDirectory, diagnostics); + WriteIndexHtml(Path.Combine(outputDirectory, "index.html"), suite.Fixtures, results, segmentResults, diagnostics); output.WriteLine("Wrote " + csvPath); output.WriteLine("Wrote " + segmentCsvPath); } @@ -667,7 +678,8 @@ private static void WriteIndexHtml( string path, IReadOnlyList fixtures, IReadOnlyList results, - IReadOnlyList segmentResults) + IReadOnlyList segmentResults, + SidConformanceDiagnostics diagnostics) { var byId = results.ToDictionary(result => result.Id, StringComparer.Ordinal); var segmentsById = segmentResults @@ -675,8 +687,10 @@ private static void WriteIndexHtml( .ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.Ordinal); var builder = new StringBuilder(); builder.AppendLine("SID Conformance"); - builder.AppendLine(""); - builder.AppendLine("

SID Conformance

"); + builder.AppendLine(""); + builder.AppendLine("

SID Conformance

"); + AppendDiagnosticIndexSummary(builder, diagnostics); + builder.AppendLine("
CategoryFixtureTagsLayerAuthorityRatioCorrWorst segmentSegmentsPlayerRawReference
"); foreach (var fixture in fixtures) { byId.TryGetValue(fixture.Id, out var result); diff --git a/CopperMod.Tools/SidConformanceDiagnostics.cs b/CopperMod.Tools/SidConformanceDiagnostics.cs new file mode 100644 index 0000000..331b821 --- /dev/null +++ b/CopperMod.Tools/SidConformanceDiagnostics.cs @@ -0,0 +1,1077 @@ +using System.Globalization; +using System.Text; +using CopperMod.Abstractions; +using CopperMod.Sid; + +namespace CopperMod.Tools; + +internal static partial class SidConformance +{ + private const double DiagnosticTraceStepSeconds = 0.004; + private const double DiagnosticTraceWindowSeconds = 0.012; + private const double NearDcReferenceAcThreshold = 0.012; + private const double NearDcAbsoluteLimit = 0.0025; + private const double NearDcRelativeLimit = 3.0; + + private static void CollectFixtureDiagnostics( + SidConformanceFixture fixture, + int sampleRate, + float[] referenceSamples, + float[] playerSamples, + float[] rawSamples, + IReadOnlyList segmentResults, + SidConformanceDiagnostics diagnostics) + { + if (IsAdsrDiagnosticsFixture(fixture.Spec)) + { + AddAdsrDiagnostics(fixture, sampleRate, referenceSamples, playerSamples, rawSamples, diagnostics); + } + + if (IsFilterDiagnosticsFixture(fixture.Spec)) + { + AddFilterDiagnostics(fixture, sampleRate, referenceSamples, playerSamples, rawSamples, segmentResults, diagnostics); + } + + if (IsWaveformEdgeDiagnosticsFixture(fixture.Spec, segmentResults)) + { + AddWaveformEdgeDiagnostics(fixture, sampleRate, referenceSamples, playerSamples, rawSamples, segmentResults, diagnostics); + } + } + + private static void WriteDiagnosticCsvs(string outputDirectory, SidConformanceDiagnostics diagnostics) + { + if (diagnostics.AdsrTraceRows.Count > 0) + { + WriteAdsrTraceCsv(Path.Combine(outputDirectory, "conformance-adsr-trace.csv"), diagnostics.AdsrTraceRows); + } + + if (diagnostics.AdsrPulseRows.Count > 0) + { + WriteAdsrPulseCsv(Path.Combine(outputDirectory, "conformance-adsr-pulses.csv"), diagnostics.AdsrPulseRows); + } + + if (diagnostics.FilterRows.Count > 0) + { + WriteFilterBandCsv(Path.Combine(outputDirectory, "conformance-filter-bands.csv"), diagnostics.FilterRows); + } + + if (diagnostics.WaveformRows.Count > 0) + { + WriteWaveformEdgeCsv(Path.Combine(outputDirectory, "conformance-waveform-edges.csv"), diagnostics.WaveformRows); + } + } + + private static void AppendDiagnosticIndexSummary(StringBuilder builder, SidConformanceDiagnostics diagnostics) + { + if (!diagnostics.HasRows) + { + return; + } + + builder.Append("
"); + if (diagnostics.AdsrTraceRows.Count > 0) + { + builder.Append("ADSR trace"); + } + + if (diagnostics.AdsrPulseRows.Count > 0) + { + builder.Append("ADSR pulses"); + } + + if (diagnostics.FilterRows.Count > 0) + { + builder.Append("Filter bands"); + } + + if (diagnostics.WaveformRows.Count > 0) + { + builder.Append("Waveform edges"); + } + + builder.Append("
"); + AppendWorstDiagnosticRows(builder, diagnostics); + } + + private static void AppendWorstDiagnosticRows(StringBuilder builder, SidConformanceDiagnostics diagnostics) + { + builder.Append("
"); + var wrote = false; + if (diagnostics.AdsrPulseRows.Count > 0) + { + var worst = diagnostics.AdsrPulseRows.OrderByDescending(row => Math.Abs(Math.Log(Math.Max(1.0e-12, row.PeakRatio), 2.0))).First(); + builder + .Append("Worst ADSR pulse: ") + .Append(Html(worst.Id)) + .Append(' ') + .Append(worst.Pulse.ToString(CultureInfo.InvariantCulture)) + .Append(" peak ") + .Append(FormatInvariant(worst.PeakRatio)) + .Append("x"); + wrote = true; + } + + if (diagnostics.FilterRows.Count > 0) + { + var worst = diagnostics.FilterRows.OrderByDescending(row => row.Score).First(); + builder + .Append("Worst filter band: ") + .Append(Html(worst.Id)) + .Append(' ') + .Append(Html(worst.Segment)) + .Append(" score ") + .Append(FormatInvariant(worst.Score)) + .Append(""); + wrote = true; + } + + if (diagnostics.WaveformRows.Count > 0) + { + var worst = diagnostics.WaveformRows.OrderByDescending(row => row.Score).First(); + builder + .Append("Worst waveform edge: ") + .Append(Html(worst.Id)) + .Append(' ') + .Append(Html(worst.Segment)) + .Append(" ratio ") + .Append(FormatInvariant(worst.PlayerRatio)) + .Append(" corr ") + .Append(worst.PlayerCorrelation.ToString("0.####", CultureInfo.InvariantCulture)) + .Append(""); + wrote = true; + } + + if (!wrote) + { + builder.Append("No specialized diagnostics emitted."); + } + + builder.Append("
"); + } + + private static bool IsAdsrDiagnosticsFixture(SidConformanceFixtureSpec spec) + => HasTag(spec, "adsr") || string.Equals(spec.TargetLayer, "digital", StringComparison.OrdinalIgnoreCase) && + spec.Id.Contains("adsr", StringComparison.OrdinalIgnoreCase); + + private static bool IsFilterDiagnosticsFixture(SidConformanceFixtureSpec spec) + => HasTag(spec, "filter") || string.Equals(spec.TargetLayer, "filter", StringComparison.OrdinalIgnoreCase); + + private static bool IsWaveformEdgeDiagnosticsFixture( + SidConformanceFixtureSpec spec, + IReadOnlyList segments) + { + if (HasTag(spec, "noise") || HasTag(spec, "combined-wave") || HasTag(spec, "waveform-edge")) + { + return true; + } + + return segments.Any(segment => IsCombinedOrNoiseSegmentName(segment.Segment)); + } + + private static bool HasTag(SidConformanceFixtureSpec spec, string tag) + => spec.Tags.Any(value => string.Equals(value, tag, StringComparison.OrdinalIgnoreCase)); + + private static void AddAdsrDiagnostics( + SidConformanceFixture fixture, + int sampleRate, + float[] referenceSamples, + float[] playerSamples, + float[] rawSamples, + SidConformanceDiagnostics diagnostics) + { + var render = RenderCopperModDiagnostic(fixture, sampleRate, captureChannels: true); + var voice0 = render.ChannelSamples is { Length: > 0 } ? render.ChannelSamples[0] : null; + var traceRows = BuildAdsrTraceRows( + fixture, + sampleRate, + referenceSamples, + playerSamples, + rawSamples, + voice0, + render.SidWrites); + diagnostics.AdsrTraceRows.AddRange(traceRows); + diagnostics.AdsrPulseRows.AddRange(BuildAdsrPulseRows(fixture, traceRows)); + } + + private static IReadOnlyList BuildAdsrTraceRows( + SidConformanceFixture fixture, + int sampleRate, + float[] referenceSamples, + float[] playerSamples, + float[] rawSamples, + float[]? voice0Samples, + SidRegisterWrite[] writes) + { + var debugRows = BuildAdsrDebugRows(fixture.Spec, writes); + var rows = new List(debugRows.Count); + var windowLength = Math.Max(32, SecondsToSamples(DiagnosticTraceWindowSeconds, sampleRate)); + var alignmentLength = Math.Min( + referenceSamples.Length, + Math.Min(playerSamples.Length, SecondsToSamples(fixture.Spec.Seconds, sampleRate))); + var alignmentOffset = alignmentLength > windowLength + ? FindBestCandidateOffset(referenceSamples, playerSamples, 0, alignmentLength, maxOffset: sampleRate / 40) + : 0; + var fundamental = SidFrequencyToHz(FindVoiceFrequency(fixture.Spec, voiceOffset: 0)); + + foreach (var debug in debugRows) + { + var center = SecondsToSamples(debug.TimeSeconds, sampleRate); + var referenceStart = WindowStart(referenceSamples.Length, center - alignmentOffset, windowLength); + var playerStart = WindowStart(playerSamples.Length, center, windowLength); + var rawStart = WindowStart(rawSamples.Length, center, windowLength); + var voice0Ac = double.NaN; + var voice0Fundamental = double.NaN; + if (voice0Samples != null && voice0Samples.Length >= windowLength) + { + var voiceStart = WindowStart(voice0Samples.Length, center, windowLength); + voice0Ac = AcRms(voice0Samples, voiceStart, windowLength); + voice0Fundamental = HarmonicMagnitude(voice0Samples, voiceStart, windowLength, fundamental, sampleRate); + } + + var referenceAc = AcRms(referenceSamples, referenceStart, windowLength); + var playerAc = AcRms(playerSamples, playerStart, windowLength); + var rawAc = AcRms(rawSamples, rawStart, windowLength); + var referenceFundamental = HarmonicMagnitude(referenceSamples, referenceStart, windowLength, fundamental, sampleRate); + var playerFundamental = HarmonicMagnitude(playerSamples, playerStart, windowLength, fundamental, sampleRate); + rows.Add(new AdsrTraceDiagnosticRow( + fixture.Id, + fixture.Spec.SidtestCategory, + fixture.Spec.Name, + debug.TimeSeconds * 1000.0, + debug.Frame, + debug.Gate, + alignmentOffset, + referenceAc, + playerAc, + playerAc / Math.Max(1.0e-12, referenceAc), + rawAc, + voice0Ac, + RatioOrNaN(playerAc, voice0Ac), + referenceFundamental, + playerFundamental, + playerFundamental / Math.Max(1.0e-12, referenceFundamental), + voice0Fundamental, + RatioOrNaN(playerFundamental, voice0Fundamental), + debug.EnvelopeCounter, + SidAnalog.ConvertEnvelope(debug.EnvelopeCounter, SidChipModel.Mos6581), + debug.EnvelopeState, + debug.RateCounter, + debug.ExponentialCounter, + debug.Control)); + } + + return rows; + } + + private static IReadOnlyList BuildAdsrDebugRows(SidConformanceFixtureSpec spec, SidRegisterWrite[] writes) + { + var chip = new SidChip( + SidChipModel.Mos6581, + SidBase, + SidConstants.PalCpuCyclesPerSecond, + SidFilterProfileId.Auto, + SidEmulationProfile.Balanced); + var replayWrites = writes + .Where(write => write.ChipIndex == 0 && write.Cycle >= 0 && IsAdsrProbeRegister(write.Register)) + .OrderBy(write => write.Cycle) + .ToArray(); + var rows = new List(); + var currentCycle = 0L; + var nextWrite = 0; + var totalSeconds = spec.Seconds; + for (var timeSeconds = DiagnosticTraceStepSeconds; timeSeconds < totalSeconds; timeSeconds += DiagnosticTraceStepSeconds) + { + var targetCycle = (long)Math.Round(timeSeconds * SidConstants.PalCpuCyclesPerSecond); + while (nextWrite < replayWrites.Length && replayWrites[nextWrite].Cycle <= targetCycle) + { + var write = replayWrites[nextWrite++]; + if (write.Cycle > currentCycle) + { + chip.Render(write.Cycle - currentCycle); + currentCycle = write.Cycle; + } + + chip.Write(write.Register, write.Value, write.Cycle); + } + + if (targetCycle > currentCycle) + { + chip.Render(targetCycle - currentCycle); + currentCycle = targetCycle; + } + + var voice = chip.DebugState.Voices[0]; + rows.Add(new AdsrDebugRow( + timeSeconds, + (int)Math.Floor(timeSeconds * (spec.SegmentRate ?? DefaultSegmentRate)), + (voice.Control & 0x01) != 0, + voice.EnvelopeCounter, + voice.EnvelopeState, + voice.RateCounter, + voice.ExponentialCounter, + voice.Control)); + } + + return rows; + } + + private static IEnumerable BuildAdsrPulseRows( + SidConformanceFixture fixture, + IReadOnlyList traceRows) + { + if (traceRows.Count == 0) + { + yield break; + } + + var pulses = FindGatePulses(fixture.Spec).ToArray(); + for (var i = 0; i < pulses.Length; i++) + { + var pulse = pulses[i]; + var onMs = pulse.OnFrame * 1000.0 / (fixture.Spec.SegmentRate ?? DefaultSegmentRate); + var offMs = pulse.OffFrame * 1000.0 / (fixture.Spec.SegmentRate ?? DefaultSegmentRate); + var peakRows = traceRows + .Where(row => row.TimeMs >= onMs && row.TimeMs < offMs + 40.0) + .ToArray(); + if (peakRows.Length == 0) + { + continue; + } + + var peak = peakRows.OrderByDescending(row => row.RefAc).First(); + var gateOff = NearestAdsrTraceRow(traceRows, offMs); + var tail = NearestAdsrTraceRow(traceRows, offMs + 40.0); + yield return new AdsrPulseDiagnosticRow( + fixture.Id, + fixture.Spec.SidtestCategory, + fixture.Spec.Name, + i + 1, + onMs, + offMs, + peak.RefAc, + peak.CandPlayerAc, + peak.PlayerRatio, + peak.CandVoice0Ac, + peak.FinalToVoice0Ac, + gateOff.RefAc, + gateOff.CandPlayerAc, + gateOff.PlayerRatio, + gateOff.CandVoice0Ac, + gateOff.FinalToVoice0Ac, + tail.RefAc, + tail.CandPlayerAc, + tail.PlayerRatio, + tail.CandVoice0Ac, + tail.FinalToVoice0Ac, + peak.EnvelopeCounter, + gateOff.EnvelopeCounter, + tail.EnvelopeCounter, + EnvelopeSlope(peak.EnvelopeCounter, gateOff.EnvelopeCounter, Math.Max(1.0, gateOff.TimeMs - peak.TimeMs)), + EnvelopeSlope(gateOff.EnvelopeCounter, tail.EnvelopeCounter, Math.Max(1.0, tail.TimeMs - gateOff.TimeMs)), + FormatEnvelopeState(tail.EnvelopeState)); + } + } + + private static IEnumerable FindGatePulses(SidConformanceFixtureSpec spec) + { + var segmentRate = spec.SegmentRate ?? DefaultSegmentRate; + _ = segmentRate; + var frame = 0; + var gate = FindCommonControlGate(spec); + int? pulseStart = gate ? 0 : null; + foreach (var segment in spec.Segments) + { + var nextGate = gate; + foreach (var write in segment.Writes) + { + if (ParseAddress(write.Address) == SidBase + 0x04) + { + nextGate = (ParseHex(write.Value, max: 0xFF) & 0x01) != 0; + } + } + + if (!gate && nextGate) + { + pulseStart = frame; + } + else if (gate && !nextGate && pulseStart.HasValue) + { + yield return new GatePulse(pulseStart.Value, frame); + pulseStart = null; + } + + gate = nextGate; + frame += segment.Frames; + } + + if (pulseStart.HasValue) + { + yield return new GatePulse(pulseStart.Value, frame); + } + } + + private static bool FindCommonControlGate(SidConformanceFixtureSpec spec) + { + foreach (var write in spec.CommonWrites) + { + if (ParseAddress(write.Address) == SidBase + 0x04) + { + return (ParseHex(write.Value, max: 0xFF) & 0x01) != 0; + } + } + + return false; + } + + private static void AddFilterDiagnostics( + SidConformanceFixture fixture, + int sampleRate, + float[] referenceSamples, + float[] playerSamples, + float[] rawSamples, + IReadOnlyList segmentResults, + SidConformanceDiagnostics diagnostics) + { + foreach (var segment in segmentResults) + { + var (start, length) = SegmentWindow(segment, sampleRate, referenceSamples, playerSamples, rawSamples); + if (length <= 0) + { + continue; + } + + var reference = MeasureBands(referenceSamples, start, length, sampleRate); + var player = MeasureBands(playerSamples, start, length, sampleRate); + var raw = MeasureBands(rawSamples, start, length, sampleRate); + diagnostics.FilterRows.Add(new FilterBandDiagnosticRow( + fixture.Id, + fixture.Spec.SidtestCategory, + fixture.Spec.Name, + segment.SegmentIndex, + segment.Segment, + segment.StartMs, + segment.EndMs, + reference.Low, + reference.Mid, + reference.High, + player.Low, + player.Mid, + player.High, + raw.Low, + raw.Mid, + raw.High)); + } + } + + private static void AddWaveformEdgeDiagnostics( + SidConformanceFixture fixture, + int sampleRate, + float[] referenceSamples, + float[] playerSamples, + float[] rawSamples, + IReadOnlyList segmentResults, + SidConformanceDiagnostics diagnostics) + { + foreach (var segment in segmentResults) + { + if (!IsCombinedOrNoiseSegmentName(segment.Segment) && !HasTag(fixture.Spec, "noise") && !HasTag(fixture.Spec, "combined-wave")) + { + continue; + } + + var (start, length) = SegmentWindow(segment, sampleRate, referenceSamples, playerSamples, rawSamples); + if (length <= 0) + { + continue; + } + + var referenceFlatness = SpectralFlatness(referenceSamples, start, length, sampleRate); + var playerFlatness = SpectralFlatness(playerSamples, start, length, sampleRate); + var rawFlatness = SpectralFlatness(rawSamples, start, length, sampleRate); + var referenceNoiseBand = NoiseBandEnergy(referenceSamples, start, length, sampleRate); + var playerNoiseBand = NoiseBandEnergy(playerSamples, start, length, sampleRate); + var rawNoiseBand = NoiseBandEnergy(rawSamples, start, length, sampleRate); + var nearDcExpected = IsNearDcSegmentName(segment.Segment); + var nearDcLimit = Math.Max(NearDcAbsoluteLimit, segment.ReferenceAc * NearDcRelativeLimit); + diagnostics.WaveformRows.Add(new WaveformEdgeDiagnosticRow( + fixture.Id, + fixture.Spec.SidtestCategory, + fixture.Spec.Name, + segment.SegmentIndex, + segment.Segment, + segment.StartMs, + segment.EndMs, + segment.ReferenceAc, + segment.PlayerAc, + segment.PlayerRatio, + segment.PlayerCorrelation, + referenceFlatness, + playerFlatness, + rawFlatness, + referenceNoiseBand, + playerNoiseBand, + rawNoiseBand, + nearDcExpected, + nearDcExpected && segment.ReferenceAc < NearDcReferenceAcThreshold ? nearDcLimit : double.NaN, + !nearDcExpected || segment.ReferenceAc >= NearDcReferenceAcThreshold || segment.PlayerAc <= nearDcLimit)); + } + } + + private static CopperModDiagnosticRender RenderCopperModDiagnostic( + SidConformanceFixture fixture, + int sampleRate, + bool captureChannels) + { + using var song = (SidSong)new SidFormat().Load(new ModuleLoadContext(File.ReadAllBytes(fixture.BinaryPath), fixture.BinaryPath)); + ((ISidEmulationProfileController)song).SidEmulationProfile = SidEmulationProfile.Balanced; + var channelProvider = (IModuleChannelWaveformProvider)song; + channelProvider.ChannelWaveformCaptureEnabled = captureChannels; + var options = new AudioRenderOptions(sampleRate, channelCount: 1); + var targetFrames = SecondsToSamples(fixture.Spec.Seconds, sampleRate); + var samples = new List(targetFrames + sampleRate); + var channelSamples = captureChannels + ? new[] { new List(targetFrames), new List(targetFrames), new List(targetFrames) } + : null; + + while (samples.Count < targetFrames) + { + var frames = song.GetCurrentTickFrameCount(options); + var buffer = new float[options.GetSampleCount(frames)]; + var result = song.RenderTick(buffer, options); + var written = Math.Min(result.SamplesWritten, buffer.Length); + var framesToKeep = Math.Min(written / options.ChannelCount, targetFrames - samples.Count); + for (var i = 0; i < framesToKeep * options.ChannelCount; i++) + { + samples.Add(buffer[i]); + } + + if (channelSamples == null) + { + continue; + } + + var waveform = channelProvider.LastChannelWaveform; + if (waveform == null) + { + throw new InvalidOperationException("SID channel waveform capture was enabled but no waveform was produced."); + } + + for (var channel = 0; channel < channelSamples.Length && channel < waveform.Channels.Count; channel++) + { + var source = waveform.Channels[channel].Samples; + for (var i = 0; i < framesToKeep && i < source.Length; i++) + { + channelSamples[channel].Add(source[i]); + } + } + } + + return new CopperModDiagnosticRender( + samples.ToArray(), + channelSamples?.Select(channel => channel.ToArray()).ToArray(), + captureChannels ? song.SidWrites.ToArray() : Array.Empty()); + } + + private static (int Start, int Length) SegmentWindow( + SidConformanceSegmentComparisonResult segment, + int sampleRate, + params float[][] sampleSets) + { + var start = SecondsToSamples(segment.StartMs / 1000.0, sampleRate); + var end = SecondsToSamples(segment.EndMs / 1000.0, sampleRate); + var available = sampleSets.Min(samples => samples.Length) - start; + return (start, Math.Min(end - start, available)); + } + + private static BandEnergies MeasureBands(float[] samples, int start, int length, int sampleRate) + { + ReadOnlySpan low = stackalloc[] { 180.0, 320.0, 640.0 }; + ReadOnlySpan mid = stackalloc[] { 1000.0, 1800.0, 3200.0 }; + ReadOnlySpan high = stackalloc[] { 5000.0, 8000.0, 12000.0 }; + return new BandEnergies( + BandMagnitude(samples, start, length, low, sampleRate), + BandMagnitude(samples, start, length, mid, sampleRate), + BandMagnitude(samples, start, length, high, sampleRate)); + } + + private static double NoiseBandEnergy(float[] samples, int start, int length, int sampleRate) + { + ReadOnlySpan bands = stackalloc[] { 1000.0, 2000.0, 4000.0, 8000.0, 12000.0, 16000.0 }; + return BandMagnitude(samples, start, length, bands, sampleRate); + } + + private static double BandMagnitude(float[] samples, int start, int length, ReadOnlySpan frequencies, int sampleRate) + { + var sum = 0.0; + foreach (var frequency in frequencies) + { + sum += HarmonicMagnitude(samples, start, length, frequency, sampleRate); + } + + return sum; + } + + private static double SpectralFlatness(float[] samples, int start, int length, int sampleRate) + { + ReadOnlySpan bands = stackalloc[] { 700.0, 1100.0, 1700.0, 2600.0, 3900.0, 5800.0, 8700.0, 13000.0, 18000.0 }; + var logSum = 0.0; + var sum = 0.0; + for (var i = 0; i < bands.Length; i++) + { + var magnitude = Math.Max(1.0e-12, HarmonicMagnitude(samples, start, length, bands[i], sampleRate)); + logSum += Math.Log(magnitude); + sum += magnitude; + } + + return Math.Exp(logSum / bands.Length) / (sum / bands.Length); + } + + private static double HarmonicMagnitude(float[] samples, int start, int length, double frequency, int sampleRate) + { + if (length <= 1) + { + return 0.0; + } + + var mean = Mean(samples, start, length); + var real = 0.0; + var imaginary = 0.0; + for (var i = 0; i < length; i++) + { + var window = 0.5 - (0.5 * Math.Cos((2.0 * Math.PI * i) / (length - 1))); + var phase = (2.0 * Math.PI * frequency * i) / sampleRate; + var sample = (samples[start + i] - mean) * window; + real += sample * Math.Cos(phase); + imaginary -= sample * Math.Sin(phase); + } + + return Math.Sqrt((real * real) + (imaginary * imaginary)) / length; + } + + private static int FindBestCandidateOffset(float[] reference, float[] candidate, int start, int length, int maxOffset) + { + var bestOffset = 0; + var bestCorrelation = double.NegativeInfinity; + for (var offset = -maxOffset; offset <= maxOffset; offset += 8) + { + if (start + offset < 0 || start + offset + length >= candidate.Length || start + length >= reference.Length) + { + continue; + } + + var correlation = Correlation(reference, candidate, start, start + offset, length); + if (correlation > bestCorrelation) + { + bestCorrelation = correlation; + bestOffset = offset; + } + } + + var refineStart = Math.Max(-maxOffset, bestOffset - 24); + var refineEnd = Math.Min(maxOffset, bestOffset + 24); + for (var offset = refineStart; offset <= refineEnd; offset++) + { + if (start + offset < 0 || start + offset + length >= candidate.Length || start + length >= reference.Length) + { + continue; + } + + var correlation = Correlation(reference, candidate, start, start + offset, length); + if (correlation > bestCorrelation) + { + bestCorrelation = correlation; + bestOffset = offset; + } + } + + return bestOffset; + } + + private static int WindowStart(int sampleCount, int center, int length) + { + if (sampleCount <= length) + { + return 0; + } + + return Math.Clamp(center - (length / 2), 0, sampleCount - length); + } + + private static ushort FindVoiceFrequency(SidConformanceFixtureSpec spec, int voiceOffset) + { + var low = FindCommonWriteByte(spec, SidBase + voiceOffset); + var high = FindCommonWriteByte(spec, SidBase + voiceOffset + 1); + return (ushort)(low | (high << 8)); + } + + private static int FindCommonWriteByte(SidConformanceFixtureSpec spec, int address) + { + for (var i = spec.CommonWrites.Count - 1; i >= 0; i--) + { + var write = spec.CommonWrites[i]; + if (ParseAddress(write.Address) == address) + { + return ParseHex(write.Value, max: 0xFF); + } + } + + return 0; + } + + private static bool IsAdsrProbeRegister(byte register) + => register is <= 0x06 or 0x0B or 0x12 or >= 0x15 and <= 0x18; + + private static bool IsCombinedOrNoiseSegmentName(string segment) + => segment.Contains("noise", StringComparison.OrdinalIgnoreCase) || + segment.Contains("triangle-saw", StringComparison.OrdinalIgnoreCase) || + segment.Contains("triangle-pulse", StringComparison.OrdinalIgnoreCase) || + segment.Contains("saw-pulse", StringComparison.OrdinalIgnoreCase) || + segment.Contains("all-three", StringComparison.OrdinalIgnoreCase) || + segment.Contains("combined", StringComparison.OrdinalIgnoreCase); + + private static bool IsNearDcSegmentName(string segment) + => segment.Contains("saw-pulse", StringComparison.OrdinalIgnoreCase) || + segment.Contains("triangle-saw-pulse", StringComparison.OrdinalIgnoreCase) || + segment.Contains("all-three", StringComparison.OrdinalIgnoreCase) || + segment.Contains("noise-all-waveforms", StringComparison.OrdinalIgnoreCase); + + private static AdsrTraceDiagnosticRow NearestAdsrTraceRow(IReadOnlyList rows, double timeMilliseconds) + { + var best = rows[0]; + var bestDistance = Math.Abs(best.TimeMs - timeMilliseconds); + for (var i = 1; i < rows.Count; i++) + { + var distance = Math.Abs(rows[i].TimeMs - timeMilliseconds); + if (distance < bestDistance) + { + best = rows[i]; + bestDistance = distance; + } + } + + return best; + } + + private static double RatioOrNaN(double numerator, double denominator) + => double.IsNaN(denominator) ? double.NaN : numerator / Math.Max(1.0e-12, denominator); + + private static double EnvelopeSlope(int from, int to, double milliseconds) + => (to - from) / milliseconds; + + private static double SidFrequencyToHz(ushort frequency) + => frequency * (double)SidConstants.PalCpuCyclesPerSecond / 16_777_216.0; + + private static string FormatEnvelopeState(int state) + => state switch + { + 0 => "attack", + 1 => "decay-sustain", + 2 => "release", + _ => state.ToString(CultureInfo.InvariantCulture) + }; + + private static string CsvDouble(double value) + => double.IsFinite(value) ? value.ToString("0.000000", CultureInfo.InvariantCulture) : "NaN"; + + private static void WriteAdsrTraceCsv(string path, IReadOnlyList rows) + { + using var writer = new StreamWriter(path); + writer.WriteLine("id,category,name,time_ms,frame,gate,alignment_offset_samples,ref_ac,cand_player_ac,player_ratio,cand_raw_ac,cand_voice0_ac,final_to_voice0_ac,ref_fund,cand_player_fund,fund_ratio,cand_voice0_fund,final_to_voice0_fund,cand_envelope,cand_envelope_dac,cand_state,cand_rate_counter,cand_exponential_counter,cand_control"); + foreach (var row in rows) + { + writer.WriteLine(row.ToCsvLine()); + } + } + + private static void WriteAdsrPulseCsv(string path, IReadOnlyList rows) + { + using var writer = new StreamWriter(path); + writer.WriteLine("id,category,name,pulse,on_ms,off_ms,ref_peak,cand_peak,peak_ratio,cand_voice0_peak,final_to_voice0_peak,ref_gate_off,cand_gate_off,gate_off_ratio,cand_voice0_gate_off,final_to_voice0_gate_off,ref_tail_40ms,cand_tail_40ms,tail_40ms_ratio,cand_voice0_tail_40ms,final_to_voice0_tail_40ms,cand_env_peak,cand_env_gate_off,cand_env_tail_40ms,cand_decay_slope_per_ms,cand_release_slope_per_ms,cand_tail_state"); + foreach (var row in rows) + { + writer.WriteLine(row.ToCsvLine()); + } + } + + private static void WriteFilterBandCsv(string path, IReadOnlyList rows) + { + using var writer = new StreamWriter(path); + writer.WriteLine("id,category,name,segment_index,segment,start_ms,end_ms,ref_low,ref_mid,ref_high,cand_player_low,cand_player_mid,cand_player_high,cand_raw_low,cand_raw_mid,cand_raw_high,player_low_ratio,player_mid_ratio,player_high_ratio,raw_low_ratio,raw_mid_ratio,raw_high_ratio"); + foreach (var row in rows) + { + writer.WriteLine(row.ToCsvLine()); + } + } + + private static void WriteWaveformEdgeCsv(string path, IReadOnlyList rows) + { + using var writer = new StreamWriter(path); + writer.WriteLine("id,category,name,segment_index,segment,start_ms,end_ms,ref_ac,cand_player_ac,player_ratio,player_corr,ref_flatness,cand_player_flatness,cand_raw_flatness,ref_noise_band,cand_player_noise_band,cand_raw_noise_band,near_dc_expected,near_dc_limit,near_dc_ok"); + foreach (var row in rows) + { + writer.WriteLine(row.ToCsvLine()); + } + } + + private sealed class SidConformanceDiagnostics + { + public List AdsrTraceRows { get; } = new(); + + public List AdsrPulseRows { get; } = new(); + + public List FilterRows { get; } = new(); + + public List WaveformRows { get; } = new(); + + public bool HasRows => AdsrTraceRows.Count > 0 || AdsrPulseRows.Count > 0 || FilterRows.Count > 0 || WaveformRows.Count > 0; + } + + private sealed record CopperModDiagnosticRender( + float[] Samples, + float[][]? ChannelSamples, + SidRegisterWrite[] SidWrites); + + private readonly record struct BandEnergies(double Low, double Mid, double High); + + private readonly record struct GatePulse(int OnFrame, int OffFrame); + + private readonly record struct AdsrDebugRow( + double TimeSeconds, + int Frame, + bool Gate, + int EnvelopeCounter, + int EnvelopeState, + int RateCounter, + int ExponentialCounter, + byte Control); + + private sealed record AdsrTraceDiagnosticRow( + string Id, + int Category, + string Name, + double TimeMs, + int Frame, + bool Gate, + int AlignmentOffsetSamples, + double RefAc, + double CandPlayerAc, + double PlayerRatio, + double CandRawAc, + double CandVoice0Ac, + double FinalToVoice0Ac, + double RefFundamental, + double CandPlayerFundamental, + double FundamentalRatio, + double CandVoice0Fundamental, + double FinalToVoice0Fundamental, + int EnvelopeCounter, + double EnvelopeDac, + int EnvelopeState, + int RateCounter, + int ExponentialCounter, + byte Control) + { + public string ToCsvLine() + => string.Join( + ',', + Id, + Category.ToString(CultureInfo.InvariantCulture), + EscapeCsv(Name), + TimeMs.ToString("0.000", CultureInfo.InvariantCulture), + Frame.ToString(CultureInfo.InvariantCulture), + Gate ? "1" : "0", + AlignmentOffsetSamples.ToString(CultureInfo.InvariantCulture), + CsvDouble(RefAc), + CsvDouble(CandPlayerAc), + CsvDouble(PlayerRatio), + CsvDouble(CandRawAc), + CsvDouble(CandVoice0Ac), + CsvDouble(FinalToVoice0Ac), + CsvDouble(RefFundamental), + CsvDouble(CandPlayerFundamental), + CsvDouble(FundamentalRatio), + CsvDouble(CandVoice0Fundamental), + CsvDouble(FinalToVoice0Fundamental), + EnvelopeCounter.ToString(CultureInfo.InvariantCulture), + CsvDouble(EnvelopeDac), + FormatEnvelopeState(EnvelopeState), + RateCounter.ToString(CultureInfo.InvariantCulture), + ExponentialCounter.ToString(CultureInfo.InvariantCulture), + "0x" + Control.ToString("X2", CultureInfo.InvariantCulture)); + } + + private sealed record AdsrPulseDiagnosticRow( + string Id, + int Category, + string Name, + int Pulse, + double OnMs, + double OffMs, + double RefPeak, + double CandPeak, + double PeakRatio, + double CandVoice0Peak, + double FinalToVoice0Peak, + double RefGateOff, + double CandGateOff, + double GateOffRatio, + double CandVoice0GateOff, + double FinalToVoice0GateOff, + double RefTail40Ms, + double CandTail40Ms, + double Tail40MsRatio, + double CandVoice0Tail40Ms, + double FinalToVoice0Tail40Ms, + int CandEnvelopePeak, + int CandEnvelopeGateOff, + int CandEnvelopeTail40Ms, + double CandDecaySlopePerMs, + double CandReleaseSlopePerMs, + string CandTailState) + { + public string ToCsvLine() + => string.Join( + ',', + Id, + Category.ToString(CultureInfo.InvariantCulture), + EscapeCsv(Name), + Pulse.ToString(CultureInfo.InvariantCulture), + OnMs.ToString("0.000", CultureInfo.InvariantCulture), + OffMs.ToString("0.000", CultureInfo.InvariantCulture), + CsvDouble(RefPeak), + CsvDouble(CandPeak), + CsvDouble(PeakRatio), + CsvDouble(CandVoice0Peak), + CsvDouble(FinalToVoice0Peak), + CsvDouble(RefGateOff), + CsvDouble(CandGateOff), + CsvDouble(GateOffRatio), + CsvDouble(CandVoice0GateOff), + CsvDouble(FinalToVoice0GateOff), + CsvDouble(RefTail40Ms), + CsvDouble(CandTail40Ms), + CsvDouble(Tail40MsRatio), + CsvDouble(CandVoice0Tail40Ms), + CsvDouble(FinalToVoice0Tail40Ms), + CandEnvelopePeak.ToString(CultureInfo.InvariantCulture), + CandEnvelopeGateOff.ToString(CultureInfo.InvariantCulture), + CandEnvelopeTail40Ms.ToString(CultureInfo.InvariantCulture), + CsvDouble(CandDecaySlopePerMs), + CsvDouble(CandReleaseSlopePerMs), + CandTailState); + } + + private sealed record FilterBandDiagnosticRow( + string Id, + int Category, + string Name, + int SegmentIndex, + string Segment, + double StartMs, + double EndMs, + double RefLow, + double RefMid, + double RefHigh, + double CandPlayerLow, + double CandPlayerMid, + double CandPlayerHigh, + double CandRawLow, + double CandRawMid, + double CandRawHigh) + { + public double PlayerLowRatio => CandPlayerLow / Math.Max(1.0e-12, RefLow); + + public double PlayerMidRatio => CandPlayerMid / Math.Max(1.0e-12, RefMid); + + public double PlayerHighRatio => CandPlayerHigh / Math.Max(1.0e-12, RefHigh); + + public double RawLowRatio => CandRawLow / Math.Max(1.0e-12, RefLow); + + public double RawMidRatio => CandRawMid / Math.Max(1.0e-12, RefMid); + + public double RawHighRatio => CandRawHigh / Math.Max(1.0e-12, RefHigh); + + public double Score => + Math.Abs(Math.Log(Math.Max(1.0e-12, PlayerLowRatio), 2.0)) + + Math.Abs(Math.Log(Math.Max(1.0e-12, PlayerMidRatio), 2.0)) + + Math.Abs(Math.Log(Math.Max(1.0e-12, PlayerHighRatio), 2.0)); + + public string ToCsvLine() + => string.Join( + ',', + Id, + Category.ToString(CultureInfo.InvariantCulture), + EscapeCsv(Name), + SegmentIndex.ToString(CultureInfo.InvariantCulture), + EscapeCsv(Segment), + StartMs.ToString("0.000", CultureInfo.InvariantCulture), + EndMs.ToString("0.000", CultureInfo.InvariantCulture), + CsvDouble(RefLow), + CsvDouble(RefMid), + CsvDouble(RefHigh), + CsvDouble(CandPlayerLow), + CsvDouble(CandPlayerMid), + CsvDouble(CandPlayerHigh), + CsvDouble(CandRawLow), + CsvDouble(CandRawMid), + CsvDouble(CandRawHigh), + CsvDouble(PlayerLowRatio), + CsvDouble(PlayerMidRatio), + CsvDouble(PlayerHighRatio), + CsvDouble(RawLowRatio), + CsvDouble(RawMidRatio), + CsvDouble(RawHighRatio)); + } + + private sealed record WaveformEdgeDiagnosticRow( + string Id, + int Category, + string Name, + int SegmentIndex, + string Segment, + double StartMs, + double EndMs, + double RefAc, + double CandPlayerAc, + double PlayerRatio, + double PlayerCorrelation, + double RefFlatness, + double CandPlayerFlatness, + double CandRawFlatness, + double RefNoiseBand, + double CandPlayerNoiseBand, + double CandRawNoiseBand, + bool NearDcExpected, + double NearDcLimit, + bool NearDcOk) + { + public double Score => + Math.Abs(Math.Log(Math.Max(1.0e-12, PlayerRatio), 2.0)) + + Math.Max(0.0, 0.75 - PlayerCorrelation) + + (NearDcExpected && !NearDcOk ? 4.0 : 0.0); + + public string ToCsvLine() + => string.Join( + ',', + Id, + Category.ToString(CultureInfo.InvariantCulture), + EscapeCsv(Name), + SegmentIndex.ToString(CultureInfo.InvariantCulture), + EscapeCsv(Segment), + StartMs.ToString("0.000", CultureInfo.InvariantCulture), + EndMs.ToString("0.000", CultureInfo.InvariantCulture), + CsvDouble(RefAc), + CsvDouble(CandPlayerAc), + CsvDouble(PlayerRatio), + PlayerCorrelation.ToString("0.000000", CultureInfo.InvariantCulture), + CsvDouble(RefFlatness), + CsvDouble(CandPlayerFlatness), + CsvDouble(CandRawFlatness), + CsvDouble(RefNoiseBand), + CsvDouble(CandPlayerNoiseBand), + CsvDouble(CandRawNoiseBand), + NearDcExpected ? "1" : "0", + CsvDouble(NearDcLimit), + NearDcOk ? "1" : "0"); + } +}
CategoryFixtureTagsLayerAuthorityRatioCorrWorst segmentSegmentsPlayerRawReference