Output the list of musical notes

27

2

This task is simple: Write a program or function that outputs the list of all musical notes (using English note names) from A♭ to G♯.

All notes without a name consisting of a single letter (i.e. black notes on a musical keyboard) should have their name printed twice, once as the sharp of a note, once as the flat of one. Sharp or flat notes that can be described with a single letter, like B♯ (C) or F♭ (E) should not be outputted.

Here is an example of the output:

Ab, A, A#, Bb, B, C, C#, Db, D, D#, Eb, E, F, F#, Gb, G, G#

Specifications

  • The program or function must not take any input.

  • The notes may be printed in any order, and in any list output permitted by our standard I/O rules

  • The sharp and flat Unicode symbols (♯/♭) may be substituted with b and #

  • As always, Standard loopholes are forbidden.

  • As this is , the smallest program, in bytes, wins.

TheOnlyMrCat

Posted 2019-08-14T22:01:59.213

Reputation: 1 079

3related – Jonathan Allan – 2019-08-14T22:32:23.393

1May we output "C " instead of "C"? – Arnauld – 2019-08-14T22:55:15.237

1@Arnauld yes you may – TheOnlyMrCat – 2019-08-14T23:04:14.743

7By the way, B# exists in music notation; it's for example in the key signature for the key of C#, where it serves as the leading tone. – Kaz – 2019-08-14T23:39:43.757

So no naturals, double-flats or double-sharps either? :-) (I kid, those are only needed for certain scales.) – ErikF – 2019-08-15T00:41:13.237

2I don't know, it feels like Cb is missing here ;) – AJFaraday – 2019-08-15T11:31:57.993

No input. Does that include any pointers to output to? – me' – 2019-08-26T07:04:42.640

Answers

13

Malbolge, 482 370 353 bytes

R1: Removed commas inbetween (as not required by the challenge)

R2: Shave off a few bytes

('<;_#!=6Z|{8xUwvt,PrqonKmk)"FhCUTdb?`+<;:[Z7YtVU2T|/g-O+i(gJrHc#EC~B{@zZxw:tt'r5Qo"!l/K-hUfe?bP``_Lo~[}|X2VCTR3Q+N`_^9+7Hji3ffdAc~w|u;]\wpon4VUSSQ.PONcb(JI^]#DCYX|@?>=<:u9NMRKo32MFj.C,Ae)>'<%:^"!~5:3WxwwuRts0q(Lnml)"Fhgfe"y?a`_zyxq7YXWlUj0RgfkjMb(JI^c\[Z~BAV?T=Rv987Mq44310FEi-,G@)>b&%#"8=6Z{{yyw/Sut1*)('Km$k(!Efe{zyx>`uz]r8ZXnm3TTih.PkNchg`&HFF[DY}Az

Try it online!

Krzysztof Szewczyk

Posted 2019-08-14T22:01:59.213

Reputation: 3 819

12

CP-1610 assembly (Intellivision), 31 DECLEs1 = 39 bytes

A routine taking an output pointer in R4 and writing the notes there, separated with spaces. In the example code, we write directly to the screen.

Hex dump (routine only)

275 001 2BD 03C 048 1DB 2B8 012 044 2A9 2BA 108 078 201 003 262
261 263 2FA 008 37A 140 225 00B 089 22C 011 2B7 018 210 000

Full source

                ROMW    10              ; use 10-bit ROM width
                ORG     $4800           ; map this program at $4800

                ;; ------------------------------------------------------------- ;;
                ;;  test code                                                    ;;
                ;; ------------------------------------------------------------- ;;
4800            SDBD                    ; set up an interrupt service routine
4801            MVII    #isr,     R0    ; to do some minimal STIC initialization
4804            MVO     R0,       $100
4806            SWAP    R0
4807            MVO     R0,       $101

4809            EIS                     ; enable interrupts

480A            MVII    #$200,    R4    ; R4 = backtab pointer
480C            CALL    notes           ; invoke our routine

480F            DECR    R7              ; loop forever

                ;; ------------------------------------------------------------- ;;
                ;;  ISR                                                          ;;
                ;; ------------------------------------------------------------- ;;
      isr       PROC

4810            MVO     R0,       $0020 ; enable display

4812            CLRR    R0
4813            MVO     R0,       $0030 ; no horizontal delay
4815            MVO     R0,       $0031 ; no vertical delay
4817            MVO     R0,       $0032 ; no border extension
4819            MVII    #$D,      R0
481B            MVO     R0,       $0028 ; light-blue background
481D            MVO     R0,       $002C ; light-blue border

481F            JR      R5              ; return from ISR

                ENDP

                ;; ------------------------------------------------------------- ;;
                ;;  routine                                                      ;;
                ;; ------------------------------------------------------------- ;;
      notes     PROC

4820            PSHR    R5              ; save return address

4821            SDBD                    ; R5 = pointer to @@chr
4822            MVII    #@@chr,   R5
4825            CLRR    R3              ; R3 = 0 (space)
4826            MVII    #$12,     R0    ; R0 = bitmask = $12
4828            SWAP    R0,       2     ; extend it to $1212

4829  @@loop    MVI@    R5,       R1    ; R1 = next symbol
482A            MVII    #('A'-32)*8, R2 ; R2 = 'A' character

482C  @@note    SARC    R0              ; right shift the bitmask
482D            BC      @@next          ; skip this note if the carry is set

482F            MVO@    R2,       R4    ; append the note
4830            MVO@    R1,       R4    ; append the symbol
4831            MVO@    R3,       R4    ; append a space

4832  @@next    ADDI    #8,       R2    ; advance to the next note
4834            CMPI    #('H'-32)*8, R2 ; is it now a 'H'?
4836            BLT     @@note          ; if not, process the inner loop

4838            TSTR    R1              ; was the symbol a space?
4839            BNEQ    @@loop          ; if not, process the outer loop

483B            PULR    R7              ; return

483C  @@chr     DECLE   ('#'-32)*8      ; '#'
483D            DECLE   ('b'-32)*8      ; 'b'
483E            DECLE   0               ; space

                ENDP

Output

output

screenshot from jzIntv


1. A CP-1610 opcode is encoded with a 10-bit value, known as a 'DECLE'. This routine is 31 DECLEs long, starting at $4820 and ending at $483E (included).

Arnauld

Posted 2019-08-14T22:01:59.213

Reputation: 111 334

9

Python 3, 50 bytes

print(*map(''.join,zip(3*'ADGBCEF',7*' '+5*'#b')))

Try it online!

Python 2: 48 bytes

This code can be adjusted as to include B# and Cb, at the cost of no additional bytes. This can be achieved by replacing 5 with 6.


Additionally, it is (finally) shorter than just outputting the plain string:

Python 3, 51 bytes

exit('Ab A A# Bb B C C# Db D D# Eb E F F# Gb G G#')

Try it online!

Python 2: 50 bytes

Jitse

Posted 2019-08-14T22:01:59.213

Reputation: 3 566

2This is a very creative solution – TheOnlyMrCat – 2019-08-15T07:46:23.847

7

05AB1E, 16 15 13 bytes

Au…b #âŽ7×bûÏ

-2 bytes thanks to @maxb.

Try it online.

Outputs as a list, where the single-char notes are with a trailing space.

Explanation:

Au             # Push the lowercase alphabet, and uppercase it
  …b #         # Push string "b #"
      â        # Take the cartesian product of both strings to create all possible pairs:
               #  ["Ab","A ","A#","Bb","B ","B#",...,"Zb","Z ","Z#"]
       Ž7×     # Push compressed integer 1999
          b    # Convert it to a binary string "11111001111"
           û   # Palindromize it to "111110011111110011111"
            Ï  # Only leave the notes in the list at the truthy values (1), (the trailing
               # items beyond the length of this binary string are also discarded)
               # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why Ž7× is 1999.

Ž7× could alternatively be ₄·< (1000, double, decrease by 1) for the same byte-count.

Kevin Cruijssen

Posted 2019-08-14T22:01:59.213

Reputation: 67 575

3Is the really necessary? Seems to run fine without it. – maxb – 2019-08-15T12:38:14.530

6

Jelly, 18?* 20 bytes

ØAḣ7µp⁾b#Żs6ḣ€4ẎḊ;W€

A monadic Link returning a list of lists of characters.

* If a mixed list of (a) lists of characters and (b) characters is acceptable remove the trailing W€ for 18.

Try it online!

How?

ØAḣ7µp⁾b#Żs6ḣ€4ẎḊ;W€ - Link: no argument
ØA                   - list of characters     [A-Z]
  ḣ7                 - head to 7              "ABCDEFG"
    µ                - new monadic link (call that X)
      ⁾b#            - list of characters     "b#"
     p               - Cartesian product      ["Ab","A#","Bb","B#","Cb","C#","Db","D#","Eb","E#","Fb","F#","Gb","G#"]
         Ż           - prepend a zero       [0,"Ab","A#","Bb","B#","Cb","C#","Db","D#","Eb","E#","Fb","F#","Gb","G#"]
          s6         - split into sixes    [[0,"Ab","A#","Bb","B#","Cb"],["C#","Db","D#","Eb","E#","Fb"],["F#","Gb","G#"]]
            ḣ€4      - head each to 4      [[0,"Ab","A#","Bb"],["C#","Db","D#","Eb"],["F#","Gb","G#"]]
               Ẏ     - tighten              [0,"Ab","A#","Bb","C#","Db","D#","Eb","F#","Gb","G#"]
                Ḋ    - dequeue                ["Ab","A#","Bb","C#","Db","D#","Eb","F#","Gb","G#"]
                  W€ - wrap each (of X)       ["A","B","C","D","E","F","G"]
                 ;   - concatenate            ["Ab","A#","Bb","C#","Db","D#","Eb","F#","Gb","G#","A","B","C","D","E","F","G"]

Jonathan Allan

Posted 2019-08-14T22:01:59.213

Reputation: 67 804

@mirabilos this is 20 bytes of source code, the Unicode characters each represent a byte of the source code - see the code page linked by the word bytes in the header. – Jonathan Allan – 2019-08-15T18:20:25.007

5

Retina 0.8.2, 33 bytes


ABCDEFG
.
 $&b $& $&#
 [BE]#...

Try it online! Explanation:


ABCDEFG

Insert the base note names.

.
 $&b $& $&#

Expand each note to include flat and sharp versions.

 [BE]#...

Delete B#, E# and also the notes following them (Cb and Eb).

Neil

Posted 2019-08-14T22:01:59.213

Reputation: 95 035

5

Perl 6, 41 bytes

{S:g/[E|B]\#...//}o{'A'..'G'X~'b #'.comb}

Try it online!

Simple cross product of the notes and the sharps/flats, followed by removing the extra invalid notes. This is an anonymous code block that produces the string:

Ab A  A# Bb B   C  C# Db D  D# Eb E   F  F# Gb G  G#

Jo King

Posted 2019-08-14T22:01:59.213

Reputation: 38 234

4

R, 50 bytes

cat("Ab,A,A#,Bb,B,C,C#,Db,D,D#,Eb,E,F,F#,Gb,G,G#")

Try it online!

Boring answer.

R, 60 bytes

cat(outer(LETTERS[1:7],c("#","","b"),paste0)[-c(2,5,17,20)])

Try it online!

Giuseppe

Posted 2019-08-14T22:01:59.213

Reputation: 21 077

1I think the list of examples of redundant notes isn't meant to be exhaustive--the example output also omits Cb and E#. – Unrelated String – 2019-08-14T22:09:11.957

4

Charcoal, 21 bytes

Φ⪪⭆…α⁷⭆b #⁺ι벧↨⊖⊘φ²κ

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

    α                   Predefined variable uppercase alphabet
   … ⁷                  First 7 letters i.e. `ABCEDFG`
  ⭆                     Map over characters and join
       b #              Literal string `b #`
      ⭆                 Map over characters and join
          ⁺ιλ           Concatenate outer and inner characters
 ⪪           ²          Split back into substrings of length 2
Φ                       Filter where nonzero
                  φ     Predefined variable 1000
                 ⊘      Halved i.e. 500
                ⊖       Decremented i.e 499
               ↨   ²    Converted to base 2 i.e. [1, 1, 1, 1, 1, 0, 0, 1, 1]
              §     κ   Cyclically indexed by outer index
                        Implicitly print matching values on separate lines

Neil

Posted 2019-08-14T22:01:59.213

Reputation: 95 035

3

Japt, 23 22 bytes

;B¯7
ï"b #" fÏÄ %9%8<6

Try it

;B          Alphabet
  ¯7        First seven characters ("ABCDEFG")
            Assign to U
ï"b #"       Cartesian product with "b #" ("Ab,A ,A#,Bb,B ,B#,Cb,C ,C#,Db,D ,D#,Eb,E ,E#,Fb,F ,F#,Gb,G ,G#")
f           Filter:
 ÏÄ           Is index + 1
   %9%8       Mod 9 Mod 8
     <6       Less than 6
            End filter ("Ab,A ,A#,Bb,B ,C ,C#,Db,D ,D#,Eb,E ,F ,F#,Gb,G ,G#")

Embodiment of Ignorance

Posted 2019-08-14T22:01:59.213

Reputation: 7 014

Your code contains <6 but it's <5 in the explanation. – TheOnlyMrCat – 2019-08-16T21:11:52.210

@TheOnlyMrCat Edited – Embodiment of Ignorance – 2019-08-16T22:35:59.113

2

dzaima/APL REPL, 38 28 25 bytes

(⊤2056111)⌿,' b#'∘.,⍨7↑⎕A

Try it online!

dzaima

Posted 2019-08-14T22:01:59.213

Reputation: 19 048

I think that you can remove the ⎕←. – Erik the Outgolfer – 2019-08-14T23:22:36.297

@EriktheOutgolfer hmm yeah, if I switch to REPL – dzaima – 2019-08-14T23:23:28.107

Oh right, I forgot to count the {} LOL. – Erik the Outgolfer – 2019-08-14T23:25:16.147

2

Stax, 17 15 bytes

║♠+I▬┌ó£ΔφΔ`╗╨Å

Run and debug it

Bonus program: print the fancy symbols

recursive

Posted 2019-08-14T22:01:59.213

Reputation: 8 616

2

Zsh, 36 bytes

<<<${${(F):-{A..G}{b,,#}}//[BE]#???}

An uglier solution, but it saves two characters. (F) joins a list on newlines, and //[BE]#??? removes the parts of the string we need.

Try it online!


Zsh, 38 bytes

<<<${${:-{A..G}{b,,#}}:#([BE]#|[CF]b)}

I always enjoy it when Zsh beats Perl (hopefully I don't speak too soon...).

<<<${${:-{A..G}{b,,#}}:#([BE]#|[CF]b)}
     ${:-             }                 # empty-fallback, basically an anonymous parameter expansion
         {A..G}{b,,#}                   # Cross product range A-G with b,(nothing),#
   ${                 :#             }  # Remove matching elements
                        ([BE]#|[CF]b)   # B#, E#, Cb, Fb
<<<                                     # Print to stdout

Try it online!

GammaFunction

Posted 2019-08-14T22:01:59.213

Reputation: 2 838

2

Ruby, 43 bytes

p (2..18).map{|i|"FCGDAEB"[i%7]+"b #"[i/7]}

Try it online!

With the range 0..20 this would print an array containing all the flats, all the naturals and all the sharps. The undesired ones Fb Cb E# B# are omitted by using the range 2..18

The notes are printed out ordered according to https://en.wikipedia.org/wiki/Circle_of_fifths , or in other words ascending by 7 semitones (a frequency ratio of almost exactly 1.5) each time.

This leads to the note letter order given, in which each note is five degrees inclusive (known as a "fifth") above the previous one. For example F->C is FGABC

Level River St

Posted 2019-08-14T22:01:59.213

Reputation: 22 049

I like that you ascend by fifths. Nice. – Wayne Conrad – 2019-08-17T01:18:00.510

2

brainfuck, 106 bytes

+++[[-<+>>++<]>]<<[-<->]++<<++<,+<-<[->+>+<<]<+[>>>>>+>-[<.<<<.>.[<]]>[>]<+[---<.<.<.[<]]>[>]<++<.<<.<<<-]

Try it online!

Outputs each note separated by carriage returns.

Jo King

Posted 2019-08-14T22:01:59.213

Reputation: 38 234

1

Jelly, 21 bytes

ØAḣ7;p¥⁾b#“¿€×Ø‘œPẎẎ€

Try it online!

Erik the Outgolfer

Posted 2019-08-14T22:01:59.213

Reputation: 38 134

1

Brachylog, 36 bytes

"#b"ẹ,Ẹ↺;Ṇh₇ᵗ↔{∋ᵐc}ᶠ⟨h₅ct₁₄⟩⟨h₁₂ct₅⟩

Try it online!

I'm currently in the process of brute-forcing the powerset index that would let me get rid of ⟨h₅ct₁₄⟩⟨h₁₂ct₅⟩ (and by extension , since the output doesn't need to be in the same order as the example output), but it's taking quite a while... maybe I should put a minute aside to actually work out what order sublists are generated in, and compute the index that way...

Unrelated String

Posted 2019-08-14T22:01:59.213

Reputation: 5 300

1

Canvas, 23 bytes

Z7m{#+¹¹b+]{“╷!↕„2┬²@?P

Try it here!

22 bytes with extra newlines in the output

dzaima

Posted 2019-08-14T22:01:59.213

Reputation: 19 048

1

PHP, 65 bytes

Makes the list with a loop. Items are separated by _ with a trailing separator.

for(;$l=ABCDEFG[$i++];)echo$l._.[$a="$l#_",$a.$b=$l.b_,$b][$i%3];

Try it online!


PHP, 43 bytes

PHP outputs anything as is, when not inside <?php and ?> tags.

Ab,A,A#,Bb,B,C,C#,Db,D,D#,Eb,E,F,F#,Gb,G,G#

Try it online!

Night2

Posted 2019-08-14T22:01:59.213

Reputation: 5 484

1

Pyth, 23 21 bytes

s<R7c.>*<r1G7"b #"2 9

Try it online!

s<R7c.>*<r1G7"b #"2 9   Implicit: G=lowercase alphabet
         r1G            Convert G to upper case
        <   7           First 7 characters
       *     "b #"      Cartesian product with "b #"
     .>           2     Rotate the above 2 places to the right
    c               9   Chop into pieces of length 9
 <R7                    Trim each to length 7
s                       Flatten, implicit print

Edit: Partial rewrite to save 2 bytes, previous version: s%2c*<r1G7"b #"xLG"fhoq Try it online!

Sok

Posted 2019-08-14T22:01:59.213

Reputation: 5 592

1

Commodore C64/TheC64 Mini (probably other Commodore 8-bit BASIC variants) - 52 tokenized BASIC bytes

 0?"{CTRL+N}Ab A A# Bb B C C# Db D D# Eb E F F# Gb GG#

Pressing the CTRL key plus N on the C64 keyboard goes into 'business mode' on the character set for upper/lower case characters. We may print this out in a string in one byte / token; and as we have 40 columns, the space from G to G# is not required.

We do not need to close the string in this case as it is not a multi-statemented line with a : separator.

How this looks on a Commodore C64 (and compatibles) screen is shown below.

Commodore 64 musical notes

Shaun Bebbers

Posted 2019-08-14T22:01:59.213

Reputation: 1 814

1

Keg, 43 bytes

The string, compressed.

AbAA\#BbBCC\#DbDD\#EbEFF\#GbGG\#(:H<[ $]')'

TIO

user85052

Posted 2019-08-14T22:01:59.213

Reputation:

0

APL (Dyalog Unicode), 45 bytes

2↓(,¨⎕A)⎕R', &'⊢'AbAA#BbBCC#DbDD#EbEFF#GbGG#'

Try it online!

Simple ⎕Replace operation, prepending , to each element in the string that matches each letter in the ⎕Alphabet, then dropping the first 2 characters, which are ,.

J. Sallé

Posted 2019-08-14T22:01:59.213

Reputation: 3 233

0

Brainfuck, 214 Bytes

>>>>++++++++[<++++<++++<++++++++++++<++++++++>>>>-]<<+++<++<+.>.>>.<<<.>>>.<<<.>>.>.<<<+.>.>>.<<<.>>>.<<<+.>>>.<<<.>>.>.<<<+.>.>>.<<<.>>>.<<<.>>.>.<<<+.>.>>.<<<.>>>.<<<+.>>>.<<<.>>.>.<<<+.>.>>.<<<.>>>.<<<.>>.>.<<<+

Try it Online!

Zachary Cotton

Posted 2019-08-14T22:01:59.213

Reputation: 679

0

Perl 5, 47 41 bytes

say for'AbA#DbD#GbG#BbEbC#F#'=~/../g,A..G

Try it online!

Xcali

Posted 2019-08-14T22:01:59.213

Reputation: 7 671

0

JavaScript (Node.js), 84 bytes

_=>[...'ABCDEFG'].map((n,i)=>`${i%3!=2?n+'b,':''}${n}${i%3!=1?`,${n}#`:''}`).join`,`

Try it online!

Just returning the string (as shown below) would be shorter by 36 bytes, but where's the fun in that?

_=>'Ab,A,A#,Bb,B,C,C#,Db,D,D#,Eb,E,F,F#,Gb,G,G#'

T. Dirks

Posted 2019-08-14T22:01:59.213

Reputation: 176

0

brainfuck, 255 115 bytes

--[----->+<]>-----[<+>>+>+<<-]>>+<<<-[->+++<]>+++[->>>+>+<
<<<]>>>>--->+++++++[-<<<<.>.>>.<<<.>>>.<<<.>>.>.<<<+>>>>]

Try it online!

Krzysztof Szewczyk

Posted 2019-08-14T22:01:59.213

Reputation: 3 819

1I would upvote this, but it's wrong. There's no black note between B and C, or between E and F. So you're not supposed to output. B#, Cb, E# or Fb. Also, I don't see anything in the spec about it being OK to use lowercase. – Level River St – 2019-08-15T22:29:46.723

@LevelRiverSt the challenge didn't state anywhere that we don't have notes between B and C & E and F – Krzysztof Szewczyk – 2019-08-16T08:12:36.563

All notes without a name consisting of a single letter (i.e. black notes on a musical keyboard) should have their name printed twice, once as the sharp of a note, once as the flat of one. Sharp or flat notes that can be described with a single letter, like B♯ (C) or F♭ (E) should not be outputted. That seems fairly clear to me, and I'm seeing no edit history on the OP on my screen. – Level River St – 2019-08-16T19:53:19.563

0

Bash 5, 42 bytes

x=`echo {A..G}{b,,#}`;echo ${x//[BE]#???/}

Output:

Ab A A# Bb B C C# Db D D# Eb E F F# Gb G G#

Thorsten

Posted 2019-08-14T22:01:59.213

Reputation: 131

0

T-SQL, 124 bytes

SELECT value+a
FROM STRING_SPLIT('A-B-C-D-E-F-G','-')
    ,(VALUES('b'),(''),('#'))b(a)
WHERE value+a NOT IN ('B#','E#','Cb','Fb')

Line breaks are for display purposes only.

Longer but much more interesting than the trivial version (50 bytes):

PRINT'Ab,A,A#,Bb,B,C,C#,Db,D,D#,Eb,E,F,F#,Gb,G,G#'

BradC

Posted 2019-08-14T22:01:59.213

Reputation: 6 099

0

C, 57 bytes

Trivial version 57 bytes in C (gcc)

#include <stdio.h>

f(){puts("Ab,A,A#,Bb,B,C,C#,Db,D,D#,Eb,E,F,F#,Gb,G,G#");}

int main()
{
    f();
}

Try it Online!

Bruce Lueckenhoff

Posted 2019-08-14T22:01:59.213

Reputation: 41

2You can omit #include <stdio.h>, and the rules also allow you to define just a function that produces the output, you don't have to have a main(). So: f(){puts(...);} would be a valid code golf submission. Don't forget to include the actual code! – G. Sliepen – 2019-08-17T14:15:51.080

0

Z80Golf, 31 29 bytes

00000000: 9d5b dc6d df7f 0603 3e40 d13c cb3a 3008  .[.m....>@.<.:0.
00000010: fff5 7b2f ffaf fff1 20f1 10ec 76         ..{/.... ...v

Try it online!

Explanation:

Z80Golf is just a simple fantasy machine based on the Z80 8-bit CPU. The program is loaded at memory location 0x0000, and the rest of the memory is filled with zeroes. Output is done by calling 0x8000, which will output the value of register A as a character.

The program starts with the data that will be processed, 6 bytes in total. Each pair of bytes specifies a note suffix, and a bitmask controlling which of the letters can be combined with this note. To save bytes, the suffix character is inverted (xor 0xff) -- this allows the data to be executed as instructions with little side effects, making it possible to remove a jump that skips this data:

               ;    GFEDCBA
    db 0xff^'b', 0b01011011 ; Ab Bb Db Eb Gb
    db 0xff^'#', 0b01101101 ; A# C# D# F# G#
    db 0xff^' ', 0b01111111 ; A  B  C  D  E  F  G
skip_data:

This is how the CPU decodes this:

    sbc a, l  ; a subtraction with carry on registers we don't care about
    ld e, e   ; put the E register back into itself. This instruction is useless
              ; but still exists to make the encoding regular.
    call c, 0xdf6d ; if the carry flag is set, call a function. The carry flag isn't set
              ; because of the initial register values (all zeroes) when the sbc above
              ; was executed
    ld a, a   ; as above, put A back into itself.

This data is read two bytes at a time into the DE register pair. The stack pointer is used to point to the next element. It starts out at 0, and because the Z80 uses a full, descending stack, any pops will read the next data pair -- all stack operations are 16-bit.

The outer loop is implemented with a decrementing counter in the B register, for which the Z80 provides special support in the form of the djnz instruction:

    ld b, 3
process_pair:
    ...
    djnz process_pair
    halt

The current letter is held in the A register. Because the increment fits well at the start of the loop, we load one less than the actual start value of A:

process_pair:
    ld a, 'A'-1
    pop de ; D = bitmask, E = suffix character
process_note:
    inc a
    srl d ; put the current bitmask bit in the carry flag
          ; also sets the zero flag if this is the last note in the pair
    jr nc, skip
    ; Print the note. Note that we need to preserve the zero flag to check it in the
    ; loop condition later.
    rst $38 ; Short encoding of call $0038.
            ; Because the program is so short, the memory in the 0038..8000 range
            ; is filled with zeroes, which happens to be the encoding for a no-op.
            ; The execution will therefore fall-through to the character-print hook.
    push af ; Save the letter on the stack (which will be just to the left of the
            ; unprocessed data) to free up A for printing other characters.
            ; (only 16-bit register pairs can be saved, so we also push the flags)
    ld a, e
    cpl     ; Undo the inversion used to make the execution fall-through the data.
            ; Done again each iteration because it takes less bytes to operate
            ; on the A register.
    rst $38 ; Print the suffix.
    xor a   ; Standard assembly practice of setting a register to zero by XORing it
            ; with itself. Saves a byte over a simple `ld a, 0`.
    rst $38 ; Print a null byte as a separator.
    pop af  ; Restore the current letter from the stack.
skip:
    jr nz, process_note ; If the zero flag (last changed at the srl d) is not set,
                        ; loop once again
    djnz process_pair
    halt

NieDzejkob

Posted 2019-08-14T22:01:59.213

Reputation: 4 630