## Crickets in February

It’s mid-February in Toronto: -10 °C and snowy. The memory of chirping summer fields is dim. But in my heart there is always a cricket-loud meadow.

Short of moving somewhere warmer, I’m going to have to make my own midwinter crickets. I have micro-controllers and tiny speakers: how hard can this be?

I could have merely made these beep away at a fixed rate, but I know that real crickets tend to chirp faster as the day grows warmer. This relationship is frequently referred to as Dolbear’s law. The American inventor Amos Dolbear published his observation (without data or species identification) in The American Naturalist in 1897: The Cricket as a Thermometer

When emulating crickets I’m less interested in the rate of chirps per minute, but rather in the period between chirps. I could also care entirely less about barbarian units, so I reformulated it in °C (t) and milliseconds (p):

t = ⅑ × (40 + 75000 ÷ p)

Since I know that the micro-controller has an internal temperature sensor, I’m particularly interested in the inverse relationship:

p = 15000 ÷ (9 * t ÷ 5 – 8)

I can check this against one of Dolbear’s observations for 70°F (= 21⅑ °C, or 190/9) and 120 chirps / minute (= 2 Hz, or a period of 500 ms):

p = 15000 ÷ (9 * t ÷ 5 – 8)
= 15000 ÷ (9 * (190 ÷ 9) ÷ 5 – 8)
= 15000 ÷ (190 ÷ 5 – 8)
= 15000 ÷ 30
= 500

Now I’ve got the timing worked out, how about the chirp sound. From a couple of recordings of cricket meadows I’ve made over the years, I observed:

1. The total duration of a chirp is about ⅛ s
2. A chirp is made up of four distinct events:
• a quieter short tone;
• a longer louder tone of a fractionally higher pitch;
• the same longer louder tone repeated;
• the first short tone repeated
3. There is a very short silence between each tone
4. Each cricket appears to chirp at roughly the same pitch: some slightly lower, some slightly higher
5. The pitch of the tones is in the range 4500–5000 Hz: around D8 on the music scale

I didn’t attempt to model the actual stridulating mechanism of a particular species of cricket. I made what sounded sort of right to me. Hey, if Amos Dolbear could make stuff up and get it accepted as a “law”, I can at least get away with pulse width modulation and tiny tinny speakers …

This is the profile I came up with:

• 21 ms of 4568 Hz at 25% duty cycle
• 7 ms of silence
• 28 ms of 4824 Hz at 50% duty cycle
• 7 ms of silence
• 28 ms of 4824 Hz at 50% duty cycle
• 7 ms of silence
• 21 ms of 4568 Hz at 25% duty cycle
• 7 ms of silence

That’s a total of 126 ms, or ⅛ish seconds. In the code I made each instance play at a randomly-selected relative pitch of ±200 Hz on the above numbers.

For the speaker, I have a bunch of cheap PC motherboard beepers. They have a Dupont header that spans four pins on a Raspberry Pi Pico header, so if you put one on the ground pin at pin 23, the output will be connected to pin 26, aka GPIO 20:

So — finally — here’s the MicroPython code:

```# cricket thermometer simulator - scruss, 2024-02
# uses a buzzer on GPIO 20 to make cricket(ish) noises
# MicroPython - for Raspberry Pi Pico
# -*- coding: utf-8 -*-

from machine import Pin, PWM, ADC, freq
from time import sleep_ms, ticks_ms, ticks_diff
from random import seed, randrange

freq(125000000)  # use default CPU freq
pwm_out = PWM(Pin(20), freq=10, duty_u16=0)  # can't do freq=0
led = Pin("LED", Pin.OUT)
TOO_COLD = 10.0  # crickets don't chirp below 10 °C (allegedly)
temps = []  # for smoothing out temperature sensor noise
personal_freq_delta = randrange(400) - 199  # different pitch every time
chirp_data = [
# there is a cadence=1 silence after each of these
[3, 16384, 4568 + personal_freq_delta],
[4, 32768, 4824 + personal_freq_delta],
[4, 32768, 4824 + personal_freq_delta],
[3, 16384, 4568 + personal_freq_delta],
]
cadence_ms = 7  # length multiplier for playback

def chirp_period_ms(t_c):
# for a given temperature t_c (in °C), returns the
# estimated cricket chirp period in milliseconds.
#
# Based on
# Dolbear, Amos (1897). "The cricket as a thermometer".
#   The American Naturalist. 31 (371): 970–971. doi:10.1086/276739
#
# The inverse function is:
#     t_c = (75000 / chirp_period_ms + 40) / 9
return int(15000 / (9 * t_c / 5 - 8))

# see pico-micropython-examples / adc / temperature.py
return (
27
)

def chirp(pwm_channel):
for peep in chirp_data:
pwm_channel.freq(peep[2])
pwm_channel.duty_u16(peep[1])
# short silence
pwm_channel.duty_u16(0)
pwm_channel.freq(10)

led.value(0)  # led off at start; blinks if chirping
### Start: pause a random amount (less than 2 s) before starting
sleep_ms(randrange(2000))

while True:
loop_start_ms = ticks_ms()
sleep_ms(5)  # tiny delay to stop the main loop from thrashing
temps.append(internal_temperature(sensor_temp))
if len(temps) > 5:
temps = temps[1:]
avg_temp = sum(temps) / len(temps)
if avg_temp >= TOO_COLD:
led.value(1)
loop_period_ms = chirp_period_ms(avg_temp)
chirp(pwm_out)
led.value(0)
loop_elapsed_ms = ticks_diff(ticks_ms(), loop_start_ms)
sleep_ms(loop_period_ms - loop_elapsed_ms)
```

There are a few more details in the code that I haven’t covered here:

1. The program pauses for a short random time on starting. This is to ensure that if you power up a bunch of these at the same time, they don’t start exactly synchronized
2. The Raspberry Pi Pico’s temperature sensor can be slightly noisy, so the chirping frequency is based on the average of (up to) the last five readings
3. There’s no chirping below 10 °C, because Amos Dolbear said so
4. The built-in LED also flashes if the board is chirping. It doesn’t mimic the speaker’s PWM cadence, though.

Before I show you the next video, I need to say: no real crickets were harmed in the making of this post. I took the bucket outside (roughly -5 °C) and the “crickets” stopped chirping as they cooled down. Don’t worry, they started back up chirping again when I took them inside.

## The glorious futility of generating NAPLPS in 2023

NAPLPS — an almost-forgotten videotex vector graphics format with a regrettable pronunciation (/nap-lips/, no really) — was really hard to create. Back in the early days when it was a worthwhile Canadian initiative called Telidon (see Inter/Access’s exhibit Remember Tomorrow: A Telidon Story) it required a custom video workstation costing \$\$\$\$\$\$. It got cheaper by the time the 1990s rolled round, but it was never easy and so interest waned.

I don’t claim what I made is particularly interesting:

but even decoding the tutorial and standards material was hard. NAPLPS made heavy use of bitfields interleaved and packed into 7 and 8-bit characters. It was kind of a clever idea (lower resolution data could be packed into fewer bytes) but the implementation is quite unpleasant.

A few of the references/tools/resources I relied on:

Here’s the fragment of code I wrote to generate the NAPLPS:

```#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# draw a disappointing maple leaf in NAPLPS - scruss, 2023-09

# stylized maple leaf polygon, quite similar to
# the coordinates used in the Canadian flag ...
maple = [
[62, 2],
[62, 35],
[94, 31],
[91, 41],
[122, 66],
[113, 70],
[119, 90],
[100, 86],
[97, 96],
[77, 74],
[85, 114],
[73, 108],
[62, 130],
[51, 108],
[39, 114],
[47, 74],
[27, 96],
[24, 86],
[5, 90],
[11, 70],
[2, 66],
[33, 41],
[30, 31],
[62, 35],
]

def colour(r, g, b):
# r, g and b are limited to the range 0-3
return chr(0o74) + chr(
64
+ ((g & 2) << 4)
+ ((r & 2) << 3)
+ ((b & 2) << 2)
+ ((g & 1) << 2)
+ ((r & 1) << 1)
+ (b & 1)
)

def coord(x, y):
# if you stick with 256 x 192 integer coordinates this should be okay
xsign = 0
ysign = 0
if x < 0:
xsign = 1
x = x * -1
x = ((x ^ 255) + 1) & 255
if y < 0:
ysign = 1
y = y * -1
y = ((y ^ 255) + 1) & 255
return (
chr(
64
+ (xsign << 5)
+ ((x & 0xC0) >> 3)
+ (ysign << 2)
+ ((y & 0xC0) >> 6)
)
+ chr(64 + ((x & 0x38)) + ((y & 0x38) >> 3))
+ chr(64 + ((x & 7) << 3) + (y & 7))
)

f = open("maple.nap", "w")
f.write(chr(0x18) + chr(0x1B))  # preamble

f.write(chr(0o16))  # SO: into graphics mode

f.write(colour(0, 0, 0))  # black
f.write(chr(0o40) + chr(0o120))  # clear screen to current colour

f.write(colour(3, 0, 0))  # red

# *** STALK ***
f.write(
chr(0o44) + coord(maple[0][0], maple[0][1])
)  # point set absolute
f.write(
chr(0o51)
+ coord(maple[1][0] - maple[0][0], maple[1][1] - maple[0][1])
)  # line relative

# *** LEAF ***
f.write(
chr(0o67) + coord(maple[1][0], maple[1][1])
)  # set polygon filled
# append all the relative leaf vertices
for i in range(2, len(maple)):
f.write(
coord(
maple[i][0] - maple[i - 1][0], maple[i][1] - maple[i - 1][1]
)
)

f.write(chr(0x0F) + chr(0x1A))  # postamble
f.close()
```

There are a couple of perhaps useful routines in there:

1. `colour(r, g, b)` spits out the code for two bits per component RGB. Inputs are limited to the range 0–3 without error checking
2. `coord(x, y)` converts integer coordinates to a NAPLPS output stream. Best limited to a 256 × 192 size. Will also work with positive/negative relative coordinates.

Here’s the generated file:

## Applied Futility: Re-creating RAND’s â€˜A Million Random Digitsâ€™

Sometimes, one must obey the inscrutable exhortations of one’s soul and travel deep into the inexplicable. The planet Why? has been left far behind, the chatter of its querulous denizens nothing more than a faint wisp of static. Where I’m going, pure patternlessness is all there is.

In 1955, military-industrial complex stalwarts RAND Corporation published a huge book of just pages and pages of â€¦ digits. The digits were deliberately as random as possible, and were intended to help the fledgling practice of data science carry out truly random simulations. The book was called â€œA Million Random Digits with 100,000 Normal Deviatesâ€. It was also made available on punched cards, with 50 digits to a card adding up to ten boxes of 2000 cards. A single box of punched cards was about 370 Ã— 200 Ã— 95 mm and weighed roughly 5 kg, so these digits had heft.

I thought it might be fun (or perhaps fun?: pronounced with a rising, questioning tone to stress the might-ness of any enjoyment arising) to dig into how RAND carried out this work, create some electronics to produce a similar random stream, and see how my random digits compare for randomness with RAND’s. For a final trick, I might even typeset the whole giant table into a book that no-one wants.

As I research and progress with this project, I’ll add in links here

I must stress: there is no reason for me to do this. A \$5 micro-controller board can generate tens to hundreds of thousands of truly random digits per second. Any results I produce will have no use beyond my own amusement.

A Million Random Digits with 100,000 Normal Deviates is Â© Copyright 2001 RAND. Apart from a couple of page images and quotations from supporting material, none of that work is reproduced here. RAND does not support or endorse my futile efforts in any way.

## importing Applesoft BASIC programs on the Apple IIe

Just what no-one has needed since about 1979 or so â€¦

BASIC on the Apple II has no easy way to import text as a program. When you LOAD a file, it must be in Apple’s tokenized format. While Apple DOS has the EXEC facility to run script files as if they were typed from the keyboard, it’s very picky about the file format:

1. There must be a carriage return character (CR, ASCII 13) before the first line
2. All line numbers must have an extra space before and after them
3. All tokens must be in upper case
4. All tokens (keywords and functions) must have a space after them.

The right way to do this conversion would be to write a tokenizer that spits out the correct binary file. But you can (kinda) fudge it with this shell command, operating from BASIC source PROG.BAS:

`sed 's/^[0-9][0-9]*/& /;s/^/ /;1s/^/\n/;s/\$/ /;s/[:()]/ & /g;' PROG.BAS | tr '\n' '\r' | ac.sh -p EG.dsk PROG T`

ac.sh is the command line version of AppleCommander, and the file EG.dsk referred to above is an Apple DOS 3.3 image created with

`ac.sh -dos140 EG.dsk`

It still needs work, as there are functions that will mess this up, and Applesoft’s parser makes a mess of code like IF A THEN â€¦, turning it into IF AT HEN â€¦.

So if I wanted to import the following futile program:

```10 REM A FUTILE PROGRAM BY SCRUSS
20 HOME
30 FOR X=1 TO 20
40 PRINT SPC(X);"FUTILE"
50 NEXT X```

Run through the script (but before EOL conversion) it would look like this:

``` 10  REM A FUTILE PROGRAM BY SCRUSS
20  HOME
30  FOR X=1 TO 20
40  PRINT SPC ( X ) ;"FUTILE"
50  NEXT X```

Make a disk and put the modified program text on it:

```ac.sh -dos140 futile.dsk
sed 's/^[0-9][0-9]*/& /;s/^/ /;1s/^/\n/;s/\$/ /;s/[:()]/ & /g;' futile.bas | tr '\n' '\r' | ac.sh -p futile.dsk FUT T```

Load the disk into your Apple II, clear out the init program, and import the code with `EXEC FUT`:

If all you get is ] cursors printed and no syntax errors, then something might be working. List it:

Run it:

Disk image: futile-AppleII-dsk.zip, containing:

```\$ ac.sh -l futile.dsk

DISK VOLUME #254
T 002 FUT
A 002 FUTILE
DOS 3.3 format; 134,144 bytes free; 9,216 bytes used.```