7-notes (Sinclair ZX Spectrum) by Ollibony
A downloadable game
# 7 Notes by Ollibony
## Introduction
In this game, you'll need to memorize and recall random music melodies generated by the computer.
If you can't hear or can't differentiate one note from another, you can still play this game, because the notes being played are accompanied by flashing colored numbers on the screen.
This game is an entry for the [BASIC 10 Liner Contest 2026](https://www.homeputerium.de/), category "PUR-80", which means 10 lines of BASIC code, each line no more than 80 characters long.
The demonstration movie (with sound) is available [on YouTube](
).
## System Requirements
A ZX Spectrum 128K (or compatible) computer is required.
This game won't work on the ZX Spectrum 48K due to the use of the PLAY command.
On modern systems, you may use an emulator. Here are some recommendations:- [JSSpeccy](https://jsspeccy.zxdemo.org/) is the easiest to start with, since it works directly in the browser without the need to download and install.
However, video quality is not superior.
- [SpecEmu](https://specemu.zxe.io/) provides good video an audio quality and is easy to use, but it is Windows-only.
- [RetroVirtualMachine](https://www.retrovirtualmachine.org/) gives the most authentic retro look-and-feel, but can be tough to use for novices.
- [Fuse](https://fuse-emulator.sourceforge.net/) emulator is fine, but use with caution: I've encountered weird bugs when wrong notes were occationall played (although this no longer seems reproducible in the final version of the game).
In general, I find audio quality in *Fuse* to be poor.
- *Speccy* emulator is not recommended, the game sounds wrong in it: volume envelopes are incorrect.
## How To Play
On the screen, you'll see a line of colored numbers (from 1 to 7), each corresponding to a musical note (from C to B).
A short melody will be played, and the numbers will flash in sync.
Then you'll need to recreate this melody using the keys from 1 to 7.
If you enter it correctly, your score increases, and the next melody is played, and so on.
The first melody is only one note long, but each subsequent melody is one note longer than the previous one. For correct answers, you earn points proportional to the length of the melody.
If you make a mistake, you lose a life, and the same melody is played again, giving you another chance to enter it correctly.
You only have three lives. When you run out of them, the game is over. Press any key to start over.
The highest score earned throughout a series of games is always displayed on the screen, so you can always see what your next goal is.
## Program Listing
The game is written in Sinclair ZX BASIC, the interpreted language embedded in the ZX Spectrum ROM.
The program listing is a perfect rectangular 10×80-character BASIC code block.
```basic
1 data "Score: ","UX10000","Game over!",0:read s$,u$,g$,h:border 0:paper 0:ink 9 2 restore 3:read s,l,c:cls:for i=1 to 7:print paper i;i;:next i:print,"Hi-";s$;h 3 data 0,3: let c=c+1:paper 8:dim m(c):for i=1 to c:let m(i)=int(rnd*7)+1:next i 4 read d,w:gosub 9:pause 50:for i=1 to c:let n=m(i):gosub 10:next i:for i=1 to c 5 data 0,0,0: restore 5: pause 0: let k$=inkey$: if k$<"1" or k$>"7" then goto 5 6 let n=val k$:gosub 10:if m(i)<>n then let d=1:gosub 9:play"O3a":goto 8-(l>0)*4 7 next i: let w=c:gosub 9: play u$+"W0O6 1c5C": goto 3: def fn z()=h+(s-h)*(s>h) 8 print ,ink 5,g$: play u$ + "W0O3 2gfeX20000 8d": pause 0: let h=fn z(): goto 2 9 let l=l-d:let s=s+w:print at 2,0;ink 7-(w>0);s$;s,ink 7-5*d;"Lives: ";l:return 10 print at 0,n-1;bright 1;n: play u$+"W0"+"cdefgab"(n): print at 0,n-1;n:return
```
You can use the [zmakebas](https://github.com/ohnosec/zmakebas) utility to convert this code to the native ZX Spectrum binary format and wrap it into a TAP file readable by emulators.
When the program is running, you can press Break (Shift + Space on most emulators), then press any key (except Enter) to show the menu. Then select the "128 Basic" menu item, and you will see the program listing. Note that the program lines will appear to be longer than 80 characters due to the fancy formatting that the ZX Spectrum applies to keywords and line numbers.
## Detailed Program Description
I'll go through the program line by line, unfold the lines to make them more readable, and explain what's going on.
### Line 1
```basic
1 data "Score: ", "UX10000", "Game over!", 0
: read s$, u$, g$, h
: border 0
: paper 0
: ink 9
```
We start by initializing some variables:
- `s$` and `g$` contain some strings to be displayed on the screen.
- `u$` contains the instruction to be used in almost every `play` command, it sets up volume envelope length.
- `h` contains hi-score.
Bulk initialization with `data` and `read` is much shorter than using `let`: in Sinclair Basic you cannot omit the `let` keyword, and you cannot do several assignments within a single `let`. You also cannot leave `h` uninitialized hoping that it will be automatically initialized to zero - it just won't work.
Then we set screen border color to black, set default background color to black, and set default text color to 'contrast', which means black on light background and white on dark background.
### Line 2
```basic
2 restore 3
: read s, l, c
: cls
: for i = 1 to 7
: print paper i; i;
: next i
: print, "Hi-"; s$; h
```
This is the start of the outer program loop. When the game is over, the new game starts from here.
We begin by initializing some more variables:
- `s` contains current score.
- `l` contains lives left.
- `c` contains the current melody length.
The `restore` statement tells the interpreter to search `data` for `read` statements in line 3. You can see that `data` in line 3 contains only 2 values, but we read 3 variables - the value for the `c` variable is taken from the next `data`, which is in line 5.
Then we clear the screen, it is filled with black according to the `paper` statement in the previous line.
Then we print the numbers, each number with its own background color. Text color is 'contrast' according to the `ink` statement in the previous line.
Finally, we print the hi-score.
### Line 3
```basic
3 data 0, 3
: let c = c + 1
: paper 8
: dim m(c)
: for i = 1 to c
: let m(i) = int(rnd * 7) + 1
: next i
```
This is the start of the main game loop. When the player enters the correct melody, the new round is started from here.
First, there is `data` for `read` statements in the previous line. Technically, it is not part of the loop. `data` statements can be placed anywhere; they are processed independently of the program flow. We've put it here just because there was space.
The real loop starts with incrementing the melody length. We initialized it to zero, so on the first round it will be 1.
We then set default background color for text output to 'no change'. This will be handy when we re-draw the numbers to make them flash (see line 10).
Then we allocate the array `m` to store the melody and fill it with random notes.
### Line 4
```basic
4 read d, w
: gosub 9
: pause 50
: for i = 1 to c
: let n = m(i)
: gosub 10
: next i
: for i = 1 to c
```
This is the start of the inner game loop.
First, we initialize variables `d` and `w`; the data is taken from line 5.
Those variables are parameters for the subroutine at line 9 that is called here.
That routine prints the current score and lives. `d` and `w` being zero means that it will just print with no side effects.
Next, we wait a second, then play the current melody. The subroutine at line 10 plays a single note and flashes the corresponding number on the screen. Variable `n` determines which note to play.
Finally, we start the inner-inner loop to read the keyboard, but the actual reading is in the next line.
### Line 5
```basic
5 data 0, 0, 0
: restore 5
: pause 0
: let k$ = inkey$
: if k$ < "1" or k$ > "7" then goto 5
```
At the beginning, there is `data` for `read` statements in lines 2 and 4.
The actual logic starts with the `restore` statement that tells the next `read` to search data here in line 5. This will be needed later when we return to line 4. Why do it now? Well, because there is space for it in this line :)
`pause 0` waits for a keypress. Then we check if the pressed key is within 1..7, and if not, repeat execution of this line.
### Line 6
```basic
6 let n = val k$
: gosub 10
: if m(i) <> n then
let d = 1
: gosub 9
: play "O3a"
: goto 8 - (l > 0) * 4
```
We call subroutine at line 10 to play/flash the entered note.
Then we verify if the entered note is correct.
If it is not, we call subroutine at line 9 which prints score and lives, but this time with `d = 1`. This will cause the subroutine to decrement the number of lives and print them with red color. After that we play the "boop" sound.
Then we decide where to go next: if there are no lives left, we go to line 8 (game over), otherwise return to line 4 (redraw lives with the default color and re-play the melody).
### Line 7
```basic
7 next i
: let w = c
: gosub 9
: play u$ + "W0O6 1c5C"
: goto 3
: def fn z() = h + (s - h) * (s > h)
```
The inner-inner loop ends here. If the player entered the note correctly, we return to line 5 and wait for the next note to be entered. If all notes are entered correctly, we proceed.
We call the subroutine at line 9, this time with `w = c`, which means that the subroutine will add current melody length to the current score and print the score with yellow color.
After that, we play the "ding-ding" sound and return to line 3 (to increment melody length and start the new round).
At the end of the line there is the definition of custom function `fn z` which will be used later. It may seem that this definition is never executed because it is placed after `goto` statement. The thing is, `def fn` statements may be placed anywhere in the program, just like `data`. When we invoke the function, the interpreter will find its definition wherever it is.
### Line 8
```basic
8 print , ink 5, g$
: play u$ + "W0O3 2gfeX20000 8d"
: pause 0
: let h = fn z()
: goto 2
```
The "game over" sequence.
We print "Game over!" with cyan color. Two commas in `print` will leave an empty line between the score / lives text and the game over text.
We play the "doom-doom-doom-dooooom" melody and wait for any key.
We update the hi-score using the custom function `fn z` defined in the previous line and return to line 2 to start the new game.
### Line 9
```basic
9 let l = l - d
: let s = s + w
: print at 2, 0; ink 7 - (w > 0); s$; s, ink 7 - 5 * d; "Lives: "; l
: return
```
The subroutine that updates and prints the current score and lives.
Variable `d` is subtracted from lives, variable `w` is added to score.
If `d = 1`, lives are printed in red. If `w > 0`, score is printed in yellow.
### Line 10
```basic
10 print at 0, n - 1; bright 1; n
: play u$ + "W0" + "cdefgab"(n)
: print at 0, n - 1; n
: return
```
This subroutine plays the note given in `n` and flashes the corresponding number in sync.
The flashing is done by printing the number with high brightness. The second `print` returns the brightness level to default. The default background color was set to 'no change' on line 3, and default text color to 'contrast' on line 1, so the colors won't change, only the brightness will.
---
© 2026 Ollibony
| Updated | 16 days ago |
| Published | 18 days ago |
| Status | Released |
| Rating | Rated 4.0 out of 5 stars (1 total ratings) |
| Author | BASIC 10Liner |
| Genre | Rhythm |
| Tags | 10liner, 8-Bit, basic, sinclair, ZX Spectrum |
Download
Install instructions
## How To Start
Configure the emulator to emulate ZX Spectrum 128K, then load the attached TAP file using the menu item or drag-and-drop.
Most emulators will automatically run the game when the TAP file is loaded.
If the game doesn't start, make sure the "Tape Loader" menu item is visible and selected, and press Enter. In *RetroVirtualMachine*, you will also have to manually start the virtual cassette player.
To run the game on a real machine, you may use the attached MP3 file to load the game via the audio input.

Comments
Log in with itch.io to leave a comment.
very good
This is such a fun game!