Friday, September 4, 2026

Midi Program Changer

Midi Program Changer github

Made this in python to change programs with the plus and minus keys on a qwerty keyboard. The easiest way to use is run the linux x86 build on github that I packaged together with pyinstall so you dont need to manually deal with dependencies. Otherwise if your own win/mac heres the python.


#!/usr/bin/env python3

import tkinter as tk
from tkinter import ttk, messagebox

import mido
from pynput import keyboard


# ============================================================
# GENERAL MIDI INSTRUMENTS
# ============================================================

GM_INSTRUMENTS = [
    "Acoustic Grand Piano",
    "Bright Acoustic Piano",
    "Electric Grand Piano",
    "Honky-tonk Piano",
    "Electric Piano 1",
    "Electric Piano 2",
    "Harpsichord",
    "Clavinet",
    "Celesta",
    "Glockenspiel",
    "Music Box",
    "Vibraphone",
    "Marimba",
    "Xylophone",
    "Tubular Bells",
    "Dulcimer",
    "Drawbar Organ",
    "Percussive Organ",
    "Rock Organ",
    "Church Organ",
    "Reed Organ",
    "Accordion",
    "Harmonica",
    "Tango Accordion",
    "Acoustic Guitar (nylon)",
    "Acoustic Guitar (steel)",
    "Electric Guitar (jazz)",
    "Electric Guitar (clean)",
    "Electric Guitar (muted)",
    "Overdriven Guitar",
    "Distortion Guitar",
    "Guitar Harmonics",
    "Acoustic Bass",
    "Electric Bass (finger)",
    "Electric Bass (pick)",
    "Fretless Bass",
    "Slap Bass 1",
    "Slap Bass 2",
    "Synth Bass 1",
    "Synth Bass 2",
    "Violin",
    "Viola",
    "Cello",
    "Contrabass",
    "Tremolo Strings",
    "Pizzicato Strings",
    "Orchestral Harp",
    "Timpani",
    "String Ensemble 1",
    "String Ensemble 2",
    "SynthStrings 1",
    "SynthStrings 2",
    "Choir Aahs",
    "Voice Oohs",
    "Synth Voice",
    "Orchestra Hit",
    "Trumpet",
    "Trombone",
    "Tuba",
    "Muted Trumpet",
    "French Horn",
    "Brass Section",
    "SynthBrass 1",
    "SynthBrass 2",
    "Soprano Sax",
    "Alto Sax",
    "Tenor Sax",
    "Baritone Sax",
    "Oboe",
    "English Horn",
    "Bassoon",
    "Clarinet",
    "Piccolo",
    "Flute",
    "Recorder",
    "Pan Flute",
    "Blown Bottle",
    "Shakuhachi",
    "Whistle",
    "Ocarina",
    "Lead 1 (square)",
    "Lead 2 (sawtooth)",
    "Lead 3 (calliope)",
    "Lead 4 (chiff)",
    "Lead 5 (charang)",
    "Lead 6 (voice)",
    "Lead 7 (fifths)",
    "Lead 8 (bass + lead)",
    "Pad 1 (new age)",
    "Pad 2 (warm)",
    "Pad 3 (polysynth)",
    "Pad 4 (choir)",
    "Pad 5 (bowed)",
    "Pad 6 (metallic)",
    "Pad 7 (halo)",
    "Pad 8 (sweep)",
    "FX 1 (rain)",
    "FX 2 (soundtrack)",
    "FX 3 (crystal)",
    "FX 4 (atmosphere)",
    "FX 5 (brightness)",
    "FX 6 (goblins)",
    "FX 7 (echoes)",
    "FX 8 (sci-fi)",
    "Sitar",
    "Banjo",
    "Shamisen",
    "Koto",
    "Kalimba",
    "Bagpipe",
    "Fiddle",
    "Shanai",
    "Tinkle Bell",
    "Agogo",
    "Steel Drums",
    "Woodblock",
    "Taiko Drum",
    "Melodic Tom",
    "Synth Drum",
    "Reverse Cymbal",
    "Guitar Fret Noise",
    "Breath Noise",
    "Seashore",
    "Bird Tweet",
    "Telephone Ring",
    "Helicopter",
    "Applause",
    "Gunshot",
]


# ============================================================
# CHANNEL 10 DRUM KITS
# ============================================================

DRUM_KITS = {
    1: "Standard Drum Kit",
    9: "Room Drum Kit",
    17: "Power Drum Kit",
    25: "Electric Drum Kit",
    26: "Rap TR808 Drums",
    33: "Jazz Drum Kit",
    41: "Brush Kit",
}

DRUM_PROGRAMS = list(DRUM_KITS.keys())


# ============================================================
# MIDI CC
# ============================================================

REVERB_CC = 91


# ============================================================
# APPLICATION
# ============================================================

class MidiProgramChanger:

    def __init__(self, root):

        self.root = root
        self.root.title("MIDI Program Changer")

        self.port = None
        self.keyboard_listener = None

        self.program = 1
        self.channel = 1

        self.reverb = 0

        self.matrix_var = tk.IntVar(value=self.program)
        self.reverb_var = tk.IntVar(value=self.reverb)

        self.matrix_buttons = []

        self.create_gui()
        self.refresh_ports()
        self.start_keyboard_listener()

        self.root.protocol(
            "WM_DELETE_WINDOW",
            self.close
        )


    # ========================================================
    # GUI
    # ========================================================

    def create_gui(self):

        main = ttk.Frame(
            self.root,
            padding=10
        )

        main.pack(
            fill="both",
            expand=True
        )


        # ----------------------------------------------------
        # MIDI OUTPUT
        # ----------------------------------------------------

        port_frame = ttk.LabelFrame(
            main,
            text="MIDI Output",
            padding=8
        )

        port_frame.pack(
            fill="x",
            pady=(0, 8)
        )

        self.port_var = tk.StringVar()

        self.port_combo = ttk.Combobox(
            port_frame,
            textvariable=self.port_var,
            state="readonly",
            width=55
        )

        self.port_combo.pack(
            side="left",
            fill="x",
            expand=True
        )

        ttk.Button(
            port_frame,
            text="Refresh",
            command=self.refresh_ports
        ).pack(
            side="left",
            padx=(5, 0)
        )

        ttk.Button(
            port_frame,
            text="Connect",
            command=self.connect_port
        ).pack(
            side="left",
            padx=(5, 0)
        )


        # ----------------------------------------------------
        # CHANNEL
        # ----------------------------------------------------

        channel_frame = ttk.Frame(main)

        channel_frame.pack(
            fill="x",
            pady=(0, 8)
        )

        ttk.Label(
            channel_frame,
            text="MIDI Channel:"
        ).pack(side="left")

        self.channel_var = tk.IntVar(value=1)

        self.channel_spin = tk.Spinbox(
            channel_frame,
            from_=1,
            to=16,
            textvariable=self.channel_var,
            width=5,
            command=self.channel_changed
        )

        self.channel_spin.pack(
            side="left",
            padx=(5, 0)
        )

        self.channel_spin.bind(
            "",
            lambda event: self.channel_changed()
        )

        self.channel_spin.bind(
            "",
            lambda event: self.channel_changed()
        )


        # ----------------------------------------------------
        # PROGRAM CONTROLS
        # ----------------------------------------------------

        control_frame = ttk.Frame(main)

        control_frame.pack(
            fill="x",
            pady=(0, 8)
        )

        ttk.Button(
            control_frame,
            text="Previous",
            command=self.previous_program
        ).pack(side="left")

        self.program_var = tk.IntVar(value=1)

        self.program_spin = tk.Spinbox(
            control_frame,
            from_=1,
            to=128,
            textvariable=self.program_var,
            width=6,
            command=self.program_spin_changed
        )

        self.program_spin.pack(
            side="left",
            padx=8
        )

        self.program_spin.bind(
            "",
            lambda event: self.program_spin_changed()
        )

        self.program_spin.bind(
            "",
            lambda event: self.program_spin_changed()
        )

        ttk.Button(
            control_frame,
            text="Next",
            command=self.next_program
        ).pack(side="left")


        # ----------------------------------------------------
        # INSTRUMENT NAME
        # ----------------------------------------------------

        self.instrument_label = ttk.Label(
            main,
            text="1  Acoustic Grand Piano",
            anchor="center",
            font=("TkDefaultFont", 12, "bold")
        )

        self.instrument_label.pack(
            fill="x",
            pady=(0, 8)
        )


        # ----------------------------------------------------
        # REVERB
        # ----------------------------------------------------

        reverb_frame = ttk.LabelFrame(
            main,
            text="Reverb (MIDI CC 91)",
            padding=8
        )

        reverb_frame.pack(
            fill="x",
            pady=(0, 8)
        )

        self.reverb_knob = tk.Scale(
            reverb_frame,
            from_=0,
            to=127,
            orient="horizontal",
            variable=self.reverb_var,
            command=self.reverb_changed,
            showvalue=True,
            resolution=1,
            length=300
        )

        self.reverb_knob.pack(
            fill="x"
        )


        # ----------------------------------------------------
        # SCROLLABLE PROGRAM MATRIX
        # ----------------------------------------------------

        matrix_frame = ttk.LabelFrame(
            main,
            text="Program",
            padding=5
        )

        matrix_frame.pack(
            fill="both",
            expand=True
        )


        # Canvas
        self.canvas = tk.Canvas(
            matrix_frame,
            highlightthickness=0
        )

        self.canvas.pack(
            side="left",
            fill="both",
            expand=True
        )


        # Vertical scrollbar
        scrollbar = ttk.Scrollbar(
            matrix_frame,
            orient="vertical",
            command=self.canvas.yview
        )

        scrollbar.pack(
            side="right",
            fill="y"
        )

        self.canvas.configure(
            yscrollcommand=scrollbar.set
        )


        # Frame inside canvas
        self.matrix_inner = ttk.Frame(
            self.canvas
        )

        self.canvas_window = self.canvas.create_window(
            (0, 0),
            window=self.matrix_inner,
            anchor="nw"
        )


        # Update scroll region when frame changes
        self.matrix_inner.bind(
            "",
            self.update_scroll_region
        )


        # Make inner frame follow canvas width
        self.canvas.bind(
            "",
            self.resize_matrix
        )


        # Mouse wheel
        self.canvas.bind_all(
            "",
            self.mousewheel
        )


        # Linux mouse wheel
        self.canvas.bind_all(
            "",
            self.mousewheel_linux_up
        )

        self.canvas.bind_all(
            "",
            self.mousewheel_linux_down
        )


        # ----------------------------------------------------
        # CREATE MATRIX
        # ----------------------------------------------------

        for program in range(1, 129):

            row = (program - 1) // 8
            col = (program - 1) % 8

            button = ttk.Radiobutton(
                self.matrix_inner,
                text=str(program),
                value=program,
                variable=self.matrix_var,
                command=lambda p=program:
                    self.matrix_program_selected(p)
            )

            button.grid(
                row=row,
                column=col,
                padx=2,
                pady=2,
                sticky="ew"
            )

            self.matrix_buttons.append(button)


        for col in range(8):

            self.matrix_inner.columnconfigure(
                col,
                weight=1
            )


        self.update_program_controls()
        self.update_matrix()
        self.update_label()


    # ========================================================
    # SCROLLING
    # ========================================================

    def update_scroll_region(self, event=None):

        self.canvas.configure(
            scrollregion=self.canvas.bbox("all")
        )


    def resize_matrix(self, event):

        self.canvas.itemconfigure(
            self.canvas_window,
            width=event.width
        )


    def mousewheel(self, event):

        self.canvas.yview_scroll(
            int(-1 * (event.delta / 120)),
            "units"
        )


    def mousewheel_linux_up(self, event):

        self.canvas.yview_scroll(
            -3,
            "units"
        )


    def mousewheel_linux_down(self, event):

        self.canvas.yview_scroll(
            3,
            "units"
        )


    # ========================================================
    # MIDI PORTS
    # ========================================================

    def refresh_ports(self):

        ports = mido.get_output_names()

        self.port_combo["values"] = ports

        if ports:
            self.port_combo.current(0)
        else:
            self.port_var.set("")


    def connect_port(self):

        name = self.port_var.get()

        if not name:

            messagebox.showwarning(
                "MIDI",
                "No MIDI output port selected."
            )

            return

        try:

            if self.port:
                self.port.close()

            self.port = mido.open_output(name)

            self.send_program()
            self.send_reverb()

        except Exception as e:

            self.port = None

            messagebox.showerror(
                "MIDI Error",
                str(e)
            )


    # ========================================================
    # MIDI PROGRAM CHANGE
    # ========================================================

    def send_program(self):

        if self.port is None:
            return

        midi_channel = self.channel - 1
        midi_program = self.program - 1

        try:

            self.port.send(
                mido.Message(
                    "program_change",
                    channel=midi_channel,
                    program=midi_program
                )
            )

        except Exception as e:

            print("MIDI error:", e)


    # ========================================================
    # REVERB CC 91
    # ========================================================

    def send_reverb(self):

        if self.port is None:
            return

        midi_channel = self.channel - 1

        try:

            self.port.send(
                mido.Message(
                    "control_change",
                    channel=midi_channel,
                    control=REVERB_CC,
                    value=self.reverb
                )
            )

        except Exception as e:

            print("MIDI reverb error:", e)


    def reverb_changed(self, value):

        try:
            self.reverb = int(float(value))
        except ValueError:
            return

        self.send_reverb()


    # ========================================================
    # CHANNEL
    # ========================================================

    def channel_changed(self):

        try:

            channel = int(
                self.channel_var.get()
            )

            if channel < 1 or channel > 16:
                raise ValueError

        except ValueError:

            self.channel_var.set(
                self.channel
            )

            return


        self.channel = channel


        # Channel 10 only supports the selected drum kits.
        if self.channel == 10:

            if self.program not in DRUM_KITS:

                self.program = DRUM_PROGRAMS[0]


        self.program_var.set(
            self.program
        )

        self.matrix_var.set(
            self.program
        )

        self.update_program_controls()
        self.update_matrix()
        self.update_label()

        self.send_program()
        self.send_reverb()


    # ========================================================
    # PROGRAM
    # ========================================================

    def set_program(self, program):

        if self.channel == 10:

            if program not in DRUM_KITS:
                return

        if program < 1 or program > 128:
            return

        self.program = program

        self.program_var.set(
            program
        )

        self.matrix_var.set(
            program
        )

        self.update_label()
        self.update_matrix()

        self.send_program()


    def program_spin_changed(self):

        try:

            program = int(
                self.program_var.get()
            )

        except ValueError:

            self.program_var.set(
                self.program
            )

            return


        if self.channel == 10:

            if program not in DRUM_KITS:

                program = min(
                    DRUM_PROGRAMS,
                    key=lambda p:
                        abs(p - program)
                )

        else:

            if program < 1:
                program = 1

            if program > 128:
                program = 128


        self.set_program(program)


    # ========================================================
    # PREVIOUS
    # ========================================================

    def previous_program(self):

        if self.channel == 10:

            current_index = DRUM_PROGRAMS.index(
                self.program
            )

            new_index = (
                current_index - 1
            ) % len(DRUM_PROGRAMS)

            self.set_program(
                DRUM_PROGRAMS[new_index]
            )

        else:

            if self.program <= 1:
                new_program = 128
            else:
                new_program = self.program - 1

            self.set_program(
                new_program
            )


    # ========================================================
    # NEXT
    # ========================================================

    def next_program(self):

        if self.channel == 10:

            current_index = DRUM_PROGRAMS.index(
                self.program
            )

            new_index = (
                current_index + 1
            ) % len(DRUM_PROGRAMS)

            self.set_program(
                DRUM_PROGRAMS[new_index]
            )

        else:

            if self.program >= 128:
                new_program = 1
            else:
                new_program = self.program + 1

            self.set_program(
                new_program
            )


    # ========================================================
    # MATRIX
    # ========================================================

    def matrix_program_selected(self, program):

        self.set_program(program)


    def update_matrix(self):

        if not self.matrix_buttons:
            return

        for program, button in enumerate(
            self.matrix_buttons,
            start=1
        ):

            if self.channel == 10:

                if program in DRUM_KITS:

                    button.configure(
                        text=f"{program}\n"
                             f"{DRUM_KITS[program]}",
                        state="normal"
                    )

                else:

                    button.configure(
                        text=str(program),
                        state="disabled"
                    )

            else:

                button.configure(
                    text=str(program),
                    state="normal"
                )


    # ========================================================
    # PROGRAM SPINBOX
    # ========================================================

    def update_program_controls(self):

        if self.channel == 10:

            self.program_spin.configure(
                values=tuple(
                    DRUM_PROGRAMS
                )
            )

        else:

            self.program_spin.configure(
                from_=1,
                to=128,
                values=()
            )


    # ========================================================
    # LABEL
    # ========================================================

    def update_label(self):

        if self.channel == 10:

            name = DRUM_KITS.get(
                self.program,
                "Unknown Drum Kit"
            )

        else:

            name = GM_INSTRUMENTS[
                self.program - 1
            ]

        self.instrument_label.configure(
            text=f"{self.program}  {name}"
        )


    # ========================================================
    # GLOBAL KEYBOARD
    # ========================================================

    def start_keyboard_listener(self):

        self.keyboard_listener = keyboard.Listener(
            on_press=self.key_pressed
        )

        self.keyboard_listener.start()


    def key_pressed(self, key):

        try:

            if key.char == "-":

                self.root.after(
                    0,
                    self.previous_program
                )

            elif key.char == "=":

                self.root.after(
                    0,
                    self.next_program
                )

        except AttributeError:

            pass


    # ========================================================
    # CLOSE
    # ========================================================

    def close(self):

        if self.keyboard_listener:

            self.keyboard_listener.stop()

        if self.port:

            self.port.close()

        self.root.destroy()


# ============================================================
# MAIN
# ============================================================

if __name__ == "__main__":

    root = tk.Tk()

    app = MidiProgramChanger(root)

    root.mainloop()

Saturday, August 29, 2026

fixing vgm2gbs for linux

once you make a gbs you can convert it to a gb rom using (or just do gb export in deflemask and skip all this)

gbs-master vgm2gbs github

convert.bat is great if your on windows for drag and drop but 2 problems
1 - vgm headers this script used are old and incompatable with most newer deflemask versions
2 - because im running it in wine and requires python its broken
THE FIX
Replace src/gd3.py with:

make a linux.sh and chmod +x it


#!/bin/bash
cd "$(dirname "$0")"
python3 src/main.py "$1"

import struct


class GD3:
    version: int
    track_name: str
    track_name_orig: str
    game_name: str
    game_name_orig: str
    system_name: str
    system_name_orig: str
    author: str
    author_orig: str
    release_date: str
    ripper: str
    notes: str

    def __init__(self, data: bytes):
        assert data[0:4] == b'Gd3 '

        self.version, data_length = struct.unpack_from(" str:
        return f'{self.track_name} - {self.author} - {self.game_name} ({self.ripper})'
 

making lsdj roms with lsdpack kit

lsdpack kit on github

make RGBDS="$HOME/Downloads/rgbds-0.9.1-linux-x86_64/"
rgbds I needed the older version to compile lsdj rom repo

I use v 8-5-1 stable the newer versions output a garbled mess
in the code_config.mk file i commented out
# ASFLAGS += -h


this projects cool because it lets you plop in your 160x144 album pics with your lsdj songs to make the rom for an interactive album

Friday, August 28, 2026

fixed raspberry pi lite resolution


sudo vim /boot/firmware/cmdline.txt

video=HDMI-A-1:1600x900@60

logitech f310 macros, shutdown, copy paste


from evdev import InputDevice, UInput, ecodes
import subprocess
import threading
import time

GAMEPAD = "/dev/input/by-id/usb-Logitech_Logitech_Dual_Action_DF5CB332-event-joystick"

device = InputDevice(GAMEPAD)

# Virtual keyboard
ui = UInput({
    ecodes.EV_KEY: [
        ecodes.KEY_LEFTCTRL,
        ecodes.KEY_C,
        ecodes.KEY_V,
    ]
}, name="Gamepad Virtual Keyboard")

button_292 = False
button_293 = False
shutdown_timer = None
lock = threading.Lock()


def ctrl_key(key):
    print(f"Sending Ctrl+{key}", flush=True)

    ui.write(ecodes.EV_KEY, ecodes.KEY_LEFTCTRL, 1)
    ui.write(ecodes.EV_KEY, key, 1)
    ui.write(ecodes.EV_KEY, key, 0)
    ui.write(ecodes.EV_KEY, ecodes.KEY_LEFTCTRL, 0)
    ui.syn()


def shutdown():
    time.sleep(1)

    with lock:
        if button_292 and button_293:
            print("\nBoth buttons held for 1 second — SHUTTING DOWN!", flush=True)
            subprocess.run(["systemctl", "poweroff"])


print(f"Gamepad: {device.name}", flush=True)
print("291 = Ctrl+C", flush=True)
print("290 = Ctrl+V", flush=True)
print("292 + 293 for 1 second = Shutdown", flush=True)
print("Waiting for input...\n", flush=True)


for event in device.read_loop():

    if event.type != ecodes.EV_KEY:
        continue

    with lock:

        # BTN_TOP = 291 -> Ctrl+C
        if event.code == 291 and event.value == 1:
            print("291: Ctrl+C", flush=True)
            ctrl_key(ecodes.KEY_C)

        # BTN_THUMB2 = 290 -> Ctrl+V
        elif event.code == 290 and event.value == 1:
            print("290: Ctrl+V", flush=True)
            ctrl_key(ecodes.KEY_V)

        # BTN_TOP2 = 292
        elif event.code == 292:
            button_292 = event.value == 1
            print(
                f"292: {'PRESSED' if button_292 else 'RELEASED'}",
                flush=True
            )

        # BTN_PINKIE = 293
        elif event.code == 293:
            button_293 = event.value == 1
            print(
                f"293: {'PRESSED' if button_293 else 'RELEASED'}",
                flush=True
            )

        # Start shutdown timer
        if button_292 and button_293 and shutdown_timer is None:
            print("Both shoulder buttons held — 1 second timer started.", flush=True)
            shutdown_timer = threading.Thread(
                target=shutdown,
                daemon=True
            )
            shutdown_timer.start()

        # Cancel shutdown
        elif not (button_292 and button_293):
            shutdown_timer = None

jiggler keeps your screen from falling asleep

jiggler github

pipx install jiggler


Launching Gambatte Gameboy Emulator From Terminal


retroarch -L

// if installed on linux mint using
// sudo apt install libretro-gambatte
// then it will probably be at:
/usr/lib/x86_64-linux-gnu/libretro/gambatte_libretro.so

// full command for me:
retroarch -L /usr/lib/x86_64-linux-gnu/libretro/gambatte_libretro.so /home/bweew/Documents/Syncthing/Lsdj/lsdj.gb

f for full screen f1 for settings like changing the key maps

You can make it a bash script


#!/bin/bash
retroarch -L /usr/lib/x86_64-linux-gnu/libretro/gambatte_libretro.so "$HOME/Documents/Syncthing/Lsdj/lsdj.gb"
chmod +x ~/lsdj.sh

//on raspberry pi 3 64 bit os
#!/bin/bash
retroarch -L /usr/lib/aarch64-linux-gnu/libretro/gambatte_libretro.so lsdj.gb

Wednesday, August 26, 2026

maj7b5 and 7sus4b5 inversions

1 4 b5 b7
1 b9 4 5
1 3 b5 7
1 2 5 b6

1 3 b5 7
1 2 5 b6
1 4 b5 b7
1 b9 4 5

Tuesday, August 25, 2026

python gba compilation builder scripts

gba emu comp on github

python scripts to build gba roms, emulators not included

Saturday, August 22, 2026

mediaboy - great for making music roms on gameboy easily

mediaboy releases

the music export is the most useful.
says it can do .mod and .uge but its not working for me
the gif and video exports work in sameboy but not on my flashcart or in gearboy
pcm samplerate settings:
9198 hz -GBVP2 parity yeilds best quality
DMG gives clearer images than colour it seems to mangle the image with blocky pixels
to

goomba paletted

use that with gameboy slideshow generator instead of that because it screws up the audio


making gb and gbc videos worked better flashed directly with gb flasher to a perfect dark cart instead of on the superchis gba sd cart also sameboy and bgb are more accurate usually but goomba breaks

renaming png and jpgs as numbers using bash


i=1; for f in *.png *.jpg *.jpeg; do [ -f "$f" ] || continue; mv -- "$f" "$i.${f##*.}"; ((i++)); done

Friday, August 21, 2026

How to make full size lsdj videos like aquallex

lsdjhd

tommitytom, the maker of retroplug made this web player that lets you view all the screens of lsdj at once.

finally a linux native retroplug for lsdj

retroplug dropped 2 weeks ago

update has a standalone, vst2, vst3, clap and commandline version
also works with nes


use retroplug-cli lsdj-rom --split channels flag foroutput

Thursday, August 20, 2026

new renoise2 mod linux native with terminal use

RELEASES -- renoise2mod github. move it to ~/.local/bin to install

might need to chmod after moving

the github mainpage for more info

flags for different cli options


renoise2mod song.xrns --type xm
renoise2mod song.xrns --type mod --ptmode hardware --ntsc
renoise2mod --help

keyd - key remapping on linux

keyd github

I wanted to be able to switch layers or something to map keys in a different localization to be able to use extra shortcuts.
--renoise doesn't detect f13 - 24 making this whole thing useless for my case. but its still a nice key remapper

Installing


git clone https://github.com/rvaiya/keyd
cd keyd
make && sudo make install
sudo systemctl enable --now keyd

Finding Keystrokes


keyd monitor

Listing Available Keys


keyd list-keys

Editing The Config


sudo vim /etc/keyd/default.conf

reload config


sudo keyd reload

emergency reset

backspace+escape+enter

Wednesday, August 12, 2026

gb studio custom waveforms

GB Studio: Advanced Custom Waveform Design for GBT Player

for making custom waves lsdj or hugetracker

sine: 79 bc de ef ff ee dc b9 75 43 21 10 00 11 23 45

tri: fe dc ba 98 76 54 32 10 01 23 45 67 89 ab cd ef

Friday, July 31, 2026

patching savestates into gb and gbc games

mattcurrie gb save states github

doesn't have every game but still cool
sudo apt install bsdiff on linux
the syntax:
bspatch YOURGAME.gbc OUTPUT.GBC THEPATCH.bsdiff