Sheet music ASCII-art generator
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
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``
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.