A reckoner seed: buzzer, a tune from the entry line — machines/seeds/seedbuzzer.shoddy
> 1 "T140 O5 CDEC CDEC EFG EFG" BUZZPLAY
and the mill (the running program) plays it.
Not a resource, which makes this the odd one out among
the seeds that reach a device. (A seed is the file that bridges one
machine's words into the reckoner, Shoddy's calculator.) A window and a
file are resources: opened, held and shut. A sound is fired.
SoundQueue takes a channel number — one of
eight channels that always exist — and returns as soon as the notes
are queued. So there is nothing to bind, nothing to name and nothing to
CLOSE. The resource table does not come into it.
What did have to change, and where. buzzer's MML parser could abort — end the whole session — five different ways, and its note-name reader two more. (MML is Music Macro Language, a small text notation for tunes.) A bridged word may not end the session. And there was no guarded builtin to build on, because the parse is not in the runtime — it is in the machine.
So buzzer grew the tree's first
machine-level guarded twins. A guarded word reports a failure
instead of aborting. TryMmlNotes and TryNoteMidi
answer a Result, a value that is either the answer or an
error. MmlNotes and NoteMidi are those answers
with the Err turned back into the abort it always was.
TryReadFile and ReadFile have that shape in the
runtime; this brings it up to a machine for the first time.
That was worth rewriting a working parser for. The alternative was a
validator walking the string a second time. That is two
statements of the same rules, and two things to drift apart. Threading a
Result through the recursion leaves exactly one. The new
parser reads longer, and all of the extra is unwrapping. That is what
carrying a failure through a recursive descent — a parser built from
functions that call each other — costs in a language with no
exceptions.
And one runtime addition, for the one sound fault
nothing could check in advance. SoundQueue refuses more than
five minutes queued ahead on a channel. Until SoundQueued
existed, there was no way to ask what was already in front of you. A word
that measured its own score and still could not see the queue would be
guessing. The abort's own message says to feed long scores
incrementally, which is the one thing that was impossible without
it.
A score is measured before it is committed.
BUZZPLAY parses the score, sums the note durations, and adds
what is already queued. If the total is too long it refuses the whole
thing before a single note goes out, because a tune half queued is a tune
that has already started. BUZZLENGTH is the same measurement
on its own, so a session can check a score without playing it.
The silence is not an error. With no audio device,
every sound word does nothing rather than failing. So
BUZZPLAY answers the same at a prompt with speakers and at
one without. That is buzzer's own contract and
this seed keeps it: a session is told when its music is wrong,
and never when the machine it is running on has no sound.
> "A4" BUZZFREQ
x: 440
> "T120 CDEF" BUZZLENGTH
x: 2000 ' four quarter notes, two seconds
> 1 "T140 O5 CDEC CDEC EFG EFG" BUZZPLAY
> 1 "sine" BUZZWAVE
> 1 0.5 BUZZGAIN
> 1 BUZZQUEUED
x: 5820 ' still playing
> 1 "CDEZ" BUZZPLAY
BUZZPLAY: MML: UNEXPECTED 'Z' AT POSITION 4
> 1 BUZZSTOP
The MML here is the subset GW-BASIC used (an early home-computer language):
A–G are notes.# sharpens a note and - flattens it.O sets the octave and T the tempo.L sets the default length.P or R is a rest.< and > step the octave.| Word | Description |
|---|---|
| BUZZPLAY ( ch mml -- ) | Play an MML tune on channel 1 to 8. Returns as soon as the tune is queued. |
| BUZZTONE ( ch hz ms -- ) | Queue one tone of hz hertz for ms milliseconds. A frequency of 0 is a rest. |
| BUZZNOTE ( ch name ms -- ) | Queue one named note — "C4", "F#3", "Bb5" — for ms milliseconds. C4 is middle C. |
| BUZZFREQ ( name -- hz ) | The frequency of a named note in hertz, and nothing is played. "A4" is 440. |
| BUZZLENGTH ( mml -- ms ) | How long that tune would take, without playing a note of it. The way to check a score before committing it. |
| BUZZQUEUED ( ch -- ms ) | How far ahead that channel is already queued, and 0 once it has drained. BUZZLENGTH measures a tune; this measures what is in front of it. |
| BUZZSTOP ( ch -- ) | Stop that channel at once and throw away whatever it had queued. |
| BUZZGAIN ( ch v -- ) | How loud that channel is, from 0 to 1. Sticky until set again. |
| BUZZWAVE ( ch name -- ) | The channel's waveform: "square", "triangle" or "sine". Sticky until set again. |
| User | How | |
|---|---|---|
| halifax | The calculator's sound words — a tune typed at the prompt, on a machine with speakers or without. |
A mill claims this seed by folding RckSeedBuzzer over its
reckoner state, which is all halifax does.
| Machine | Why | |
|---|---|---|
| buzzer | The domain this seed bridges — and, for it, the machine grew TryMmlNotes and TryNoteMidi so that a malformed score could be reported rather than fatal. | |
| cuttle | The Cell type the arguments cross the bridge as. | |
| reckoner | RckReg and the argument readers every registered word is built from. | |
| seq | Folding the note list to a duration, and Pair for the multi-argument words. |