g | x | w | all
Bytes Lang Time Link
203ZX Spectrum 48K Basic240918T121524ZNeil
307JavaScript240920T204734Zrougepie
174C GCC using OSS240921T162403ZG. Sliep
228Chipmunk Basic240918T153621Zroblogic
2302Zsh +ffplay240923T094444Zroblogic
320JavaScript240919T161621ZJordan
nanCP1610 machine code240919T013051ZArnauld
270Javascript240920T193816Zccprog
077MATL 1 language240917T230511ZLuis Men
290PowerShell240918T153022Zbigyihsu

ZX Spectrum 48K Basic, 246 203 bytes

  10 LET t=VAL ".14": LET a$="<0
247<?0237<<0247<?0237<C0247<A024
7<@047;<>047;<": FOR i=SGN PI TO
 LEN a$ STEP VAL "6": FOR j=NOT
PI TO INT PI: FOR k=SGN PI TO VA
L "4": BEEP t,VAL "12"*j+CODE a$
(i+k)-CODE a$(i): NEXT k: NEXT j
: FOR j=INT PI TO NOT PI STEP -S
GN PI: FOR k=VAL "5" TO VAL "2"
STEP -SGN PI: BEEP t,VAL "12"*j+
CODE a$(i+k)-CODE a$(i): NEXT k:
 NEXT j: NEXT i

The above is how the program lists on screen as the ZX Spectrum's screen is 32 characters wide. The byte count was calculated by subtracting the system variables for the end and start of the program; this will be one less than the save file length as an empty program has a save file length of 1. Unfortunately I was using an online emulator which only has a save snapshot option, so you'll have to retype this yourself to test it. Note that if you try to enter this program literally in 128K mode it will try to tokenise the <> inside the string, breaking the program, so either replace it with <"+">, do some magic with POKE, or use 48K mode and type <> as two characters rather than the token. In both modes you may also need to use the keyboard layout to enter some special characters such as - or *. Explanation:

  10

Arbitrary line number (always costs 2 bytes).

LET t=VAL ".14"

Timing variable, adjusted to make the overall run time about 48s of real time. (Processing overhead makes the program take about 50% longer than the actual notes).

LET a$="<0247<?0237<<0247<?0237<C0247<A0247<@047;<>047;<"

String of tonic and arpeggio data.

FOR i=SGN PI TO LEN a$ STEP VAL "6"

This steps through each tonic in the data string, thus repeating 8 times. SGN PI is used as it's even shorter than VAL "1", which saves a few bytes because numeric literals take up six bytes for their internal representation.

FOR j=NOT PI TO INT PI

Repeat for four octaves; NOT PI is 0 and INT PI is of course 3.

FOR k=SGN PI TO VAL "4"

Repeat for four notes.

BEEP t,VAL "12"*j+CODE a$(i+k)-CODE a$(i)

Compute the note index (Cā‚„=0) and play it.

NEXT k: NEXT j

Repeat for the rest of the ascending arpeggio.

FOR j=INT PI TO NOT PI STEP -SGN PI

Loop back down through the octaves.

FOR k=VAL "5" TO VAL "2" STEP -SGN PI

Loop down through the notes.

BEEP t,VAL "12"*j+CODE a$(i+k)-CODE a$(i)

Compute and play the note.

NEXT k: NEXT j: NEXT i

Repeat for the remainder of the prelude.

Edit: Saved 43 bytes thanks to @Arnauld's suggesting of indexing into a single data string.

ZX Spectrum 128K Basic, 195 bytes

  10 LET a$="CDEG  #BGED abCE  A
     ECb  fgaC  FCag  gabD  GDBa
       $aC$EG$AG$EC$bDFA $BAFd "
     : FOR i= NOT PI TO VAL "84"
      STEP VAL "12": FOR j= INT
     PI TO VAL "6" STEP INT PI :
      FOR k=j TO VAL "9"-j STEP
     SGN ( VAL "9"-j-j): PLAY "O
     "+ STR$ k+" 2"+(a$( TO VAL
     "24")+a$)(i+j+j- VAL "5" TO
      i+j+j): NEXT k: NEXT j: NE
     XT i

The above is how the program displays during edit mode as the ZX Spectrum's screen is 32 characters wide and it wraps lines to column 5. The byte count was calculated by subtracting the system variables for the end and start of the program as the code is automatically tokenised (in edit mode tokens are always preceded with spaces but if you list the program then some of those will disappear) although when typing in the program you won't need to enter spaces except inside the string literal or between consecutive tokens. Explanation:

  10

Arbitrary line number (always costs 2 bytes).

LET a$="CDEG  #BGED abCE  AECb  fgaC  FCag  gabD  GDBa  $aC$E$G$AG$EC$bDFA $BAFd "

Arpeggio data. Lower case letters play an octave below, so each octave command actually selects two octaves from which notes can be played, but to be able to play all of the arpeggios using the given four octaves I have had to cheat and play the C₇ as a Bš„°ā‚†. Note that the first two arpeggios are only shown once here and doubled up below.

FOR i= NOT PI TO VAL "84" STEP VAL "12"

Loop eight times, stepping though the arpeggio data string.

FOR j = INT PI TO VAL "6" STEP INT PI

Switch between starting at octave 3 and octave 6.

FOR k=j TO VAL "9"-j STEP SGN ( VAL "9"-j-j)

Loop through the four octaves.

PLAY "O"+ STR$ k+" 2"+(a$( TO VAL "24")+a$)(i+j+j- VAL "5" TO i+j+j)

Select the octave and the note duration (the default tempo is 120 bpm but a note length code of 2 gives us the desired duration at this tempo) and play the current arpeggio at this octave.

NEXT k: NEXT j: NEXT i

Repeat for the remainder of the prelude.

The figure of 195 bytes compares well to the 272 byte string needed to play the prelude in a single PLAY command: (line wrapping not part of the string)

T80O3(1CDEGO5cdegCDEGO7cdegCgedcO5GEDCgedcO3GED
abCEABO5ceabCEABO7ceaeaO5BAECbaecO3BAECb)
fgaCFGAO5cfgaCFGAO7cfcO5AGFCagfcO3AGFCag
gabDGABO5dgabDGABO7dgdO5BAGDbagdO3BAGDba
$aC$EG$AO5c$eg$aC$EG$AO7c$eg$ag$ecO5$AG$EC$ag$ecO3$AG$EC
$bDFA$BO5dfa$bDFA$BO7dfa$bafdO5$BAFD$bafdO3$BAFD

JavaScript, 324 307 bytes

I used an array containing as many elements as there are notes to play, making sure that each element can be represented by a single hexadecimal character.

This character represents a number of semitones of difference between this note and the previous note (except for the first element which is played as is)

I then realized that some big chunks were repeated (those corresponding to the arpeggios in add2 and Maj7) and that I could factorize these portions of code

EDIT: with the suggestion from @G.Sliepen, @Neil, @Arnauld and @Fhuvi, I got a 307 bytes. Thanks guys!

f=

_=>{a=new AudioContext;with(a.createOscillator(r=(s,n=4)=>(""+s).repeat(n))){[...`6${b=r("889b")+r(1344,c=3)+134}1${d=r("87ab")+r(1254,3)+125}7${b+1+d+0+b+6+b}5${D=r("a9a7")+r(5232,3)+523}4`+D].map((u,i)=>{frequency.setValueAtTime(110*2**(c/12),i/5);c+="0x"+u-6});connect(a.destination);start();stop(51.2)}}
<button type="button" onclick="f()">Play (Warning: Loud)</button>

C (GCC) using OSS, 201 197 177 174 bytes

i;main(c,n,a){for(open("/dev/dsp",1);write(3,&n,1);n=(a+=901*exp2(n/4+("047;02470237"[n%4+(5732>>c*2&12)]-",/,/310."[c])/12.))>>9)n=i++/1500,c=n/32%8,n%=32,n=n>16?32-n:n;}

The score includes 3 bytes for the -lm compiler flag. It runs on any UNIX system with support for the Open Sound System (OSS), or any other sound system that provides compatibility with OSS. On modern Linux, you might have to sudo modprobe snd_pcm_oss if this is not done automatically already. If you compile with GCC 14 or later, you might need to pass the -std=gnu89 flag, but earlier versions don't need it.

The code opens /dev/dsp, which by default accepts 8-bit, unsigned, 8 kHz mono audio. It has three nested loops:

However, you'll only see one for-loop; it calculates the chord number and position in the arpeggio from the sample counter i. It also reuses the arpeggio position n for the sample value.

It uses a several lookup tables to find out which note to play:

It then calculates the change in phase per sample for the waveform at that frequency in fixed point format, and adds it to the phase accumulator a. This is then converted to a byte to be send straight to the audio device, thus generating a sawtooth wave with the right frequency.

This version loops (for at least six days). To make it write the output to stdout instead of /dev/dsp, change the first 3 in the code to a 1. You can then pipe it through aplay -fU8 -c1 -r8000 or a similar command.

Chipmunk Basic, 677 303 280 241 228 bytes

dim m(20):d$="GIKNDFGKGIKNDFGK@BDGBDFICGJNEILP":for k=0to 7
for i=0to 4:for o=1to 4:m(o+4*i)=555*2^(asc(mid$(d$,k*4+o))/12+i-8):next:next
for i=1to 17:s(m(i)):next:for i=1to 16:s(m(17-i)):next:next
sub s(x):sound x,.2,50:return

Try the debug version on TutorialsPoint!

The info about chords and octaves in the "music theory" section helped a lot.
Ungolfed:

dim m(20)
d$="GIKNDFGKGIKNDFGK@BDGBDFICGJNEILP"
for k=0to 7                       :' the 8 chords
  for i=0to 4                     :' ascend arpeggio for 5 octaves
    for o=1to 4                   :' broken into 4 notes
      m(o+4*i)=555*2^(asc(mid$(d$,k*4+o))/12+i-8)
      :' ^      ^       ^-- get an ascii value from the middle of d$
      :' |      |           and convert it to a frequency (Hz)
      :' |      +-- base frequency A=440Hz, adjusted slightly
      :' +-- populate m(1) to m(20), but only use 17 notes
    next
  next
  for i=1to 17:s(m(i)):next       :' ascend
  for i=1to 16:s(m(17-i)):next    :' descend
next
sub s(x):sound x,.2,50:return

677 bytes

Zsh +ffplay, 296 241 261 230 bytes x 2

for z (GIKN DFGK GIKN DFGK @BDG BDFI CGJN EILP){W=
for i ({0..4})for o (${(s::)z})W+=($[555*2**(##$o/12.+i-8)])
for q (${W:0:18} ${(Oa)W:0:17})S+=between(t,$[p++]/5,$p/5)*$q+
};ffplay -f lavfi aevalsrc="'0.1*sin(2*PI*t*(${S%+}))'"

Try it Online! Adjusted by ear; frequencies from ffplay seemed a bit off. Uses base of 550 instead of 440.

for z grabs 4 pseudo-hex ASCII characters
for i and for o populate array W with 20 notes based on z; expressed as
ā€ƒ note number: n=$[##$o-71+12*i], combined with
ā€ƒ frequency (Hz): f=$[440*2**((n-21)/12.)]
for q goes up & down 17 notes of W, adding to string S:
ā€ƒ 0.2s time increments p++/5,p/5
ā€ƒ frequency q
Finally, we call ffplay using the string S (trailing + truncated).

The linked TIO example doesn't play sound (no ffplay), but you can see the humungous string S.

Debug 296B 241B 261B

JavaScript, 320 bytes

I don't golf with JavaScript much so I'm sure there's a lot of opportunities for golfing here. Should work on most browsers.

x=>{a=new AudioContext
with(a.createOscillator()){[y=[0,2,4,7],z=[-3,-1,0,4],y,z,[-7,-5,-3,0],[-5,-3,-1,2],[-4,0,3,7],[-2,2,5,9]].map((c,t)=>{p=s=>frequency.setValueAtTime(440*2**(s-1.75),(t*32+i)*.19)
for(i=0;i<32;i++)p(i<17?c[i%4]/12+(i/4|0):c[3-(i-1)%4]/12+((32-i)/4|0))})
connect(a.destination)
start()
stop(48.64)}}

-3 bytes thanks to Neil

Try it

f =

x=>{a=new AudioContext
with(a.createOscillator()){[y=[0,2,4,7],z=[-3,-1,0,4],y,z,[-7,-5,-3,0],[-5,-3,-1,2],[-4,0,3,7],[-2,2,5,9]].map((c,t)=>{p=s=>frequency.setValueAtTime(440*2**(s-1.75),(t*32+i)*.19)
for(i=0;i<32;i++)p(i<17?c[i%4]/12+(i/4|0):c[3-(i-1)%4]/12+((32-i)/4|0))})
connect(a.destination)
start()
stop(48.64)}}
<button type="button" onclick="f()">Play (Warning: Loud)</button>

CP-1610 machine code, 125 DECLEs1=156.25 bytes

1. CP-1610 instructions are encoded with 10-bit values (0x000 to 0x3FF), known as DECLEs. Although the Intellivision is also able to work on 16-bit data, programs were really stored in 10-bit ROM back then.

A fantasy console?!? Let's do it on a real one!

This code is meant to be run on a PAL Intellivision (50Hz). It would also work on an NTSC system (60Hz), but at a different pitch and speed.

Demo

Here is what we get on the real hardware (YouTube video), using the LTO Flash!.

Source code

                             ROMW  10          ; use 10-bit ROM width
0x4800                       ORG   $4800       ; map our code at $4800
                   
                     ;; -------------------------------------------------------- ;;
                     ;;  build the note period table in RAM:                     ;;
                     ;;    p(0) = 1432                                           ;;
                     ;;    p(n) = p(n-1) * 483 + 350 >> 9                        ;;
                     ;; -------------------------------------------------------- ;;
4800   001                   SDBD              ; R0 = note period, starting at ...
4801   2B8 098 005           MVII  #1432, R0   ; ... the highest value 1432 for F-2
4804   2BC 2F0               MVII  #$2F0, R4   ; R4 = write pointer in RAM
                   
4806   260           init    MVO@  R0, R4      ; write the period
                   
4807   1D2                   CLRR  R2          ; use R3:R2 as a 32-bit value
4808   1DB                   CLRR  R3          ; (initialized to 0)
                   
4809   2B9 1E3               MVII  #483, R1    ; process multiplication by 483
                   
480B   0C2           mult    ADDR  R0, R2      ; by adding R0 that many times
480C   02B                   ADCR  R3          ; to R3:R2
480D   011                   DECR  R1
480E   22C 004               BNEQ  mult
                   
4810   2FA 15E               ADDI  #350, R2    ; R3:R2 += 350
                                               ; (carry never set -> no ADCR R3)
                   
4812   042                   SWAP  R2          ; R2 = R3:R2 >> 9
4813   043                   SWAP  R3
4814   3BA 0FF               ANDI  #$FF, R2
4816   1DA                   XORR  R3, R2
4817   07A                   SARC  R2
                   
4818   090                   MOVR  R2, R0      ; copy R2 to R0
                   
4819   378 00F               CMPI  #$F, R0     ; loop until R0 = $F
481B   22E 016               BGT   init
                   
                     ;; -------------------------------------------------------- ;;
                     ;;  play the song                                           ;;
                     ;; -------------------------------------------------------- ;;
481D   240 1FB               MVO   R0, $1FB    ; set the volume on channel A to $F
                   
481F   001           loop    SDBD              ; R4 = pointer into chords table
4820   2BC 057 048           MVII  #chords, R4
                   
4823   2A2           chord   MVI@  R4, R2      ; R2 = initial note address
4824   092                   TSTR  R2          ; time to loop?
4825   224 007               BEQ   loop
                   
4827   274                   PSHR  R4          ; save R4 on the stack
4828   2E4                   ADD@  R4, R4      ; R4 = pointer into delta values
4829   2BD 008               MVII  #8, R5      ; R5 = octave counter
                   
482B   093           octave  MOVR  R2, R3      ; copy R2 to R3
482C   2B9 004               MVII  #4, R1      ; R1 = note counter
                   
482E   298           note    MVI@  R3, R0      ; R0 = note period
482F   240 1F0               MVO   R0, $1F0    ; save the low bits
4831   040                   SWAP  R0          ; and the high bits
4832   240 1F4               MVO   R0, $1F4    ; into the PSG registers
                   
4834   001                   SDBD              ; wait for ~184500 cycles
4835   2B8 00C 030           MVII  #12300, R0  ; (approximately 185ms)
                   
4838   010           spin    DECR  R0          ; (6 cycles)
4839   22C 002               BNEQ  spin        ; (9 cycles)
                   
483B   2E3                   ADD@  R4, R3      ; update R3
483C   33B 005               SUBI  #5, R3
483E   011                   DECR  R1          ; decrement the note counter
483F   22C 012               BNEQ  note        ; loop if not zero
                   
4841   37D 005               CMPI  #5, R5      ; compare octave counter with 5
4843   20C 003               BNEQ  phase       ; time to switch the phase? ...
                   
4845   09A                   MOVR  R3, R2      ; ... yes: copy R3 to R2
4846   200 008               B     next
                   
4848   20E 002       phase   BGT   asc         ; ascending phase?
                   
484A   33A 018               SUBI  #24, R2     ; descending phase: -12 semitones
484C   2FA 00C       asc     ADDI  #12, R2     ; ascending phase: +12 semitones
                   
484E   33C 004               SUBI  #4, R4      ; rewind R4
                   
4850   015           next    DECR  R5          ; decrement the octave counter
4851   22C 027               BNEQ  octave      ; loop if not zero
                   
4853   2B4                   PULR  R4          ; advance to the next chord
4854   00C                   INCR  R4
4855   220 033               B     chord
                   
                     ;; -------------------------------------------------------- ;;
                     ;;  chords tables                                           ;;
                     ;; -------------------------------------------------------- ;;
4857   2F7 00F       chords  DECLE $2F7, add2   - $ - 1  ; C/add2
4859   2F4 014               DECLE $2F4, add2_m - $ - 1  ; Am/add2
485B   2F7 00B               DECLE $2F7, add2   - $ - 1  ; C/add2
485D   2F4 010               DECLE $2F4, add2_m - $ - 1  ; Am/add2
485F   2F0 007               DECLE $2F0, add2   - $ - 1  ; F/add2
4861   2F2 005               DECLE $2F2, add2   - $ - 1  ; G/add2
4863   2F3 011               DECLE $2F3, maj7   - $ - 1  ; G#/maj7
4865   2F5 00F               DECLE $2F5, maj7   - $ - 1  ; A#/maj7
4867   000                   DECLE 0
                   
                             ; delta values, with +5 offset
4868   007 007 ...   add2    DECLE 7, 7, 8, 10, 0, 2, 3
486F   007 006 ...   add2_m  DECLE 7, 6, 9, 10, 0, 1, 4
4876   009 008 ...   maj7    DECLE 9, 8, 9,  6, 4, 1, 2
                   
0x487D               end

How it works

About the Programmable Sound Generator (PSG)

The PAL-based Intellivision uses a 4.00MHz clock. The PSG is driven from a clock signal at half this rate. Internally, the PSG divides down its clock by 16 to determine the final square-wave frequency. The frequency of a tone produced by a PAL Intellivision is therefore given by:

$$F\_tone=\frac{4000000}{32\times P\_channel}$$

where \$P\_channel\$ is the period register setting for the given channel.

(adapted from the psg.txt file included in jzIntv)

Building the note period table

The frequency ratio between two consecutive semitones is given by:

$$\sqrt[12]{2}\approx 1,0594631$$

And what we really need is the period ratio between two consecutive semitones:

$$1/\sqrt[12]{2}\approx 0,9438743$$

To apply this ratio to a period \$P_n\$, we use the following approximation:

$$P_{n+1}=\left\lfloor\frac{P_n \times 483 + 350}{512}\right\rfloor$$

We start with \$P_0=1432\$, which is the period for F2 (87.31Hz) on a PAL Intellivision, computed with the formula described in the previous section. We stop when a period of 15 is reached -- an arbitrary choice to have a register ready to initialize the volume on the PSG.

The CP-1610 doesn't have any multiplication instruction, so we have to compute it with either additions and shifts (see the previous revision) or just repeated additions (the current revision). Besides, we need 32-bit values to have enough precision. So we use either one or two pairs of 16-bit registers depending on the method (R0/R1 and R2/R3).

The integer division by 512 is of course much simpler since it boils down to a right-shift by 9.

Chords encoding

Each chord is described by the address of the first note in our period table, followed by a pointer to 7 delta values:

a0, a1, a2, s, d0, d1, d2

where:

The actual delta values are obtained by subtracting 5 to the stored values.

Javascript, 270 bytes

x=>{a=new AudioContext
with(a.createOscillator()){
for(i=0;i<256;i++){n=[60,49,60,49,32,40,46,54][i>>5]
q=[1258,810,1804][n%4]
frequency.setValueAtTime(55*2**((b=i&31?b+(i%32>16?-(q>>i%4*3&7):q>>(257-i)%4*3&7):n>>2)/12),i*3/16)}
connect(a.destination)
start()
stop(48)}}

If you have to express the frequency in Herz, the formula given in the question is unneccessary complicated. The note A is defined as 440Hz, each octave below halves that, and the A that is lower than the lowest note played is Aā‚ƒ at 55Hz.

The start note frequencies of the arpeggios from that become

f = 55 * 2 ** (s / 12)

with the semitones distance form the base note s being

15 12 15 12 8 10 11 13

There are only three different chords being used that can be described by their sequence of semitone differences. The last column is a magic number encoding the differences as octal. For golfing reasons the digits are ordered as 1-2-3-0

0:  major add2  |  2 2 3 5  |  0o2352 = 1258
1:  minor add2  |  2 1 4 5  |  0o1452 =  810
2:  major 7     |  4 3 4 1  |  0o3414 = 1804

The whole sequence can be described as a two-dimensional array

[[15, 0], [12, 1], [15, 0], [12, 1], [8, 0], [10, 0], [11, 2] [13, 2]]

or with the chord index as the lowest two bits in one number

[60, 49, 60, 49, 32, 40, 46, 54]

From there, using the same code as @Jordan for the AudioContext interface (ungolfed):

x => {
  a=new AudioContext
  with (a.createOscillator()) {
    for (i = 0; i < 256; i++) {
      
      n = [60,49,60,49,32,40,46,54][i>>5]  // after each 32 a new arpegio
      q = [1258,810,1804][n%4]             // magic chord number
      b = i&31 ?                           // inside arpeggio sequence,
        b + (                              // add semitone difference
          i%32 > 16 ?
          -(q>>i%4*3&7) :                 // downward
          q>>(257-i)%4*3&7                // upward
        ) :
        n >> 2                            // start new arpeggio at base note
      
      frequency.setValueAtTime(55 * 2 ** (b/12), i * 3 / 16)
    }
    connect(a.destination)
    start()
    stop(48)
  }
}

Try it

f =

x=>{a=new AudioContext
with(a.createOscillator()){
for(i=0;i<256;i++){n=[60,49,60,49,32,40,46,54][i>>5]
q=[1258,810,1804][n%4]
frequency.setValueAtTime(55*2**((b=i&31?b+(i%32>16?-(q>>i%4*3&7):q>>(257-i)%4*3&7):n>>2)/12),i*3/16)}
connect(a.destination)
start()
stop(48)}}
<button type="button" onclick="f()">Play (Warning: Loud)</button>

MATL (1 language), 78 77 bytes

0'!4XC@nRAxt~&'F5:ZaKeK1X"tP[AaABOlHN]0Y(_&vhYs"1875:YPE*76/@12/W*Y,E]&h1e4Y#

This uses equal temperament, with a C3 frequency of 131.6 Hz (more accurate values could be achieved at the cost of 2 or 3 bytes, namely replacing 76 in the code by 76.4 or 76.45).

The timbre corresponds to a sine wave hard-clipped at half its amplitude (this clipping costs 1 byte but produces nicer sound than a pure sine wave; the latter can be obtained by removing the E near the end of the code).

Try it at MATL online!

How it works

Number 0 is first pushed to the stack. This will serve as the initial note. The rest of the notes will be defined by consecutive differences, measured in semitones.

The basic structure of the song is described by the following 4Ɨ8 matrix:

2 2 2 2 2 2 4 4
2 1 2 1 2 2 3 3
3 4 3 4 3 3 4 4
5 5 5 5 5 5 1 1

which contains the increments (in semitones) of the ascending arpeggios, each column corresponding to one of the 8 chords in order.

To generate this matrix, the string '!4XC@nRAxt~&' is interpreted as a "number" in the base defined by all ASCII characters except single quote, and is decoded into the base formed by the numbers [1 2 3 4 5], producing a row vector (this operation is done by the code F5:Za). Reshaping this with 4 rows (Ke) gives the above matrix. Then, the matrix is extended by replicating it 4 times vertically (K1X"), because each ascending arpeggio is played 4 times. This yields a 16Ɨ8 matrix.

A duplicate (t) of this 16Ɨ8 matrix is produced, and it is then flipped vertically (P). This represents the descending arpeggios, except that the sign should be negated; and the last row is incorrect, because it does not consider the transition to the next chord. Specifically, the values of the last row should be [5 āˆ’1 5 6 0 1 2 NaN] (represented compactly in the code as [AaABOlHN]). The final NaN value indicates that there is no following chord in the last column. Thus, the last row of the second 16Ɨ8 matrix is overwritten with that vector (0Y(), and the two matrices are concatenated vertically (&v). The resulting 32Ɨ8 matrix describes the repeated up and down arpeggios for each chord, with the correct transition to the next chord. To produce the notes it should be read in column-major order (down, then across).

To transform the increments between notes into the actual notes, the 0 produced at the beginning of the program is concatenated horizontally (h) with the 32Ɨ8 matrix. This causes the matrix to be read in column-major order, and gives a 1Ɨ257 row vector. Its cumulative sum (Ys) produces the notes, starting at 0, where each note is represented by the number S of semitones up or down from C3. The last value is NaN, which will be interpreted as no note.

A for each loop ("...]) reads each note, represented by the corresponding number S. To generate the waveform for the note, a time vector [1 2 ... 1875] is first created (1875:). The length of this vector, when replayed at 10000 samples per second, determines a tempo of 80 bpm, as required. This vector is element-wise multiplied by 2Ļ€ (YPE*), divided by 76 (76/), and then multiplied by 2 raised to the number that results from dividing S by 12 (@12/W*). Applying the sine function (Y,) produces a 1Ɨ1875 vector with samples of a sinusoid of the frequency correponding to S. The values of this vector are multiplied by 2 (E). The last sinusoid contains NaN values and will produce no sound.

The 257 resulting waveforms, each with 1875 samples, are concatened horizontally (&h) into a single waveform. This waveform is then replayed, without scaling, at the indicated sample rate (1e4Y#). When replaying, sample values are clipped to the interval [1, āˆ’1].

PowerShell, 401 379 340 304 290 bytes

$C=130,146,164,196,261;$A=110,123,130,174,220;$G=103,130,155,196,207
$q=[console]
function P($c=$C,$h=0){0..7|%{$i=$_;if($i-lt4){0..3|%{$q::Beep(($c[$_])*[math]::Pow(2,$i+$h/12),187)}}else{4..1|%{$q::Beep(($c[$_])*[math]::Pow(2,7-$i+$h/12),187)}}}}
P;P $A;P;P $A;P $C -7;P $C -5;P $G;P $G 2

Attempt This Online!

An extremely brute-force solution, since I'm a novice at PowerShell golf. Defines a function P that plays a chord (array of numbers, truncated to the nearest integer) at a given half-step offset, first up 4 times then down 4 times. Each note is played using the builtin [System.Console]::Beep method.

To run, paste this into a .ps1 file, then run it in PowerShell; or paste it directly into a PowerShell terminal.

P.S. I keep getting this weird issue where some notes are missed; I modified the code to write out the current note and index that it's on and it is indeed running correctly, but the beeps are being clobbered by something else.