diff --git a/calibration/chirp-bin/scarlett-artifact.json b/calibration/chirp-bin/scarlett-artifact.json new file mode 100644 index 0000000..a21901f --- /dev/null +++ b/calibration/chirp-bin/scarlett-artifact.json @@ -0,0 +1,2326 @@ +{ + "ModelId": "chirp-bin-20260523T191655Z", + "CreatedAt": "2026-05-23T19:16:55.9803706+00:00", + "SampleRate": 192000, + "ReferenceSourceId": "asio-ch2", + "Paths": [ + { + "SourceId": "asio-ch0", + "SampleRate": 192000, + "Profile": { + "SourceId": "asio-ch0", + "SampleRate": 192000, + "FrameCount": 8, + "AnchorCount": 0, + "ClockConfidence": 0, + "MeanAnchorErrorSamples": 0, + "Bands": [ + { + "CenterHz": 3625, + "MeanEnergy": 0.03913795797334273, + "PeakEnergy": 0.07305074809319667, + "RelativeGain": 0.9031274571076037, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 4075, + "MeanEnergy": 0.028378157645841395, + "PeakEnergy": 0.06260494595938466, + "RelativeGain": 0.6548398199401124, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 4525, + "MeanEnergy": 0.029450143520437407, + "PeakEnergy": 0.05873913227546674, + "RelativeGain": 0.6795764165105972, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 4975, + "MeanEnergy": 0.04333602933376392, + "PeakEnergy": 0.089830384056493, + "RelativeGain": 1, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 5425, + "MeanEnergy": 0.025098166610740144, + "PeakEnergy": 0.06088170700446069, + "RelativeGain": 0.5791524280510326, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 5875, + "MeanEnergy": 0.023348415166252516, + "PeakEnergy": 0.03873787074531297, + "RelativeGain": 0.5387760605944885, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 6325, + "MeanEnergy": 0.024440126399055725, + "PeakEnergy": 0.028984479357744262, + "RelativeGain": 0.563967829420264, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 6775, + "MeanEnergy": 0.014346409009942268, + "PeakEnergy": 0.023781208939574427, + "RelativeGain": 0.3310503807224607, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 7225, + "MeanEnergy": 0.017928237014792882, + "PeakEnergy": 0.034450221655744274, + "RelativeGain": 0.4137028078118051, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 7675, + "MeanEnergy": 0.012511349747870226, + "PeakEnergy": 0.029690682396459226, + "RelativeGain": 0.28870549379387644, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 8125, + "MeanEnergy": 0.014082854634327, + "PeakEnergy": 0.02978395174916663, + "RelativeGain": 0.32496873504178614, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 8575, + "MeanEnergy": 0.014166240006282685, + "PeakEnergy": 0.032449764564376087, + "RelativeGain": 0.32689289314389264, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 9025, + "MeanEnergy": 0.014949054633497034, + "PeakEnergy": 0.031211037161692847, + "RelativeGain": 0.3449567222313546, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 9475, + "MeanEnergy": 0.01598732169771644, + "PeakEnergy": 0.022043530041922513, + "RelativeGain": 0.36891524081696186, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 9925, + "MeanEnergy": 0.020915908006712388, + "PeakEnergy": 0.03807360398880363, + "RelativeGain": 0.48264477221996915, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 10375, + "MeanEnergy": 0.016560658256372204, + "PeakEnergy": 0.030453798151008158, + "RelativeGain": 0.38214526136730026, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 10825, + "MeanEnergy": 0.010364612796337729, + "PeakEnergy": 0.021158941509049876, + "RelativeGain": 0.23916849226106796, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 11275, + "MeanEnergy": 0.008895896623590851, + "PeakEnergy": 0.01512100554982274, + "RelativeGain": 0.2052771506839435, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 11725, + "MeanEnergy": 0.0070253514944388275, + "PeakEnergy": 0.01355432162581175, + "RelativeGain": 0.16211341007574137, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 12175, + "MeanEnergy": 0.010056778683663989, + "PeakEnergy": 0.019258362032579232, + "RelativeGain": 0.2320650700646578, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 12625, + "MeanEnergy": 0.011261164532357664, + "PeakEnergy": 0.017268940437937266, + "RelativeGain": 0.2598568605726847, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 13075, + "MeanEnergy": 0.008621657525495872, + "PeakEnergy": 0.018929333152272788, + "RelativeGain": 0.19894894982403422, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 13525, + "MeanEnergy": 0.008660113899150099, + "PeakEnergy": 0.018669616981169514, + "RelativeGain": 0.19983634939075604, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 13975, + "MeanEnergy": 0.011187424435002015, + "PeakEnergy": 0.020384498091683367, + "RelativeGain": 0.2581552718833352, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 14425, + "MeanEnergy": 0.010552117191449764, + "PeakEnergy": 0.016668621424328497, + "RelativeGain": 0.2434952475728645, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 14875, + "MeanEnergy": 0.008890250773902491, + "PeakEnergy": 0.014590860318431953, + "RelativeGain": 0.2051468699504486, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 15325, + "MeanEnergy": 0.007801691431406243, + "PeakEnergy": 0.014537042081991903, + "RelativeGain": 0.18002783252982058, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 15775, + "MeanEnergy": 0.013197788520475354, + "PeakEnergy": 0.026741608738675923, + "RelativeGain": 0.3045454030600978, + "ObservationCount": 8, + "Usable": false + }, + { + "CenterHz": 16225, + "MeanEnergy": 0.015930425224870523, + "PeakEnergy": 0.028386465156178056, + "RelativeGain": 0.3676023269732012, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 16675, + "MeanEnergy": 0.016853262201619375, + "PeakEnergy": 0.03226346177158823, + "RelativeGain": 0.38889724002675713, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 17125, + "MeanEnergy": 0.019183361719107617, + "PeakEnergy": 0.026489025614269062, + "RelativeGain": 0.4426654221447441, + "ObservationCount": 8, + "Usable": true + }, + { + "CenterHz": 17575, + "MeanEnergy": 0.020148588662393548, + "PeakEnergy": 0.026481812373487085, + "RelativeGain": 0.46493850433813055, + "ObservationCount": 8, + "Usable": true + } + ], + "UsableBandCount": 15 + }, + "CodebookPlan": { + "ReliableSymbolCount": 0, + "RecommendedOrder": 8, + "ReliableSymbolIds": [] + }, + "Symbols": [], + "Confusion": [], + "DelayHypotheses": [] + }, + { + "SourceId": "asio-ch1", + "SampleRate": 192000, + "Profile": { + "SourceId": "asio-ch1", + "SampleRate": 192000, + "FrameCount": 23, + "AnchorCount": 21, + "ClockConfidence": 0.8630578147481293, + "MeanAnchorErrorSamples": 37.291584033245464, + "Bands": [ + { + "CenterHz": 3625, + "MeanEnergy": 0.07604257070110779, + "PeakEnergy": 0.1401972838450978, + "RelativeGain": 0.12387005178726239, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 4075, + "MeanEnergy": 0.17829723823556212, + "PeakEnergy": 1.2388193344455247, + "RelativeGain": 0.29043847321488825, + "ObservationCount": 23, + "Usable": true + }, + { + "CenterHz": 4525, + "MeanEnergy": 0.6138898757522541, + "PeakEnergy": 1.2266248952300964, + "RelativeGain": 1, + "ObservationCount": 23, + "Usable": true + }, + { + "CenterHz": 4975, + "MeanEnergy": 0.07926446199507334, + "PeakEnergy": 1.148185667935492, + "RelativeGain": 0.12911837305989696, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 5425, + "MeanEnergy": 0.017644521234816076, + "PeakEnergy": 0.08597903561317417, + "RelativeGain": 0.028742160331598023, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 5875, + "MeanEnergy": 0.021699433399213774, + "PeakEnergy": 0.13289042061046374, + "RelativeGain": 0.03534743649685299, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 6325, + "MeanEnergy": 0.023856681006375436, + "PeakEnergy": 0.157188760201815, + "RelativeGain": 0.038861499348139136, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 6775, + "MeanEnergy": 0.06738464767771958, + "PeakEnergy": 0.8679642624875925, + "RelativeGain": 0.10976667043929851, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 7225, + "MeanEnergy": 0.09613414051941083, + "PeakEnergy": 0.8095031054659771, + "RelativeGain": 0.1565983481998446, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 7675, + "MeanEnergy": 0.006966987915686608, + "PeakEnergy": 0.01288522554439906, + "RelativeGain": 0.011348921347089062, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 8125, + "MeanEnergy": 0.0072653256582989635, + "PeakEnergy": 0.01459307033791203, + "RelativeGain": 0.01183490059906284, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 8575, + "MeanEnergy": 0.004937089034154387, + "PeakEnergy": 0.013829997151026315, + "RelativeGain": 0.008042304050224857, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 9025, + "MeanEnergy": 0.0035920096761061907, + "PeakEnergy": 0.010705744571023681, + "RelativeGain": 0.0058512280752383815, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 9475, + "MeanEnergy": 0.0022603096498280146, + "PeakEnergy": 0.009713029712701021, + "RelativeGain": 0.0036819464518098714, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 9925, + "MeanEnergy": 0.0018129464602818417, + "PeakEnergy": 0.0054030700107880044, + "RelativeGain": 0.002953211205935391, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 10375, + "MeanEnergy": 0.0014427400191521856, + "PeakEnergy": 0.00634236456535317, + "RelativeGain": 0.0023501609590551847, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 10825, + "MeanEnergy": 0.0011374093746133074, + "PeakEnergy": 0.004264435318383495, + "RelativeGain": 0.0018527905729338477, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 11275, + "MeanEnergy": 0.0010757061746231808, + "PeakEnergy": 0.0029966445065518587, + "RelativeGain": 0.0017522787345287654, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 11725, + "MeanEnergy": 0.0010824555915236204, + "PeakEnergy": 0.0024713942718322312, + "RelativeGain": 0.0017632732421221167, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 12175, + "MeanEnergy": 0.0007859625349032106, + "PeakEnergy": 0.0021708548358275064, + "RelativeGain": 0.0012802989036756805, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 12625, + "MeanEnergy": 0.0006357850515006968, + "PeakEnergy": 0.0016578989419019621, + "RelativeGain": 0.001035666292299759, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 13075, + "MeanEnergy": 0.0006138442164321321, + "PeakEnergy": 0.0016684254196232822, + "RelativeGain": 0.0009999256229465162, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 13525, + "MeanEnergy": 0.0005673541958751057, + "PeakEnergy": 0.0008959942935431642, + "RelativeGain": 0.0009241953944587797, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 13975, + "MeanEnergy": 0.00042043036956721555, + "PeakEnergy": 0.0010401193764853743, + "RelativeGain": 0.0006848628494679517, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 14425, + "MeanEnergy": 0.00048328668908086055, + "PeakEnergy": 0.0013801185419960926, + "RelativeGain": 0.0007872530695976151, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 14875, + "MeanEnergy": 0.000464296823345008, + "PeakEnergy": 0.0008551336522606569, + "RelativeGain": 0.0007563194013845947, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 15325, + "MeanEnergy": 0.000482258742316937, + "PeakEnergy": 0.0013037789603530822, + "RelativeGain": 0.0007855785888730976, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 15775, + "MeanEnergy": 0.0003753288551728027, + "PeakEnergy": 0.0010527771239291612, + "RelativeGain": 0.0006113944373376068, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 16225, + "MeanEnergy": 0.0004655070469994536, + "PeakEnergy": 0.0007271438229257128, + "RelativeGain": 0.000758290803263413, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 16675, + "MeanEnergy": 0.0004362942277345238, + "PeakEnergy": 0.001217409576080948, + "RelativeGain": 0.0007107043868411961, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 17125, + "MeanEnergy": 0.0005625077108242511, + "PeakEnergy": 0.0013332699699953883, + "RelativeGain": 0.0009163006803703355, + "ObservationCount": 23, + "Usable": false + }, + { + "CenterHz": 17575, + "MeanEnergy": 0.000503526700159773, + "PeakEnergy": 0.001054670204701983, + "RelativeGain": 0.0008202231703898957, + "ObservationCount": 23, + "Usable": false + } + ], + "UsableBandCount": 2 + }, + "CodebookPlan": { + "ReliableSymbolCount": 2, + "RecommendedOrder": 8, + "ReliableSymbolIds": [ + 3, + 8 + ] + }, + "Symbols": [ + { + "SymbolId": 1, + "Reliability": 0.02319056845550582, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4468.75, + "MeanEnergy": 0.9902022429801735, + "MeanTimingResidualSamples": 807.8313302832285, + "TimingResidualSpreadSamples": 208.19093681468166, + "MeanPhaseRadians": -1.1959642801490071, + "ObservationCount": 8 + }, + { + "SymbolId": 2, + "Reliability": 0.16743780377184878, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4475, + "MeanEnergy": 0.9196472718940074, + "MeanTimingResidualSamples": -106.64683404730425, + "TimingResidualSpreadSamples": 186.34542688028313, + "MeanPhaseRadians": 1.008577262190213, + "ObservationCount": 9 + }, + { + "SymbolId": 3, + "Reliability": 1.148185667935492, + "ExpectedCenterHz": 4975, + "ObservedCenterHz": 4975, + "MeanEnergy": 1.148185667935492, + "MeanTimingResidualSamples": -22.558693408966064, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -0.5902763444645657, + "ObservationCount": 1 + }, + { + "SymbolId": 4, + "Reliability": 0, + "ExpectedCenterHz": 5425, + "ObservedCenterHz": 4975, + "MeanEnergy": 0.30966049519505534, + "MeanTimingResidualSamples": -720.2603219151497, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": 2.5499560816611844, + "ObservationCount": 1 + }, + { + "SymbolId": 6, + "Reliability": 0, + "ExpectedCenterHz": 6325, + "ObservedCenterHz": 6775, + "MeanEnergy": 0.8679642624875925, + "MeanTimingResidualSamples": 896.3364210426807, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": 3.098070627204246, + "ObservationCount": 1 + }, + { + "SymbolId": 7, + "Reliability": 0, + "ExpectedCenterHz": 6775, + "ObservedCenterHz": 7225, + "MeanEnergy": 0.5299773190124039, + "MeanTimingResidualSamples": 1098.634792521596, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": 0.19503307191358674, + "ObservationCount": 1 + }, + { + "SymbolId": 8, + "Reliability": 0.6732785913925556, + "ExpectedCenterHz": 7225, + "ObservedCenterHz": 7225, + "MeanEnergy": 0.6732785913925556, + "MeanTimingResidualSamples": -8.06683599948883, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": 2.6277982959406794, + "ObservationCount": 1 + } + ], + "Confusion": [ + { + "EventIndex": 2975, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.6320307152592042, + "TimingResidualSamples": 969.2773541212082, + "Confidence": 0.6320307152592042, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -1.6624052502584878 + }, + { + "EventIndex": 2976, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.8652845939929102, + "TimingResidualSamples": 2.7101446241140366, + "Confidence": 0.8652845939929102, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0180307422947092 + }, + { + "EventIndex": 2977, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.8922343380058734, + "TimingResidualSamples": 2.142935112118721, + "Confidence": 0.8922343380058734, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0220387933489175 + }, + { + "EventIndex": 2978, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 1.2266248952300964, + "TimingResidualSamples": 902.5757256150246, + "Confidence": 1.2266248952300964, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -0.7101835897546481 + }, + { + "EventIndex": 2979, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.9053756459646586, + "TimingResidualSamples": 1.008516103029251, + "Confidence": 0.9053756459646586, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0334368755807082 + }, + { + "EventIndex": 2980, + "ExpectedSymbolId": 3, + "ObservedSymbolId": 3, + "ExpectedCenterHz": 4975, + "ObservedCenterHz": 4975, + "ObservedEnergy": 1.148185667935492, + "TimingResidualSamples": -22.558693408966064, + "Confidence": 1.148185667935492, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -0.5902763444645656 + }, + { + "EventIndex": 2981, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 1.1922110739849172, + "TimingResidualSamples": 900.8740970939398, + "Confidence": 1.1922110739849172, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -0.7107129185351657 + }, + { + "EventIndex": 2982, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.8953269035780607, + "TimingResidualSamples": -0.6931124180555344, + "Confidence": 0.8953269035780607, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0251732541617515 + }, + { + "EventIndex": 2983, + "ExpectedSymbolId": 4, + "ObservedSymbolId": 3, + "ExpectedCenterHz": 5425, + "ObservedCenterHz": 4975, + "ObservedEnergy": 0.30966049519505534, + "TimingResidualSamples": -720.2603219151497, + "Confidence": 0.30966049519505534, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 2.5499560816611844 + }, + { + "EventIndex": 2984, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.6198462152151802, + "TimingResidualSamples": 964.2498763252806, + "Confidence": 0.6198462152151802, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -1.6367658651449744 + }, + { + "EventIndex": 2985, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.8629764995587222, + "TimingResidualSamples": -2.39474093914032, + "Confidence": 0.8629764995587222, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0292647496608314 + }, + { + "EventIndex": 2987, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.6170315850664282, + "TimingResidualSamples": 962.4708400517702, + "Confidence": 0.6170315850664282, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -1.6334918021334714 + }, + { + "EventIndex": 2988, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.8633924891830707, + "TimingResidualSamples": -4.096369445323944, + "Confidence": 0.8633924891830707, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0289449583188552 + }, + { + "EventIndex": 2989, + "ExpectedSymbolId": 6, + "ObservedSymbolId": 7, + "ExpectedCenterHz": 6325, + "ObservedCenterHz": 6775, + "ObservedEnergy": 0.8679642624875925, + "TimingResidualSamples": 896.3364210426807, + "Confidence": 0.8679642624875925, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 3.098070627204246 + }, + { + "EventIndex": 2990, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 1.1921949851942821, + "TimingResidualSamples": 895.7692115306854, + "Confidence": 1.1921949851942821, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -0.703020204805768 + }, + { + "EventIndex": 2991, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.881859333373252, + "TimingResidualSamples": -5.7979979664087296, + "Confidence": 0.881859333373252, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0345538888401813 + }, + { + "EventIndex": 2992, + "ExpectedSymbolId": 7, + "ObservedSymbolId": 8, + "ExpectedCenterHz": 6775, + "ObservedCenterHz": 7225, + "ObservedEnergy": 0.5299773190124039, + "TimingResidualSamples": 1098.634792521596, + "Confidence": 0.5299773190124039, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 0.19503307191358674 + }, + { + "EventIndex": 2993, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.2388193344455247, + "TimingResidualSamples": -24.9324169754982, + "Confidence": 1.2388193344455247, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -1.8128993540776832 + }, + { + "EventIndex": 2994, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 0.9011319584205876, + "TimingResidualSamples": -7.499626487493515, + "Confidence": 0.9011319584205876, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 1.0288652611619757 + }, + { + "EventIndex": 2995, + "ExpectedSymbolId": 8, + "ObservedSymbolId": 8, + "ExpectedCenterHz": 7225, + "ObservedCenterHz": 7225, + "ObservedEnergy": 0.6732785913925556, + "TimingResidualSamples": -8.06683599948883, + "Confidence": 0.6732785913925556, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": 2.6277982959406794 + }, + { + "EventIndex": 2996, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4525, + "ObservedEnergy": 1.202859139445754, + "TimingResidualSamples": 892.365954503417, + "Confidence": 1.202859139445754, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -0.7043348957346999 + }, + { + "EventIndex": 2997, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.209243684968931, + "TimingResidualSamples": -945.2012550085783, + "Confidence": 1.209243684968931, + "DelayHypothesisSamples": -71375003.58864856, + "PhaseRadians": -1.9807483218485091 + } + ], + "DelayHypotheses": [] + }, + { + "SourceId": "asio-ch2", + "SampleRate": 192000, + "Profile": { + "SourceId": "asio-ch2", + "SampleRate": 192000, + "FrameCount": 24, + "AnchorCount": 22, + "ClockConfidence": 0.9945090136670505, + "MeanAnchorErrorSamples": 1.411854858157612, + "Bands": [ + { + "CenterHz": 3625, + "MeanEnergy": 0.6624755521547112, + "PeakEnergy": 1.9831606924956897, + "RelativeGain": 0.8944231516807526, + "ObservationCount": 24, + "Usable": true + }, + { + "CenterHz": 4075, + "MeanEnergy": 0.7406735289776681, + "PeakEnergy": 1.9831852678192377, + "RelativeGain": 1, + "ObservationCount": 24, + "Usable": true + }, + { + "CenterHz": 4525, + "MeanEnergy": 0.08429458472689423, + "PeakEnergy": 1.98317479150165, + "RelativeGain": 0.11380801585179343, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 4975, + "MeanEnergy": 0.08337506164059473, + "PeakEnergy": 1.9831581977468316, + "RelativeGain": 0.11256654703952373, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 5425, + "MeanEnergy": 0.08321944241002051, + "PeakEnergy": 1.9831595137173625, + "RelativeGain": 0.11235644201418414, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 5875, + "MeanEnergy": 0.08310533327536378, + "PeakEnergy": 1.983165098459816, + "RelativeGain": 0.11220238070350894, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 6325, + "MeanEnergy": 0.0830356303720685, + "PeakEnergy": 1.9831633177029786, + "RelativeGain": 0.11210827324513725, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 6775, + "MeanEnergy": 0.08291350719729566, + "PeakEnergy": 1.9831607953145476, + "RelativeGain": 0.11194339199854889, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 7225, + "MeanEnergy": 0.00026026652816593665, + "PeakEnergy": 0.002720711271139252, + "RelativeGain": 0.0003513916968589057, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 7675, + "MeanEnergy": 0.0001383844467230625, + "PeakEnergy": 0.0012441852619940823, + "RelativeGain": 0.0001868359558010273, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 8125, + "MeanEnergy": 0.00010149909684072828, + "PeakEnergy": 0.001138369491888335, + "RelativeGain": 0.00013703621483654853, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 8575, + "MeanEnergy": 7.942045739380608E-05, + "PeakEnergy": 0.0009847181166187187, + "RelativeGain": 0.00010722734684933052, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 9025, + "MeanEnergy": 6.932803696279827E-05, + "PeakEnergy": 0.0009655563464050287, + "RelativeGain": 9.360134290000874E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 9475, + "MeanEnergy": 5.938080471592461E-05, + "PeakEnergy": 0.0008718621230498649, + "RelativeGain": 8.017136078548176E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 9925, + "MeanEnergy": 5.2521749034327335E-05, + "PeakEnergy": 0.00080611693950029, + "RelativeGain": 7.091079534976996E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 10375, + "MeanEnergy": 4.889393044360893E-05, + "PeakEnergy": 0.0007887016857020034, + "RelativeGain": 6.601279582800254E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 10825, + "MeanEnergy": 4.3700374122193164E-05, + "PeakEnergy": 0.0007147968712550945, + "RelativeGain": 5.9000858559791686E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 11275, + "MeanEnergy": 4.111038749022518E-05, + "PeakEnergy": 0.0006912043825421561, + "RelativeGain": 5.5504059321478315E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 11725, + "MeanEnergy": 3.8716235531850445E-05, + "PeakEnergy": 0.0006644785111916305, + "RelativeGain": 5.2271660883155135E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 12175, + "MeanEnergy": 3.5572873283649724E-05, + "PeakEnergy": 0.00061454059289895, + "RelativeGain": 4.802773677189464E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 12625, + "MeanEnergy": 3.432883628711727E-05, + "PeakEnergy": 0.0006063088423829821, + "RelativeGain": 4.634813442637871E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 13075, + "MeanEnergy": 3.222173250933059E-05, + "PeakEnergy": 0.0005737700002909321, + "RelativeGain": 4.3503286196558673E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 13525, + "MeanEnergy": 3.047105236971771E-05, + "PeakEnergy": 0.0005456783063992556, + "RelativeGain": 4.113965354179201E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 13975, + "MeanEnergy": 2.980150164862842E-05, + "PeakEnergy": 0.0005382715392447802, + "RelativeGain": 4.023567804530916E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 14425, + "MeanEnergy": 2.8434512414593615E-05, + "PeakEnergy": 0.0005069691775315045, + "RelativeGain": 3.839007511695607E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 14875, + "MeanEnergy": 2.826022879847868E-05, + "PeakEnergy": 0.0004940107220304877, + "RelativeGain": 3.8154770884664284E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 15325, + "MeanEnergy": 2.8804816579022977E-05, + "PeakEnergy": 0.00048205186326239093, + "RelativeGain": 3.8890031102882126E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 15775, + "MeanEnergy": 3.0095438998520765E-05, + "PeakEnergy": 0.0004574916715202174, + "RelativeGain": 4.063252947632771E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 16225, + "MeanEnergy": 3.519857204648069E-05, + "PeakEnergy": 0.00045171867786498104, + "RelativeGain": 4.752238424811042E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 16675, + "MeanEnergy": 5.010758773662952E-05, + "PeakEnergy": 0.0006879073880343965, + "RelativeGain": 6.765138185212543E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 17125, + "MeanEnergy": 0.00013397831817283423, + "PeakEnergy": 0.0027218985136102307, + "RelativeGain": 0.00018088714248740725, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 17575, + "MeanEnergy": 0.0826517522937974, + "PeakEnergy": 1.9831585964902498, + "RelativeGain": 0.11158999081265858, + "ObservationCount": 24, + "Usable": false + } + ], + "UsableBandCount": 2 + }, + "CodebookPlan": { + "ReliableSymbolCount": 9, + "RecommendedOrder": 6, + "ReliableSymbolIds": [ + 2, + 5, + 6, + 7, + 4, + 31, + 3, + 0, + 1 + ] + }, + "Symbols": [ + { + "SymbolId": 0, + "Reliability": 1.9664832701324948, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "MeanEnergy": 1.9831606924956895, + "MeanTimingResidualSamples": -3.323582007240475, + "TimingResidualSpreadSamples": 0.40708013416224276, + "MeanPhaseRadians": -1.8543981407092287, + "ObservationCount": 8 + }, + { + "SymbolId": 1, + "Reliability": 1.9550716359955316, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "MeanEnergy": 1.9724028422080755, + "MeanTimingResidualSamples": -3.3198127467403538, + "TimingResidualSpreadSamples": 0.42550762994344243, + "MeanPhaseRadians": -1.895408858286294, + "ObservationCount": 9 + }, + { + "SymbolId": 2, + "Reliability": 1.98317479150165, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "MeanEnergy": 1.98317479150165, + "MeanTimingResidualSamples": 2.0495747824315913, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -1.1988765139742783, + "ObservationCount": 1 + }, + { + "SymbolId": 3, + "Reliability": 1.9831581977468316, + "ExpectedCenterHz": 4975, + "ObservedCenterHz": 4975, + "MeanEnergy": 1.9831581977468316, + "MeanTimingResidualSamples": 1.846034715330461, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -1.1546958595762624, + "ObservationCount": 1 + }, + { + "SymbolId": 4, + "Reliability": 1.9831595137173625, + "ExpectedCenterHz": 5425, + "ObservedCenterHz": 5425, + "MeanEnergy": 1.9831595137173625, + "MeanTimingResidualSamples": -3.357505351770669, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -2.031073191995889, + "ObservationCount": 1 + }, + { + "SymbolId": 5, + "Reliability": 1.983165098459816, + "ExpectedCenterHz": 5875, + "ObservedCenterHz": 5875, + "MeanEnergy": 1.983165098459816, + "MeanTimingResidualSamples": -3.5610454188426957, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -2.075246822614443, + "ObservationCount": 1 + }, + { + "SymbolId": 6, + "Reliability": 1.9831633177029786, + "ExpectedCenterHz": 6325, + "ObservedCenterHz": 6325, + "MeanEnergy": 1.9831633177029786, + "MeanTimingResidualSamples": 1.235414514085278, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -1.0221685852666889, + "ObservationCount": 1 + }, + { + "SymbolId": 7, + "Reliability": 1.9831607953145476, + "ExpectedCenterHz": 6775, + "ObservedCenterHz": 6775, + "MeanEnergy": 1.9831607953145476, + "MeanTimingResidualSamples": 1.0318744470132515, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -0.9779871637154909, + "ObservationCount": 1 + }, + { + "SymbolId": 31, + "Reliability": 1.9831585964902498, + "ExpectedCenterHz": 17575, + "ObservedCenterHz": 17575, + "MeanEnergy": 1.9831585964902498, + "MeanTimingResidualSamples": -2.5433450834352698, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": 3.0592955096959598, + "ObservationCount": 1 + } + ], + "Confusion": [ + { + "EventIndex": 0, + "ExpectedSymbolId": 31, + "ObservedSymbolId": 31, + "ExpectedCenterHz": 17575, + "ObservedCenterHz": 17575, + "ObservedEnergy": 1.9831585964902498, + "TimingResidualSamples": -2.5433450834352698, + "Confidence": 1.9831585964902498, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": 3.0592955096959598 + }, + { + "EventIndex": 1, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -2.6111917724629166, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 2, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -2.6790384614942013, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 3, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -2.746885150525486, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 4, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -2.814731839534943, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 5, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -2.8825785285444, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 6, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 1.98317479150165, + "TimingResidualSamples": 2.0495747824315913, + "Confidence": 1.98317479150165, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.1988765139742783 + }, + { + "EventIndex": 7, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.0182719065924175, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 8, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.08611859564553, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 9, + "ExpectedSymbolId": 3, + "ObservedSymbolId": 3, + "ExpectedCenterHz": 4975, + "ObservedCenterHz": 4975, + "ObservedEnergy": 1.9831581977468316, + "TimingResidualSamples": 1.846034715330461, + "Confidence": 1.9831581977468316, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.1546958595762624 + }, + { + "EventIndex": 10, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.2218119737226516, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 11, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.2896586627466604, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 12, + "ExpectedSymbolId": 4, + "ObservedSymbolId": 4, + "ExpectedCenterHz": 5425, + "ObservedCenterHz": 5425, + "ObservedEnergy": 1.9831595137173625, + "TimingResidualSamples": -3.357505351770669, + "Confidence": 1.9831595137173625, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -2.031073191995889 + }, + { + "EventIndex": 13, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.425352040794678, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 14, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.493198729818687, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 15, + "ExpectedSymbolId": 5, + "ObservedSymbolId": 5, + "ExpectedCenterHz": 5875, + "ObservedCenterHz": 5875, + "ObservedEnergy": 1.983165098459816, + "TimingResidualSamples": -3.5610454188426957, + "Confidence": 1.983165098459816, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -2.075246822614443 + }, + { + "EventIndex": 16, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.6288921078667045, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 17, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.6967387968907133, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 18, + "ExpectedSymbolId": 6, + "ObservedSymbolId": 6, + "ExpectedCenterHz": 6325, + "ObservedCenterHz": 6325, + "ObservedEnergy": 1.9831633177029786, + "TimingResidualSamples": 1.235414514085278, + "Confidence": 1.9831633177029786, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.0221685852666889 + }, + { + "EventIndex": 19, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.832432174938731, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 20, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.9002788639627397, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 21, + "ExpectedSymbolId": 7, + "ObservedSymbolId": 7, + "ExpectedCenterHz": 6775, + "ObservedCenterHz": 6775, + "ObservedEnergy": 1.9831607953145476, + "TimingResidualSamples": 1.0318744470132515, + "Confidence": 1.9831607953145476, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -0.9779871637154909 + }, + { + "EventIndex": 22, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -4.035972242010757, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 23, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.8861434373187795, + "TimingResidualSamples": -4.103818931034766, + "Confidence": 1.8861434373187795, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8703739684798266 + } + ], + "DelayHypotheses": [ + { + "DelaySamples": 1235.9868702719023, + "BinShift": 0, + "SupportCount": 13, + "Confidence": 0.9976303970711632, + "MeanResidualSamples": 0.2770071918989393 + }, + { + "DelaySamples": 1235.2760457299416, + "BinShift": 0, + "SupportCount": 5, + "Confidence": 0.9938390323850242, + "MeanResidualSamples": 0.1139824375626631 + }, + { + "DelaySamples": 1240.947727951323, + "BinShift": 0, + "SupportCount": 2, + "Confidence": 0.9948364888123916, + "MeanResidualSamples": 0.10177003353601322 + }, + { + "DelaySamples": 1240.1335676829913, + "BinShift": 0, + "SupportCount": 2, + "Confidence": 0.9948364888123916, + "MeanResidualSamples": 0.10177003353601322 + } + ] + }, + { + "SourceId": "asio-ch3", + "SampleRate": 192000, + "Profile": { + "SourceId": "asio-ch3", + "SampleRate": 192000, + "FrameCount": 24, + "AnchorCount": 22, + "ClockConfidence": 0.9945090136670505, + "MeanAnchorErrorSamples": 1.411854858157612, + "Bands": [ + { + "CenterHz": 3625, + "MeanEnergy": 0.6624755521547112, + "PeakEnergy": 1.9831606924956897, + "RelativeGain": 0.8944231516807526, + "ObservationCount": 24, + "Usable": true + }, + { + "CenterHz": 4075, + "MeanEnergy": 0.7406735289776681, + "PeakEnergy": 1.9831852678192377, + "RelativeGain": 1, + "ObservationCount": 24, + "Usable": true + }, + { + "CenterHz": 4525, + "MeanEnergy": 0.08429458472689423, + "PeakEnergy": 1.98317479150165, + "RelativeGain": 0.11380801585179343, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 4975, + "MeanEnergy": 0.08337506164059473, + "PeakEnergy": 1.9831581977468316, + "RelativeGain": 0.11256654703952373, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 5425, + "MeanEnergy": 0.08321944241002051, + "PeakEnergy": 1.9831595137173625, + "RelativeGain": 0.11235644201418414, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 5875, + "MeanEnergy": 0.08310533327536378, + "PeakEnergy": 1.983165098459816, + "RelativeGain": 0.11220238070350894, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 6325, + "MeanEnergy": 0.0830356303720685, + "PeakEnergy": 1.9831633177029786, + "RelativeGain": 0.11210827324513725, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 6775, + "MeanEnergy": 0.08291350719729566, + "PeakEnergy": 1.9831607953145476, + "RelativeGain": 0.11194339199854889, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 7225, + "MeanEnergy": 0.00026026652816593665, + "PeakEnergy": 0.002720711271139252, + "RelativeGain": 0.0003513916968589057, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 7675, + "MeanEnergy": 0.0001383844467230625, + "PeakEnergy": 0.0012441852619940823, + "RelativeGain": 0.0001868359558010273, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 8125, + "MeanEnergy": 0.00010149909684072828, + "PeakEnergy": 0.001138369491888335, + "RelativeGain": 0.00013703621483654853, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 8575, + "MeanEnergy": 7.942045739380608E-05, + "PeakEnergy": 0.0009847181166187187, + "RelativeGain": 0.00010722734684933052, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 9025, + "MeanEnergy": 6.932803696279827E-05, + "PeakEnergy": 0.0009655563464050287, + "RelativeGain": 9.360134290000874E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 9475, + "MeanEnergy": 5.938080471592461E-05, + "PeakEnergy": 0.0008718621230498649, + "RelativeGain": 8.017136078548176E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 9925, + "MeanEnergy": 5.2521749034327335E-05, + "PeakEnergy": 0.00080611693950029, + "RelativeGain": 7.091079534976996E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 10375, + "MeanEnergy": 4.889393044360893E-05, + "PeakEnergy": 0.0007887016857020034, + "RelativeGain": 6.601279582800254E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 10825, + "MeanEnergy": 4.3700374122193164E-05, + "PeakEnergy": 0.0007147968712550945, + "RelativeGain": 5.9000858559791686E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 11275, + "MeanEnergy": 4.111038749022518E-05, + "PeakEnergy": 0.0006912043825421561, + "RelativeGain": 5.5504059321478315E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 11725, + "MeanEnergy": 3.8716235531850445E-05, + "PeakEnergy": 0.0006644785111916305, + "RelativeGain": 5.2271660883155135E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 12175, + "MeanEnergy": 3.5572873283649724E-05, + "PeakEnergy": 0.00061454059289895, + "RelativeGain": 4.802773677189464E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 12625, + "MeanEnergy": 3.432883628711727E-05, + "PeakEnergy": 0.0006063088423829821, + "RelativeGain": 4.634813442637871E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 13075, + "MeanEnergy": 3.222173250933059E-05, + "PeakEnergy": 0.0005737700002909321, + "RelativeGain": 4.3503286196558673E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 13525, + "MeanEnergy": 3.047105236971771E-05, + "PeakEnergy": 0.0005456783063992556, + "RelativeGain": 4.113965354179201E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 13975, + "MeanEnergy": 2.980150164862842E-05, + "PeakEnergy": 0.0005382715392447802, + "RelativeGain": 4.023567804530916E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 14425, + "MeanEnergy": 2.8434512414593615E-05, + "PeakEnergy": 0.0005069691775315045, + "RelativeGain": 3.839007511695607E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 14875, + "MeanEnergy": 2.826022879847868E-05, + "PeakEnergy": 0.0004940107220304877, + "RelativeGain": 3.8154770884664284E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 15325, + "MeanEnergy": 2.8804816579022977E-05, + "PeakEnergy": 0.00048205186326239093, + "RelativeGain": 3.8890031102882126E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 15775, + "MeanEnergy": 3.0095438998520765E-05, + "PeakEnergy": 0.0004574916715202174, + "RelativeGain": 4.063252947632771E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 16225, + "MeanEnergy": 3.519857204648069E-05, + "PeakEnergy": 0.00045171867786498104, + "RelativeGain": 4.752238424811042E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 16675, + "MeanEnergy": 5.010758773662952E-05, + "PeakEnergy": 0.0006879073880343965, + "RelativeGain": 6.765138185212543E-05, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 17125, + "MeanEnergy": 0.00013397831817283423, + "PeakEnergy": 0.0027218985136102307, + "RelativeGain": 0.00018088714248740725, + "ObservationCount": 24, + "Usable": false + }, + { + "CenterHz": 17575, + "MeanEnergy": 0.0826517522937974, + "PeakEnergy": 1.9831585964902498, + "RelativeGain": 0.11158999081265858, + "ObservationCount": 24, + "Usable": false + } + ], + "UsableBandCount": 2 + }, + "CodebookPlan": { + "ReliableSymbolCount": 9, + "RecommendedOrder": 6, + "ReliableSymbolIds": [ + 2, + 5, + 6, + 7, + 4, + 31, + 3, + 0, + 1 + ] + }, + "Symbols": [ + { + "SymbolId": 0, + "Reliability": 1.9664832701324948, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "MeanEnergy": 1.9831606924956895, + "MeanTimingResidualSamples": -3.323582007240475, + "TimingResidualSpreadSamples": 0.40708013416224276, + "MeanPhaseRadians": -1.8543981407092287, + "ObservationCount": 8 + }, + { + "SymbolId": 1, + "Reliability": 1.9550716359955316, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "MeanEnergy": 1.9724028422080755, + "MeanTimingResidualSamples": -3.3198127467403538, + "TimingResidualSpreadSamples": 0.42550762994344243, + "MeanPhaseRadians": -1.895408858286294, + "ObservationCount": 9 + }, + { + "SymbolId": 2, + "Reliability": 1.98317479150165, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "MeanEnergy": 1.98317479150165, + "MeanTimingResidualSamples": 2.0495747824315913, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -1.1988765139742783, + "ObservationCount": 1 + }, + { + "SymbolId": 3, + "Reliability": 1.9831581977468316, + "ExpectedCenterHz": 4975, + "ObservedCenterHz": 4975, + "MeanEnergy": 1.9831581977468316, + "MeanTimingResidualSamples": 1.846034715330461, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -1.1546958595762624, + "ObservationCount": 1 + }, + { + "SymbolId": 4, + "Reliability": 1.9831595137173625, + "ExpectedCenterHz": 5425, + "ObservedCenterHz": 5425, + "MeanEnergy": 1.9831595137173625, + "MeanTimingResidualSamples": -3.357505351770669, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -2.031073191995889, + "ObservationCount": 1 + }, + { + "SymbolId": 5, + "Reliability": 1.983165098459816, + "ExpectedCenterHz": 5875, + "ObservedCenterHz": 5875, + "MeanEnergy": 1.983165098459816, + "MeanTimingResidualSamples": -3.5610454188426957, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -2.075246822614443, + "ObservationCount": 1 + }, + { + "SymbolId": 6, + "Reliability": 1.9831633177029786, + "ExpectedCenterHz": 6325, + "ObservedCenterHz": 6325, + "MeanEnergy": 1.9831633177029786, + "MeanTimingResidualSamples": 1.235414514085278, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -1.0221685852666889, + "ObservationCount": 1 + }, + { + "SymbolId": 7, + "Reliability": 1.9831607953145476, + "ExpectedCenterHz": 6775, + "ObservedCenterHz": 6775, + "MeanEnergy": 1.9831607953145476, + "MeanTimingResidualSamples": 1.0318744470132515, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": -0.9779871637154909, + "ObservationCount": 1 + }, + { + "SymbolId": 31, + "Reliability": 1.9831585964902498, + "ExpectedCenterHz": 17575, + "ObservedCenterHz": 17575, + "MeanEnergy": 1.9831585964902498, + "MeanTimingResidualSamples": -2.5433450834352698, + "TimingResidualSpreadSamples": 0, + "MeanPhaseRadians": 3.0592955096959598, + "ObservationCount": 1 + } + ], + "Confusion": [ + { + "EventIndex": 0, + "ExpectedSymbolId": 31, + "ObservedSymbolId": 31, + "ExpectedCenterHz": 17575, + "ObservedCenterHz": 17575, + "ObservedEnergy": 1.9831585964902498, + "TimingResidualSamples": -2.5433450834352698, + "Confidence": 1.9831585964902498, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": 3.0592955096959598 + }, + { + "EventIndex": 1, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -2.6111917724629166, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 2, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -2.6790384614942013, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 3, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -2.746885150525486, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 4, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -2.814731839534943, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 5, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -2.8825785285444, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 6, + "ExpectedSymbolId": 2, + "ObservedSymbolId": 2, + "ExpectedCenterHz": 4525, + "ObservedCenterHz": 4525, + "ObservedEnergy": 1.98317479150165, + "TimingResidualSamples": 2.0495747824315913, + "Confidence": 1.98317479150165, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.1988765139742783 + }, + { + "EventIndex": 7, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.0182719065924175, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 8, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.08611859564553, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 9, + "ExpectedSymbolId": 3, + "ObservedSymbolId": 3, + "ExpectedCenterHz": 4975, + "ObservedCenterHz": 4975, + "ObservedEnergy": 1.9831581977468316, + "TimingResidualSamples": 1.846034715330461, + "Confidence": 1.9831581977468316, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.1546958595762624 + }, + { + "EventIndex": 10, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.2218119737226516, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 11, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.2896586627466604, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 12, + "ExpectedSymbolId": 4, + "ObservedSymbolId": 4, + "ExpectedCenterHz": 5425, + "ObservedCenterHz": 5425, + "ObservedEnergy": 1.9831595137173625, + "TimingResidualSamples": -3.357505351770669, + "Confidence": 1.9831595137173625, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -2.031073191995889 + }, + { + "EventIndex": 13, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.425352040794678, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 14, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.493198729818687, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 15, + "ExpectedSymbolId": 5, + "ObservedSymbolId": 5, + "ExpectedCenterHz": 5875, + "ObservedCenterHz": 5875, + "ObservedEnergy": 1.983165098459816, + "TimingResidualSamples": -3.5610454188426957, + "Confidence": 1.983165098459816, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -2.075246822614443 + }, + { + "EventIndex": 16, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.6288921078667045, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 17, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.6967387968907133, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 18, + "ExpectedSymbolId": 6, + "ObservedSymbolId": 6, + "ExpectedCenterHz": 6325, + "ObservedCenterHz": 6325, + "ObservedEnergy": 1.9831633177029786, + "TimingResidualSamples": 1.235414514085278, + "Confidence": 1.9831633177029786, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.0221685852666889 + }, + { + "EventIndex": 19, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -3.832432174938731, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 20, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.9831852678192377, + "TimingResidualSamples": -3.9002788639627397, + "Confidence": 1.9831852678192377, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8985378977427367 + }, + { + "EventIndex": 21, + "ExpectedSymbolId": 7, + "ObservedSymbolId": 7, + "ExpectedCenterHz": 6775, + "ObservedCenterHz": 6775, + "ObservedEnergy": 1.9831607953145476, + "TimingResidualSamples": 1.0318744470132515, + "Confidence": 1.9831607953145476, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -0.9779871637154909 + }, + { + "EventIndex": 22, + "ExpectedSymbolId": 0, + "ObservedSymbolId": 0, + "ExpectedCenterHz": 3625, + "ObservedCenterHz": 3625, + "ObservedEnergy": 1.9831606924956897, + "TimingResidualSamples": -4.035972242010757, + "Confidence": 1.9831606924956897, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8543981407092287 + }, + { + "EventIndex": 23, + "ExpectedSymbolId": 1, + "ObservedSymbolId": 1, + "ExpectedCenterHz": 4075, + "ObservedCenterHz": 4075, + "ObservedEnergy": 1.8861434373187795, + "TimingResidualSamples": -4.103818931034766, + "Confidence": 1.8861434373187795, + "DelayHypothesisSamples": 1238.9999232024563, + "PhaseRadians": -1.8703739684798266 + } + ], + "DelayHypotheses": [ + { + "DelaySamples": 1235.9868702719023, + "BinShift": 0, + "SupportCount": 13, + "Confidence": 0.9976303970711632, + "MeanResidualSamples": 0.2770071918989393 + }, + { + "DelaySamples": 1235.2760457299416, + "BinShift": 0, + "SupportCount": 5, + "Confidence": 0.9938390323850242, + "MeanResidualSamples": 0.1139824375626631 + }, + { + "DelaySamples": 1240.947727951323, + "BinShift": 0, + "SupportCount": 2, + "Confidence": 0.9948364888123916, + "MeanResidualSamples": 0.10177003353601322 + }, + { + "DelaySamples": 1240.1335676829913, + "BinShift": 0, + "SupportCount": 2, + "Confidence": 0.9948364888123916, + "MeanResidualSamples": 0.10177003353601322 + } + ] + } + ] +} \ No newline at end of file diff --git a/config/mimir-runtime.probes.example.json b/config/mimir-runtime.probes.example.json index ea12adf..4aefd12 100644 --- a/config/mimir-runtime.probes.example.json +++ b/config/mimir-runtime.probes.example.json @@ -2,11 +2,31 @@ "bufferSeconds": 5.0, "audioSync": { "mode": "hybrid", - "referenceSourceId": "loopback-scarlett-speakers", + "referenceSourceId": "asio-ch2", "calibrationGain": 2.0, - "watermarkGain": 0.035 + "watermarkGain": 0.035, + "calibrationModelPath": "../calibration/chirp-bin/scarlett-artifact.json" }, "streams": [ + { + "sourceId": "focusrite-asio", + "kind": "Audio", + "origin": "LocalDevice", + "enabled": true, + "adapter": "frame-events", + "command": "../native/probes/asio_audio_cadence/build/Release/asio_audio_cadence.exe", + "arguments": [ + "--set-sample-rate", + "192000", + "--emit-json-blocks" + ], + "acceptSourceIds": [ + "asio-ch0", + "asio-ch1", + "asio-ch2", + "asio-ch3" + ] + }, { "sourceId": "local-ks-probe", "kind": "Video", diff --git a/docs/audio-field.md b/docs/audio-field.md index 52a828d..61444c8 100644 --- a/docs/audio-field.md +++ b/docs/audio-field.md @@ -75,11 +75,15 @@ the de Bruijn triplet trellis. A constrained local waveform correlation around the decoded anchor delay provides the final fractional offset. The decoder keeps the full bin-energy surface for each classified chirp and aggregates it into per-band response evidence, so the same stream can calibrate timing and the -speaker/room/mic transfer function. `MimirChirpBinCalibrationProfile` is the -first named surface for that evidence: it summarizes frames, anchors, clock -confidence, anchor error, usable bands, and strongest measured bins per source. -The profile is kept even when a timing report is rejected, because a failed -sync window can still teach us which bands survived the speaker/room/mic path. +speaker/room/mic transfer function. `MimirChirpBinCalibrationModel` is the live +surface for that evidence. It stores measured usable bands per source, +expected-symbol versus observed-bin confusion observations, timing residuals, +delay hypotheses, phase summaries, and an adaptive codebook plan. The raw +profile is kept even when a timing report is rejected, because a failed sync +window can still teach us which bands survived the speaker/room/mic path. The +decoder can consume the persisted model to weight reliable symbols, downweight +dead bands, apply first-order group-delay correction, and seed global delay +hypotheses before selecting the coherent anchor path. The synthetic invariant is `dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --chirp-bin-self-test`; it renders the chirp-bin timeline, decodes by dechirp plus Goertzel bins, and @@ -123,8 +127,11 @@ buffer, and captures nonzero 4-channel `Int32LSB` input callbacks at 192 kHz. Raven also has a loopback-capable Scarlett ASIO path at 192 kHz for co-streamer/game timing evidence, but Starfire owns the heavy local alignment, soundfield, and sensor-fusion work. -The next cut is not more WASAPI probing; it is feeding those ASIO buffers -directly into `Mimir.Runtime`. +The worker now also supports `--emit-json-blocks`, which streams each ASIO +callback as sample-bearing `audio-block` events (`asio-ch0` through `asio-ch3`) +for `MimirFrameEventProcessStreamSource`. That is a continuous ASIO runtime +ingest path through the existing process boundary; the later native ABI cut +should remove stdout/base64 overhead, not change ASIO ownership. The probe also has a `--monitor-sweep` mode for measuring the acoustic ceiling. At 192 kHz, ASIO loopback detected emitted 8-40 kHz tones cleanly, but the @@ -174,12 +181,15 @@ that window. Reports carry rounded integer delay, fractional delay, and the count/confidence of timeline-symbol anchors used to derive the report. The same timeline also starts the frequency-response path. Each active chirp-bin -decode emits a calibration profile for the source, whether or not that source -earns a timing report. That is not a finished room/mic normalizer yet, but it is -the live surface that will become response-curve estimation: loopback carries -what was emitted, each mic carries what survived speaker, air, room, and -capsule, and the ratio over the continuous timeline becomes gain/phase -correction evidence. +decode can emit a persisted calibration model for the source set, whether or +not each source earns a timing report. That model is not a finished room/mic +normalizer yet, but it is the live surface that will become response-curve +estimation: loopback carries what was emitted, each mic carries what survived +speaker, air, room, and capsule, and the ratio over the continuous timeline +becomes gain/phase correction evidence. Use +`Mimir.BufferSmoke --calibrate-chirp-bin-asio-f32` to render the calibration +timeline, optionally capture it through the ASIO worker with `--capture-asio`, +compute the response/confusion/delay model, and persist it for runtime decode. `MimirAudioSynchronizationStateTracker` turns continuous observations into state. It confidence-gates reports, smooths fractional delay per source, and diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 604d5fc..508f60f 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -102,11 +102,12 @@ a named invariant that the native runtime cannot protect yet. with constrained local waveform correlation around the decoded active delay. Each classified chirp carries the full bin-energy response surface, and stream decodes aggregate that into per-band calibration evidence for frequency - response normalization. `MimirChirpBinCalibrationProfile` preserves that - evidence per source, including frames, anchors, clock confidence, anchor - error, usable band count, and strongest measured bins, even when the source - does not produce a timing report. Reports/states expose delay in microseconds - as well as fractional samples. + response normalization. `MimirChirpBinCalibrationModel` now preserves usable + bands, expected-symbol versus observed-bin confusion observations, timing + residuals, delay hypotheses, phase summaries, and an adaptive codebook plan. + The active decoder can consume that model as learned response weighting, + first-order group-delay correction, and global delay-hypothesis seeds. + Reports/states expose delay in microseconds as well as fractional samples. Hybrid emits this as low-gain half-second bursts every two seconds only while passive confidence is weak. Use `--chirp-bin-self-test` to prove the codebook/decoder and @@ -116,12 +117,14 @@ a named invariant that the native runtime cannot protect yet. - `Mimir.BufferSmoke --render-chirp-bin-f32` and `--analyze-asio-f32` provide the current active Scarlett artifact proof. A 192 kHz chirp-bin run decoded Focusrite `Loopback 1 -> Loopback 2` at `0.000 us` with 12 matched anchors and - 0.999 confidence. The ASIO analyzer now prints chirp-bin calibration profiles - for every decoded source; physical input 1 still failed pairwise timing in - the stored artifact, but it produced 14 frames, 12 anchors, 0.865 clock - confidence, and a different strongest band set. Acoustic robustness remains - separate from the clean loopback timing proof, but failed timing windows now - leave usable response evidence instead of silence. + 0.999 confidence. `--calibrate-chirp-bin-asio-f32` can render/capture/analyze + a calibration session and persist the response/confusion/delay model under + `calibration/chirp-bin/`. `--analyze-asio-f32 --calibration ...` loads that + model into the active decoder. Physical input 1 still failed pairwise timing + in the stored artifact, but it produced a useful response/confusion model + with two reliable symbols. Acoustic robustness remains separate from the clean + loopback timing proof, but failed timing windows now leave usable response + evidence instead of silence. A standalone 192 kHz synthetic receiver test recovers a 500 ms delayed audio stream to below printed microsecond precision using only the chirp-bin codebook and schedule state. diff --git a/native/probes/asio_audio_cadence/asio_audio_cadence.cpp b/native/probes/asio_audio_cadence/asio_audio_cadence.cpp index 5dc8a91..999f91e 100644 --- a/native/probes/asio_audio_cadence/asio_audio_cadence.cpp +++ b/native/probes/asio_audio_cadence/asio_audio_cadence.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,8 @@ struct CaptureState std::vector* recordedInput = nullptr; unsigned long long recordFrames = 0; unsigned long long sweepFrame = 0; + bool emitJsonBlocks = false; + std::mutex emitMutex; std::atomic callbacks{0}; std::atomic frames{0}; std::atomic nonZeroSamples{0}; @@ -219,6 +222,51 @@ void writeSample(unsigned char* sample, ASIOSampleType type, double value) } } +std::string base64Encode(const unsigned char* data, size_t size) +{ + static constexpr char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string output; + output.reserve(((size + 2) / 3) * 4); + for (size_t index = 0; index < size; index += 3) + { + const unsigned int a = data[index]; + const unsigned int b = index + 1 < size ? data[index + 1] : 0; + const unsigned int c = index + 2 < size ? data[index + 2] : 0; + const unsigned int triple = (a << 16) | (b << 8) | c; + output.push_back(alphabet[(triple >> 18) & 0x3f]); + output.push_back(alphabet[(triple >> 12) & 0x3f]); + output.push_back(index + 1 < size ? alphabet[(triple >> 6) & 0x3f] : '='); + output.push_back(index + 2 < size ? alphabet[triple & 0x3f] : '='); + } + + return output; +} + +void emitJsonAudioBlock(size_t channel, unsigned long long startFrame, const std::vector& samples) +{ + if (!g_capture.emitJsonBlocks || g_capture.sampleRate <= 0.0 || samples.empty()) + { + return; + } + + const auto timestampNs = static_cast( + (static_cast(startFrame) / g_capture.sampleRate) * 1000000000.0); + const auto encoded = base64Encode( + reinterpret_cast(samples.data()), + samples.size() * sizeof(float)); + std::lock_guard lock(g_capture.emitMutex); + std::printf( + "{\"type\":\"audio-block\",\"sourceId\":\"asio-ch%zu\",\"timestampNs\":%llu,\"sequence\":%llu,\"sampleRate\":%.0f,\"channels\":1,\"sampleFormat\":\"Float32\",\"frameCount\":%zu,\"byteLength\":%zu,\"samplesBase64\":\"%s\"}\n", + channel, + timestampNs, + startFrame / static_cast(std::max(1L, g_capture.bufferSize)), + g_capture.sampleRate, + samples.size(), + samples.size() * sizeof(float), + encoded.c_str()); + std::fflush(stdout); +} + void processOutput(long doubleBufferIndex) { const bool hasPlayback = g_capture.playbackSamples && !g_capture.playbackSamples->empty(); @@ -335,6 +383,7 @@ void processSweepInput(long doubleBufferIndex) void bufferSwitch(long doubleBufferIndex, ASIOBool) { + const auto callbackStartFrame = g_capture.frames.load(std::memory_order_relaxed); g_capture.callbacks.fetch_add(1, std::memory_order_relaxed); g_capture.frames.fetch_add(static_cast(g_capture.bufferSize), std::memory_order_relaxed); processOutput(doubleBufferIndex); @@ -357,13 +406,25 @@ void bufferSwitch(long doubleBufferIndex, ASIOBool) continue; } + std::vector emittedSamples; + if (g_capture.emitJsonBlocks) + { + emittedSamples.resize(static_cast(g_capture.bufferSize)); + } + for (long frame = 0; frame < g_capture.bufferSize; ++frame) { const auto absoluteFrame = g_capture.sweepFrame + static_cast(frame); + const auto value = static_cast(readSample(raw + frame * sampleBytes, info.type)); if (g_capture.recordedInput && absoluteFrame < g_capture.recordFrames) { (*g_capture.recordedInput)[static_cast(absoluteFrame) * static_cast(g_capture.inputCount) + channel] = - static_cast(readSample(raw + frame * sampleBytes, info.type)); + value; + } + + if (g_capture.emitJsonBlocks) + { + emittedSamples[static_cast(frame)] = value; } if (sampleIsNonZero(raw + frame * sampleBytes, sampleBytes)) @@ -371,6 +432,8 @@ void bufferSwitch(long doubleBufferIndex, ASIOBool) ++nonZero; } } + + emitJsonAudioBlock(channel, callbackStartFrame, emittedSamples); } g_capture.nonZeroSamples.fetch_add(nonZero, std::memory_order_relaxed); } @@ -509,6 +572,7 @@ int main(int argc, char** argv) const auto captureSeconds = doubleOption(argc, argv, "--capture-seconds", 0.0); const auto setSampleRate = doubleOption(argc, argv, "--set-sample-rate", 0.0); const bool monitorSweep = hasArg(argc, argv, "--monitor-sweep"); + const bool emitJsonBlocks = hasArg(argc, argv, "--emit-json-blocks"); const char* playFloat32MonoPath = option(argc, argv, "--play-f32-mono", nullptr); const char* recordFloat32InterleavedPath = option(argc, argv, "--record-f32-interleaved", nullptr); const auto playGain = doubleOption(argc, argv, "--play-gain", 1.0); @@ -670,7 +734,7 @@ int main(int argc, char** argv) std::printf("asio playback-f32-mono: path=%s samples=%zu gain=%.5f\n", playFloat32MonoPath, playbackSamples.size(), playGain); } - if (captureSeconds > 0.0 || monitorSweep || !playbackSamples.empty()) + if (captureSeconds > 0.0 || monitorSweep || !playbackSamples.empty() || emitJsonBlocks) { std::vector sweepFrequencies; if (monitorSweep) @@ -681,6 +745,10 @@ int main(int argc, char** argv) auto runSeconds = monitorSweep ? (sweepToneSeconds + sweepGapSeconds) * static_cast(sweepFrequencies.size()) + 0.25 : captureSeconds; + if (runSeconds <= 0.0 && emitJsonBlocks) + { + runSeconds = 86400.0; + } if (runSeconds <= 0.0 && !playbackSamples.empty() && currentRate > 0.0) { runSeconds = static_cast(playbackSamples.size()) / currentRate; @@ -739,8 +807,8 @@ int main(int argc, char** argv) { const auto requestedFrames = static_cast(std::ceil(runSeconds * currentRate)) + static_cast(preferredSize); recordedInput.assign(static_cast(requestedFrames) * static_cast(inputs), 0.0f); - g_capture.recordedInput = &recordedInput; - g_capture.recordFrames = requestedFrames; + g_capture.recordedInput = &recordedInput; + g_capture.recordFrames = requestedFrames; } g_capture.bufferSize = preferredSize; g_capture.inputCount = inputs; @@ -752,6 +820,7 @@ int main(int argc, char** argv) g_capture.sweepGain = sweepGain; g_capture.playbackSamples = playbackSamples.empty() ? nullptr : &playbackSamples; g_capture.playbackGain = playGain; + g_capture.emitJsonBlocks = emitJsonBlocks; g_capture.sweepFrame = 0; g_capture.sweepEnergy.assign(sweepFrequencies.size() * static_cast(inputs), 0.0); g_capture.sweepPeak.assign(sweepFrequencies.size() * static_cast(inputs), 0.0); @@ -854,6 +923,7 @@ int main(int argc, char** argv) g_capture.outputChannels = nullptr; g_capture.playbackSamples = nullptr; g_capture.recordedInput = nullptr; + g_capture.emitJsonBlocks = false; driver->disposeBuffers(); } diff --git a/notes/current-system-map.md b/notes/current-system-map.md index fe26e49..27f5a4c 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -111,12 +111,13 @@ scheduled-waveform correlation for standalone source-offset refinement. Pairwise sync compares matched anchors first, then only accepts independent clock-fit offsets inside the live latency horizon so period aliases do not become absurd reports. Each classified chirp carries the full dechirped bin-energy surface, -and decodes aggregate that into a `MimirChirpBinCalibrationProfile` per source -with frames, anchors, clock confidence, anchor error, usable band count, and -strongest bins. The analyzer keeps those profiles even when no timing report is -accepted, so a physical mic path can fail synchronization while still teaching -the next decoder which bands survived. Reports/states expose `delayUs` next to -fractional sample delay. +and decodes aggregate that into a `MimirChirpBinCalibrationModel` with measured +usable bands, expected-symbol versus observed-bin confusion observations, +timing residuals, delay hypotheses, phase summaries, and an adaptive codebook +plan. The analyzer keeps raw profiles even when no timing report is accepted, +and the active decoder can consume a persisted model for learned response +weighting, first-order group-delay correction, and global delay-hypothesis +seeding. Reports/states expose `delayUs` next to fractional sample delay. `Mimir.BufferSmoke --chirp-only-sync-self-test --sample-rate 192000` and `--hybrid-sync-self-test --sample-rate 192000` both recover a 1269.5-sample synthetic delay with printed @@ -158,7 +159,13 @@ evidence; Starfire still owns the heavy soundfield and sensor-fusion work. The ASIO probe can now play raw mono Float32 timeline audio through the Focusrite outputs while capturing every ASIO input as raw interleaved Float32. `Mimir.BufferSmoke --analyze-asio-f32` feeds those captured channels into the -same runtime active analyzer. A real 192 kHz chirp-bin Scarlett artifact decoded +same runtime active analyzer. `--calibrate-chirp-bin-asio-f32` computes and +persists the response/confusion/delay model, and `--analyze-asio-f32 +--calibration ...` loads it into the active decoder. The ASIO worker also +supports `--emit-json-blocks`, so Focusrite ASIO callbacks can feed +sample-bearing `audio-block` events into `Mimir.Runtime` continuously through +the existing frame-event adapter while the native ABI cut is pending. A real +192 kHz chirp-bin Scarlett artifact decoded `Loopback 1 -> Loopback 2` at exactly `0.000 us` with 12 matched anchors and 0.999 confidence. The same analyzer now prints calibration profiles for decoded sources. Physical input 1 still fails pairwise timing in the stored diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index b4b0d9c..7d82e07 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -90,8 +90,11 @@ Get-Content .\state\evidence.jsonl -Tail 8 correlation for final fractional delay. It also supports standalone source offset recovery from schedule/codebook state, which is the Raven/phone shape. Each classified chirp carries the full dechirped bin-energy surface; sync - reports expose those bands, and `MimirChirpBinCalibrationProfile` preserves - per-source calibration evidence even when timing is rejected. + reports expose those bands, and `MimirChirpBinCalibrationModel` preserves + measured usable bands, response/confusion observations, timing residuals, + delay hypotheses, phase summaries, and adaptive codebook plans. The active + decoder can load the model for learned weighting, first-order group-delay + correction, and global delay-hypothesis seeds. `Mimir.BufferSmoke --chirp-only-sync-self-test --sample-rate 192000` and `--hybrid-sync-self-test --sample-rate 192000` both recover a 1269.5-sample @@ -119,20 +122,22 @@ Get-Content .\state\evidence.jsonl -Tail 8 comparison and is now diagnostic only. A real Scarlett chirp-bin artifact run at 192 kHz decoded `Loopback 1 -> Loopback 2` at `0.000 us` delay with 12 matched anchors and 0.999 confidence in the normal active analyzer. Physical - inputs still do not earn accepted timing against loopback, but the active - analyzer now prints calibration profiles for decoded channels. In the stored - artifact, physical input 1 produced 14 frames, 12 anchors, 0.865 clock - confidence, and a different strongest-bin profile while failing pairwise - timing. Acoustic robustness is still open, but failed timing now leaves - response evidence. + inputs still do not earn accepted timing against loopback, but + `--calibrate-chirp-bin-asio-f32` now persists a real response/confusion/delay + model and `--analyze-asio-f32 --calibration ...` consumes it. In the stored + artifact, physical input 1 produced two reliable symbols and a different + strongest-bin profile while failing pairwise timing. The ASIO native worker + also supports `--emit-json-blocks` so `Mimir.Runtime` can ingest Focusrite ASIO + callbacks continuously through the frame-event adapter. Acoustic robustness is + still open, but failed timing now leaves response evidence. - Raven also has a 192 kHz loopback-capable Scarlett for co-streamer/game timing evidence. Do not move the heavy soundfield or sensor-fusion workload there. ## Immediate Re-entry Instruction -Use the chirp-bin calibration profiles from real microphones to tune the active -codebook/bands/weighting, then feed Scarlett ASIO callbacks into `Mimir.Runtime` -and turn decoded clock fits into the SRO/fractional-delay actuator. Keep -loopback as timing authority. Do not call synchronization analysis from -UI/telemetry readouts. Do not restore deleted script infrastructure because a -stale doc once missed it. +Use the chirp-bin calibration models from real microphones to tune the active +codebook/bands/weighting, then replace the ASIO JSON-block process bridge with +a native ABI callback source and turn decoded clock fits into the +SRO/fractional-delay actuator. Keep loopback as timing authority. Do not call +synchronization analysis from UI/telemetry readouts. Do not restore deleted +script infrastructure because a stale doc once missed it. diff --git a/src/Mimir.BufferSmoke/Program.cs b/src/Mimir.BufferSmoke/Program.cs index d5fa72e..3ae4147 100644 --- a/src/Mimir.BufferSmoke/Program.cs +++ b/src/Mimir.BufferSmoke/Program.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Mimir.Runtime.Synchronization; if (args.Any(arg => string.Equals(arg, "--chirplet-self-test", StringComparison.OrdinalIgnoreCase))) @@ -55,7 +56,19 @@ if (args.Any(arg => string.Equals(arg, "--analyze-asio-f32", StringComparison.Or ParseIntOption(args, "--sample-rate", MimirChirpletTimeline.SampleRate), ParseIntOption(args, "--channels", 4), ParseIntOption(args, "--reference-channel", 2), - ParseIntOption(args, "--candidate-channel", -1)); + ParseIntOption(args, "--candidate-channel", -1), + ParseStringOption(args, "--calibration", "")); +} + +if (args.Any(arg => string.Equals(arg, "--calibrate-chirp-bin-asio-f32", StringComparison.OrdinalIgnoreCase))) +{ + return CalibrateChirpBinAsioFloat32( + args, + ParseStringOption(args, "--input", "artifacts/asio/scarlett-chirp-bin-192k-f32.raw"), + ParseStringOption(args, "--output", "calibration/chirp-bin/latest.json"), + ParseIntOption(args, "--sample-rate", MimirChirpBinTimeline.SampleRate), + ParseIntOption(args, "--channels", 4), + ParseIntOption(args, "--reference-channel", 2)); } var duration = TimeSpan.FromSeconds(ParseDoubleOption(args, "--seconds", 10.0)); @@ -474,7 +487,13 @@ static int RenderChirpBinFloat32(string outputPath, int sampleRate, double secon return 0; } -static int AnalyzeAsioFloat32(string inputPath, int sampleRate, int channels, int referenceChannel, int candidateChannel) +static int AnalyzeAsioFloat32( + string inputPath, + int sampleRate, + int channels, + int referenceChannel, + int candidateChannel, + string calibrationPath) { if (channels <= 0) { @@ -533,9 +552,12 @@ static int AnalyzeAsioFloat32(string inputPath, int sampleRate, int channels, in ? new HashSet(StringComparer.Ordinal) { $"asio-ch{candidateChannel}" } : null; var analyzer = new MimirAudioSynchronizationAnalyzer(); - var reports = analyzer.Analyze(buffers, referenceSourceId, MimirAudioSyncMode.ChirpOnly, candidates); + var calibration = string.IsNullOrWhiteSpace(calibrationPath) || !File.Exists(calibrationPath) + ? null + : MimirChirpBinCalibrationModel.Load(calibrationPath); + var reports = analyzer.Analyze(buffers, referenceSourceId, MimirAudioSyncMode.ChirpOnly, candidates, calibration); - Console.WriteLine($"asio-f32-analysis input={inputPath} sampleRate={sampleRate} channels={channels} frames={frameCount} reference={referenceSourceId} candidate={(candidateChannel >= 0 ? $"asio-ch{candidateChannel}" : "all")}"); + Console.WriteLine($"asio-f32-analysis input={inputPath} sampleRate={sampleRate} channels={channels} frames={frameCount} reference={referenceSourceId} candidate={(candidateChannel >= 0 ? $"asio-ch{candidateChannel}" : "all")} calibration={(calibration == null ? "none" : calibrationPath)}"); foreach (var trace in analyzer.LastDecodeTraces.OrderBy(trace => trace.SourceId, StringComparer.Ordinal)) { Console.WriteLine( @@ -556,6 +578,152 @@ static int AnalyzeAsioFloat32(string inputPath, int sampleRate, int channels, in return reports.Count > 0 ? 0 : 1; } +static int CalibrateChirpBinAsioFloat32( + string[] args, + string inputPath, + string outputPath, + int sampleRate, + int channels, + int referenceChannel) +{ + if (args.Any(arg => string.Equals(arg, "--capture-asio", StringComparison.OrdinalIgnoreCase))) + { + var probe = ParseStringOption( + args, + "--asio-probe", + "native/probes/asio_audio_cadence/build/Release/asio_audio_cadence.exe"); + var seconds = ParseDoubleOption(args, "--seconds", 6.0); + var gain = ParseDoubleOption(args, "--gain", 1.0); + var renderPath = Path.Combine("artifacts", "asio", $"chirp-bin-calibration-{sampleRate}.raw"); + RenderChirpBinFloat32(renderPath, sampleRate, seconds); + var probeExit = RunAsioProbeCapture(probe, renderPath, inputPath, sampleRate, seconds, gain); + if (probeExit != 0) + { + return probeExit; + } + } + + var channelSamples = ReadInterleavedFloat32(inputPath, channels, out var frameCount); + if (frameCount == 0) + { + Console.Error.WriteLine("chirp-bin calibration failed: capture file contains no complete frames"); + return 1; + } + + if (referenceChannel < 0 || referenceChannel >= channels) + { + Console.Error.WriteLine("chirp-bin calibration failed: reference channel is outside the capture channel count"); + return 1; + } + + var decodes = new Dictionary(StringComparer.Ordinal); + for (var channel = 0; channel < channels; channel++) + { + decodes[$"asio-ch{channel}"] = MimirChirpBinTimeline.Default.DecodeStreamWindow(channelSamples[channel], sampleRate); + } + + var referenceSourceId = $"asio-ch{referenceChannel}"; + var model = MimirChirpBinCalibrationModel.FromDecodes(referenceSourceId, sampleRate, decodes); + model.Save(outputPath); + Console.WriteLine($"chirp-bin-calibration path={outputPath} input={inputPath} sampleRate={sampleRate} channels={channels} frames={frameCount} reference={referenceSourceId}"); + foreach (var path in model.Paths) + { + var reliable = path.CodebookPlan.ReliableSymbolIds.Count == 0 + ? "none" + : string.Join(",", path.CodebookPlan.ReliableSymbolIds.Take(16)); + Console.WriteLine( + $"chirp-bin-calibration-path {path.SourceId}: frames={path.Profile.FrameCount} anchors={path.Profile.AnchorCount} clock={path.Profile.ClockConfidence:0.000} usableBins={path.Profile.UsableBandCount}/{path.Profile.Bands.Count} symbols={path.Symbols.Count} reliableSymbols={path.CodebookPlan.ReliableSymbolCount} recommendedOrder={path.CodebookPlan.RecommendedOrder} reliable={reliable}"); + foreach (var hypothesis in path.DelayHypotheses.Take(3)) + { + Console.WriteLine( + $"chirp-bin-delay-hypothesis {path.SourceId}: delaySamples={hypothesis.DelaySamples:0.000} binShift={hypothesis.BinShift} support={hypothesis.SupportCount} confidence={hypothesis.Confidence:0.000} residual={hypothesis.MeanResidualSamples:0.000}"); + } + } + + return 0; +} + +static int RunAsioProbeCapture( + string probePath, + string playbackPath, + string capturePath, + int sampleRate, + double seconds, + double gain) +{ + var startInfo = new ProcessStartInfo(Path.GetFullPath(probePath)) + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + startInfo.ArgumentList.Add("--set-sample-rate"); + startInfo.ArgumentList.Add(sampleRate.ToString()); + startInfo.ArgumentList.Add("--play-f32-mono"); + startInfo.ArgumentList.Add(Path.GetFullPath(playbackPath)); + startInfo.ArgumentList.Add("--play-gain"); + startInfo.ArgumentList.Add(gain.ToString("0.#####")); + startInfo.ArgumentList.Add("--record-f32-interleaved"); + startInfo.ArgumentList.Add(Path.GetFullPath(capturePath)); + startInfo.ArgumentList.Add("--capture-seconds"); + startInfo.ArgumentList.Add(seconds.ToString("0.###")); + + using var process = Process.Start(startInfo); + if (process == null) + { + Console.Error.WriteLine($"chirp-bin calibration failed: could not start ASIO probe {probePath}"); + return 1; + } + + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + } + }; + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.Error.WriteLine(e.Data); + } + }; + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + return process.ExitCode; +} + +static float[][] ReadInterleavedFloat32(string inputPath, int channels, out int frameCount) +{ + if (channels <= 0) + { + frameCount = 0; + return []; + } + + var bytes = File.ReadAllBytes(inputPath); + frameCount = bytes.Length / (sizeof(float) * channels); + var channelSamples = new float[channels][]; + for (var channel = 0; channel < channels; channel++) + { + channelSamples[channel] = new float[frameCount]; + } + + var span = bytes.AsSpan(0, frameCount * channels * sizeof(float)); + for (var frame = 0; frame < frameCount; frame++) + { + for (var channel = 0; channel < channels; channel++) + { + channelSamples[channel][frame] = BitConverter.ToSingle(span.Slice((frame * channels + channel) * sizeof(float), sizeof(float))); + } + } + + return channelSamples; +} + static int ParseIntOption(string[] args, string name, int fallback) { for (var index = 0; index < args.Length - 1; index++) diff --git a/src/Mimir.Runtime/Synchronization/MimirAudioSynchronizationAnalyzer.cs b/src/Mimir.Runtime/Synchronization/MimirAudioSynchronizationAnalyzer.cs index 928cd18..54899d3 100644 --- a/src/Mimir.Runtime/Synchronization/MimirAudioSynchronizationAnalyzer.cs +++ b/src/Mimir.Runtime/Synchronization/MimirAudioSynchronizationAnalyzer.cs @@ -23,7 +23,8 @@ public sealed class MimirAudioSynchronizationAnalyzer IEnumerable buffers, string referenceSourceId, MimirAudioSyncMode mode = MimirAudioSyncMode.ChirpOnly, - IReadOnlySet? candidateSourceIds = null) + IReadOnlySet? candidateSourceIds = null, + MimirChirpBinCalibrationModel? calibrationModel = null) { var audioBuffers = buffers .Where(buffer => buffer.Descriptor.Kind == MimirStreamKind.Audio) @@ -168,10 +169,16 @@ public sealed class MimirAudioSynchronizationAnalyzer } var comparedReferenceDecode = useChirpBinTimeline - ? MimirChirpBinTimeline.Default.DecodeStreamWindow(referenceWindow, activeReferenceBlock.SampleRate) + ? MimirChirpBinTimeline.Default.DecodeStreamWindow( + referenceWindow, + activeReferenceBlock.SampleRate, + calibrationModel?.PathFor(reference.Descriptor.SourceId)) : MimirChirpletTimeline.Default.DecodeStreamWindow(referenceWindow, activeReferenceBlock.SampleRate); var candidateDecode = useChirpBinTimeline - ? MimirChirpBinTimeline.Default.DecodeStreamWindow(candidateWindow, activeReferenceBlock.SampleRate) + ? MimirChirpBinTimeline.Default.DecodeStreamWindow( + candidateWindow, + activeReferenceBlock.SampleRate, + calibrationModel?.PathFor(buffer.Descriptor.SourceId)) : MimirChirpletTimeline.Default.DecodeStreamWindow(candidateWindow, activeReferenceBlock.SampleRate); if (useChirpBinTimeline) { diff --git a/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationModel.cs b/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationModel.cs new file mode 100644 index 0000000..d7961ba --- /dev/null +++ b/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationModel.cs @@ -0,0 +1,225 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Mimir.Runtime.Synchronization; + +public sealed record MimirChirpBinCalibrationModel( + string ModelId, + DateTimeOffset CreatedAt, + int SampleRate, + string ReferenceSourceId, + IReadOnlyList Paths) +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + WriteIndented = true, + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter() }, + }; + + public MimirChirpBinPathCalibration? PathFor(string sourceId) => + Paths.FirstOrDefault(path => string.Equals(path.SourceId, sourceId, StringComparison.Ordinal)); + + public static MimirChirpBinCalibrationModel Load(string path) => + JsonSerializer.Deserialize(File.ReadAllText(path), JsonOptions) + ?? throw new InvalidOperationException($"Could not read chirp-bin calibration model: {path}"); + + public void Save(string path) + { + var directory = Path.GetDirectoryName(Path.GetFullPath(path)); + if (!string.IsNullOrWhiteSpace(directory)) + { + Directory.CreateDirectory(directory); + } + + File.WriteAllText(path, JsonSerializer.Serialize(this, JsonOptions)); + } + + public static MimirChirpBinCalibrationModel FromDecodes( + string referenceSourceId, + int sampleRate, + IReadOnlyDictionary decodes) + { + var paths = decodes + .OrderBy(pair => pair.Key, StringComparer.Ordinal) + .Select(pair => MimirChirpBinPathCalibration.FromDecode(pair.Key, sampleRate, pair.Value)) + .ToArray(); + return new MimirChirpBinCalibrationModel( + $"chirp-bin-{DateTimeOffset.UtcNow:yyyyMMddTHHmmssZ}", + DateTimeOffset.UtcNow, + sampleRate, + referenceSourceId, + paths); + } +} + +public sealed record MimirChirpBinPathCalibration( + string SourceId, + int SampleRate, + MimirChirpBinCalibrationProfile Profile, + MimirChirpBinCodebookPlan CodebookPlan, + IReadOnlyList Symbols, + IReadOnlyList Confusion, + IReadOnlyList DelayHypotheses) +{ + public double SymbolWeight(int symbolId) + { + var symbol = Symbols.FirstOrDefault(item => item.SymbolId == symbolId); + return symbol == null ? 1.0 : Math.Clamp(symbol.Reliability, 0.05, 1.0); + } + + public double GroupDelayCorrectionSamples(int symbolId) + { + var symbol = Symbols.FirstOrDefault(item => item.SymbolId == symbolId); + return symbol?.MeanTimingResidualSamples ?? 0.0; + } + + public static MimirChirpBinPathCalibration FromDecode( + string sourceId, + int sampleRate, + MimirChirpletStreamDecode decode) + { + var profile = MimirChirpBinCalibrationProfile.FromDecode(sourceId, sampleRate, decode); + var observations = MimirChirpBinTimeline.Default.CalibrationObservations(decode, sampleRate); + var symbols = observations + .GroupBy(observation => observation.ExpectedSymbolId) + .Select(group => + { + var items = group.ToArray(); + var meanConfidence = items.Average(item => item.Confidence); + var meanResidual = items.Average(item => item.TimingResidualSamples); + var residualSpread = items.Average(item => Math.Abs(item.TimingResidualSamples - meanResidual)); + var correct = items.Count(item => item.ExpectedSymbolId == item.ObservedSymbolId); + var reliability = meanConfidence * + (correct / (double)items.Length) * + (1.0 / (1.0 + residualSpread / Math.Max(1.0, sampleRate * 0.00025))); + var phase = CircularMean(items.Select(item => item.PhaseRadians)); + return new MimirChirpBinSymbolCalibration( + group.Key, + reliability, + items.Average(item => item.ExpectedCenterHz), + items.Average(item => item.ObservedCenterHz), + items.Average(item => item.ObservedEnergy), + meanResidual, + residualSpread, + phase, + items.Length); + }) + .OrderBy(symbol => symbol.SymbolId) + .ToArray(); + var hypotheses = BuildDelayHypotheses(decode); + var plan = MimirChirpBinCodebookPlan.FromSymbols(symbols); + return new MimirChirpBinPathCalibration( + sourceId, + sampleRate, + profile, + plan, + symbols, + observations, + hypotheses); + } + + private static IReadOnlyList BuildDelayHypotheses(MimirChirpletStreamDecode decode) + { + if (decode.ClockFit == null) + { + return []; + } + + var liveHorizonSamples = decode.ClockFit.EffectiveSampleRate * 10.0; + var hypotheses = decode.Anchors + .GroupBy(anchor => Math.Round(anchor.SampleOffset - anchor.TimelineSeconds * decode.ClockFit.EffectiveSampleRate)) + .Select(group => + { + var delay = group.Average(anchor => anchor.SampleOffset - anchor.TimelineSeconds * decode.ClockFit!.EffectiveSampleRate); + var residual = group.Average(anchor => Math.Abs(anchor.SampleOffset - (delay + anchor.TimelineSeconds * decode.ClockFit!.EffectiveSampleRate))); + return new MimirChirpBinDelayHypothesis( + delay, + 0, + group.Count(), + group.Average(anchor => anchor.Confidence), + residual); + }) + .Where(hypothesis => Math.Abs(hypothesis.DelaySamples) <= liveHorizonSamples) + .OrderByDescending(hypothesis => hypothesis.Confidence * hypothesis.SupportCount) + .Take(8) + .ToArray(); + if (hypotheses.Length > 0) + { + return hypotheses; + } + + return Math.Abs(decode.ClockFit.SourceOffsetSamples) <= liveHorizonSamples + ? [new MimirChirpBinDelayHypothesis(decode.ClockFit.SourceOffsetSamples, 0, decode.ClockFit.AnchorCount, decode.ClockFit.Confidence, decode.ClockFit.MeanAbsoluteErrorSamples)] + : []; + } + + private static double CircularMean(IEnumerable radians) + { + var sin = 0.0; + var cos = 0.0; + var count = 0; + foreach (var value in radians) + { + sin += Math.Sin(value); + cos += Math.Cos(value); + count++; + } + + return count == 0 ? 0.0 : Math.Atan2(sin / count, cos / count); + } +} + +public sealed record MimirChirpBinCodebookPlan( + int ReliableSymbolCount, + int RecommendedOrder, + IReadOnlyList ReliableSymbolIds) +{ + public static MimirChirpBinCodebookPlan FromSymbols(IReadOnlyList symbols) + { + var reliable = symbols + .Where(symbol => symbol.Reliability >= 0.20) + .OrderByDescending(symbol => symbol.Reliability) + .ThenBy(symbol => symbol.SymbolId) + .Select(symbol => symbol.SymbolId) + .ToArray(); + var count = Math.Max(2, reliable.Length); + var order = 3; + while (Math.Pow(count, order) < 120_000.0 && order < 8) + { + order++; + } + + return new MimirChirpBinCodebookPlan(reliable.Length, order, reliable); + } +} + +public sealed record MimirChirpBinSymbolCalibration( + int SymbolId, + double Reliability, + double ExpectedCenterHz, + double ObservedCenterHz, + double MeanEnergy, + double MeanTimingResidualSamples, + double TimingResidualSpreadSamples, + double MeanPhaseRadians, + int ObservationCount); + +public sealed record MimirChirpBinConfusionObservation( + ulong EventIndex, + int ExpectedSymbolId, + int ObservedSymbolId, + double ExpectedCenterHz, + double ObservedCenterHz, + double ObservedEnergy, + double TimingResidualSamples, + double Confidence, + double DelayHypothesisSamples, + double PhaseRadians); + +public sealed record MimirChirpBinDelayHypothesis( + double DelaySamples, + int BinShift, + int SupportCount, + double Confidence, + double MeanResidualSamples); diff --git a/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationProfile.cs b/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationProfile.cs index c888dac..cf2df6f 100644 --- a/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationProfile.cs +++ b/src/Mimir.Runtime/Synchronization/MimirChirpBinCalibrationProfile.cs @@ -71,7 +71,7 @@ public sealed record MimirChirpBinCalibrationProfile( decode.Frames.Count, decode.Anchors.Count, decode.ClockFit?.Confidence ?? 0.0, - decode.ClockFit?.MeanAbsoluteErrorSamples ?? double.NaN, + decode.ClockFit?.MeanAbsoluteErrorSamples ?? 0.0, bands); } } diff --git a/src/Mimir.Runtime/Synchronization/MimirChirpBinTimeline.cs b/src/Mimir.Runtime/Synchronization/MimirChirpBinTimeline.cs index 3d93954..ea3334a 100644 --- a/src/Mimir.Runtime/Synchronization/MimirChirpBinTimeline.cs +++ b/src/Mimir.Runtime/Synchronization/MimirChirpBinTimeline.cs @@ -15,6 +15,10 @@ internal sealed record ChirpBinKernelSet( ChirpBinKernel[] Symbols, double ReferenceEnergy); +internal sealed record ChirpBinScore( + double Energy, + double PhaseRadians); + public sealed class MimirChirpBinTimeline { public const int SampleRate = 48_000; @@ -57,21 +61,24 @@ public sealed class MimirChirpBinTimeline return samples; } - public MimirChirpletStreamDecode DecodeStreamWindow(ReadOnlySpan samples, int sampleRate) + public MimirChirpletStreamDecode DecodeStreamWindow( + ReadOnlySpan samples, + int sampleRate, + MimirChirpBinPathCalibration? calibration = null) { if (samples.Length == 0) { return new MimirChirpletStreamDecode([], [], [], null, []); } - var frames = DetectFrames(samples, sampleRate); + var frames = DetectFrames(samples, sampleRate, calibration); var symbols = frames .Select(frame => new MimirChirpletSymbolObservation( frame.BestCandidate.SymbolId, frame.SampleOffset, frame.BestCandidate.Energy)) .ToArray(); - var anchors = DecodeAnchors(frames, sampleRate); + var anchors = DecodeAnchors(frames, sampleRate, calibration); var clock = FitClock(anchors, sampleRate); if (clock != null) { @@ -82,6 +89,44 @@ public sealed class MimirChirpBinTimeline return new MimirChirpletStreamDecode(frames, symbols, anchors, clock, EstimateBandResponse(frames)); } + public IReadOnlyList CalibrationObservations( + MimirChirpletStreamDecode decode, + int sampleRate) + { + if (decode.ClockFit == null) + { + return []; + } + + var observations = new List(decode.Frames.Count); + foreach (var frame in decode.Frames) + { + if (!TryExpectedEventForSample(frame.SampleOffset, sampleRate, decode.ClockFit, out var timelineEvent)) + { + continue; + } + + var best = frame.BestCandidate; + var bestBand = best.BandResponses? + .OrderByDescending(response => response.Energy) + .FirstOrDefault(); + var expectedSample = decode.ClockFit.SampleForTimelineSeconds(timelineEvent.StartSeconds); + observations.Add(new MimirChirpBinConfusionObservation( + timelineEvent.Index, + timelineEvent.SymbolId, + best.SymbolId, + SymbolCenterHz(Symbols[timelineEvent.SymbolId]), + bestBand?.CenterHz ?? SymbolCenterHz(Symbols[best.SymbolId]), + bestBand?.Energy ?? best.Energy, + frame.SampleOffset - expectedSample, + best.Energy, + decode.ClockFit.SourceOffsetSamples, + bestBand?.PhaseRadians ?? 0.0)); + } + + return observations; + } + public MimirChirpletTimelineEvent EventForIndex(ulong eventIndex) { var symbolId = SymbolForEvent(eventIndex); @@ -118,7 +163,10 @@ public sealed class MimirChirpBinTimeline return events; } - private static IReadOnlyList DetectFrames(ReadOnlySpan samples, int sampleRate) + private static IReadOnlyList DetectFrames( + ReadOnlySpan samples, + int sampleRate, + MimirChirpBinPathCalibration? calibration) { var chirpSamples = Math.Max(1, (int)Math.Round(ChirpDurationSeconds * sampleRate)); if (samples.Length < chirpSamples) @@ -149,7 +197,7 @@ public sealed class MimirChirpBinTimeline .Take(maxExpectedEvents) .Order()) { - var frame = ClassifyAt(samples, sampleRate, proposal, sampleRate / 1_000); + var frame = ClassifyAt(samples, sampleRate, proposal, sampleRate / 1_000, calibration); if (frame != null) { frames.Add(frame); @@ -163,7 +211,8 @@ public sealed class MimirChirpBinTimeline ReadOnlySpan samples, int sampleRate, int predictedOffset, - int searchRadiusSamples) + int searchRadiusSamples, + MimirChirpBinPathCalibration? calibration) { var chirpSamples = Math.Max(1, (int)Math.Round(ChirpDurationSeconds * sampleRate)); if (samples.Length < chirpSamples) @@ -179,7 +228,7 @@ public sealed class MimirChirpBinTimeline var step = Math.Max(2, sampleRate / 4_000); for (var offset = start; offset <= end; offset += step) { - var candidates = ScoreBins(samples.Slice(offset, chirpSamples), sampleRate, offset); + var candidates = ScoreBins(samples.Slice(offset, chirpSamples), sampleRate, offset, calibration); var energy = candidates.Length == 0 ? 0.0 : candidates[0].Energy; if (energy > bestEnergy || PreferLaterPlateauOffset(sampleRate, energy, bestEnergy, offset, bestOffset)) { @@ -194,7 +243,7 @@ public sealed class MimirChirpBinTimeline var localEnd = Math.Min(end, bestOffset + localStep * 4); for (var offset = localStart; offset <= localEnd; offset += localStep) { - var candidates = ScoreBins(samples.Slice(offset, chirpSamples), sampleRate, offset); + var candidates = ScoreBins(samples.Slice(offset, chirpSamples), sampleRate, offset, calibration); var energy = candidates.Length == 0 ? 0.0 : candidates[0].Energy; if (energy > bestEnergy || PreferLaterPlateauOffset(sampleRate, energy, bestEnergy, offset, bestOffset)) { @@ -228,7 +277,8 @@ public sealed class MimirChirpBinTimeline var correctedEnergy = ScoreSymbolEnergy( samples.Slice((int)Math.Round(correctedOffset), chirpSamples), sampleRate, - canonicalSymbol); + canonicalSymbol) * (calibration?.SymbolWeight(canonicalSymbol) ?? 1.0); + correctedOffset -= calibration?.GroupDelayCorrectionSamples(canonicalSymbol) ?? 0.0; correctedCandidates.Add(new MimirChirpletSymbolCandidate( canonicalSymbol, correctedOffset, @@ -247,10 +297,11 @@ public sealed class MimirChirpBinTimeline private static MimirChirpletSymbolCandidate[] ScoreBins( ReadOnlySpan samples, int sampleRate, - int sampleOffset) + int sampleOffset, + MimirChirpBinPathCalibration? calibration) { var kernels = KernelSets.GetOrAdd(sampleRate, BuildKernelSet); - var scores = new double[Symbols.Length]; + var scores = new ChirpBinScore[Symbols.Length]; var sampleEnergy = 0.0; for (var index = 0; index < samples.Length; index++) { @@ -265,15 +316,23 @@ public sealed class MimirChirpBinTimeline for (var symbol = 0; symbol < Symbols.Length; symbol++) { var score = DechirpedBin(samples, kernels.Symbols[symbol]); - scores[symbol] = 2.0 * score / Math.Sqrt(sampleEnergy * kernels.ReferenceEnergy); + var energy = 2.0 * score.Energy / Math.Sqrt(sampleEnergy * kernels.ReferenceEnergy); + scores[symbol] = score with { Energy = energy }; } var bandResponses = scores - .Select((energy, symbol) => new MimirChirpletBandResponse(SymbolCenterHz(Symbols[symbol]), energy)) + .Select((score, symbol) => new MimirChirpletBandResponse( + SymbolCenterHz(Symbols[symbol]), + score.Energy, + score.PhaseRadians)) .ToArray(); return scores - .Select((energy, symbol) => new MimirChirpletSymbolCandidate(symbol, sampleOffset, energy, bandResponses)) + .Select((score, symbol) => new MimirChirpletSymbolCandidate( + symbol, + sampleOffset - (calibration?.GroupDelayCorrectionSamples(symbol) ?? 0.0), + score.Energy * (calibration?.SymbolWeight(symbol) ?? 1.0), + bandResponses)) .OrderByDescending(candidate => candidate.Energy) .Take(MaxSymbolCandidatesPerFrame) .ToArray(); @@ -294,7 +353,7 @@ public sealed class MimirChirpBinTimeline } var score = DechirpedBin(samples, kernels.Symbols[symbolId]); - return 2.0 * score / Math.Sqrt(sampleEnergy * kernels.ReferenceEnergy); + return 2.0 * score.Energy / Math.Sqrt(sampleEnergy * kernels.ReferenceEnergy); } private static double RefineOffset( @@ -334,7 +393,7 @@ public sealed class MimirChirpBinTimeline offset > bestOffset; } - private static double DechirpedBin(ReadOnlySpan samples, ChirpBinKernel kernel) + private static ChirpBinScore DechirpedBin(ReadOnlySpan samples, ChirpBinKernel kernel) { var real = 0.0; var imaginary = 0.0; @@ -344,12 +403,15 @@ public sealed class MimirChirpBinTimeline imaginary += samples[index] * kernel.Imaginary[index]; } - return Math.Sqrt(real * real + imaginary * imaginary); + return new ChirpBinScore( + Math.Sqrt(real * real + imaginary * imaginary), + Math.Atan2(imaginary, real)); } private static IReadOnlyList DecodeAnchors( IReadOnlyList frames, - int sampleRate) + int sampleRate, + MimirChirpBinPathCalibration? calibration) { if (frames.Count < TimelineOrder) { @@ -402,12 +464,13 @@ public sealed class MimirChirpBinTimeline } } - return SelectCoherentAnchorPath(candidates, sampleRate); + return SelectCoherentAnchorPath(candidates, sampleRate, calibration); } private static IReadOnlyList SelectCoherentAnchorPath( IReadOnlyList candidates, - int sampleRate) + int sampleRate, + MimirChirpBinPathCalibration? calibration) { if (candidates.Count == 0) { @@ -416,9 +479,8 @@ public sealed class MimirChirpBinTimeline List bestPath = []; var bestScore = double.NegativeInfinity; - foreach (var seed in candidates.OrderByDescending(anchor => anchor.Confidence).Take(64)) + foreach (var seedOffset in CandidateDelaySeeds(candidates, sampleRate, calibration)) { - var seedOffset = seed.SampleOffset - seed.TimelineSeconds * sampleRate; var path = candidates .Where(anchor => Math.Abs((anchor.SampleOffset - anchor.TimelineSeconds * sampleRate) - seedOffset) <= sampleRate * 0.006) .GroupBy(anchor => anchor.EventIndex) @@ -453,6 +515,27 @@ public sealed class MimirChirpBinTimeline .ToArray(); } + private static IEnumerable CandidateDelaySeeds( + IReadOnlyList candidates, + int sampleRate, + MimirChirpBinPathCalibration? calibration) + { + if (calibration != null) + { + foreach (var hypothesis in calibration.DelayHypotheses + .OrderByDescending(hypothesis => hypothesis.Confidence * hypothesis.SupportCount) + .Take(16)) + { + yield return hypothesis.DelaySamples; + } + } + + foreach (var seed in candidates.OrderByDescending(anchor => anchor.Confidence).Take(64)) + { + yield return seed.SampleOffset - seed.TimelineSeconds * sampleRate; + } + } + private static MimirChirpletClockFit? FitClock( IReadOnlyList anchors, int sampleRate) @@ -738,6 +821,24 @@ public sealed class MimirChirpBinTimeline private static double EventStartSeconds(ulong eventIndex) => FirstEventSeconds + eventIndex * EventSpacingSeconds; + private static bool TryExpectedEventForSample( + double sampleOffset, + int sampleRate, + MimirChirpletClockFit clockFit, + out MimirChirpletTimelineEvent timelineEvent) + { + var timelineSeconds = (sampleOffset - clockFit.SourceOffsetSamples) / clockFit.EffectiveSampleRate; + var eventIndex = (long)Math.Round((timelineSeconds - FirstEventSeconds) / EventSpacingSeconds); + if (eventIndex < 0) + { + timelineEvent = default!; + return false; + } + + timelineEvent = Default.EventForIndex((ulong)eventIndex); + return Math.Abs(timelineSeconds - timelineEvent.StartSeconds) <= Math.Max(0.012, 6.0 / sampleRate); + } + private static int[] BuildDeBruijn(int alphabetSize, int order) { var a = new int[alphabetSize * order]; diff --git a/src/Mimir.Runtime/Synchronization/MimirChirpletTimeline.cs b/src/Mimir.Runtime/Synchronization/MimirChirpletTimeline.cs index daecad9..a419002 100644 --- a/src/Mimir.Runtime/Synchronization/MimirChirpletTimeline.cs +++ b/src/Mimir.Runtime/Synchronization/MimirChirpletTimeline.cs @@ -14,7 +14,8 @@ public sealed record MimirChirpletTone( public sealed record MimirChirpletBandResponse( double CenterHz, - double Energy); + double Energy, + double PhaseRadians = 0.0); public sealed record MimirChirpletTimelineEvent( ulong Index, diff --git a/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs b/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs index b4e70bd..78ea3a4 100644 --- a/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs +++ b/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs @@ -201,6 +201,8 @@ public sealed class MimirAudioSyncConfig public float WatermarkGain { get; set; } = float.NaN; + public string CalibrationModelPath { get; set; } = ""; + public MimirAudioSynchronizationSettings ToSettings(MimirAudioSynchronizationSettings fallback) { return new MimirAudioSynchronizationSettings @@ -215,6 +217,9 @@ public sealed class MimirAudioSyncConfig WatermarkGain = float.IsFinite(WatermarkGain) ? Math.Clamp(WatermarkGain, 0.0f, 0.25f) : fallback.WatermarkGain, + CalibrationModelPath = string.IsNullOrWhiteSpace(CalibrationModelPath) + ? fallback.CalibrationModelPath + : CalibrationModelPath.Trim(), }; } } diff --git a/src/Mimir.Runtime/Synchronization/MimirSynchronizationHub.cs b/src/Mimir.Runtime/Synchronization/MimirSynchronizationHub.cs index 268cdea..8e153c2 100644 --- a/src/Mimir.Runtime/Synchronization/MimirSynchronizationHub.cs +++ b/src/Mimir.Runtime/Synchronization/MimirSynchronizationHub.cs @@ -6,6 +6,7 @@ public sealed class MimirSynchronizationHub : IDisposable private readonly MimirAudioSynchronizationAnalyzer audioSynchronization = new(); private readonly MimirAudioSynchronizationStateTracker audioSynchronizationState = new(); private readonly Dictionary audioSynchronizationReports = new(StringComparer.Ordinal); + private readonly MimirChirpBinCalibrationModel? chirpBinCalibrationModel; private ulong ingestedSamples; private int nextAudioSynchronizationCandidate; @@ -13,6 +14,7 @@ public sealed class MimirSynchronizationHub : IDisposable { Settings = settings; Buffers = new MimirStreamBufferSet(settings.BufferDuration); + chirpBinCalibrationModel = TryLoadCalibrationModel(settings.Audio.CalibrationModelPath); foreach (var stream in settings.Streams.Where(stream => stream.Enabled)) { Buffers.EnsureBuffer(stream); @@ -85,7 +87,7 @@ public sealed class MimirSynchronizationHub : IDisposable buffer.Latest?.AudioBlock != null) .Select(buffer => buffer.Descriptor.SourceId) .ToArray(); - var reports = audioSynchronization.Analyze(Buffers.Buffers, referenceSourceId, mode); + var reports = audioSynchronization.Analyze(Buffers.Buffers, referenceSourceId, mode, calibrationModel: chirpBinCalibrationModel); StoreAudioSynchronizationReports(reports, candidates); return reports; } @@ -120,13 +122,24 @@ public sealed class MimirSynchronizationHub : IDisposable Buffers.Buffers, referenceSourceId, mode, - selected); + selected, + chirpBinCalibrationModel); StoreAudioSynchronizationReports(reports, selected); return reports; } private bool disposed; + private static MimirChirpBinCalibrationModel? TryLoadCalibrationModel(string path) + { + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + { + return null; + } + + return MimirChirpBinCalibrationModel.Load(path); + } + private void StoreAudioSynchronizationReports( IReadOnlyList reports, IEnumerable analyzedCandidateIds) diff --git a/src/Mimir.Runtime/Synchronization/MimirSynchronizationSettings.cs b/src/Mimir.Runtime/Synchronization/MimirSynchronizationSettings.cs index 6669344..4065924 100644 --- a/src/Mimir.Runtime/Synchronization/MimirSynchronizationSettings.cs +++ b/src/Mimir.Runtime/Synchronization/MimirSynchronizationSettings.cs @@ -66,6 +66,8 @@ public sealed class MimirAudioSynchronizationSettings public float WatermarkGain { get; init; } = DefaultWatermarkGain; + public string CalibrationModelPath { get; init; } = ""; + public static MimirAudioSynchronizationSettings FromEnvironment() { return new MimirAudioSynchronizationSettings().WithEnvironmentOverrides(); @@ -79,6 +81,7 @@ public sealed class MimirAudioSynchronizationSettings ReferenceSourceId = ReadReferenceSourceId(ReferenceSourceId), CalibrationGain = ReadCalibrationGain(CalibrationGain), WatermarkGain = ReadWatermarkGain(WatermarkGain), + CalibrationModelPath = ReadCalibrationModelPath(CalibrationModelPath), }; } @@ -119,6 +122,12 @@ public sealed class MimirAudioSynchronizationSettings ? Math.Clamp(gain, 0.0f, 0.25f) : fallback; } + + private static string ReadCalibrationModelPath(string fallback) + { + var path = Environment.GetEnvironmentVariable("MIMIR_CHIRP_BIN_CALIBRATION"); + return string.IsNullOrWhiteSpace(path) ? fallback : path.Trim(); + } } public enum MimirAudioSyncMode diff --git a/state/evidence.jsonl b/state/evidence.jsonl index b2c451f..8fd9efa 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -71,3 +71,4 @@ {"ts": "2026-05-23T19:40:50+01:00", "type": "implementation", "status": "ok", "note": "Extended active chirp-bin decoding toward the Perfect Machine receiver shape: standalone DecodeStreamWindow now refines source offset against the scheduled waveform so a listener with only codebook/schedule state can place delayed audio on canonical time. Synthetic 192 kHz standalone tests recover 1269.5 samples exactly, 96000 samples / 500 ms to below printed microsecond precision, and 13789.75 samples within 0.021 us. Pairwise Mimir sync now rejects independent clock-fit differences outside a 10s live horizon so coherent period aliases do not become absurd delays. Fensalir project references were updated from removed Aquarium.LocalCast to Aquarium.Fensalir."} {"ts": "2026-05-23T19:47:34+01:00", "type": "implementation", "status": "ok", "note": "Active chirp-bin decoding now preserves calibration evidence as well as timing: each classified chirp carries the full dechirped 32-bin energy surface, stream decodes aggregate those bins into BandResponses, and sync reports print/expose those per-band response estimates. This makes the chirp stream a timing witness and a frequency-response calibration surface. Verified dotnet build, 192 kHz chirp-only self-test, 500 ms standalone self-test, and Scarlett ASIO loopback artifact with response bands."} {"ts": "2026-05-23T20:03:21+01:00", "type": "implementation", "status": "ok", "note": "Added chirp-bin calibration profiles as the live meatspace response surface. MimirChirpBinCalibrationProfile summarizes per-source frames, anchors, clock confidence, anchor error, usable bins, and strongest measured bands from active chirp-bin decodes. MimirAudioSynchronizationAnalyzer keeps profiles even when timing reports are rejected, and BufferSmoke now prints them for runtime and ASIO artifact analysis. Verified build, 192 kHz chirp-only self-test, 500 ms standalone receiver self-test, Scarlett loopback sync, and physical input calibration evidence from the stored ASIO artifact."} +{"ts": "2026-05-23T20:19:36+01:00", "type": "implementation", "status": "ok", "note": "Replaced the profile-only chirp-bin calibration cut with a usable physical calibration model. The model records per-source usable bands, symbol reliability, expected-vs-observed confusion observations, timing residuals, delay hypotheses, phase summaries, and an adaptive codebook plan. MimirChirpBinTimeline can now decode with learned per-symbol weights, group-delay correction, and persisted delay-hypothesis seeds. BufferSmoke can run --calibrate-chirp-bin-asio-f32 to render/capture/analyze/persist the model, and --analyze-asio-f32 can load it via --calibration. The ASIO native worker now supports --emit-json-blocks so Focusrite ASIO callbacks can feed Mimir.Runtime continuously through the existing frame-event adapter while the native ABI cut is still pending."} diff --git a/state/map.yaml b/state/map.yaml index d9fbf09..c8bf8a9 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -110,12 +110,14 @@ accepted_design: final active delay with constrained local waveform correlation around the decoded anchor delay. Each classified chirp carries the full dechirped bin-energy surface, and decodes aggregate that into per-band response - evidence for frequency-response normalization. `MimirChirpBinCalibrationProfile` - now preserves that evidence per source with frame count, anchor count, clock - confidence, anchor error, usable band count, and strongest measured bins. - The analyzer keeps calibration profiles even when a source fails pairwise - timing, so physical mic failures can still guide acoustic codebook and - response-weighting work. Reports and sync states expose + evidence for frequency-response normalization. `MimirChirpBinCalibrationModel` + now preserves measured usable bands, expected-symbol versus observed-bin + confusion observations, timing residuals, delay hypotheses, phase summaries, + and an adaptive codebook plan. The active decoder can consume a persisted + model for learned response weighting, first-order group-delay correction, + and global delay-hypothesis seeding. The analyzer keeps calibration profiles + even when a source fails pairwise timing, so physical mic failures can still + guide acoustic codebook and response-weighting work. Reports and sync states expose delay in microseconds as well as fractional samples. Hybrid emits it as low-gain half-second bursts every two seconds while passive confidence is weak, never as catch-up backlog. The analyzer no longer applies the passive two-second window floor @@ -132,10 +134,13 @@ accepted_design: `native/probes/asio_audio_cadence --play-f32-mono --record-f32-interleaved` now provides a real Scarlett ASIO artifact proof for active mode. At 192 kHz, Loopback 1 -> Loopback 2 decoded at 0.000 us with 12 anchors and 0.999 - confidence. Physical inputs still fail accepted pairwise timing against - loopback in the stored artifact, but decoded mic channels now leave - calibration profiles; physical input 1 produced 14 frames, 12 anchors, 0.865 - clock confidence, and strongest measured bins around 4525, 4075, and 7225 Hz. + confidence. `Mimir.BufferSmoke --calibrate-chirp-bin-asio-f32` now renders + or ingests a calibration capture and persists the response/confusion/delay + model under `calibration/chirp-bin/`; `--analyze-asio-f32 --calibration` + loads it back into the active decoder. Physical inputs still fail accepted + pairwise timing against loopback in the stored artifact, but decoded mic + channels now leave calibration models; physical input 1 produced two reliable + symbols and a different strongest measured band set. Acoustic robustness remains open. The old arbitrary-chirplet matcher is a diagnostic/reference artifact only, not the runtime hot path. @@ -237,9 +242,9 @@ current_status: Kiyo 30.18 fps, PS3 Eye 0 59.66 fps, and PS3 Eye 1 59.95 fps. A mouse in the Leap USB neighborhood does not reproduce the Focusrite-induced collapse. next_action: > - Use chirp-bin calibration profiles from real microphones to tune the active - codebook/bands/weighting, then feed Scarlett ASIO callbacks directly into - Mimir.Runtime and add the synchronization + Use chirp-bin calibration models from real microphones to tune the active + codebook/bands/weighting, then replace the ASIO JSON-block process bridge + with a native ABI callback source and add the synchronization actuator. Current app testing proves Fensalir WASAPI output wakes Scarlett speaker loopback, live mic buffers stay populated, runtime sync analysis now runs as