DIRECTORY ColorizeViewPointSweep, ColorizeViewPoint, ColorizeViewPointBackdoor, Convert, ImagerPixel, ImagerSample, IO, IPMaster, Profiles, Real, RealFns, Rope, SF; ColorizeViewPointSweepImpl: CEDAR PROGRAM IMPORTS ColorizeViewPoint, ColorizeViewPointBackdoor, Convert, ImagerPixel, ImagerSample, IO, IPMaster, Profiles, Real, RealFns, Rope, SF EXPORTS ColorizeViewPointSweep ~ BEGIN OPEN ColorizeViewPointSweep; ROPE: TYPE ~ Rope.ROPE; ASSERTION: TYPE ~ BOOL [TRUE..TRUE]; defaultMaxHalftoneLevels: ROPE ~ "72"; maxSampleSize: NAT ~ 255; ConstructSweepPixelArray: PUBLIC PROC [def: LIST OF ROPE, palette: Profiles.Profile] RETURNS [iSize: NAT _ 0, paFrag: ROPE] ~ { ENABLE UNWIND => NULL; sweepRope: ROPE ~ RopeFromList[def]; maxHalftoneLevels: NAT; size, maxColorDistance, interpolationSteps: SF.Vec; maxHalftoneLevels _ Convert.CardFromRope[Profiles.Token[profile: palette, key: "SweepLevels", default: defaultMaxHalftoneLevels] ! Convert.Error => {SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, Rope.Concat["Sweep levels must be a positive number. Setting sweep levels to default of ", defaultMaxHalftoneLevels]]; maxHalftoneLevels _ Convert.CardFromRope[defaultMaxHalftoneLevels]; CONTINUE}]; maxHalftoneLevels _ MIN[maxHalftoneLevels, maxSampleSize]; [size: size, iSize: iSize] _ UninterpolatedPASize[sweepRope]; [maxColorDistance: maxColorDistance] _ MaxColorDistanceBetweenAdjacentPixels[sweepRope: sweepRope, size: size, iSize: iSize]; interpolationSteps _ [ --these are the minimum number of steps needed to step gracefully over the widest color distance in the f and s directions, assuming the printer has maxHalftoneLevels steps available s: MAX[1, INTEGER[INT[maxColorDistance.s]*INT[maxHalftoneLevels]/maxSampleSize]], f: MAX[1, INTEGER[INT[maxColorDistance.f]*INT[maxHalftoneLevels]/maxSampleSize]] ]; RETURN [iSize: iSize, paFrag: BuildInterpolatedPARope[sweepRope: sweepRope, size: size, iSize: iSize, interpolationSteps: interpolationSteps]]; }; UninterpolatedPASize: PROC [sweepRope: ROPE] RETURNS [size: SF.Vec, iSize: NAT] ~ { maxV: SF.Vec _ [INTEGER.FIRST, INTEGER.FIRST]; maxI: NAT _ 0; CollectSweepStatistics: PROC [v: SF.Vec, i, val: NAT] ~ { maxV _ maxV.Max[v]; maxI _ MAX[maxI, i]; }; ParseSweepRope[sweepRope, CollectSweepStatistics]; RETURN [size: maxV.Add[[1,1]], iSize: maxI+1]; }; MaxColorDistanceBetweenAdjacentPixels: PROC [sweepRope: ROPE, size: SF.Vec, iSize: NAT] RETURNS [maxColorDistance: SF.Vec _ [1,1]] ~ { FindMaxColorDistances: PROC [v: SF.Vec, i, val: NAT] ~ { IF v.s#prevS THEN { --(a new v.s has been passed from ParseSweepRope - the scratchbuffers must be processed before proceeding). IF p0#NIL THEN { --both p0 and p1 buffers are full; check pairs to find maxColorDistances FOR i: NAT IN [0..iSize) DO FOR f: NAT IN [0..size.f-1) DO --find maxColorDistance.f maxColorDistance.f _ MAX[maxColorDistance.f, AbsDiff[p0[i][f], p0[i][f+1]]] ENDLOOP; FOR f: NAT IN [0..size.f) DO --find maxColorDistance.s maxColorDistance.s _ MAX[maxColorDistance.s, AbsDiff[p0[i][f], p1[i][f]]]; ENDLOOP; ENDLOOP; }; [p0, p1] _ RollScratchBuffers[p0: p0, p1: p1, iSize: iSize, fSize: size.f]; prevS _ v.s; --this will cause all the above to be skipped again until p1 is refilled (marked by v.s being passed in with a new value) IF v.s=size.s THEN --this checks maxColorDistance.f on the final scanline which is held in p1. v.s is set to size.s on the FindMaxColorDistances call below which forces the final flush FOR i: NAT IN [0..iSize) DO FOR f: NAT IN [0..size.f-1) DO maxColorDistance.f _ MAX[maxColorDistance.f, AbsDiff[p1[i][f], p1[i][f+1]]]; ENDLOOP; ENDLOOP; }; p1[i][v.f] _ val; --fill p1 with the sample values read by ParseSweepRope. p1 will fill until a new v.s is passed in. When v.s changes, i and v.f restart at 0. }; p0, p1: ImagerPixel.PixelBuffer _ NIL; --scratch buffers used in FindMaxColorDistances and BuildPARope prevS: NAT _ NAT.LAST; --Initializing to .LAST forces initting of p1 to occur ParseSweepRope[sweepRope, FindMaxColorDistances]; FindMaxColorDistances[v: [size.s, 0], i: 0, val: 0]; --forces final eval of p1 ImagerPixel.ReleaseScratchPixels[pixels: p0]; ImagerPixel.ReleaseScratchPixels[pixels: p1]; }; BuildInterpolatedPARope: PROC [sweepRope: ROPE, size, interpolationSteps: SF.Vec, iSize: NAT] RETURNS [paFrag: ROPE] ~ { BuildPARope: PROC [v: SF.Vec, i, val: NAT] ~ { --paRope is a rope which expresses a color sweep. BuildPARope takes info passed in from ParseSweepRope which gives [multiple] start and end colors in two directions for the sweep. BuildPARope fills scratch buffer p1 with the first row start colors (v.f/f/fast direction). Then when v.s changes it dumps that to p0 and fills p1 with the next fast direction row of colors. Once both p0 and p1 are filled (signalled by v.s changing again), BuildPARope calls InterpolateScan to interpolate each scan of colors from the row defined by p0 upto (not including) the row defined by p1 - using interpolationSteps.s steps. Each row is interpolated by InterpolateScan using interpolationSteps.f steps between each fast direction color (the v.f loop), for each sample (the i loop). Then p1 is filled again and the process repeated for each pair of rows defined. IF v.s#prevS THEN { --(a new v.s has been passed from ParseSweepRope - the scratchbuffers must be processed before proceeding). IF p0#NIL THEN { --both p0 and p1 buffers are full; interpolate them scan by scan and flush each scan into paRope FOR s: NAT IN [0..interpolationSteps.s) DO --this loop interpolates between the row of colors held in p0 upto (but not including) the row of colors in p1 paRope _ paRope.Concat[Rope.FromRefText[InterpolateScan[scan: s, p0: p0, p1: p1, size: size, interpolationSteps: interpolationSteps, iSize: iSize, interpolatedScan: NewText[]]]]; --flush interpolatedScan into paRope... ENDLOOP; --...then loop for new s }; [p0, p1] _ RollScratchBuffers[p0: p0, p1: p1, iSize: iSize, fSize: size.f]; prevS _ v.s; --this will cause all the above to be skipped again until p1 is refilled (marked by v.s being passed in with a new value) IF v.s=size.s THEN paRope _ paRope.Concat[Rope.FromRefText[InterpolateScan[scan: interpolationSteps.s, p0: p0, p1: p1, size: size, interpolationSteps: interpolationSteps, iSize: iSize, interpolatedScan: NewText[]]]]; --this picks up the final scanline for the last row of the vertical interpolation process. v.s is set to size.s on the BuildPARope call below which forces the final flush }; p1[i][v.f] _ val; --fill p1 with the sample values read by ParseSweepRope. p1 will fill until a new v.s is passed in. When v.s changes, i and v.f restart at 0. }; --end of BuildPARope p0, p1: ImagerPixel.PixelBuffer _ NIL; --scratch buffers used in BuildPARope paRope: Rope.ROPE _ "\001"; --1 byte/sample in pixel array prevS: NAT _ NAT.LAST; -- .LAST forces init to occur in BuildPixelArray interpPASize: SF.Vec ~ [ s: (size.s-1)*interpolationSteps.s+1, f: (size.f-1)*interpolationSteps.f+1 ]; tLen: NAT ~ interpPASize.f*iSize; NewText: PROC RETURNS [t: REF TEXT] ~ INLINE {(t _ NEW[TEXT[tLen]]).length _ tLen}; ParseSweepRope[sweepRope, BuildPARope]; BuildPARope[v: [size.s, 0], i: 0, val: 0]; --forces final flush of p0, p1 { --Build arguments to MAKESAMPLEDCOLOR out: IO.STREAM ~ IO.ROS[]; IPMaster.PutInt[stream: out, n: interpPASize.s]; --xPixels IPMaster.PutInt[stream: out, n: interpPASize.f]; --yPixels IPMaster.PutInt[stream: out, n: iSize]; --samplesPerPixel IPMaster.PutInt[stream: out, n: maxSampleSize]; --maxSampleSize IPMaster.PutInt[stream: out, n: 1]; --samplesInterleaved~TRUE IPMaster.PutRational[stream: out, n: 1, d: interpPASize.s]; --T _ 1/xPixels 1/yPixels SCALE2 IPMaster.PutRational[stream: out, n: 1, d: interpPASize.f]; IPMaster.PutOp[stream: out, op: scale2]; IPMaster.PutSequenceRope[stream: out, seq: sequenceLargeVector, rope: paRope]; --The actual pixel array data IPMaster.PutOp[stream: out, op: makepixelarray]; --MAKEPIXELARRAY paFrag _ IO.RopeFromROS[self: out]; }; ImagerPixel.ReleaseScratchPixels[pixels: p0]; ImagerPixel.ReleaseScratchPixels[pixels: p1]; }; InterpolateScan: PROC [scan: INT, p0, p1: ImagerPixel.PixelBuffer, size, interpolationSteps: SF.Vec, iSize: NAT, interpolatedScan: REF TEXT] RETURNS [REF TEXT] ~ { --"scan" is a number which gives the particular horizontal row within a vertical interpolation being done scan by scan. I.e. scan IN [0..interpolationSteps.s]. assert: ASSERTION ~ interpolatedScan.length = iSize*((size.f-1)*interpolationSteps.f+1); scanComplement: INT ~ interpolationSteps.s-scan; FOR i: NAT IN [0..iSize) DO FOR uf --uninterpolated f--: NAT IN [0..size.f-1) DO --Interpolation is done from one defined color point up to (but not including) the next within a scanline for each sample. Then scanIndex jumps ahead, we loop back to here and interpolation is done for the next pair of color points (if any). Because it is "up to but not including", the final color must be added in at the end. See below. from: NAT ~ (scan*p1[i][uf]+scanComplement*p0[i][uf])/interpolationSteps.s; --this is the beginning color for a particular scan to: NAT ~ (scan*p1[i][uf+1]+scanComplement*p0[i][uf+1])/interpolationSteps.s; --this is the next defined color in the scan: it is the interpolation target scanIndex: NAT _ uf*iSize*interpolationSteps.f + i; FOR f: NAT IN [0 .. interpolationSteps.f) DO --this does the interpolation along a scanline, from one defined color upto the next interpolatedScan[scanIndex] _ VAL[BYTE[(INT[f]*to+INT[interpolationSteps.f-f]*from)/INT[interpolationSteps.f]]]; scanIndex _ scanIndex+iSize; ENDLOOP; ENDLOOP; { --The above loop takes care of all pairs of colors along a scanline, but the last defined color is left out. Put it in the scanLine here: (This incidentally also takes care of the case where size.f=1) scanIndex: NAT _ interpolatedScan.length - iSize + i; to: NAT ~ (scan*p1[i][size.f-1]+scanComplement*p0[i][size.f-1])/interpolationSteps.s; --same as "to" above, with size.f-1 indexing the last color specified in p0 and p1 interpolatedScan[scanIndex] _ VAL[BYTE[to]]; --same as above with f=interpolationSteps.f }; ENDLOOP; RETURN [interpolatedScan] }; --end of InterpolateScan AbsDiff: PROC [a,b: ImagerSample.Sample] RETURNS [INTEGER] ~ INLINE { RETURN [ABS[INTEGER[a]-INTEGER[b]]] }; ParseSweepRope: PROC [rope: ROPE, action: PROC [v: SF.Vec, i, val: NAT]] ~ {--expects values btwn 0 and 1; returns values btwn 0 and maxSampleSize IPFragBreak: IO.BreakProc = { RETURN [SELECT char FROM IN [IO.NUL .. IO.SP] , '\t => sepr, IN ['0..'9], '. => other, ENDCASE => break]; }; n, m, i, val: NAT _ 0; stream: IO.STREAM ~ IO.RIS[rope: rope]; numFound: BOOL _ FALSE; --number must be first in Sweep rope DO token: ROPE; tokenChar: CHAR; token _ IO.GetTokenRope[stream: stream, breakProc: IPFragBreak ! IO.EndOfStream => EXIT].token; SELECT tokenChar _ token.Fetch FROM IN ['0..'9] , '. => { test, realVal: REAL; numFound _ TRUE; test _ Convert.RealFromRope[r: token ! Convert.Error => { SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Difficulty converting token [%g] in sweep profile entry %g. Value set to 1.0.", v1: [rope[token]], v2: [rope[rope]]]]; test _ 1.0; CONTINUE } ]; IF test>1.0 THEN SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Sweep color values should be between 0 and 1; %g is being set to 1 in sweep %g", v1: [real[test]], v2: [rope[rope]] ]]; realVal _ MIN[1, MAX[0, test]]; action[v: [s: n, f: m], i: i, val: ColorizeViewPointBackdoor.AltRound[realVal * maxSampleSize]]; i _ i+1; }; '- => {IF ~numFound THEN GOTO BadRope; m _ m+1; i _ 0; numFound _ FALSE}; ', => {IF ~numFound THEN GOTO BadRope; n _ n+1; m _ i _ 0; numFound _ FALSE}; ENDCASE => SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Unexpected character [%g] in sweep profile entry %g.", v1: [character[tokenChar]], v2: [rope[rope]]]]; REPEAT BadRope => ERROR ColorizeViewPoint.Error[$MalformedPaletteEntry, IO.PutFR[format: "Numbers must come first in sweep profile entry %g.", v1: [rope[rope]]]]; ENDLOOP; }; RollScratchBuffers: PROC [p0, p1: ImagerPixel.PixelBuffer, iSize, fSize: NAT] RETURNS [np0, np1: ImagerPixel.PixelBuffer] ~ { IF p1=NIL THEN { assert: ASSERTION ~ p0=NIL; p1 _ ImagerPixel.ObtainScratchPixels[samplesPerPixel: iSize, length: fSize]; FOR i: NAT IN [0..iSize) DO ImagerSample.ClearSamples[buffer: p1[i]]; ENDLOOP; } ELSE { assertP1: ASSERTION ~ p1.samplesPerPixel=iSize AND p1.length=fSize; assertP0: ASSERTION ~ p0=NIL OR (p0.samplesPerPixel=iSize AND p0.length=fSize); IF p0=NIL THEN p0 _ ImagerPixel.ObtainScratchPixels[samplesPerPixel: iSize, length: fSize]; FOR i: NAT IN [0..iSize) DO ImagerSample.CopySamples[dst: p0[i], src: p1[i]]; --roll by copying ENDLOOP; }; RETURN [p0, p1]; }; RopeFromList: PROC [list: LIST OF ROPE] RETURNS [rope: ROPE _ NIL] ~ INLINE {IF list=NIL THEN ERROR ColorizeViewPoint.Error[$MalformedPaletteEntry, "Sweep definition missing - no colors follow the word \"Sweep\""] ELSE FOR each: LIST OF ROPE _ list, each.rest UNTIL each=NIL DO rope _ rope.Cat[" ", each.first]; ENDLOOP}; ConstructSweepTransform: PUBLIC PROC [ts: SampledColorTransformSet, sc: REF SampledColorIPFragments] RETURNS [transform: ROPE] ~ { --Constructs an Interpress fragment defining the um transformation needed by makesampledcolor. A sampled color is produced by combining sc.beforeTransform, transform, sc.afterTransform. If the ts is a rectangle (width.y=height.x=0), it will be rotated to be the correct transform for the angle sc.sweepAngleMod360 RotateTransformSet: PROC [] ~ { --rotates rectangles to match sweep angle IF sc.sweepAngleMod360#0 THEN { --the algorithm below specifies the minimum rectangle needed to completely enclose the original rectangle, when tilted at sweepAngle. sinA: REAL _ RealFns.SinDeg[sc.sweepAngleMod360 MOD 90]; -- move angle to first quadrant cosA: REAL _ RealFns.CosDeg[sc.sweepAngleMod360 MOD 90]; w: REAL _ ts.width.x; h: REAL _ ts.height.y; ts.offset.x _ ts.offset.x - h*sinA*cosA; ts.offset.y _ ts.offset.y + h*sinA*sinA; ts.width.x _ cosA*(h*sinA + w*cosA); ts.width.y _ -sinA*(h*sinA + w*cosA); ts.height.x _ sinA*(h*cosA + w*sinA); ts.height.y _ cosA*(h*cosA + w*sinA); SELECT sc.sweepAngleMod360/90 + 1 --quadrant-- FROM --rotate the offset and edges according to the quadrant 1 => NULL; 2 => { temp: VEC ~ ts.width; ts.offset.x _ ts.offset.x + ts.height.x; ts.offset.y _ ts.offset.y + ts.height.y; ts.width.x _ -1*ts.height.x; ts.width.y _ -1*ts.height.y; ts.height _ temp; }; 3 => { ts.offset.x _ ts.offset.x + ts.height.x + ts.width.x; ts.offset.y _ ts.offset.y + ts.height.y + ts.width.y; ts.width.x _ -1*ts.width.x; ts.width.y _ -1*ts.width.y; ts.height.x _ -1*ts.height.x; ts.height.y _ -1*ts.height.y; }; 4 => { temp: VEC ~ ts.height; ts.offset.x _ ts.offset.x + ts.width.x; ts.offset.y _ ts.offset.y + ts.width.y; ts.height.x _ -1*ts.width.x; ts.height.y _ -1*ts.width.y; ts.width _ temp; }; ENDCASE => ERROR; }; }; out: IO.STREAM ~ IO.ROS[]; IF ts#nullTransformSet AND ts.height.x=0 AND ts.width.y=0 THEN RotateTransformSet[]; IPMaster.PutReal[stream: out, val: ts.height.x]; IPMaster.PutReal[stream: out, val: ts.width.x]; IPMaster.PutReal[stream: out, val: ts.offset.x]; IPMaster.PutReal[stream: out, val: ts.height.y]; IPMaster.PutReal[stream: out, val: ts.width.y]; IPMaster.PutReal[stream: out, val: ts.offset.y]; RETURN [out.RopeFromROS[]]; }; END. όColorizeViewPointSweepImpl.mesa Copyright Σ 1987 by Xerox Corporation. All rights reserved. Bob Coleman, September 6, 1990 12:48:45 pm PDT Eric Nickell, May 16, 1989 1:04:04 pm PDT Constructs an Interpress fragment defining a pixel array that will sweep between the colors specified in the passed-in definition. To print the sweep, insert the fragment as the "pa" in MakeSampledColor or SetSampledColor in an Interpress master. Constructs an Interpress fragment defining a pixel array that will sweep between the colors specified in def. palette should contain the number of sweep levels desired. iSize is returned for use in determining the ColorModelOperator needed in MakeSampledColor. Given a sweep rope, computes the size of the uninterpolated pixel array. For the uninterpolated pixel array, compute the maximum differences between adjacent samples. I. Do First if Buffers Are Full: Ia. If p0 (& therefore p1) are non-NIL, analyze the s & f color distances: Ib. Roll p1 into p0, and obtain a new p1 II. Do always- drop current value into p1: Constructs an Interpress fragment defining a pixel array that will sweep between the colors specified in sweepRope. I. Do First if Buffers Are Full: Ia. If p0 & p1 are full, build the pixel array: Ib. Roll the buffers ... p0 _ p1, and prepare p1 to be filled again: II. Do always- fill p1: [char: CHAR] RETURNS [IO.CharClass] This part works for angles > 0 and < 90 degrees This part adjusts for angles >= 90 and <360 build the transform for the pixel array Κ~•NewlineDelimiter – "cedar" style˜code•Mark outsideHeaderšœ™Kšœ<™K–&[stream: STREAM, n: INT, d: INT]šœ< ₯˜\Kšœ;˜;K–%[stream: STREAM, op: IPMaster.Op]šœ(˜(K–i[stream: STREAM, seq: IPMaster.SequenceType, rope: ROPE, start: INT _ 0, len: INT _ 2147483647]šœO ˜lK–%[stream: STREAM, op: IPMaster.Op]šœ1 ˜AK–&[self: STREAM, close: BOOL _ TRUE]šœ œ˜#Kšœ˜—K–#[pixels: ImagerPixel.PixelBuffer]˜-K–#[pixels: ImagerPixel.PixelBuffer]˜-K˜K™—šŸœœœ=œ œœœœœœ  ˜ΔKšœ œG˜XKšœœ˜0šœœœ ˜š œ œœœœ Φ˜‹KšœœC 3˜KšœœG L˜šKšœ œ&˜4š œœœœ T˜Kš œœœœœœ˜pK˜Kšœ˜—Kšœ˜—šœ Ι˜ΛKšœ œ'˜5KšœœO S˜©Kšœœœ +˜XK˜—Kšœ˜—Kšœ˜šœ ˜K˜——š Ÿœœœœœ˜EKšœœœœ˜#K˜K˜—š Ÿœœœ œœœ F˜’–' -- [char: CHAR] RETURNS [IO.CharClass]šŸ œœ˜Kšœœœœ ™#šœœ˜Kš œœœœœ˜#Kšœ˜Kšœ ˜—K˜—Kšœœ˜Kš œœœœœ ˜'Kšœ œœ $˜<š˜Kšœœ˜ K–-[stream: STREAM, breakProc: IO.BreakProc]šœ œ˜Kšœœ7œœ˜_šœ˜#šœ˜K–-[r: ROPE, defaultBase: Convert.Base _ 10]šœœ˜Kšœ œ˜šœ%˜%šœ˜Kšœ3œ‡˜ΒK˜ Kšœ˜——Kšœ œœ3œ‡˜ΣKšœ œœ ˜Kšœa˜aKšœ˜Kšœ˜—Kš œœ œœ%œ˜IKš œœ œœ)œ˜MKšœœ3œv˜Ό—š˜Kšœ œ1œX˜›—Kšœ˜—K˜K˜K™—šŸœœ1œœ(˜}šœœœ˜Kšœ œœ˜K–'[samplesPerPixel: NAT, length: NAT]˜Lšœœœ ˜K–M[buffer: ImagerSample.SampleBuffer, start: NAT _ 0, count: NAT _ 32767]˜)Kšœ˜—Kšœ˜—šœ˜Kšœ  œœ˜CKš œ  œœœœ˜OKšœœœM˜[šœœœ ˜K–„[dst: ImagerSample.SampleBuffer, src: ImagerSample.SampleBuffer, dstStart: NAT _ 0, srcStart: NAT _ 0, count: NAT _ 32767]šœ2 ˜CKšœ˜—Kšœ˜—Kšœ ˜K˜K˜—Kš,Ÿ œœœœœœœœœœœœœsœœœœœœœœ#œ˜ΑK™—K˜š Ÿœœœ$œœ œ »˜Ύ–1[x: SymTab.Ref, key: ROPE, val: SymTab.Val]šŸœœ )˜Išœœ ‡˜§Kšœ/™/Kšœœ&œ  ˜ZKšœœ&œ˜8Kšœœ˜Kšœœ˜Kšœ(˜(Kšœ(˜(Kšœ$˜$K˜%K˜%K˜%Kšœ+™+šœ  œ 7˜kKšœœ˜ šœ˜Kšœœ ˜KšœQ˜QK˜9K˜K˜—šœ˜Kšœk˜kKšœ7˜7K˜;K˜—šœ˜Kšœœ ˜KšœO˜OKšœ9˜9K˜K˜—Kšœœ˜Kšœ˜——K˜—Kš œœœœœ˜Kšœœœœ˜TK–[stream: STREAM, val: REAL]šœ'™'Kšœ1˜1K–[stream: STREAM, val: REAL]˜/K–[stream: STREAM, val: REAL]˜0K–[stream: STREAM, val: REAL]šœ1˜1K–[stream: STREAM, val: REAL]˜/K–[stream: STREAM, val: REAL]˜0Kšœ˜K˜K˜K˜K˜—Kšœ˜—…—