Sheet music ASCII-art generator

31

2

Note: Anders Kaseorg warns me that this could be a duplicate of another previous question. It seems so, and I'm sorry I did not find that question before posting this one. Nonetheless, that question only received one answer and this one is simpler, just in case you all want to try this time. I'll understand if this question ends up being marked as duplicate, though.

The challenge

Given an input like this:

8g 8Df 4cs 2C 1A

Write the shortest program/function that produces an output like this:

    /\                                         -o-
   | |
---|-|---------------------------------------------------|-|
   |/                                                    | |
---/|--------|\----b-*-----------------------------------|-|
  / |        | |    |                  o                 | |
-|--|--------|------|-----------------|------------------|-|
 | (| \      |      | |               |                  | |
-|--|--)----*-------|/---------|------|------------------|-|
  \ | /                        |      |                  | |
-----|-------------------------|-------------------------|-|
     |                         |
   *_/                      #-*-

Rules

The output must consist of a five-line staff beginning with the drawing of a G-clef exactly as shown above, aligned to the left of the staff and leaving a single column after the starting of the staff.

    /\  
   | |
---|-|--
   |/   
---/|---
  / |   
-|--|---
 | (| \ 
-|--|--)
  \ | / 
-----|--
     |  
   *_/
^
Single column

The notes must begin with a * or a o character depending on its type.

There must be exactly eight columns of separation every * or a o character:

    /\                                         -o-
   | |
---|-|---------------------------------------------------|-|
   |/                                                    | |
---/|--------|\----b-*-----------------------------------|-|
  / |        | |    |                  o                 | |
-|--|--------|------|-----------------|------------------|-|
 | (| \      |      | |               |                  | |
-|--|--)----*-------|/---------|------|------------------|-|
  \ | /                        |      |                  | |
-----|-------------------------|-------------------------|-|
     |                         |
   *_/                      #-*-
   ↑        ↑        ↑        ↑        ↑        ↑        ↑
   8 columns of separation

The staff must end with the terminator aligned to the right as shown in the example. The left bar of the terminator must be separated 8 columns from the last note.

The input will be a single string containing the notes (at least one, no empty inputs), each one separated by a whitespace (you can consider every note will be a proper one, so no need to check for errors). You can also take the input as an array of strings, with a note per element in the array. For each note, the first character will be the denominator of the note length (1 for a whole note/semibreve, starts with o; 2 for a half note/minim, starts with o; 4 for a quarter note/crotchet, starts with *; and 8 for an eighth note/quaver, starts with *). The second character will be the note (see next table), and the third, optional character will be f or F for flat notes and s or S for sharp notes.

    ---    A (ledger line)
           G
--------   F
           E
--------   D
           C
--------   b
           a
--------   g
           f
--------   e
           d
    ---    c (ledger line)

Obviously, the input must respect the case for the notes, but you can choose the case of the f and s modifiers.

Notes c and A must add two - (ledger lines), one at each side, as they must extend the staff. Notes d and G are out of the staff but do not need ledger lines.

Flatten or sharpened notes must add b or # two positions at the left of the note.

The stems (if any) must be drawn with 4 vertical bars. Notes from b and above must draw the stem downwards and at the left side of the note. Notes from a and below must draw the stem upwards and at the right side of the note. Quavers must add the flag, always rightwards and exactly as shown, and do not need to be beamed if there are several of them in a row.

--------------------------

-----|\----b-*------------
     | |    |             
-----|------|-------------
     |      | |           
----*-------|/---------|--
    ↑↑                 |  
----||------↑↑---------|--
    ||      ||         |
    ||      ||      #-*-
    |\      |\        ↑↑
    | Stem  | Note    | Stem
    Note    Stem      Note

As usual, your program/function can directly draw the output or return a string, array of strings, matrix of characters or any other reasonable format.

Useful links

This is , so may the shortest program/function for each language win!

Bonus: try to write examples with famous melodies and let everyone try to guess which melody it is!

Charlie

Posted 2017-07-06T07:01:08.153

Reputation: 11 448

Sandbox. – Charlie – 2017-07-06T07:02:03.067

Duplicate? – Anders Kaseorg – 2017-07-06T07:29:03.593

... so we can't even use the case of the letter to tell which way to draw the stem? – Neil – 2017-07-06T08:02:18.983

1

@Neil sorry, I'm afraid you can't. I did not make up that rule, I checked that: "the stems usually point down for notes on the middle line or higher, and up for those below".

– Charlie – 2017-07-06T08:07:26.490

1Can we take an array of strings, with each string being a single note, as input? – Shaggy – 2017-07-06T14:56:47.627

@Shaggy yes, you can, I'll update the question. – Charlie – 2017-07-06T15:05:01.163

Answers

13

SOGL V0.12, 178 175 174 173 172 171 bytes

l9*6«+@*@¶¹┐∑:@┌ŗ4Ο"γ;]∑«;‽ΗmzΖH+īN D‼,ΨU‛y‚_○¤└yΨšI‘7n2∆╬5;{" -o-”;l3=?Jζ2%Ƨ#bWGk+;}Jz7m««:U+;W7«κArBb3>?Ζo*ŗ}a2\?┌@ŗ}ē9*LI+a╬5b1>?4┐∙b8=?"■QD³‘┼}e9*5+a4-8a>?5+;2-;G↕№}╬5

Try it Here! (θ added for ease of use; To run as 171 bytes it expects the input to be on the stack)

As far as I can tell this works, but if you find any problems, tell me.

Explanation:

first part: canvas creation

l                                get the length of that array
 9*                              multiply by 9
   6«+                           add 12
      @*                         get that many spaces
        @¶                       push a space and a newline
          ¹                      put all the strings on the stack in an array
           ┐∑                    join with vertical bars
             :                   duplicate that string (which is a line with the ending barline but no staff)
              @┌ŗ                replace spaces with dashes (to make it a line with staff)
                 4Ο              encase 4 copies of the space lines in lines with the dashes
                   "...‘         push the G-clef without newlines
                        7n       split into an array of items of length 7
                          2∆╬5   at 1-indexed coordinates [2; -1] place the G-clef in the staff lines, extending the arrays size 
                              ;  get the input split on spaces back on top of the stack

second part: loop, note head placement

{                        loop over the input split on spaces
" -o-”                    push a template for a note head and leger lines
      ;                   get the input optop
       l3=?            }  if the length of the input is 3, then
           J                pop the last letter off from the input
            ζ               get its unicode point
             2%             modulo 2
               Ƨ#bW         get its index in "#b"
                   G        get the template ontop
                    k       remove its 1st letter
                     +      join the replaced input and the template
                      ;     get the input back ontop to be consisntent with how the if started

sidequest: parse the rest of the inputs
J                  pop the last letter off of the remaining input string (the note), leaving the note length as string on the stack below
 z                 push the lowercase alphabet
  7m               get its first 7 letters
    ««             put the first 2 at the end
      :            duplicate it
       U+          append it uppercased to the original
         ;W        get the notes letter ontop and get its 1-indexed index in that just created string
           7«κ     subtract it from 14
              A    save on variable A
               r   convert the note length to a number
                B  save on variable B

b3>?    }          if b>3 (aka if note length is either 4 or 8)
    Ζo*ŗ             replace "o" with "*"
         a2\?   }  if a divides by 2 (aka there isn't staff nor leger lines required)
             ┌@ŗ     replace "-" with " "

ē          push the value of variable E and after that increase it (default is user input number, which errors and defaults to 0)
 9*        multiply by 9
   LI+     increase by 11
      a    push variable a
       ╬5  at those positions (e*9+11, a) insert the note head template in the canvas

third part: flags and stems

b1>?                      if b (note length)>1 (aka if the stem is needed at all)
    4┐∙                   get an array of 4 vertical bars
       b8=?       }       if b==8 (aka if the flag is needed)
           "■QD³‘           push "\    |"
                 ┼          add verically-down-then-horizontally-right

e9*                       push e*9 (now e starts with 1 as it's been increased) (the X coordinate for the flag)
   5+                     add 5 to it
     a4-                  push a-4 (the Y coordinate, 4 less than the note head as arrays get inserted from the top-left corner)
        8a>?         }    if 8>a (aka if the flag needs to be rotated)
            5+              add 5 to the Y coordinate
              ;2-;          subtract 2 from the X coordinate
                  G         get the stem&flag or stem ontop
                   ↕№       reverse it vertically and mirror characters
                      ╬5  insert the array of the stem and maybe flag at those coordinates

dzaima

Posted 2017-07-06T07:01:08.153

Reputation: 19 048

Your code is almost perfect. The only nitpick is that note 2bs in your example must have the stem pointing downwards. – Charlie – 2017-07-06T13:26:00.483

@CarlosAlejo fixed – dzaima – 2017-07-06T13:27:04.423

Great! And thanks for the explanation! – Charlie – 2017-07-06T13:30:33.677

10

JavaScript(ES6), 616 527 bytes

Thanks @shaggy for removing almost 90 bytes

I had no idea about notes... until now, hope I got it right.

f=i=>i.map((v,p)=>(k[e=(w=q+12)*(l="AGFEDCbagfedc".search(v[1]))+p*9+12]="o*"[(s=v[0])>3|0],l<1|l>11&&(k[e-1]=k[e+1]="-"),(t=v[2])&&(k[e-2]="b#"[t>"f"|0]),--s&&[1,2,3,4].map(i=>(k[(b=l<8)?e+w*i-1:e-w*i+1]="|",s>6&&( b?k[e+w*4]="/":k[e-w*4+2]="\\",k[b?e+w*3+1:e-w*3+3]='|')))),k=[...`    /\\  ${s=" ".repeat(q=i.length*9)}  
   | |  ${s}    
---|-|--${l="-".repeat(q)+"|-|"}
   |/   ${t=s+"| |"}
---/|---${l}
  / |   ${t}
-|--|---${l}
 | (| \\ ${t}
-|--|--)${l}
  \\ | / ${t}
-----|--${l}
     |  ${s}   
   *_/  ${s}`])&&k.join``

console.log(f(["8g","8Df","4cs","2C","1A"]))
.as-console-wrapper { max-height: 100% !important; top: 0 }
.as-console-row:after { display: none !important; }

explanation

f=i=>i.map((v,p)=>( // for each note

  k[e=(w=q+12)*(l="AGFEDCbagfedc".search(v[1]))+p*9+12]= // position in 1D array to set the note to
  "o*"[(s=v[0])>3|0], // note value (either o or *)

  l<1|l>11&&(k[e-1]=k[e+1]="-"), // add leger line

  (t=v[2])&&(k[e-2]="b#"[t>"f"|0]), // add sharp or flat

  --s&&[1,2,3,4].map(i=> // add the 4 stem lines
                     (k[(b=l<8)?e+w*i-1:e-w*i+1]="|", // durration over eigth note => add stem

                      s>6&&( // if to add a flag
                        b?k[e+w*4]="/":k[e-w*4+2]="\\", // add flag either on left or the right side

                        k[b?e+w*3+1:e-w*3+3]='|') // add the line after the flag
                     )
                    )
),
// template, extended to the final length with lines
k=[...`    /\\  ${s=" ".repeat(q=i.length*9)}  
   | |  ${s}   
---|-|--${l="-".repeat(q)+"|-|"}
   |/   ${t=s+"| |"}
---/|---${l}
  / |   ${t}
-|--|---${l}
 | (| \\ ${t}
-|--|--)${l}
  \\ | / ${t}
-----|--${l}
     |  ${s}   
   *_/  ${s}`])&&k.join``

arcs

Posted 2017-07-06T07:01:08.153

Reputation: 201

I think you can save some bytes by saving t[0] and t[2], and then just do q=t.length*9 – Stephen – 2017-07-06T14:01:35.357

2

Welcome to PPCG. Very nice first answer :) There is a good bit more golfing that can be done, though. I took a quick pass over if and got it down to 520 bytes, which should give you a good start.

– Shaggy – 2017-07-06T15:46:27.063

Surely some replacing + atob/btoa would save some bytes – Downgoat – 2017-07-06T16:41:28.533

1@Shaggy thank you very much. I have a lot to learn. – arcs – 2017-07-06T18:21:16.117

You're welcome:) You have a stray space after s>6&&( which can save you a byte. You can also save another byte with by replacing (w=q+12) with w, repeat(q=i.length*9) with repeat(w=i.length*9) and repeat(q) with repeat(w,w+=12). – Shaggy – 2017-07-07T08:34:31.753

9

Charcoal, 180 171 168 163 bytes

F⁵⁺⸿⸿×-⁺²⁷×⁸№θ ↑⁹←↙↓⁹J⁴¦⁹↑⁶↗¹↑²↖¹↓↙¹↓³↙²↓³ \M²↑(| ↘¹↙)↙¹↓²↙¹↑←_*F⪪θ «A⌕AGFEDCbagfedc§ι¹λJχλA⁺⁹χχ¿⁼³Lι§b#⁼§ι²s→P׳¬﹪λ²→P§o*›ι4¿›ι2¿›λ⁶«↗↑⁴¿›ι8«↘↘¹↓¹»»«↙↓⁴¿›ι8«↗↗¹↑¹

Try it online! Link is to verbose version of code. Explanation:

F⁵⁺⸿⸿×-⁺²⁷×⁸№θ ↑⁹←↙↓⁹

Print the stave.

J⁴¦⁹↑⁶↗¹↑²↖¹↓↙¹↓³↙²↓³ \M²↑(| ↘¹↙)↙¹↓²↙¹↑←_*

Print the clef.

F⪪θ «

Loop through each note.

A⌕AGFEDCbagfedc§ι¹λ

Find the Y-coordinate of the note.

JχλA⁺⁹χχ

This is really sneaky: χ is a variable that is predefined to 10, which is exactly the X-coordinate of the first note's accidental, if it has one. After jumping to that position, 9 is added to it, representing the next note position.

¿⁼³Lι§b#⁼§ι²s→

Print the accidental, if there is one.

P׳¬﹪λ²→P§o*›ι4

Print the ledger lines if necessary and the note. In fact the line is printed on any even y-coordinate although printing it over the stave has no effect of course.

¿›ι2

Nothing more to do for semibreves.

¿›λ⁶«

For notes below the midpoint,

↗↑⁴

draw the stem upwards,

¿›ι8«↘↘¹↓¹

and the flag for quavers.

»»«

For notes above the midpoint,

↙↓⁴

draw the stem downwards,

¿›ι8«↗↗¹↑¹

and the flag for quavers.

Neil

Posted 2017-07-06T07:01:08.153

Reputation: 95 035

You beat SOGL! :-) – Charlie – 2017-07-06T17:14:26.287

@CarlosAlejo I expected it to happen. But I did expect it to beat me by like 20% also. – dzaima – 2017-07-06T17:15:33.847

@dzaima maybe if it were a pure ASCII-art challenge, but this one also has a logic part that makes the output depends on the input. Nonetheless I am still getting used to what each language can do better. – Charlie – 2017-07-06T17:21:51.547