| Bytes | Lang | Time | Link |
|---|---|---|---|
| 203 | ZX Spectrum 48K Basic | 240918T121524Z | Neil |
| 307 | JavaScript | 240920T204734Z | rougepie |
| 174 | C GCC using OSS | 240921T162403Z | G. Sliep |
| 228 | Chipmunk Basic | 240918T153621Z | roblogic |
| 2302 | Zsh +ffplay | 240923T094444Z | roblogic |
| 320 | JavaScript | 240919T161621Z | Jordan |
| nan | CP1610 machine code | 240919T013051Z | Arnauld |
| 270 | Javascript | 240920T193816Z | ccprog |
| 077 | MATL 1 language | 240917T230511Z | Luis Men |
| 290 | PowerShell | 240918T153022Z | bigyihsu |
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:
- Outer loop for each chord
- Middle loop for the arpeggio
- Inner loop to generate samples
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:
5732is 8 2-bit values storing which type of chord to play (major seventh, major and minor)"047;02470237"stores the notes of each of the three chord types",/,/310."stores the first note of the start of each up-down arpeggio, inverted (higher ASCII code means lower note), as it will be subtracted from the previous table's values
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
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.
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:
a0,a1,a2are the delta values for the ascending phase, repeated 4 times and starting at the next octave each timesis the delta value applied when switching from the ascending to the descending phased0,d1,d2are the delta values for the descending phase, repeated 4 times and starting at the previous octave each time
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
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.
- -22 from general golfs
- -39 from replacing
for-loops with ranges piped intoForEach-Objects. - -36 from reusing the back 4 notes of the chords, in reverse, for the descending arpeggios.
- -14 with default arguments and some simplification
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.