verovio icon indicating copy to clipboard operation
verovio copied to clipboard

Layer interactions for unison notes with matching @accid/@accid.ges

Open craigsapp opened this issue 3 years ago • 6 comments

In the following example, measures 3 and 4 are incorrectly rendered, or at least in an inconsistent style compared to measures 1 and 2:

Screen Shot 2020-07-21 at 11 31 15 PM

In all three measures the two notes have the same performance accidental. For the first measure they are both natural. For the last three measures they are both flat.

In measure 2, both notes have @accid="f".

In measure 3, the first-layer note has @accid="f" while the second-layer has @accid.ges="f".

In measure 4, the first-layer note has @accid.ges="f" while the second-layer has @accid="f".

The notes in measures 3 and 4 where @accid.ges matches to @accid in the other layer, should be merged to share the same notehead (i.e., be superimposed) in a manner similar to the first two measures when @accid both match.

MEI data:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
 <meiHead>
  <fileDesc>
   <titleStmt>
    <title />
   </titleStmt>
   <pubStmt />
  </fileDesc>
  <encodingDesc>
   <appInfo>
    <application isodate="2020-07-21T23:27:56" version="3.0.0-dev-00fd663-dirty">
     <name>Verovio</name>
     <p>Transcoded from Humdrum</p>
    </application>
   </appInfo>
  </encodingDesc>
  <workList>
   <work>
    <title />
   </work>
  </workList>
 </meiHead>
 <music>
  <body>
   <mdiv xml:id="mdiv-0000000420927924">
    <score xml:id="score-0000000767373093">
     <scoreDef xml:id="scoredef-0000000098095488">
      <staffGrp xml:id="staffgrp-0000001068191147">
       <staffDef xml:id="staffdef-0000002120674126" n="1" lines="5">
        <clef xml:id="clef-0000001316423260" shape="G" line="2" />
        <meterSig xml:id="metersig-L2F1" count="2" unit="4" />
       </staffDef>
      </staffGrp>
     </scoreDef>
     <section xml:id="section-L1F1">
      <measure xml:id="measure-L1" n="1">
       <staff xml:id="staff-0000000492772448" n="1">
        <layer xml:id="layer-L1F1N1" n="1">
         <note xml:id="note-L5F1" dur="2" oct="4" pname="b" accid.ges="n" />
        </layer>
        <layer xml:id="layer-L5F2N2" n="2">
         <note xml:id="note-L5F2" dur="2" oct="4" pname="b" accid.ges="n" />
        </layer>
       </staff>
      </measure>
      <measure xml:id="measure-L6" n="2">
       <staff xml:id="staff-L6F1N1" n="1">
        <layer xml:id="layer-L6F1N1" n="1">
         <note xml:id="note-L7F1" dur="2" oct="4" pname="b" accid="f" />
        </layer>
        <layer xml:id="layer-L6F2N2" n="2">
         <note xml:id="note-L7F2" dur="2" oct="4" pname="b" accid="f" />
        </layer>
       </staff>
      </measure>
      <measure xml:id="measure-L8" n="3">
       <staff xml:id="staff-L8F1N1" n="1">
        <layer xml:id="layer-L8F1N1" n="1">
         <note xml:id="note-L9F1" dur="2" oct="4" pname="b" accid="f" />
        </layer>
        <layer xml:id="layer-L8F2N2" n="2">
         <note xml:id="note-L9F2" dur="2" oct="4" pname="b" accid.ges="f" />
        </layer>
       </staff>
      </measure>
      <measure xml:id="measure-L10" n="4">
       <staff xml:id="staff-L10F1N1" n="1">
        <layer xml:id="layer-L10F1N1" n="1">
         <note xml:id="note-L11F1" dur="2" oct="4" pname="b" accid.ges="f" />
        </layer>
        <layer xml:id="layer-L10F2N2" n="2">
         <note xml:id="note-L11F2" dur="2" oct="4" pname="b" accid="f" />
        </layer>
       </staff>
      </measure>
     </section>
    </score>
   </mdiv>
  </body>
 </music>
</mei>

craigsapp avatar Jul 22 '20 06:07 craigsapp

@craigsapp This is the same behavior that was acceptable in https://github.com/rism-digital/verovio/issues/620. Why should it be different now? I'm not quite sure what the best solution should be here in MEI. However converting from MusicXML always looks weird, because usually exporters put a visual accidental only on one of the notes.

rettinghaus avatar Dec 07 '21 11:12 rettinghaus

Ideally there would be some way of overriding the default behavior with some sort of overlay attribute that would force the same pitch in different layers to share a notehead or not.

Here is a complicated case from Debussy's Cinq poèmes de Charles Baudelaire (Five Poems by Charles Baudelaire) for Voice and Piano, measure 6:

Screen Shot 2021-12-07 at 12 41 45 PM

Verovio rendering:

Screen Shot 2021-12-07 at 12 44 59 PM

Since the C4-D4 dyad is too messy to show the individual notes in both layers, they have been merged into shared noteheads in the manual typesetting. Note that the quarter notes that follow are not aligned by stems (in both the Verovio and manual typesetting).

MEI data:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
 <meiHead>
  <fileDesc>
   <titleStmt>
    <title />
   </titleStmt>
   <pubStmt />
  </fileDesc>
  <encodingDesc>
   <appInfo>
    <application isodate="2021-12-07T12:45:01" version="3.8.0-dev-7abcef1-dirty">
     <name>Verovio</name>
     <p>Transcoded from Humdrum</p>
    </application>
   </appInfo>
  </encodingDesc>
  <workList>
   <work>
    <title />
   </work>
  </workList>
 </meiHead>
 <music>
  <body>
   <mdiv xml:id="mjjs6gu">
    <score xml:id="sxahuuz">
     <scoreDef xml:id="sx3id3e">
      <staffGrp xml:id="sv3hwwb">
       <staffDef xml:id="s8ia5pq" n="1" lines="5">
        <clef xml:id="cfq9e8r" shape="G" line="2" />
       </staffDef>
      </staffGrp>
     </scoreDef>
     <section xml:id="section-L1F1">
      <measure xml:id="measure-L1">
       <staff xml:id="sfs9j45" n="1">
        <layer xml:id="layer-L1F1N1" n="1">
         <chord xml:id="chord-L3F1" dur="8">
          <note xml:id="note-L3F1S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L3F1S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L4F1" dur="4">
          <note xml:id="note-L4F1S1" oct="5" pname="c" accid.ges="n" />
          <note xml:id="note-L4F1S2" oct="5" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L5F1" dur="8">
          <note xml:id="note-L5F1S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L5F1S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L6F1" dur="4">
          <note xml:id="note-L6F1S1" oct="5" pname="c" accid.ges="n" />
          <note xml:id="note-L6F1S2" oct="5" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L7F1" dur="8">
          <note xml:id="note-L7F1S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L7F1S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L8F1" dur="4">
          <note xml:id="note-L8F1S1" oct="5" pname="c" accid.ges="n" />
          <note xml:id="note-L8F1S2" oct="5" pname="d" accid.ges="n" />
         </chord>
        </layer>
        <layer xml:id="layer-L3F2N2" n="2">
         <chord xml:id="chord-L3F2" dur="8">
          <note xml:id="note-L3F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L3F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L4F2" dur="4">
          <note xml:id="note-L4F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L4F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L5F2" dur="8">
          <note xml:id="note-L5F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L5F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L6F2" dur="4">
          <note xml:id="note-L6F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L6F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L7F2" dur="8">
          <note xml:id="note-L7F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L7F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L8F2" dur="4">
          <note xml:id="note-L8F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L8F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
        </layer>
       </staff>
       <slur xml:id="slur-L3F1-L4F1" staff="1" startid="#chord-L3F1" endid="#chord-L4F1" />
       <slur xml:id="slur-L5F1-L6F1" staff="1" startid="#chord-L5F1" endid="#chord-L6F1" />
       <slur xml:id="slur-L7F1-L8F1" staff="1" startid="#chord-L7F1" endid="#chord-L8F1" />
       <tie xml:id="tie-L3F2S1-L4F2S1" startid="#note-L3F2S1" endid="#note-L4F2S1" />
       <tie xml:id="tie-L3F2S2-L4F2S2" startid="#note-L3F2S2" endid="#note-L4F2S2" curvedir="above" />
       <tie xml:id="tie-L5F2S1-L6F2S1" startid="#note-L5F2S1" endid="#note-L6F2S1" />
       <tie xml:id="tie-L5F2S2-L6F2S2" startid="#note-L5F2S2" endid="#note-L6F2S2" curvedir="above" />
       <tie xml:id="tie-L7F2S1-L8F2S1" startid="#note-L7F2S1" endid="#note-L8F2S1" />
       <tie xml:id="tie-L7F2S2-L8F2S2" startid="#note-L7F2S2" endid="#note-L8F2S2" curvedir="above" />
      </measure>
     </section>
    </score>
   </mdiv>
  </body>
 </music>
</mei>

craigsapp avatar Dec 07 '21 11:12 craigsapp

Well, that is a completely different case, that should be encoded with sameas I guess:

<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
 <meiHead>
  <fileDesc>
   <titleStmt>
    <title />
   </titleStmt>
   <pubStmt />
  </fileDesc>
  <encodingDesc>
   <appInfo>
    <application isodate="2021-12-07T12:45:01" version="3.8.0-dev-7abcef1-dirty">
     <name>Verovio</name>
     <p>Transcoded from Humdrum</p>
    </application>
   </appInfo>
  </encodingDesc>
  <workList>
   <work>
    <title />
   </work>
  </workList>
 </meiHead>
 <music>
  <body>
   <mdiv xml:id="mjjs6gu">
    <score xml:id="sxahuuz">
     <scoreDef xml:id="sx3id3e">
      <staffGrp xml:id="sv3hwwb">
       <staffDef xml:id="s8ia5pq" n="1" lines="5">
        <clef xml:id="cfq9e8r" shape="G" line="2" />
       </staffDef>
      </staffGrp>
     </scoreDef>
     <section xml:id="section-L1F1">
      <measure xml:id="measure-L1">
       <staff xml:id="sfs9j45" n="1">
        <layer xml:id="layer-L1F1N1" n="1">
         <chord xml:id="chord-L3F1" dur="8">
          <note xml:id="note-L3F1S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L3F1S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L4F1" dur="4">
          <note xml:id="note-L4F1S1" oct="5" pname="c" accid.ges="n" />
          <note xml:id="note-L4F1S2" oct="5" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L5F1" dur="8">
          <note xml:id="note-L5F1S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L5F1S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L6F1" dur="4">
          <note xml:id="note-L6F1S1" oct="5" pname="c" accid.ges="n" />
          <note xml:id="note-L6F1S2" oct="5" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L7F1" dur="8">
          <note xml:id="note-L7F1S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L7F1S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L8F1" dur="4">
          <note xml:id="note-L8F1S1" oct="5" pname="c" accid.ges="n" />
          <note xml:id="note-L8F1S2" oct="5" pname="d" accid.ges="n" />
         </chord>
        </layer>
        <layer xml:id="layer-L3F2N2" n="2">
         <chord xml:id="chord-L3F2" dur="8" sameas="#chord-L3F1">
          <note xml:id="note-L3F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L3F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L4F2" dur="4">
          <note xml:id="note-L4F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L4F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L5F2" dur="8" sameas="#chord-L5F1">
          <note xml:id="note-L5F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L5F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L6F2" dur="4">
          <note xml:id="note-L6F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L6F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L7F2" dur="8" sameas="#chord-L7F1">
          <note xml:id="note-L7F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L7F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
         <chord xml:id="chord-L8F2" dur="4">
          <note xml:id="note-L8F2S1" oct="4" pname="c" accid.ges="n" />
          <note xml:id="note-L8F2S2" oct="4" pname="d" accid.ges="n" />
         </chord>
        </layer>
       </staff>
       <slur xml:id="slur-L3F1-L4F1" staff="1" startid="#chord-L3F1" endid="#chord-L4F1" />
       <slur xml:id="slur-L5F1-L6F1" staff="1" startid="#chord-L5F1" endid="#chord-L6F1" />
       <slur xml:id="slur-L7F1-L8F1" staff="1" startid="#chord-L7F1" endid="#chord-L8F1" />
       <tie xml:id="tie-L3F2S1-L4F2S1" startid="#note-L3F2S1" endid="#note-L4F2S1" />
       <tie xml:id="tie-L3F2S2-L4F2S2" startid="#note-L3F2S2" endid="#note-L4F2S2" curvedir="above" />
       <tie xml:id="tie-L5F2S1-L6F2S1" startid="#note-L5F2S1" endid="#note-L6F2S1" />
       <tie xml:id="tie-L5F2S2-L6F2S2" startid="#note-L5F2S2" endid="#note-L6F2S2" curvedir="above" />
       <tie xml:id="tie-L7F2S1-L8F2S1" startid="#note-L7F2S1" endid="#note-L8F2S1" />
       <tie xml:id="tie-L7F2S2-L8F2S2" startid="#note-L7F2S2" endid="#note-L8F2S2" curvedir="above" />
      </measure>
     </section>
    </score>
   </mdiv>
  </body>
 </music>
</mei>

But then something more messing shows up: Debussy

Trying to use it on notes instead of chords: Debussy

rettinghaus avatar Dec 07 '21 12:12 rettinghaus

We expect unison noteheads to be merged automatically whenever possible. If they have different accidentals obviously this shouldn't happen. But what about same sounding accidentals but different visuals? Is the logical encoding of same or higher importance as the visual one?

rettinghaus avatar Dec 07 '21 12:12 rettinghaus

@rettinghaus This rendering only works if both notes are the same when visualized. If you have the same sounding pitch but different visuals, layering the notes on top of each other won't work (because if they occupy the same space, how do you tell which one is different?)

ahankinson avatar Dec 07 '21 12:12 ahankinson

Well, that is a completely different case, that should be encoded with sameas I guess.

No, it is the same case, except that chords are being used. In this case it is fairly clear that the noteheads should be shared between the two layers since offsetting the notes from each other creates quite complicated notation due to the seconds. For single notes, the decision to merge noteheads or not becomes more of a style issue, since it is easy to find two different layout methods for the same music (examples given further below for half note and eighth note overlays).

Using @sameas is problematic in this case, since this is used to indicate an identical (graphical) object, but in this case there is a stem up and a stem down and both of those stems create two separate complete layers of music. @sameas is being used in verovio to align clefs when they change in the middle of a note in one layer, and the same-as clef is not actually rendered (I believe) since it is handled with the non-same-as clef:

Screen Shot 2021-12-08 at 3 39 30 AM
MEI data for above example
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
 <meiHead>
  <fileDesc>
   <titleStmt>
    <title />
   </titleStmt>
   <pubStmt />
  </fileDesc>
  <encodingDesc>
   <appInfo>
    <application isodate="2021-12-08T03:39:42" version="3.8.0-dev-7abcef1-dirty">
     <name>Verovio</name>
     <p>Transcoded from Humdrum</p>
    </application>
   </appInfo>
  </encodingDesc>
  <workList>
   <work>
    <title />
   </work>
  </workList>
 </meiHead>
 <music>
  <body>
   <mdiv xml:id="mroho3p">
    <score xml:id="s8mw4dq">
     <scoreDef xml:id="sn5jede">
      <staffGrp xml:id="s3mm9e0">
       <staffDef xml:id="so6zfmf" n="1" lines="5">
        <clef xml:id="clef-L3F1" shape="F" line="4" />
        <meterSig xml:id="metersig-L2F1" count="4" unit="4" />
       </staffDef>
      </staffGrp>
     </scoreDef>
     <section xml:id="section-L1F1">
      <measure xml:id="measure-L1">
       <staff xml:id="s6l0wte" n="1">
        <layer xml:id="layer-L1F1N1" n="1">
         <note xml:id="note-L6F1" dur="4" oct="4" pname="c" accid.ges="n" />
         <note xml:id="note-L7F1" dur="4" oct="4" pname="e" accid.ges="n" />
         <clef xml:id="clef-L8F1" shape="G" line="2" />
         <note xml:id="note-L9F1" dur="4" oct="4" pname="g" accid.ges="n" />
         <note xml:id="note-L10F1" dur="4" oct="5" pname="c" accid.ges="n" />
        </layer>
        <layer xml:id="layer-L3F1N2" n="2">
         <note xml:id="note-L6F2" dur="1" oct="3" pname="c" accid.ges="n" />
         <clef xml:id="clef-L8F2" sameas="#clef-L8F1" shape="G" line="2" />
        </layer>
       </staff>
      </measure>
     </section>
    </score>
   </mdiv>
  </body>
 </music>
</mei>

Without the same as, there will be two clefs displayed in the score, with the second one after then end of the whole note, which is not overlayed on top of the other clef since they do not have the same timestamp.

Screen Shot 2021-12-08 at 4 02 48 AM

After @sameas was implemented for clefs in verovio, the clef@visible attribute was added to MEI as well as implemented in verovio. This has an identical result to the @sameas method given above (is @visible intended to be a replacement for @sameas in the case of clefs)? Here is the @visibile version of the example:

Click to view visible attribute encoding for layer 2 clef.
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/4.0.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="4.0.0">
 <meiHead>
  <fileDesc>
   <titleStmt>
    <title />
   </titleStmt>
   <pubStmt />
  </fileDesc>
  <encodingDesc>
   <appInfo>
    <application isodate="2021-12-08T03:39:42" version="3.8.0-dev-7abcef1-dirty">
     <name>Verovio</name>
     <p>Transcoded from Humdrum</p>
    </application>
   </appInfo>
  </encodingDesc>
  <workList>
   <work>
    <title />
   </work>
  </workList>
 </meiHead>
 <music>
  <body>
   <mdiv xml:id="mroho3p">
    <score xml:id="s8mw4dq">
     <scoreDef xml:id="sn5jede">
      <staffGrp xml:id="s3mm9e0">
       <staffDef xml:id="so6zfmf" n="1" lines="5">
        <clef xml:id="clef-L3F1" shape="F" line="4" />
        <meterSig xml:id="metersig-L2F1" count="4" unit="4" />
       </staffDef>
      </staffGrp>
     </scoreDef>
     <section xml:id="section-L1F1">
      <measure xml:id="measure-L1">
       <staff xml:id="s6l0wte" n="1">
        <layer xml:id="layer-L1F1N1" n="1">
         <note xml:id="note-L6F1" dur="4" oct="4" pname="c" accid.ges="n" />
         <note xml:id="note-L7F1" dur="4" oct="4" pname="e" accid.ges="n" />
         <clef xml:id="clef-L8F1" shape="G" line="2" />
         <note xml:id="note-L9F1" dur="4" oct="4" pname="g" accid.ges="n" />
         <note xml:id="note-L10F1" dur="4" oct="5" pname="c" accid.ges="n" />
        </layer>
        <layer xml:id="layer-L3F1N2" n="2">
         <note xml:id="note-L6F2" dur="1" oct="3" pname="c" accid.ges="n" />
         <clef xml:id="clef-L8F2" visible="false" shape="G" line="2" />
        </layer>
       </staff>
      </measure>
     </section>
    </score>
   </mdiv>
  </body>
 </music>
</mei>

Here are other complicated cases where half notes and eighth notes are involved:

In Chopin Sonata op. 4, the eighth and half notes are separated:

Screen Shot 2021-12-08 at 3 09 57 AM

But in Chopin Piano concerto no. 1, they are merged:

Screen Shot 2021-12-08 at 3 32 08 AM

(verovio will not merge half and eight notes)

Here are some complicated cases from Chopin's first Ballade:

Here there is a hidden 8th note D which is being superimposed by a half note D in the other voice (which is also in a chord containing a second interval) Screen Shot 2021-12-08 at 3 30 04 AM

And here is a case where the half and eighth notes in the two layers are sharing a stem:

Screen Shot 2021-12-08 at 3 29 58 AM

craigsapp avatar Dec 08 '21 03:12 craigsapp