DIRECTORY CursorDefs, Beam USING [Draw, SetStems], Graphics USING [DrawScreenArea, SetPaint, SetTexture], Interface USING [InsertNote, Object], MusicDefs, Note USING [Draw, DrawTie, SetAccidental, SetEmbellishment], Piece USING [AddSync, DeleteSync, NearestNote, NearestObject, RemoveSync], ProcessDefs USING [SetTimeout], Real USING [FixI], Score USING [GetKey, Redraw], Selection, -- USING everything Sheet USING [AlternateTime, DrawClef, DrawOctava, FindLine, FindSection, GetStyle, Height, NearestStaff, NearestTime, NextLine, NextStaff, NormalPitch, OctavaHeight, PriorStaff, Reset, ScreenPoint, SetClef, SetOctava], Sync USING [Draw, GetScoreIndex, GetStaff, Octava], Utility; -- USING everything InterfaceImplB: CEDAR MONITOR IMPORTS Beam, Graphics, Interface, MusicDefs, Note, Piece, ProcessDefs, Real, Score, Selection, Sheet, Sync, Utility EXPORTS Interface = BEGIN OPEN Graphics, MusicDefs; timeout: CONDITION; Error: SIGNAL = CODE; count: PUBLIC BOOL; DeleteGraphical: PUBLIC PROC = { type: EventType; obj: ObjectType; s: SyncPTR _ NIL; time, carry, next: Time; p: UnspecifiedPTR _ NIL; value, oldClef, oldStyle: INTEGER _ 0; SetBrush[black, invert]; WHILE YellowBug[] DO [obj, p] _ Piece.NearestObject[]; IF p#NIL THEN SELECT obj FROM note => Note.Draw[p]; measure => Sync.Draw[p]; leftBeam => [] _ Beam.Draw[p]; rightBeam=> [] _ Beam.Draw[p]; ENDCASE; Wait[1]; IF p#NIL THEN SELECT obj FROM note => Note.Draw[p]; measure => Sync.Draw[p]; leftBeam => [] _ Beam.Draw[p]; rightBeam=> [] _ Beam.Draw[p]; ENDCASE; ENDLOOP; IF p#NIL AND obj=note THEN Selection.RemoveNote[p]; IF obj#measure OR p=NIL THEN RETURN; s _ LOOPHOLE[p, SyncPTR]; IF voice AND s.type IN [clef..octava2] THEN RETURN; type _ s.type; time _ s.time; value _ s.value; IF type=clef THEN oldClef _ Clef[value, time]; IF type=staves THEN { next _ sheet[Sheet.NextLine[Sheet.FindLine[time]]].time; oldStyle _ Sheet.GetStyle[next]}; carry _ GetCarry[s]; Piece.DeleteSync[s]; SetDirty[time, time]; IF type=keySignature THEN SetDirty[time, carry]; IF type=keySignature OR type=clef THEN ResetSheet[time, time]; IF type=clef AND oldClef#Clef[value, time] THEN SetDirty[time, carry]; IF type=staves THEN { Sheet.Reset[]; IF Sheet.GetStyle[time]#value THEN SetDirty[begin, endTime]; IF Sheet.GetStyle[next]#oldStyle THEN SetDirty[begin, endTime]}; IF type IN [octava1..octava2] THEN Sheet.Reset[]; Score.Redraw[min, max]; count _ TRUE; }; ChangeSpelling: PROC[object: Interface.Object] = { n: NotePTR; min _1000000; max _ -1; SetBrush[black, invert]; WHILE BlueBug[] DO IF (n _ Piece.NearestNote[])=NIL THEN LOOP; Note.Draw[n]; Note.Draw[n]; ENDLOOP; SetBrush[black, paint]; IF n=NIL OR n.rest THEN RETURN; IF voice AND n.voice#selectedVoice THEN RETURN; SELECT object FROM doubleFlat => Note.SetAccidental[n, doubleFlat]; flat => Note.SetAccidental[n, flat]; natural => Note.SetAccidental[n, natural]; inKey => Note.SetAccidental[n, inKey]; sharp => Note.SetAccidental[n, sharp]; doubleSharp=> Note.SetAccidental[n, doubleSharp]; ENDCASE; IF flash THEN {Flash[]; RETURN}; IF n.spelled#inKey THEN n.show _ TRUE ELSE n.show _ FALSE; Score.Redraw[min, max]; }; Embellish: PROC[object: Interface.Object] = { x, y: INTEGER; n: NotePTR _ NIL; min _1000000; max _ -1; SetBrush[black, invert]; WHILE BlueBug[] DO [x, y] _ Sheet.ScreenPoint[]; IF (n _ Piece.NearestNote[x, y])=NIL THEN LOOP; Note.Draw[n]; Note.Draw[n]; ENDLOOP; SetBrush[black, paint]; IF n=NIL OR n.rest THEN RETURN; SELECT object FROM trill => Note.SetEmbellishment[n, trill]; mordent1 => Note.SetEmbellishment[n, mordent1]; mordent2 => Note.SetEmbellishment[n, mordent2]; ENDCASE; Score.Redraw[min, max]; }; Insert: PROC[object: Interface.Object] = { OPEN CursorDefs; tempCursor: Cursor _ cursor^; cursor^ _ ALL[0]; SELECT object FROM staves, measure, repeat1, repeat2, doubleMeasure, endMeasure => InsertMeasure[object]; bass, treble => InsertClefChange[object]; octava => {cursor^ _ tempCursor; InsertOctava[object]}; doubleFlat, flat, natural, inKey, sharp, doubleSharp => ChangeSpelling[object]; trill, mordent1, mordent2 => Embellish[object]; note, rest => Interface.InsertNote[object]; ENDCASE => ERROR; cursor^ _ tempCursor; count _ TRUE; }; InsertMeasure: PROC[object: Interface.Object] = { x, y: INTEGER; s: SyncPTR _ Utility.NewSync[]; SELECT object FROM measure => s.type _ measure; staves => s.type _ staves; repeat1 => s.type _ repeat1; repeat2 => s.type _ repeat2; endMeasure => s.type _ endMeasure; doubleMeasure => s.type _ doubleMeasure; ENDCASE => ERROR; [x, y] _ Sheet.ScreenPoint[]; [s.time,] _ Sheet.NearestTime[x, y]; SetBrush[black, invert]; WHILE BlueBug[] DO Sync.Draw[s]; Sync.Draw[s]; [x, y] _ Sheet.ScreenPoint[]; [s.time,] _ Sheet.NearestTime[]; ENDLOOP; IF YellowBug[] THEN RETURN; IF object=staves THEN { s.value _ Sheet.GetStyle[s.time]; LOOPHOLE[s.event, Staves] _ style[s.value]}; Piece.AddSync[score, s]; SetDirty[s.time, s.time]; Score.Redraw[min, max]; }; InsertClefChange: PROC[object: Interface.Object] = { old, new: Section; sync: SyncPTR _ NIL; pitch, height: INTEGER; oldTime, newTime: Time _ 0; change: BOOL _ FALSE; oldStaff, newStaff, oldClef: INTEGER _ 10; SELECT object FROM bass => pitch _ 27; treble => pitch _ 48; ENDCASE => Error; SetBrush[black, invert]; [newTime, height] _ Sheet.NearestTime[]; newStaff _ Sheet.NearestStaff[newTime, height]; WHILE BlueBug[] DO [newTime, height] _ Sheet.NearestTime[]; newStaff _ Sheet.NearestStaff[newTime, height]; IF newTime=oldTime AND newStaff=oldStaff THEN LOOP; IF oldStaff#10 THEN Sheet.DrawClef[pitch, oldStaff, oldTime]; Sheet.DrawClef[pitch, newStaff, newTime]; oldTime _ newTime; oldStaff _ newStaff; ENDLOOP; Sheet.DrawClef[pitch, newStaff, newTime]; IF YellowBug[] THEN RETURN; old _ sheet[Sheet.FindLine[newTime]]; oldClef _ Clef[newStaff, newTime]; Sheet.SetClef[pitch, newStaff, newTime]; new _ sheet[Sheet.FindLine[newTime]]; IF flash THEN {Flash[]; RETURN}; IF old#new THEN SetDirty[new.time, new.time]; SetDirty[newTime, newTime]; IF pitch=oldClef THEN {Score.Redraw[min, max]; RETURN}; FOR i: CARDINAL IN [0..scoreLength) DO IF score[i].time#newTime THEN LOOP; IF score[i].type#clef THEN LOOP; IF score[i].value#newStaff THEN LOOP; sync _ score[i]; EXIT; ENDLOOP; IF sync#NIL THEN SetDirty[sync.time, GetCarry[sync]]; Score.Redraw[min, max]; }; InsertOctava: PROC[object: Interface.Object] = { begin, end, oldEnd: Time_ -1; staff, oldStaff, alternate: INTEGER _ 1; pitch, height, oldHeight, octHeight, limit: INTEGER _ 0; SetBrush[black, invert]; [begin, oldHeight] _ Sheet.NearestTime[]; staff _ Sheet.NearestStaff[begin, oldHeight]; WHILE BlueBug[] DO [end, height, staff] _ Slide[oldEnd, oldHeight, oldStaff]; IF end=oldEnd AND height=oldHeight THEN LOOP; IF oldEnd#-1 THEN Sheet.DrawOctava[pitch, oldStaff, octHeight, begin, oldEnd]; oldEnd _ end; oldStaff _ staff; oldHeight _ height; octHeight _ height - Sheet.Height[end,, staff]; pitch _ (IF Sheet.NormalPitch[staff]=27 THEN 15 ELSE 60); Sheet.DrawOctava[pitch, staff, octHeight, begin, end]; ENDLOOP; Sheet.DrawOctava[pitch, staff, octHeight, begin, end]; IF YellowBug[] THEN RETURN; IF begin>=end THEN RETURN; Sheet.SetOctava[pitch, staff, octHeight, begin, end]; IF flash THEN {Flash[]; RETURN}; Score.Redraw[begin, end]; }; Slide: PROC[oldTime: Time, oldHeight: INTEGER, oldStaff: CARDINAL] RETURNS[time: Time, height: INTEGER, staff: CARDINAL] = { limit: INTEGER; staves: StavesPTR; staff _ oldStaff; [time, height] _ Sheet.NearestTime[]; IF oldTime#-1 AND ABS[time-oldTime]>200 AND time>oldTime THEN [time, height] _ Sheet.AlternateTime[time, height,-1]; IF oldTime#-1 AND ABS[time-oldTime]>200 AND time= limit THEN { -- we went above an upper limit IF staff=oldStaff THEN { -- must be last line [time, height] _ Sheet.AlternateTime[time, height,-1]; staff _ staves.sl}; RETURN}; staff _ oldStaff; }; MoveGraphical: PUBLIC PROC[object: Interface.Object] = { s: SyncPTR; p: UnspecifiedPTR _ NIL; obj: ObjectType; IF object#none THEN { Insert[object]; RETURN; }; SetBrush[black, invert]; WHILE BlueBug[] DO [obj, p] _ Piece.NearestObject[]; IF p=NIL THEN LOOP; s _ LOOPHOLE[p]; IF voice AND s.type IN [clef..octava2] THEN LOOP; SELECT obj FROM measure => { IF s.type IN [octava1..octava2] THEN MoveOctava[s] ELSE MoveMeasure[s]}; leftBeam => MoveBeam[p]; rightBeam=> ChangeTilt[p]; tie=> MoveTie[p]; ENDCASE; ENDLOOP; IF p#NIL THEN count _ TRUE; }; MoveMeasure: PROC[s: SyncPTR] = { old: Time=s.time; SetBrush[black, invert]; SetDirty[s.time, s.time]; WHILE BlueBug[] DO Sync.Draw[s]; s.time _ Sheet.NearestTime[].time; Sync.Draw[s]; ENDLOOP; Sync.Draw[s]; SetDirty[s.time, s.time]; SELECT s.type FROM clef => MoveClef[s, old, s.time]; staves => MoveStaves[s, old, s.time]; keySignature => MoveKey[s, old, s.time]; ENDCASE => {Piece.RemoveSync[score, s]; Piece.AddSync[score, s]}; Score.Redraw[min, max]; }; MoveClef: PROC[s: SyncPTR, old, new: Time] = { oldCarry: Time; time: Time=s.time; newClef, clef: INTEGER; newClef _ Clef[s.value, s.time]; s.time _ old; oldCarry _ GetCarry[s]; s.time _ new; Piece.RemoveSync[score, s]; Piece.AddSync[score, s]; ResetSheet[min, max]; clef _ Sync.GetStaff[s, s.value].pitch; IF clef#newClef THEN SetDirty[new, GetCarry[s]]; IF clef#Clef[s.value, old] THEN SetDirty[old, oldCarry]; }; MoveStaves: PROC[s: SyncPTR, old, new: Time] = { next: Time; nextStyle, oldStyle, newStyle: INTEGER; next _ sheet[Sheet.NextLine[Sheet.FindLine[MAX[old, new]]]].time; nextStyle _ Sheet.GetStyle[next]; oldStyle _ Sheet.GetStyle[old]; newStyle _ Sheet.GetStyle[new]; s.time _ new; Piece.RemoveSync[score, s]; Piece.AddSync[score, s]; Sheet.Reset[]; IF nextStyle#Sheet.GetStyle[next] THEN SetDirty[begin, endTime]; IF oldStyle#Sheet.GetStyle[old] THEN SetDirty[begin, endTime]; IF newStyle#Sheet.GetStyle[new] THEN SetDirty[begin, endTime]; }; MoveKey: PROC[s: SyncPTR, old, new: Time] = { oldCarry: Time; newKey: INTEGER; s.time _ old; oldCarry _ GetCarry[s]; newKey _ Score.GetKey[new]; s.time _ new; Piece.RemoveSync[score, s]; Piece.AddSync[score, s]; ResetSheet[min, max]; IF new>old AND newKey#s.value THEN SetDirty[new, GetCarry[s]]; IF new=octava2.time THEN { Piece.DeleteSync[octava1]; -- deletes octava2 as well Score.Redraw[min, max]; RETURN}; Piece.RemoveSync[score, s]; Piece.AddSync[score, s]; -- Remove/Add keeps the score sorted Sheet.Reset[]; Score.Redraw[min, max]; }; MoveBeam: PROC[b: BeamPTR] = { height, y: INTEGER _ 0; SetBrush[black, invert]; y _ MouseY^; WHILE BlueBug[] DO height _ b.height+(y-MouseY^); IF height=b.height THEN LOOP; [] _ Beam.Draw[b]; b.height _ height; y _ MouseY^; Beam.SetStems[b]; [] _ Beam.Draw[b]; ENDLOOP; Score.Redraw[b.sync1.time, b.sync2.time]; }; ChangeTilt: PROC[b: BeamPTR] = { height, oldHeight, y: INTEGER _ 0; l: REAL _ 1; SetBrush[black, invert]; oldHeight _ b.height + Real.FixI[b.tilt*(b.sync2.time-b.sync1.time)]; y _ MouseY^; WHILE BlueBug[] DO height _ oldHeight+(y-MouseY^); IF height=oldHeight THEN LOOP; [] _ Beam.Draw[b]; b.tilt _ l*(height-b.height)/(b.sync2.time-b.sync1.time); oldHeight _ height; y _ MouseY^; Beam.SetStems[b]; [] _ Beam.Draw[b]; ENDLOOP; Score.Redraw[b.sync1.time, b.sync2.time]; }; MoveTie: PROC[n: NotePTR] = { y: INTEGER; time: Time; time _ (n.sync.time+n.tie.sync.time)/2; y _ MouseY^; SetBrush[black, invert]; WHILE BlueBug[] DO IF y=MouseY^ THEN LOOP; Note.DrawTie[n]; n.tieHeight _ n.tieHeight+(y-MouseY^); y _ MouseY^; Note.DrawTie[n]; ENDLOOP; Score.Redraw[n.tie.sync.time, n.sync.time]; }; ResetSheet: PROC[t1, t2: Time] = { line: CARDINAL; old1, old2: Section; new1, new2: Section; line _ Sheet.FindLine[MIN[t1, t2]]; old1 _ sheet[line]; line _ Sheet.NextLine[line]; old2 _ sheet[line]; Sheet.Reset[]; line _ Sheet.FindLine[MIN[t1, t2]]; new1 _ sheet[line]; line _ Sheet.NextLine[line]; new2 _ sheet[line]; IF old1#new1 THEN SetDirty[new1.time, new1.time+15]; IF old2#new2 THEN SetDirty[new2.time, new2.time+15]; IF old1.key#new1.key THEN SetDirty[begin, endTime]; IF old2.key#new2.key THEN SetDirty[begin, endTime]; }; GetCarry: PROC[s: SyncPTR] RETURNS[Time] = { index: CARDINAL; IF s.type#clef AND s.type#keySignature THEN RETURN[s.time]; IF s.type=keySignature AND Score.GetKey[s.time-1]=s.value THEN RETURN[s.time]; index _ Sync.GetScoreIndex[s]; IF s.type=clef THEN FOR i: CARDINAL IN (index..scoreLength] DO IF i=scoreLength THEN RETURN[endTime]; IF score[i].type NOT IN SheetSwitch THEN LOOP; IF score[i].type = staves AND ~Similar[s, score[i]] THEN RETURN[score[i].time]; IF score[i].type = staves THEN LOOP; IF Sync.GetStaff[score[i], score[i].value].y#Sync.GetStaff[s, s.value].y THEN LOOP; RETURN[score[i].time]; ENDLOOP; IF s.type=keySignature THEN FOR i: CARDINAL IN (index..scoreLength] DO IF i=scoreLength THEN RETURN[endTime]; IF score[i].type # keySignature THEN LOOP; RETURN[score[i].time]; ENDLOOP; RETURN[s.time]; }; Similar: PROC[s1, s2: SyncPTR] RETURNS[BOOL] = { n, o: BOOL; oldS, newS: StavesPTR; oldS _ LOOPHOLE[@s1.event]; newS _ LOOPHOLE[@s2.event]; IF oldS.sl#newS.sl THEN RETURN[FALSE]; FOR i: CARDINAL IN [1..newS.sl] DO n _ newS.staff[i].y=newS.staff[i-1].y; o _ oldS.staff[i].y=oldS.staff[i-1].y; IF n#o THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; Clef: PROC[staff: CARDINAL, time: Time] RETURNS[INTEGER] = { staves: StavesPTR = sheet[Sheet.FindSection[time]].staves; RETURN[staves.staff[staff].pitch]; }; Flash: PUBLIC PROC = { SetBrush[black, invert]; Graphics.DrawScreenArea[context]; Wait[1]; Graphics.DrawScreenArea[context]; flash _ FALSE; }; Wait: PUBLIC ENTRY PROC[ticks: CARDINAL] = { FOR i: CARDINAL IN [0..ticks) DO WAIT timeout; ENDLOOP; }; ProcessDefs.SetTimeout[@timeout, 1]; END. ÀInterfaceImplB.mesa Copyright (C) 1981, 1984 Xerox Corporation. All rights reserved. Author: John Maxwell last modified: December 15, 1981 4: 06 PM Edited by Doug Wyatt, June 14, 1984 3:42:04 pm PDT ****************************************************************** deleting objects ****************************************************************** IF type=staves THEN y _ sheet[Sheet.FindLine[time]].y; IF sheet[Sheet.FindLine[time]].y#y THEN SetDirty[begin, endTime]; ****************************************************************** inserting objects ****************************************************************** how much of the score does this affect? WE HAVE A MOVEMENT should we move down to the next staff/line? should we move up to the next staff/line? ****************************************************************** moving things ****************************************************************** y _ sheet[Sheet.FindLine[old]].y; ****************************************************************** utility procedures ****************************************************************** get state before reset get state after reset compare before and after is this insertion/deletion a NOOP? Êi˜šœ™Jšœ@™@Jšœ™Jšœ*™*J™2J˜—šÏk ˜ J˜ Jšœœ˜Jšœ œ(˜6Jšœ œ˜%J˜ Jšœœ2˜Jšœ œœ˜Fšœ œ˜J˜Jšœœ˜J˜)J˜J˜Jšœ˜—J˜)Jšœ œœ˜J˜%J˜"J˜(J˜%Jšœœ œ˜ Jšœ œ˜-Jšœ'™'J˜Jšœœœ˜7šœœœ˜&Jšœœœ˜#Jšœœœ˜ Jšœœœ˜%J˜Jšœœ˜—Jšœœœ%˜5J˜Jšœ˜—J˜šŸ œœ˜0J˜Jšœœ˜(Jšœ,œ˜8J˜J˜)J˜-šœ ˜J˜:Jšœ œœœ˜-Jšœ™Jšœ œ=˜NJ˜ J˜J˜J˜/Jšœ œœœ˜9J˜6Jšœ˜—J˜6Jšœ œœ˜Jšœ œœ˜J˜5Jšœœ œ˜ J˜Jšœ˜—J˜šŸœœœ œœœ œ˜|Jšœœ˜J˜J˜J˜%š œ œœœ˜=J˜6—š œ œœœ˜=J˜6—Jšœœœœ˜1Jšœ+™+J˜/J˜(J˜!Jšœœ!˜7šœœž˜;šœœž˜.J˜6J˜ —Jšœ˜—Jšœ)™)J˜)J˜Jšœœ!˜7šœœž˜9šœœž˜.J˜6J˜—Jšœ˜—J˜Jšœ˜—J˜J˜JšœB™BJšœ ™ JšœB™BJ˜šŸ œœœ˜8J˜ Jšœœ˜J˜Jšœ œœ˜0J˜šœ œ˜J˜!šœœœœ˜Jšœœ˜Jš œœœœœ˜2—šœ˜˜ šœœ˜Jšœ˜Jšœ˜——J˜J˜J˜Jšœ˜—Jšœ˜—Jšœœœ œ˜Jšœ˜—J˜šŸ œœ˜!J˜J˜J˜šœ ˜J˜ J˜"J˜ Jšœ˜—J˜ J˜šœ˜J˜!J˜%J˜(Jšœ:˜A—J˜Jšœ˜—J˜šŸœœ ˜.J˜J˜Jšœœ˜J˜ J˜ J˜J˜ J˜J˜J˜J˜'Jšœœ˜0Jšœœ˜8Jšœ˜—J˜šŸ œœ ˜0J˜ Jšœœ˜'Jšœ+œ˜AJ˜!Jšœ!™!J˜J˜J˜ J˜J˜J˜Jšœ œ˜@Jšœœ˜>Jšœœ˜>Jšœ˜—J˜šŸœœ!˜.J˜Jšœœ˜J˜ J˜J˜J˜ J˜J˜J˜Jšœ œœ˜>Jšœ œœ˜FJšœ˜—J˜šŸ œœ˜ Jšœœ˜J˜Jšœœ˜J˜Jšœ œœœ˜9Jšœ œœœ˜9Jšœ œœ œ˜&Jšœ œ˜"J˜J˜J˜J˜*šœ ˜J˜J˜7J˜DJ˜9J˜Jšœ˜—J˜š œ œœ œœ˜!šœœ˜"Jšœž˜5J˜Jšœ˜——J˜Jšœž$˜=J˜J˜Jšœ˜—J˜šŸœœ˜Jšœ œ˜J˜J˜ šœ ˜J˜Jšœœœ˜J˜J˜J˜J˜Jšœ˜—J˜)Jšœ˜—J˜šŸ œœ˜ Jšœœ˜"Jšœœ˜ J˜J˜EJ˜ šœ ˜J˜Jšœœœ˜J˜J˜9J˜ J˜J˜Jšœ˜—J˜)Jšœ˜—J˜šŸœœ˜Jšœœ˜ J˜ J˜'J˜ J˜šœ ˜Jšœ œœ˜J˜J˜&J˜ J˜Jšœ˜—J˜+Jšœ˜—J˜JšœB™BJšœ™JšœB™BJ˜šŸ œœ˜"Jšœœ˜J˜J˜Jšœ™Jšœœ ˜#J˜J˜J˜J˜Jšœ™Jšœœ ˜#J˜J˜J˜Jšœ™Jšœ œ#˜4Jšœ œ#˜4Jšœœ˜3Jšœœ˜3Jšœ˜—J˜šŸœœ œ ˜,Jšœœ˜Jšœ œœœ ˜;Jšœ"™"Jšœœ œœ ˜NJ˜šœ œ˜šœœœ˜*Jšœœœ ˜&Jš œœœ œœ˜.Jšœœœœ˜PJšœœœ˜$JšœGœœ˜SJšœ˜Jšœ˜——šœœ˜šœœœ˜*Jšœœœ ˜&Jšœœœ˜*Jšœ˜Jšœ˜——Jšœ ˜Jšœ˜—J˜šŸœœœœ˜0Jšœœ˜ J˜Jšœœ ˜Jšœœ ˜Jšœœœœ˜&šœœœ˜"J˜&J˜&Jšœœœœ˜Jšœ˜—Jšœœ˜ Jšœ˜—J˜š Ÿœœœœœ˜