14
\$\begingroup\$

Play the initial section of the Final Fantasy Prelude. This is a 4-octave up-down arpeggio of the following chords:

Cadd2, Amadd2, Cadd2, Amadd2, Fadd2, Gadd2, G♯maj7, A♯maj7

Rules:

  • Actual sound must play when running your code. What environment is used is up to you, for example it could be a fantasy console, or a computer with an IBM PC compatible internal speaker.
  • Any instrument is fine, including simple beeps, as long as the melody is clearly recognizable.
  • The tempo is 80 bpm, and the individual notes are sixteenth notes, so playing the arpeggio of the 8 chords should take 48 seconds.
  • The first note is a C3, which has a frequency of \$440\cdot2^{-21/12}\approx130.8\$ Hz. After 4 octaves of going up, the arpeggio goes back down starting at C7, using the notes of the same chord. Then the next chord starts. The other chords start at: A2, C3, A2, F2, G2, G♯2 and A♯2.
  • Slight inaccuracies in frequencies and tempo are allowed.
  • If it makes things easier, you may substitute A♭maj7 and B♭maj7 for the last two chords.

Scoring:

  • This is code golf, so shortest code wins.
  • The score is multiplied by the number of languages and external tools you are using. So calling system("sox …") from C has a multiplier of 3, as it uses C, a shell and sox.
  • The interpreter (if any) of your program does not count for the multiplier. So if your program is just an MP3 file, its interpreter can be a music player, so it has a multiplier of 1.

Music theory quickstart:

A note, like C, can appear in many octaves. If you go one octave up, its frequency doubles, if you go one down, the frequency halves. Most "western" music uses the 12 tone equal temperament, which means there are twelve notes in one octave, such that the ratio of frequencies between any two consecutive notes is the same, so \$\sqrt[12]{2}\$, or equivalently \$2^{1/12}\$.

Chords are combination of notes that are normally played simultaneously, but in this case they are meant to be played individually. To avoid having to learn chord notation, here are the note numbers for the first octave of each chord:

  • 0, 2, 4, 7
  • -3, -1, 0, 4
  • 0, 2, 4, 7
  • -3, -1, 0, 4
  • -7, -5, -3, 0
  • -5, -3, -1, 2
  • -4, 0, 3, 7
  • -2, 2, 5, 9

Where number 0 has been chosen for C3. So to convert a given note number \$n\$ to a frequency, use the formula \$440\cdot2^{(n-21)/12}\$. To go one octave up, just add 12 to \$n\$.

\$\endgroup\$
11
  • 1
    \$\begingroup\$ @l4m2 It's four octaves up starting from C₃: [0,2,4,7,12,14,…, 43], then four octaves down starting from C₇ : [48,43,40,…2], then the next chords are arpeggiated in the same way. \$\endgroup\$
    – G. Sliepen
    Commented 2 days ago
  • 1
    \$\begingroup\$ @G.Sliepen I'm not a music person. What do the numbers "[0,2,4,7,12,14,…, 43]" correspond to? Those numbers appear in your comment but not in the question itself. \$\endgroup\$
    – Jordan
    Commented 2 days ago
  • 5
    \$\begingroup\$ @G.Sliepen Can you list all of the semitones—or all of the frequencies—for the piece in order? Maybe I'm alone in this but it seems like otherwise the bulk of this task will be translating chord names to semitones or frequencies, rather than actually golfing. \$\endgroup\$
    – Jordan
    Commented 2 days ago
  • 7
    \$\begingroup\$ That is unhelpful. It's forcing people to learn music theory to obtain a sequence of numbers that you could easily have listed in the question. I will ask CoPilot \$\endgroup\$
    – roblogic
    Commented yesterday
  • 2
    \$\begingroup\$ Jordan, @roblogic, I've added a section about converting the chords to frequencies. Is this enough information? \$\endgroup\$
    – G. Sliepen
    Commented yesterday

6 Answers 6

11
\$\begingroup\$

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$E$G$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
\$\endgroup\$
6
  • \$\begingroup\$ I don't know the subtleties of the ZX Spectrum Basic tokenizer, but would it save bytes to do VAL "12"*j+CODE a$(k)-CODE a$(0) with k incremented and $a and $b merged into the same string? Or could you even discard DATA entirely and use a single string instead with some additional indexing math? \$\endgroup\$
    – Arnauld
    Commented yesterday
  • \$\begingroup\$ @Arnauld I'm not sure about the indexing mathematics but certainly your first idea looks good. (It would be CODE a$(NOT PI) of course... \$\endgroup\$
    – Neil
    Commented yesterday
  • 1
    \$\begingroup\$ Actually, CODE defaults to the first character, so it's just CODE a$. \$\endgroup\$
    – Neil
    Commented yesterday
  • 1
    \$\begingroup\$ About the single string: I mean something like VAL "12"*j+CODE a$(VAL "6"*i+k)-CODE a$(VAL "6"*i) with a$="<0247<?0237<<0247<?0237<C0247<A0247<@047;<>047;<". \$\endgroup\$
    – Arnauld
    Commented yesterday
  • 1
    \$\begingroup\$ @Arnauld Merging pairs of strings got it down to 211 bytes but a single string gets it down to 203 bytes, but unfortunately only in 48K mode, because the 128K tokeniser tries to tokenise the <> in the string... \$\endgroup\$
    – Neil
    Commented yesterday
7
\$\begingroup\$

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 each note, a time vector [1 2 ... 1875] is first generated (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*pi (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].

\$\endgroup\$
5
\$\begingroup\$

PowerShell, 401 379 340 304 bytes

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

  • -22 from general golfs
  • -39 from replacing for-loops with ranges piped into ForEach-Objects.
  • -36 from reusing the back 4 notes of the chords, in reverse, for the descending arpeggios.

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.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Looks like you left in a couple spaces in P: $i + $h/12. \$\endgroup\$
    – Jordan
    Commented yesterday
  • 1
    \$\begingroup\$ I don't know PowerShell but in ($c[$n % $c.Length] can $c.Length be replaced with 8, or am I misunderstanding what the value of $c is there? \$\endgroup\$
    – Jordan
    Commented yesterday
  • 1
    \$\begingroup\$ Looking at some PowerShell examples I think you can save some bytes by replacing e.g. for($i=0;$i-lt8;$i++){...$i...} with 0..7|%{...$_...}. 0..7 is a range and % is an alias for ForEach-Object. Unfortunately I don't have a Windows machine handy to test this. \$\endgroup\$
    – Jordan
    Commented 22 hours ago
  • 1
    \$\begingroup\$ Pasting this into Powershell was a surreal experience. \$\endgroup\$
    – Stevoisiak
    Commented 15 hours ago
  • 1
    \$\begingroup\$ My PowerShell is silent. I guess my PC's speaker is only connected to my sound card. Sadfaces... \$\endgroup\$
    – Neil
    Commented 15 hours ago
3
\$\begingroup\$

CP-1610 machine code, 136 DECLEs1=170 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   1C9                  CLRR  R1          ; R1 and R3 are the most significant
4808   1DB                  CLRR  R3          ; words in R1:R0 and R3:R2 (32-bit)
                   
                            ; multiplication by 483, split into 2 + 1 - 32 + 512
4809   082                  MOVR  R0, R2      ; R2 = R0  \
480A   048                  SLL   R0          ; R0 *= 2   |__ 16-bit operations
480B   0C2                  ADDR  R0, R2      ; R2 += R0  |
480C   04C                  SLL   R0, 2       ; R0 *= 4  /
480D   05C                  SLLC  R0, 2       ; 32-bit values required from here
480E   055                  RLC   R1, 2       ; R1:R0 *= 4
480F   102                  SUBR  R0, R2      ; R3:R2 -= R1:R0
                                              ; (carry never set -> no ADCR R3)
4810   013                  DECR  R3
4811   10B                  SUBR  R1, R3
4812   05C                  SLLC  R0, 2       ; R1:R0 *= 16
4813   055                  RLC   R1, 2
4814   05C                  SLLC  R0, 2
4815   055                  RLC   R1, 2
4816   0C2                  ADDR  R0, R2      ; R3:R2 += R1:R0
4817   02B                  ADCR  R3
4818   0CB                  ADDR  R1, R3
                   
4819   2FA 15E              ADDI  #350, R2    ; R3:R2 += 350
                                              ; (carry never set -> no ADCR R3)
                   
481B   042                  SWAP  R2          ; R2 = R3:R2 >> 9
481C   043                  SWAP  R3
481D   3BA 0FF              ANDI  #$FF, R2
481F   1DA                  XORR  R3, R2
4820   07A                  SARC  R2
                   
4821   090                  MOVR  R2, R0      ; copy R2 to R0
                   
4822   378 00F              CMPI  #$F, R0     ; loop until R0 = $F
4824   22E 01F              BGT   init
                   
                    ;; -------------------------------------------------------- ;;
                    ;;  play the song                                           ;;
                    ;; -------------------------------------------------------- ;;
4826   240 1FB              MVO   R0, $1FB    ; set the volume on channel A to $F
                   
4828   001          loop    SDBD              ; R4 = pointer into chords table
4829   2BC 063 048          MVII  #chords, R4
                   
482C   2A2          chord   MVI@  R4, R2      ; R2 = initial note address
482D   274                  PSHR  R4          ; save R4 on the stack
482E   2E4                  ADD@  R4, R4      ; R4 = pointer into delta values
482F   2BD 008              MVII  #8, R5      ; R5 = octave counter
                   
4831   093          octave  MOVR  R2, R3      ; copy R2 to R3
4832   2B9 004              MVII  #4, R1      ; R1 = note counter
                   
4834   298          note    MVI@  R3, R0      ; R0 = note period
4835   240 1F0              MVO   R0, $1F0    ; save the low bits
4837   040                  SWAP  R0
4838   240 1F4              MVO   R0, $1F4    ; save the high bits
                   
483A   001                  SDBD              ; wait for ~184500 cycles
483B   2B8 00C 030          MVII  #12300, R0  ; (approximately 185ms)
                   
483E   010          spin    DECR  R0          ; (6 cycles)
483F   22C 002              BNEQ  spin        ; (9 cycles)
                   
4841   2E3                  ADD@  R4, R3      ; update R3
4842   33B 005              SUBI  #5, R3
4844   011                  DECR  R1          ; decrement the note counter
4845   22C 012              BNEQ  note        ; loop if not zero
                   
4847   37D 005              CMPI  #5, R5      ; compare octave counter with 5
4849   20C 003              BNEQ  phase       ; time to switch the phase? ...
                   
484B   09A                  MOVR  R3, R2      ; ... yes: copy R3 to R2
484C   200 008              B     next
                   
484E   20E 002      phase   BGT   asc         ; ascending phase?
                   
4850   33A 018              SUBI  #24, R2     ; descending phase: -12 semitones
4852   2FA 00C      asc     ADDI  #12, R2     ; ascending phase: +12 semitones
                   
4854   33C 004              SUBI  #4, R4      ; rewind R4
                   
4856   015          next    DECR  R5          ; decrement the octave counter
4857   22C 027              BNEQ  octave      ; loop if not zero
                   
4859   2B4                  PULR  R4          ; advance to the next chord
485A   00C                  INCR  R4
                   
485B   001                  SDBD              ; was it the last chord?
485C   37C 073 048          CMPI  #ch_end, R4
485F   225 034              BLT   chord
4861   220 03A              B     loop        ; if yes, repeat from the beginning
                   
                    ;; -------------------------------------------------------- ;;
                    ;;  chords tables                                           ;;
                    ;; -------------------------------------------------------- ;;
4863   2F7 00E      chords  DECLE $2F7, add2   - $ - 1  ; C/add2
4865   2F4 013              DECLE $2F4, add2_m - $ - 1  ; Am/add2
4867   2F7 00A              DECLE $2F7, add2   - $ - 1  ; C/add2
4869   2F4 00F              DECLE $2F4, add2_m - $ - 1  ; Am/add2
486B   2F0 006              DECLE $2F0, add2   - $ - 1  ; F/add2
486D   2F2 004              DECLE $2F2, add2   - $ - 1  ; G/add2
486F   2F3 010              DECLE $2F3, maj7   - $ - 1  ; G#/maj7
4871   2F5 00E              DECLE $2F5, maj7   - $ - 1  ; A#/maj7
0x4873              ch_end
                   
                            ; delta values, with +5 offset
4873   007 007 ...  add2    DECLE 7, 7, 8, 10, 0, 2, 3
487A   007 006 ...  add2_m  DECLE 7, 6, 9, 10, 0, 1, 4
4881   009 008 ...  maj7    DECLE 9, 8, 9,  6, 4, 1, 2

0x4888              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 additions and shifts. Besides, we need 32-bit values to have enough precision. So two pairs of 16-bit registers are used (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, a2 are the delta values for the ascending phase, repeated 4 times and starting at the next octave each time
  • s is the delta value applied when switching from the ascending to the descending phase
  • d0, d1, d2 are 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.

\$\endgroup\$
3
  • \$\begingroup\$ I found out that I could optimise the 128K Spectrum's PLAY command to get it down to 195 bytes... only to find that you had got your answer back below that... \$\endgroup\$
    – Neil
    Commented 8 hours ago
  • \$\begingroup\$ @Neil I guess a Z80 assembly answer would be much shorter than mine since it seems to have a built-in note period table somewhere in ROM. \$\endgroup\$
    – Arnauld
    Commented 8 hours ago
  • \$\begingroup\$ There is a note frequency table but it's in floating-point and you have to figure out the timing yourself, so just using BEEP is much easier really. \$\endgroup\$
    – Neil
    Commented 8 hours ago
2
\$\begingroup\$

JavaScript, 381 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
o=a.createOscillator()
d=.19;[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=>o.frequency.setValueAtTime(440*2**((s-21)/12),(t*32+j)*d)
for(i=0,j=0;i<17;i++,j++)p(c[j%4]+12*(i/4|0))
for(i=0;i<15;i++,j++)p(c[3-(j-1)%4]+12*((15-i)/4|0))})
o.connect(a.createGain()).connect(a.destination)
o.start()
o.stop(256*d)}

Try it

f =

x=>{a=new AudioContext
o=a.createOscillator()
d=.19;[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=>o.frequency.setValueAtTime(440*2**((s-21)/12),(t*32+j)*d)
for(i=0,j=0;i<17;i++,j++)p(c[j%4]+12*(i/4|0))
for(i=0;i<15;i++,j++)p(c[3-(j-1)%4]+12*((15-i)/4|0))})
o.connect(a.createGain()).connect(a.destination)
o.start()
o.stop(256*d)}
<button type="button" onclick="f()">Play (Warning: Loud)</button>

\$\endgroup\$
1
\$\begingroup\$

Chipmunk Basic, 677 303 bytes

data 0,2,4,7,-3,-1,0,4,0,2,4,7,-3,-1,0,4,-7,-5,-3,0,-5,-3,-1,2,-4,0,3,7,-2,2,5,9
for k=0to 7:dim a(4):for i=1to 4:read a(i):next
dim m(16):for i=0to 3:for j=1to 4:n=a(j)+12*i:m(j+4*i)=440*2^((n-21)/12):next:next
for i=1to 16:s(m(i)):next:for i=1to 15:s(m(16-i)):next:next
sub s(n):sound n,.18,50:return

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

677 bytes : I am sure there is a better way of coding this ...

\$\endgroup\$
1
  • \$\begingroup\$ Note also: ffplay -autoexit -nodisp -f lavfi -i sine=f=1000:d=0.2 (all platforms) and spkr-beep (linux/windows) could be used in other solutions... \$\endgroup\$
    – roblogic
    Commented 10 hours ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.