### Vintage X Bitmap Font "VT100 graphics characters"

In an old "5x9" bitmap font a friend gave me I was puzzled about the chacters in positions 1..31, normally used as control characters.

It turns out these are the "VT100 graphics characters", and the xfonts-utils program ucs2any can optionally put specific Unicode code points in these spots:

##
`/* DEC VT100 graphics characters in the range 1-31 (as expected by
some old xterm versions and a few other applications) */`

#define decmap_size 31 static int decmap[decmap_size] = { 0x25C6, /* BLACK DIAMOND */ 0x2592, /* MEDIUM SHADE */ 0x2409, /* SYMBOL FOR HORIZONTAL TABULATION */ 0x240C, /* SYMBOL FOR FORM FEED */ 0x240D, /* SYMBOL FOR CARRIAGE RETURN */ 0x240A, /* SYMBOL FOR LINE FEED */ 0x00B0, /* DEGREE SIGN */ 0x00B1, /* PLUS-MINUS SIGN */ 0x2424, /* SYMBOL FOR NEWLINE */ 0x240B, /* SYMBOL FOR VERTICAL TABULATION */ 0x2518, /* BOX DRAWINGS LIGHT UP AND LEFT */ 0x2510, /* BOX DRAWINGS LIGHT DOWN AND LEFT */ 0x250C, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ 0x2514, /* BOX DRAWINGS LIGHT UP AND RIGHT */ 0x253C, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ 0x23BA, /* HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ 0x23BB, /* HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ 0x2500, /* BOX DRAWINGS LIGHT HORIZONTAL */ 0x23BC, /* HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ 0x23BD, /* HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ 0x251C, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ 0x2524, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */ 0x2534, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */ 0x252C, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ 0x2502, /* BOX DRAWINGS LIGHT VERTICAL */ 0x2264, /* LESS-THAN OR EQUAL TO */ 0x2265, /* GREATER-THAN OR EQUAL TO */ 0x03C0, /* GREEK SMALL LETTER PI */ 0x2260, /* NOT EQUAL TO */ 0x00A3, /* POUND SIGN */ 0x00B7 /* MIDDLE DOT */

Apparently on a real VT100 these are printed by sending various escape codes that cause them to be mapped at character values 95..126. various escape sequences.

### datetime in local time zone

ruff and flake8 consider that it is a mistake to ever use "naive" datetime objects.

However, this leaves no "obvious" way to do something like convert between local and UTC time using the datetime module!

I was not able to find a way in standard Python, but I was able to find it in dateutil (based on some stackoverflow where the top answer was a wrong answer, purposely not linked)

So: Use `dateutil.tz.gettz()` to get a timezone object that reflects the system's local time zone. This will pass ruff's checks.

>>> localtime = dateutil.tz.gettz() >>> localnow datetime.datetime(2024, 7, 8, 21, 33, 51, 897086, tzinfo=tzfile('/etc/localtime')) >>> localnow.astimezone(datetime.timezone.utc) datetime.datetime(2024, 7, 9, 2, 33, 51, 897086, tzinfo=datetime.timezone.utc)

You can also use a piece of code from the documentation called LocalTimeZone that makes a best effort to implement the local system time zone based on the items available in the "time" module. This has caveats, such as not working properly when a zone historically had different offsets than it does today, and you can't pip install it, you have to copy & paste it.

### Sunflower Butter aka Sun Butter or Sunbutter (plus Chocolate Sun Butter)

These two bread toppings are part of our regular breakfasts and we hate to be without them.

Recipe makes two pint jars: one of regular sun butter and one of chocolate sun butter.

If you're used to nutella, this chocolate sun butter will be a surprise (but a good one, in my opinion)! It has much less sugar. If you like a sweeter product, you can use milk chocolate chips, or add additional refined sugar.

- 3 quarts raw unsalted shelled sunflower seeds
- olive oil as needed (around 2 tablespoons?)
- salt according to taste (from a pinch to a teaspoon/5 grams)
- generous 1/2 cup dark chocolate chips
- up to 1/2 cup cocoa nibs for chunky chocolate sun butter

Special equipment needed:

- "Half sheet" rimmed baking tray (US: 13x18 inches; I don't know how these are called in the rest of the world but that's about 330x450mm)
- Food processor with 6 cup (1.5l) capacity

Directions:

- Preheat oven to 350°F (175°C)
- Place sunflower seeds on tray and place in oven
- Bake for a total of 25-30 minutes according to preference. Stir after 15 minutes
- Allow to cool for 10 minutes, then scoop all seeds into food processsor (or reserve 1/4 cup if you want a chunky sun butter)
- Allow to process in the food processor, regularly stopping to scoop the insides and bottom. At first this will be very powdery or grainy.
- When you feel you aren't making any progress, add olive oil in small increments and continue processing. At some point the mass will turn clumpy and finally become almost like a liquid.
- Process until the desired consistency is achieved. Taste for salt, oil, and texture; but be aware that the food processor has heated the mixture.
- If making chunky sun butter, add the reserved seeds and process briefly
- Scoop out a pint (470ml) of the paste: this is your finished sun butter.
- Add the chocolate chips to the food processor. It should be warm enough to melt the chips. Process long enough to thoroughly mix, scooping the inside at least once to ensure complete mixing.
- If making chunky chocolate sun butter, add the reserved seeds and/or cocoa nibs and process briefly
- Scoop out the remainder. This is your finished chocolate sun butter.

To make just a quart of sun butter, use 2 quarts sunflower seeds.

My food processor doesn't do a great job with just 1 quart of sunflower seeds, so if you want just the chocolate version I recommend using 2 quarts sunflower seeds & double the chocolate ingredeients.

We keep this in the cupboard and eat a batch over the course of a few weeks. It will also refrigerate for longer storage, but it's best to eat at room temperature.

### An efficient pair of polynomials for approximating sincos

In CicuitPython, I was looking for an efficient polynomial approximation of sin(x) and cos(x) over the interval [0:π/2].

Being a simple person, I searched enough documentation to find that numpy was happy to make a polynomial approximation of any table of data:

>>> x = np.linspace(0, np.pi/2, 10000) >>> Polynomial.fit(x, np.cos(x), deg=5) Polynomial([ 0.70710188, -0.55535829, -0.21798633, 0.05707696, 0.01090002, -0.00171961], domain=[0. , 1.57079633], window=[-1., 1.], symbol='x') >>> Polynomial.fit(x, np.sin(x), deg=5) Polynomial([ 0.70710188, 0.55535829, -0.21798633, -0.05707696, 0.01090002, 0.00171961], domain=[0. , 1.57079633], window=[-1., 1.], symbol='x')

But wait, the coefficients are the same, except for some of the signs? What's going on? That's not how sin and cos are related.

The design of numpy's polynomial fit routine has given us an unexpected gift: the original domain of 0 to π/2 has been changed (offset & scaled) to the window -1 to 1. This happens to make the shifted-sin and shifted-cos routines reflect around the line x=0. So it's no surprise that the even coefficients are the same (as (-x)^2k = x^2k for integers k) and the odd coefficients are negated.

This leads to an implementation of sincos that only has to do fewer multiplications than I expected, and will therefore be a bit quicker.

Oh, and the maximum absolute error for this polynomial seems to be about 5e-6, or little enough that it probably can't be noticed when processing 16-bit audio.

C implementation of the algorithm:

Compiled on godbolt with -Os -mcpu=cortex-m4 -mhard-float -fsingle-precision-constant, it gives some very tidy-looking code.

fast_offset_scaled_sincos(float, sincos_result*): vmul.f32 s15, s0, s0 vldr.32 s12, .L2 vmul.f32 s14, s15, s15 vmul.f32 s13, s0, s15 vmul.f32 s14, s14, s12 vldr.32 s12, .L2+4 vfma.f32 s14, s15, s12 vldr.32 s12, .L2+8 vmul.f32 s15, s15, s13 vadd.f32 s14, s14, s12 vldr.32 s12, .L2+12 vmul.f32 s15, s15, s12 vldr.32 s12, .L2+16 vfma.f32 s15, s13, s12 vldr.32 s13, .L2+20 vfma.f32 s15, s0, s13 vadd.f32 s13, s14, s15 vsub.f32 s14, s14, s15 ; function epilogue and table of constants (L2) omitted

Note: More optimal coefficients can come from better algorithms like Remez, as implemented by py-remezfit. The resulting worst error is a bit higher but the average error should be lower.

$ python3 remezfit.py -d single -- "lambda x: np.cos((x+1)*np.pi/4)" -1 1 5 p = array([ 0.7071076 , -0.5553769 , -0.21797441 , 0.05672453 , 0.011838001 , -0.0023185865], dtype=float32) prec = 7.189810e-06

### Leaving my roles in LinuxCNC

It's been a long time since I actively participated in this project and I want to let you all know that I have begun to remove myself from roles in various places including sourceforge & github. I'm in private discussions to ensure this happens without disrupting the project.

To the developers & community: Thank you so much for letting me play a part in this project. It was an important chapter of my life (that started around 20 years ago!) and I wish you all the best for the future. I hope and trust you'll continue to take this project in good directions. I wish you all the best.

### Linux ThinkPad T16 Microphone "Muted" Indicator

This laptop's keyboard has an indicator on the F4 key, which also serves as the mic mute toggle key.

Frustratingly, in Debian 12 ("bookworm") with pipewire audio, the LED doesn't actually follow the mic mute state. This appears to be because pipewire doesn't mute the mic at the hardware level, so setting the corresponding LED's "trigger" to "audio-micmute" does nothing.

I don't know what the proper solution for this is, but I implemented a solution of my own and it seems to work.

First, the LED control file (in my case "/sys/devices/platform/thinkpad_acpi/leds/platform::micmute/brightness") has to be made writable by my user (I don't care about multi-user situations). I did this by making a boot-time cron job as root:

@reboot chown jepler.jepler /sys/devices/platform/thinkpad_acpi/leds/platform::micmute/brightness

Second, I have to run a script that watches the PulseAudio mic mute status and updates the LED. It's shown at the bottom of this blog post. It requires python3 and the pulsectl library, installable via pip.

I start this script in the background at login time. In my case I do this via the ".xsession" script, but you will need to know the correct way to do it in your desktop environment.

Now, when I press the mute key, Fn-F4, the LED's state follows the mute state.

### Notes on using skyui with Skyrim Anniversary Edition from GOG

1. install Lutris 2. enter GOG credentials 3. Install Elder Scrolls Special Edition 4. Install DLC 5. Run skyrim once and quit 6. manually download: - vortex mod from nexusmods - skse64 from https://skse.silverlock.org/ - skyui from https://www.nexusmods.com/skyrimspecialedition/mods/12604 - unofficial patch from nexusmods 7. In lutris, "run exe inside wine prefix", select vortex mod exe (uncheck the "run vortex" option in the last installer step) 8. In lutris, duplicate the skyrim launcher. Call the duplicate "vortex (skyrim se) and set its executable to .../Program Files/Black Tree Gaming Ltd/Vortex/Vortex.exe. Open the new program, and select the first option when prompted 9. On first run, agree to downloading microsoft .net 10. find "skyrim special edition" under unmanged (use "search for a game") and then select its location under My Computer / Drive C / GOG 11. Click to the mods pane. 12. drag & drop the downloaded skse64 and skyui 7z files to vortex 13. change the skyrim launcher path to .../GOG Games/Skyrim Anniversary Edition/skse64_loader.exe 14. Launch skyrim, choosing the first option

### "Letter Boxed" puzzle statistics

You might wonder, for a fully random "letter boxed" puzzle, what proportion are unsolveable?

I modified my solver from the previous post to also generate random puzzles, and analyzed the results of 100,000 runs. For these runs, I used a dictionary from github that claimed to be "the scrabble dictionary", though I have no way of verifying it. It contained 178691 words which I had to convert to lowercase due to the way my search program is implemented.

To the statistics:

- About 37% of puzzles did not result in a solution of up to 12 words
- The most frequent number of letters in a solution was 18; the least was 13 and the most was 38
- The median frequent number of letters in a solution was 19
- The most frequent and median number of words in a solution was 4; the least was 2 and the most was 10
- About 5% of puzzles with a solution had a 2-word solution; about 30% had a 3-word solution.

I haven't played enough official puzzles to know, but I suspect that there are additional constraints on the letters (For example, no Q without U; vowels on at least 3 edges), or puzzles are constrained by having at least one "relatively short" answer, or the puzzle is fully human curated.

Here's the worst puzzle I found, using that dictionary from above. It has a shortest solution of 10 words & 39 characters: fysvzhcplgim

## puzzle answer for fys / vzh / cpl / gim

ghi - ich - hip - phiz - zips - shiv - vivific - chili - ism - mycNot all of those seem like words to me, but that is how scrabble dictionaries are.

### NYT "Letter Boxed" solver in C++

### Conservation of Experience

### Xerox 820 & CP/M

### Welcome to the Polity

### One way to fix pip externally-managed-environment error in debian

### A quick example of transforming Python with libcst

### Local coordinate systems in OpenSCAD

### Recent keyboard deeds

All older entries

Website Copyright © 2004-2024 Jeff Epler