# wavering is a ring of bits of audio data allocated during system init

cell p0x61default	# system setting for bits 3-7
cell p0x61		# vary bit 2 
cell wavebit		# bit of ringed data on deck


when not zero nobeepchange

	= @ p0x61 to A
	= 0x61 to D
	XOR 2 to @ p0x61
	send byte
	= A to @ p0x61	

L nobeepchange




echo "

How fast can you send audio to the PC beep speaker? The fastest on-off
switch would be the cassette/speaker switch between the PIT0-timer2 and
the speaker. The gate for that is the last thing on the computer side
before the speaker's amp. The control of that gate is via bit 1 of the
write side of port 0x61. That's part of the keyboard controller (since the
XT). There's a sweet and tender mercy here. All the other bits in that
port byte will remain static as set at bootup. That means there's no
read-modify-write requirement. You know what the whole byte will be
besides the 1 bit, so it's a constant literal besides the audio data bit,
once the machine is booted.

Moving back from the gate, we have to get the PIT0/2 timer stuck on high.
Then p0x61/1=1 is high, 0 is low, and we can send bits to the speaker as
fast as the rest of the system can muster. The speaker end of the problem
has a big effect on how fast that is, and sending one of two possible byte
values to a port is snappy enough.  

As much computation as possible should be done in the top end of the
metabeeper. That's stock systems design stuff, but I really really mean
it. The bottom end is the NMI reflex that sends the current bit to the
port. The top end is the code that populates and updates the wayvring
bitbuffer. Operations in the NMI reflex come with interrupt handler
overhead, not the least of which is uninterruptibility. We mant to leave
some CPU for the rest of the world.

All the bit-transposition annoyances of getting the appropriate bit value
into bit position 1 (second bit) of the byte can be placed in the top end
side increasing the buffer size by a factor of 8 and storing the entire
p0x61 byte for each audio bit. On the buffer-filler side maunfacturing
those bytes can take full advantage of the x86 looping instructions. The
8:1 size hit is a hot bargain with today's RAM prices.

What must remain on the NMI side is rincrementing the NMI side's pointer
into the buffer, because that is the NMI side's timescale.

Consider the sending of the one variable bit (in an otherwise constant
byte) to p0x61 in an NMI reflex. You only have to send it when it changes.
The polarity of the active bit is going to be changing frequently,
however, so that's not much of a win, if at all. At 1.19 megabits per
second, alternating bits is amplitude control, so expect frequent polarity
changes. The send to the port happens at CPU speed as far as the CPU is
concerned, so send isn't any more expensive than about two mundane
instructions like =.

The wayvring wants to be allocated in memory in a way that requires the
least computation to use it. Rings in general sould be an even power of 2
in size for masking off the increment into a ringcrement. This is stock
osimplay, and there's a word in osimplay to compute the appropriate power
of 2 for an arbitrary number.

Masking is fast, and eliminates bounds-checking the ringcrement. Masking
can be done with a constant literal mask if we know the wavering size. Use
a big standard wavering size and save the load of the mask value.

Masking gives you a low-order value to be combined with the start address
of the buffer. That's an add, or can be simple Boolean stuff if the buffer
is allocated buffer-size-aligned. The add isn't any worse, so size-aligned
allocation doesn't look advantageous. The rincrement operation has to be
on JUST the ring portion of the bit address, as far as I can see. This is
reflex-side stuff, unfortunately.

Let's look at size-aligning the wayvring again. Lets look at the bitbyte
address from another angle. Let's say our buffer is 64k bytes, and we keep
the pointer as an absolute pointer. The first thing we do on reflex is
load and increment the pointer. Once every 64k reflexes the part of the
size-aligned pointer that is the offset to the buffer as a whole gets
incremented, and is wrong. That is, we run off the end of the buffer. When
we do, the buffer portion of the pointer is 0. Most convenient. All we
have to do is overwrite the pointer with the constant literal address of 0
in the buffer. Once on each trip around the ring. We have to check for
runaround every time though, BUT, if the buffer is size-aligned, we just
ANDtest with onbits for the buffer portion of the entire pointer. One
ANDtest and one branch-not-taken, and a bit of extra baggage 1 time in
64k reflexes.

That's so nice I'm going to set the low half-meg of the second meg of
Ha3sm address-space to reserved-for-size-aligned-buffers. That could be
handy for gigabit Ethernet and algorithmic radio signals. OK, I'm off to
twiddle boot/memmap or whatever it is for a minute.

OK, NMI reflex uses ring64a. Data0 of ring64a is ring64a+smno, which is a
64k-aligned number. Thus when bitringbyte AND (64k-1) is zero (which is
osimplay maskdual), reset bitringbyte to ring64a+smno and keep on rockin.


And play your PC just like aringin a bell.

" > /dev/null

