Friday, September 12, 2025

getting a usb qwerty to be a midi keyboard without a computer

samd2695 + esp32s3

this m5 stack midi module

akai mpk play has built in sounds and a speaker but wasnt a fan. you could get a qy100 or some other old midi module on ebay or a pawn shop. An mt-32, soundcanvas, maybe a proteus. but now for 30$ on ali you can get one of these

Wednesday, September 10, 2025

Tuesday, September 9, 2025

how to convert mkv to mp4

so you've recorded a video on linux with guvcview and now you want to play it/edit it on an iphone,
heres a one liner you can use with ffmpeg to convert it to mp4:


for f in *.mkv; do ffmpeg -i "$f" -c:v libx264 -profile:v high -level 4.1 -c:a aac -movflags +faststart "${f%.mkv}.mp4"; done

Tuesday, September 2, 2025

build your own arduino drum brain

this kit is cool

beam forming

pretty cool these days to be able to dip your toes into medical ultrasound technology using cheaply available microcontrollers and sensors.
diy sonar scanner

Saturday, August 30, 2025

adding more gpio to an arduino nano/ raspberry pi

the mcp23017

the mcp3008 for adc good for adding pots

picoIO64
22$ some soldering required. gives you 64 gpio

modmypi mcp2308 pi zero hat adds 16 gpio 14.95$

Thursday, August 21, 2025

how to make pcbs with kicad

kicad tutorial one of the best. straight to the point

Tuesday, August 19, 2025

my sunvox shortcuts

reposting this because I put down sunvox for a while and had to relearn it.

  • cyclic shift is #1 the most important shortcut to add.
  • being able to move between patterns is another one.
  • shift space to play from line
  • doing chords with ctrl g b i
  • ctrl p paste evenly
  • ctrl t select track

my sunvox shortcuts

Friday, August 15, 2025

remaping the f310 to send midi with watchdog for auto reload


#!/usr/bin/env python3
import evdev
import rtmidi
from evdev import ecodes
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import os
import sys

DEVICE_PATH = '/dev/input/by-id/usb-Logitech_Logitech_Dual_Action_DF5CB332-event-joystick'

# ----------------------------
# Corrected human-readable button mappings
# ----------------------------
buttons = {
    'A': 61,    # physical A triggers MIDI note B
    'B': 62,    # physical B triggers MIDI note X
    'X': 60,    # physical X triggers MIDI note A
    'Y': 63,
    'L': 64,
    'R': 65,
    'Select': 66,
    'Start': 67,
    'Up': 68,
    'Down': 69,
    'Left': 70,
    'Right': 71
}

evdev_button_map = {
    288: 'A',
    289: 'B',
    290: 'X',
    291: 'Y',
    292: 'L',
    293: 'R',
    296: 'Select',
    297: 'Start',
    294: 'Up',
    295: 'Down',
    298: 'Left',
    299: 'Right'
}

# ----------------------------
# Axis mappings
# ----------------------------
axes = {
    'LX':  (10, -32768, 32767),
    'LY':  (11, -32768, 32767),
    'LT':  (12, 0, 255),
    'RT':  (13, 0, 255),
    'RX':  (14, -32768, 32767),
    'RY':  (15, -32768, 32767)
}

evdev_axis_map = {0: 'LX', 1: 'LY', 2: 'LT', 5: 'RT', 3: 'RX', 4: 'RY'}

# ----------------------------
# MIDI setup
# ----------------------------
midi_out = rtmidi.MidiOut()
midi_out.open_virtual_port("F310 MIDI")

def scale(value, min_val, max_val):
    return int((value - min_val) / (max_val - min_val) * 127)

# ----------------------------
# Main gamepad loop
# ----------------------------
def run():
    gamepad = evdev.InputDevice(DEVICE_PATH)
    print(f"Listening on {gamepad.name} ({gamepad.path})")
    for event in gamepad.read_loop():
        if event.type == ecodes.EV_KEY and event.code in evdev_button_map:
            label = evdev_button_map[event.code]
            note = buttons[label]
            if event.value == 1:
                midi_out.send_message([0x90, note, 112])
                print(f"Button pressed: {label}")
            elif event.value == 0:
                midi_out.send_message([0x80, note, 0])
        elif event.type == ecodes.EV_ABS and event.code in evdev_axis_map:
            label = evdev_axis_map[event.code]
            cc, min_val, max_val = axes[label]
            val = scale(event.value, min_val, max_val)
            midi_out.send_message([0xB0, cc, val])

# ----------------------------
# Watchdog for hot-reload
# ----------------------------
class ReloadHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.src_path.endswith(os.path.basename(__file__)):
            print("Script changed. Restarting...")
            os.execv(sys.executable, ['python3'] + sys.argv)

observer = Observer()
observer.schedule(ReloadHandler(), path='.', recursive=False)
observer.start()

# ----------------------------
# Run forever
# ----------------------------
try:
    while True:
        run()
except KeyboardInterrupt:
    observer.stop()
observer.join()


Thursday, August 14, 2025

1 liner gpg encryption + qr

gpg - c creates, gpg - d decodes
this uses python qrcode (pip install it if you dont have it)


read -p "Input GPG file: " infile; read -p "Output PNG file: " outfile; python3 -c "import qrcode, base64; f=open('$infile','rb'); data=base64.b64encode(f.read()).decode(); f.close(); qrcode.make(data).save('$outfile')"

this passes the input of the qr as input and decodes the gpg (requires pyzbar. opencv is good to have also


read -p "QR PNG file: " qrfile; read -p "Output file: " outfile; python3 -c "import cv2, pyzbar.pyzbar as pyzbar, base64, sys; img=cv2.imread('$qrfile'); data=pyzbar.decode(img)[0].data; sys.stdout.buffer.write(base64.b64decode(data))" | gpg -d > "$outfile"

Saturday, August 9, 2025

tgpt ios push notifications from anywhere with pi connect

This script lets you ask tgpt a question and it will send the response as notification to your phone
using the ios app ntfy.sh
you can remotely trigger it outside your local network using raspberry pi connect

create this bash alias on your pi
alias ntfygpt='f(){ curl -d "$(tgpt -q "$*")" https://ntfy.sh/YOURTOPICGOESHERE; }; f'

make sure YOURTOPICGOES HERE is replaced on your phone, thats where it will be sent to

Friday, August 8, 2025

Udev Rules and Systemd Service for Auto Running python scripts on USB insert / removal

How to Run .py When usb is plugged/unplugged

1. /etc/udev/rules.d/99-gamepad.rules - CREATED

# Start service when F310 is plugged in
ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c216", KERNEL=="event*", TAG+="systemd", ENV{SYSTEMD_WANTS}="macros@%k.service"

# Stop service when F310 is unplugged
ACTION=="remove", SUBSYSTEM=="input", ENV{ID_MODEL}=="Logitech_Dual_Action", KERNEL=="event*", RUN+="/bin/systemctl stop macros@%k.service"

Purpose: Detects when the Logitech F310 gamepad (product ID c216) is plugged in or unplugged and triggers systemd service actions.

2. /etc/systemd/system/macros@.service - CREATED

[Unit]
Description=Logitech F310 Macros for %i
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/mint22/macros.py
Restart=on-failure
User=mint22
Group=mint22
Environment=HOME=/home/mint22
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Purpose: Systemd template service that runs the Python script when triggered by udev. The @ makes it a template service that can be instantiated with device names (like macros@event13.service).

3. /home/mint22/macros.py - EXISTING (permissions updated)

Changes made:

  • Ensured executable permissions: chmod +x /home/mint22/macros.py
  • Added user to input group: sudo usermod -a -G input mint22

Purpose: Your existing Python script that contains the gamepad macro functionality.

Commands Run to Apply Changes:

# Reload udev rules to recognize the new gamepad detection rule
sudo udevadm control --reload-rules

# Reload systemd to recognize the new service template
sudo systemctl daemon-reload

# Set proper permissions
chmod +x /home/mint22/macros.py
sudo usermod -a -G input mint22

How It Works:

  1. When F310 is plugged in → udev detects it → starts macros@eventX.service → runs macros.py
  2. When F310 is unplugged → udev detects removal → stops the service → terminates macros.py

The system automatically handles starting and stopping the script based on the physical presence of the gamepad.

prettify json from cli with jq and this bash alias


alias prettifyjson='f(){ jq . "$1" > tmp && mv tmp "$1"; }; f'
source ~/.bashrc

Thursday, August 7, 2025

Using Logitech FCB310 Gamepad as a Macropad on Raspberry Pi 4

This tutorial shows how to use your Logitech FCB310 USB gamepad as a macropad on a Raspberry Pi 4, with automatic startup and hotplug detection. When plugged in, your gamepad buttons will trigger keyboard macros on the Pi, no manual script launching needed.


Requirements

  • Raspberry Pi 4 with Raspberry Pi OS (desktop)
  • Logitech FCB310 USB Gamepad
  • Internet connection for installing packages

Step 1: Install Required Software

Open a terminal and run:

sudo apt update
sudo apt install python3-pip xdotool
pip3 install inputs pyudev watchdog
  • xdotool: simulates keyboard keypresses
  • inputs: reads gamepad events
  • pyudev: detects USB device plug/unplug
  • watchdog: monitors macro config file changes

Step 2: Prepare Macro Configuration File

Create a CSV file to map gamepad buttons to keyboard shortcuts.

Example file: /home/pi/macropad/macros.csv

BTN_A,ctrl+alt+t
BTN_B,ctrl+w
  • First column: gamepad button code (e.g., BTN_A)
  • Second column: key combo for xdotool (e.g., ctrl+alt+t)

Step 3: Create the Macro Script

Create a folder and the Python script:

mkdir -p ~/macropad
vim ~/macropad/gamepad_macro.py

Paste this full script into it:

import csv
import subprocess
from inputs import get_gamepad
import pyudev
import threading
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time

mapping = {}

def load_macros():
    global mapping
    mapping = {}
    try:
        with open("/home/pi/macropad/macros.csv", newline='') as csvfile:
            reader = csv.reader(csvfile)
            for row in reader:
                if len(row) >= 2:
                    btn = row[0].strip()
                    keys = row[1].strip()
                    mapping[btn] = [keys]
        print("Macros reloaded:", mapping)
    except Exception as e:
        print("Failed to load macros:", e)

def send_macro(keys):
    for combo in keys:
        subprocess.run(["xdotool", "key", combo])

def listen_gamepad(stop_event):
    while not stop_event.is_set():
        try:
            events = get_gamepad()
            for event in events:
                if event.ev_type == "Key" and event.state == 1:
                    if event.code in mapping:
                        send_macro(mapping[event.code])
        except Exception:
            pass

class ConfigChangeHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.src_path.endswith("macros.csv"):
            print("Config file changed, reloading macros")
            load_macros()

def device_event(observer, device):
    global gamepad_thread, stop_event
    if device.action == "add" and "event" in device.device_node:
        print("Gamepad connected, starting listener")
        load_macros()
        if gamepad_thread and gamepad_thread.is_alive():
            stop_event.set()
            gamepad_thread.join()
        stop_event.clear()
        gamepad_thread = threading.Thread(target=listen_gamepad, args=(stop_event,), daemon=True)
        gamepad_thread.start()
    elif device.action == "remove":
        print("Gamepad disconnected")
        stop_event.set()
        if gamepad_thread:
            gamepad_thread.join()

if __name__ == "__main__":
    stop_event = threading.Event()
    gamepad_thread = None

    context = pyudev.Context()
    monitor = pyudev.Monitor.from_netlink(context)
    monitor.filter_by('input')

    observer = pyudev.MonitorObserver(monitor, device_event)
    observer.start()

    # Watch macros.csv for changes
    config_handler = ConfigChangeHandler()
    config_observer = Observer()
    config_observer.schedule(config_handler, path="/home/pi/macropad/", recursive=False)
    config_observer.start()

    print("Waiting for gamepad to connect...")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        stop_event.set()
        if gamepad_thread:
            gamepad_thread.join()
        config_observer.stop()
        config_observer.join()

Step 4: Make Script Executable

chmod +x ~/macropad/gamepad_macro.py

Step 5: Setup Autostart with systemd

Create a service file:

sudo vim /etc/systemd/system/macropad.service

Add:

[Unit]
Description=Gamepad Macro Pad Service
After=graphical.target

[Service]
ExecStart=/usr/bin/python3 /home/pi/macropad/gamepad_macro.py
Restart=always
User=pi

[Install]
WantedBy=graphical.target

Enable and start service:

sudo systemctl enable macropad.service
sudo systemctl start macropad.service

Step 6: Reboot and Test

Reboot your Pi:

sudo reboot
  • Plug in your FCB310 gamepad.
  • Press buttons mapped in macros.csv.
  • Macros should trigger automatically as keyboard inputs.

Customization

  • Edit /home/pi/macropad/macros.csv anytime.
  • The script auto-reloads macros on file changes.
  • Add/remove mappings without restarting.

Troubleshooting

  • Ensure your gamepad device shows up in /dev/input/ (use evtest to confirm).
  • Adjust key combos to fit your needs (see xdotool docs).
  • Check logs with journalctl -u macropad.service -f.

Tuesday, July 29, 2025

wpa_supplicant no longer works to setup wifi on boot

nmtui worked for me to connect my rav filehub
I'm using it so I can run a server with music on a raspberry pi that I can connect to from the ios files app
It's cool because I can use Tau DJ without worrying about running out of internal storage space

Tuesday, July 22, 2025

Kwanzaa Fonts

don't sleep on the kwanzaa Fonts

the google fonts link
  • Monoton
  • Bungee Hairline
  • Sankofa Display

Monday, July 21, 2025

fixing smb read only from pi to ios

add to /etc/samba/smb.conf

force user = bweew
vfs objects = streams_xattr

Friday, July 18, 2025

loopy pro + bs-16

Instrument MIDI Command
drums B0 00 78 B0 20 00 C0
piano B0 00 00 B0 20 00 C0 01
bass B0 00 00 B0 20 00 C0 21
jazz-gt B0 00 00 B0 20 00 C0 1A

on release, scroll down to the bottom where Midi Actions are to choose send midi message. Custom hex midi messages are key! Before I tried using program change and it wouldn't let me get my drums even if i told it it send on midi channel 10.

my updated loopy pro template

zxcv records first 4 loops, asdf selects drums,bass,jazzgt,piano. I added buttons to select other sounds, kept it small to work on an iphone se but more roomy on a bigger screen of course.

bash ghostscript conversion of color pdfs to b&w


#!/bin/bash
# Loop through all PDF files in the current directory
for file in *.pdf; do
  # Skip if no PDF files
  [ -e "$file" ] || continue

  # Set output file name
  output="${file%.pdf}.bw.pdf"

  echo "Converting: $file -> $output"

  # Ghostscript command to convert to grayscale
  gs \
    -sDEVICE=pdfwrite \
    -dCompatibilityLevel=1.4 \
    -dProcessColorModel=/DeviceGray \
    -dColorConversionStrategy=/Gray \
    -dColorConversionStrategyForImages=/Gray \
    -dAutoRotatePages=/None \
    -dNOPAUSE -dBATCH -dQUIET \
    -sOutputFile="$output" "$file"
done