<> <> DIRECTORY DifferenceAveragers, BasicTime, Buttons, Containers, Imager, IO, Menus, Process, Real, RealFns, Rope, Sliders, VFonts, ViewerClasses, ViewerOps; DifferenceAveragersImpl: CEDAR MONITOR LOCKS a USING a: Averager IMPORTS BasicTime, Buttons, Containers, Imager, IO, Process, Real, RealFns, Rope, Sliders, VFonts, ViewerOps EXPORTS DifferenceAveragers = BEGIN OPEN DifferenceAveragers; Time: TYPE = BasicTime.GMT; Averager: TYPE = REF AveragerPrivate; AveragerPrivate: PUBLIC TYPE = MONITORED RECORD [ logMin, logMax: INT, min, scale: REAL, samplePeriod, halfLife, originalHalfLife: Seconds, weightedSums: ARRAY Role OF REAL, lastTime: Time, container, samplePeriodButton, halfLifeButton: Viewer _ NIL, valueShowers: ARRAY Role OF Viewer _ ALL[NIL], Sample: PROC [clientData: REF ANY] RETURNS [add, subtract: REAL], clientData: REF ANY, timeToSample: CONDITION, sampler: PROCESS _ NIL ]; Role: TYPE = {add, subtract, difference}; lnOneHalf: REAL = RealFns.Ln[0.5]; halfMaxHalfLife: Seconds = LONG[12*60]*60; sep1: INT _ 1; sep2: INT _ 2; timeWidth: INT _ VFonts.StringWidth["12:34:56"]; foreground: Imager.Color _ Imager.MakeGray[0.5]; Create: PUBLIC PROC [ viewerInfo: ViewerClasses.ViewerRec _ [], samplePeriod, halfLife: Seconds, logMin, logMax: INT, Sample: PROC [clientData: REF ANY] RETURNS [add, subtract: REAL], clientData: REF ANY _ NIL ] RETURNS [a: Averager] = { boundSP: Seconds = BoundSP[samplePeriod, halfLife]; a _ NEW [AveragerPrivate _ [ logMin: logMin, logMax: logMax, min: RealFns.Power[10, logMin], scale: 1.0/(logMax - logMin), samplePeriod: boundSP, halfLife: halfLife, originalHalfLife: halfLife, weight: 1.0/(1.0 - RealFns.Power[0.5, REAL[boundSP]/halfLife]), weightedSums: ALL[0.0], lastTime: BasicTime.Now[], container: Containers.Create[viewerInfo], Sample: Sample, clientData: clientData ]]; TRUSTED { Process.InitializeCondition[@a.timeToSample, Process.SecondsToTicks[a.samplePeriod]]; Process.EnableAborts[@a.timeToSample]; }; a.samplePeriodButton _ Buttons.Create[info: [parent: a.container, wx: 0, wy: 0, name: SPBName[a], border: FALSE], proc: TweakSamplePeriod, clientData: a, paint: FALSE]; a.halfLifeButton _ Buttons.Create[info: [parent: a.container, wx: a.samplePeriodButton.wx+a.samplePeriodButton.ww+sep1, wy: 0, border: FALSE, name: HLBName[a]], proc: TweakHalfLife, clientData: a, paint: FALSE]; FOR role: Role IN Role DO dy: INT = a.container.ch/(Role.LAST.ORD+1); a.valueShowers[role] _ Sliders.Create[info: [parent: a.container, wx: a.halfLifeButton.wx+a.halfLifeButton.ww+sep1, wy: dy*role.ORD, ww: 10, wh: dy, border: FALSE], orientation: horizontal, foreground: foreground, value: Normalize[a, 0.0], clientData: a, paint: FALSE]; Containers.ChildXBound[a.container, a.valueShowers[role]]; ENDLOOP; ViewerOps.PaintViewer[a.container, all]; a.sampler _ FORK Sampler[a]; }; GetLayout: PUBLIC PROC [a: Averager, ancestor: Viewer] RETURNS [samplePeriodButton, halfLifeButton, bars: XRange --in client coords of ancestor (screen if NIL)--] = { Loc: PROC [child: Viewer] RETURNS [loc: XRange] = { loc _ [ left: ViewerOps.UserToScreenCoords[a.container, child.wx, child.wy].sx - (IF ancestor # NIL THEN ViewerOps.UserToScreenCoords[ancestor, 0, 0].sx ELSE 0), width: child.ww]; }; samplePeriodButton _ Loc[a.samplePeriodButton]; halfLifeButton _ Loc[a.halfLifeButton]; bars _ Loc[a.valueShowers[add]]; }; Normalize: PROC [a: Averager, value: REAL] RETURNS [normalized: REAL] = INLINE { normalized _ MIN[1.0, MAX[RealFns.Log[10, MAX[a.min, ABS[value]]]-a.logMin, 0] * a.scale]}; minFraction: REAL = 1.0/4096.0 --half of the 24 bits of mantissa in a REAL--; maxRatio: REAL = -RealFns.Log[2.0, minFraction]; minRatio: REAL = -RealFns.Log[2.0, 1-minFraction]; BoundSP: PROC [candidateSamplePeriod, halfLife: Seconds] RETURNS [kosherSamplePeriod: Seconds] = { kosherSamplePeriod _ Real.Round[MIN[maxRatio*halfLife, MAX[minRatio*halfLife, REAL[candidateSamplePeriod]]]]}; GoodSP: PROC [halfLife: Seconds] RETURNS [aGoodSamplePeriod: Seconds] = { aGoodSamplePeriod _ 1}; TweakHalfLife: PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift, control: BOOL _ FALSE]--Buttons.ButtonProc-- = { a: Averager = NARROW[clientData]; EnterAnd: ENTRY PROC [a: Averager] = { ENABLE UNWIND => NULL; newHalfLife: Seconds = SELECT mouseButton FROM red => MAX[MIN[halfMaxHalfLife, a.halfLife]*2, 1], yellow => a.originalHalfLife, blue => MAX[a.halfLife/2, 1], ENDCASE => ERROR; IF newHalfLife # a.halfLife THEN { a.halfLife _ newHalfLife; a. Buttons.ReLabel[a.halfLifeButton, HLBName[a]]; InnerSetSamplePeriod[a, BoundSP[a.samplePeriod, a.halfLife]]; }; }; EnterAnd[a]; }; TweakSamplePeriod: PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift, control: BOOL _ FALSE]--Buttons.ButtonProc-- = { a: Averager = NARROW[clientData]; SetSamplePeriod[ a, SELECT mouseButton FROM red => BoundSP[a.samplePeriod*2, a.halfLife], yellow => GoodSP[a.halfLife], blue => BoundSP[MAX[a.samplePeriod/2, 1], a.halfLife], ENDCASE => ERROR ]; }; SetSamplePeriod: ENTRY PROC [a: Averager, new: Seconds] = { ENABLE UNWIND => NULL; InnerSetSamplePeriod[a, new]}; InnerSetSamplePeriod: INTERNAL PROC [a: Averager, new: Seconds] = { IF new # a.samplePeriod THEN { a.samplePeriod _ new; Buttons.ReLabel[a.samplePeriodButton, SPBName[a]]; TRUSTED {Process.SetTimeout[@a.timeToSample, Process.SecondsToTicks[a.samplePeriod]]}; BROADCAST a.timeToSample; }; }; AsViewer: PUBLIC PROC [a: Averager] RETURNS [v: Viewer] = { v _ a.container}; Sampler: ENTRY PROC [a: Averager] = { ENABLE UNWIND => NULL; DO WAIT a.timeToSample; [ IF a.container.destroyed THEN EXIT; { now: Time = BasicTime.Now[]; period: Seconds = BasicTime.Period[a.lastTime, now]; keep: REAL = RealFns.Exp[a. a.lastTime _ now; a.weight _ a.weight*keep + 1; FOR role: Role IN Role DO a.weightedSums[role] _ keep*a.weightedSums[role] + Sliders.SetContents[a.valueShowers[role], Normalize[a, a.weightedSums[role]/a.weight]]; ENDLOOP; }ENDLOOP; }; SPBName: PROC [a: Averager] RETURNS [name: ROPE] = { name _ FmtPeriod[a.samplePeriod].Concat["/"]}; HLBName: PROC [a: Averager] RETURNS [name: ROPE] = { name _ FmtPeriod[a.halfLife]}; FmtPeriod: PROC [period: Seconds] RETURNS [asRope: ROPE] = { hours: INT = period/3600; rem1: INT = period - hours*3600; minutes: INT = rem1 / 60; seconds: INT = rem1 - minutes*60; asRope _ IO.PutFR["%02g:%02g:%02g", [integer[hours]], [integer[minutes]], [integer[seconds]]]; }; END.