A beat a program sets for itself

The only regular thing on this machine was the screen finishing a frame,
sixty times a second and not negotiable - a clock a program BORROWS rather
than one it sets. Every duration became a multiple of 16,667 cycles, so a
sixteenth note at 120 beats a minute, which is 125,000, is seven and a half
frames and cannot be asked for at all. The way round it was to choose a
tempo whose subdivisions happen to land on whole frames, which is making
the music fit the machine. Examples/tune.asm says so in its own header.

  0x50  Status: a period went by, it is running, it will interrupt
  0x51  Control: run, repeat, interrupt
  0x52-0x54  The period, in cycles, most significant first

THE PERIOD IS IN CYCLES because that is what everything else here is
counted in - the cost model counts them and a frame is measured in them -
so a timer counting anything else would be a second unit to remember.
Twenty four bits reaches from one cycle to sixteen and a half seconds, with
120 beats a minute at 500,000 in the middle, and there is no range left for
a prescaler to buy.

Starting loads the period; asking it to run while it already is does not,
so turning interrupts on half way through a period does not silently move
the beat being kept. What is left over carries into the next period, so a
period of 1,000 ticks every 1,000 and not every 1,000 plus however late
anybody looked. Reading the status takes the tick down and the line with
it, which is the rule this machine settled two days ago about every status
port.

The timing check is in terminal.sh and not the manifest, and the reason is
worth keeping: settle() strips cycle counts from recordings, which is right
for every other program and useless for a clock. "It printed eight dots"
would pass on a timer that fired them all at once. terminal.sh measures
that eight periods of 125,000 come to a million within a couple of hundred
cycles, and that 99.97% of them were spent asleep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-31 17:06:28 -04:00
co-authored by Claude Opus 5
parent 3e48e9690d
commit 6b51d6391f
11 changed files with 393 additions and 2 deletions
+53 -1
View File
@@ -551,6 +551,7 @@ If nothing is installed for the vector a device refused with, the machine stops
| 0x12 | A device that owns 256 bytes of memory. Writing to its port fills that memory with the byte written, standing in for a disk controller reading a sector. Its memory is unreachable until it is registered as a bank. | 0x12 |
| 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 |
| 0x40 - 0x4F | The sound device. See Making A Noise. Four channels, played by writing to ports; it brings no memory. | 0x15 |
| 0x50 - 0x54 | The timer. See Keeping Time. Counts the machine's cycles and says when a period has gone by. | 0x16 |
| 0xE0 - 0xEF | The memory controller. See The Memory Controller. | 0x03 |
| 0xFF | The bus registry. See Asking What Is There. | 0x01 |
@@ -988,7 +989,58 @@ One thing to be careful of: the registry remembers which port it was asked about
| 0x13 | Disk. |
| 0x14 | Screen. |
| 0x15 | Sound. |
| 0x16 - 0xFF | Peripherals. |
| 0x16 | Timer. |
| 0x17 - 0xFF | Peripherals. |
## Keeping Time:
A period, in cycles, and a bit that says when one has gone by.
Before this the only regular beat on the machine was the screen finishing a frame, and that is
a clock a program **borrows** rather than one it sets. A frame is 16,667 cycles and not
negotiable, so every duration becomes a multiple of it - and a sixteenth note at 120 beats a
minute is 125,000 cycles, which is seven and a half frames. It cannot be asked for at all. The
way round it is to choose a tempo whose subdivisions happen to land on whole frames, which is
making the music fit the machine.
| Port | Register |
| --- | --- |
| 0x50 | Status. Bit 0 a period has gone by, bit 1 it is running, bit 2 it is set to interrupt. |
| 0x51 | Control. Bit 0 run, bit 1 repeat, bit 2 interrupt. |
| 0x52 - 0x54 | The period, in cycles, most significant byte first. |
**The period is in cycles**, because that is what everything else here is counted in: it is
what the cost model counts and what a frame is measured in, so a timer counting anything else
would be a second unit to remember. Twenty four bits reaches from one cycle to sixteen and a
half seconds, and 120 beats a minute sits at 500,000 in the middle of it. There is no
prescaler, because there is no range left for one to buy.
**Starting it loads the period.** Writing the control byte with the run bit already set does
not, so a program that turns interrupts on half way through a period does not silently move
the beat it was keeping.
**With the repeat bit it reloads; without it, it stops** and the status port says so. What is
left over carries into the next period, so a timer asked for 1,000 cycles ticks every 1,000
and not every 1,000 plus however late anybody looked.
**Reading the status is what answers it**: the tick comes down when it is read, and the line
with it. A program that polls is not one that will answer a handler.
```asm
; A sixteenth note at 120 beats a minute, waited for rather than counted.
INIA 0x01
OUTA 0x52
INIA 0xE8
OUTA 0x53
INIA 0x48
OUTA 0x54 ; 0x01E848, which is 125,000
INIA 0x07
OUTA 0x51 ; Run, repeat, interrupt
SIF
WAIT
```
Eight of those is one second, and a machine doing it spends 999,720 of those cycles asleep.
## The Memory Controller: