Skip to content

TheMarco/pvsneslib-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pvsneslib-toolkit

A free, open toolkit for turning everyday assets — MIDI/MML/MOD music, PNG images, and TTF fonts — into SNES-ready data for PVSnesLib games.

Writing a Super Nintendo game means feeding the hardware data in formats from 1990: planar tiles, BGR555 palettes, Impulse Tracker modules built from tiny chiptune voices. These tools do those conversions for you. They're small, dependency-light Python and Bash scripts, each with a proper command-line interface — drop them into any PVSnesLib project and point them at your art and music.

Extracted and generalised from the tooling that built Deadfall, a complete from-scratch SNES game.

💛 100% free, no strings attached

This toolkit is released into the public domain (see License). Use it for anything — personal, commercial, closed-source, whatever — with no attribution required and no permission needed. If it saves you time and you'd like to say thanks, you can buy me a coffee on Ko-fi. Entirely optional. The tools are yours either way.

Support me on Ko-fi


Contents


Why this exists

The SNES is a wonderful but unforgiving target:

  • Music plays through snesmod, whose converter smconv only accepts Impulse Tracker .it modules. The console has a single audio chip (the APU) shared by music and sound effects, so you can't just upload a ripped .spc — that would replace the sound driver and silence your SFX. The audio tools here solve this by keeping the real notes and timing of your song but rebuilding it from a handful of synthesised chiptune voices that fit in ARAM next to your effects.
  • Graphics must be planar tiles with packed BGR555 palettes. png2bg8 turns an ordinary PNG into a tile set + tilemap + palette, deduplicating repeated tiles (including mirrored ones) automatically.
  • Text needs a tile-based font. ttf2font rasterises any TrueType/OpenType font into 8×8 2bpp glyph tiles.

Every tool is standalone — there's no framework, no config files, no build system to adopt. Run them by hand or wire them into your Makefile.


Requirements & install

  • Python 3.8+
  • numpy (audio tools) and Pillow (graphics tools)
  • The toolchain helpers are Bash and macOS-specific
git clone https://github.com/TheMarco/pvsneslib-toolkit.git
cd pvsneslib-toolkit
pip install -r requirements.txt        # numpy, Pillow

That's it — the scripts run in place. There's nothing to build or install beyond the two Python packages.


Quick start

# A MIDI tune -> a SNES music module (+ a .wav you can listen to first)
python3 audio/mid2it.py mysong.mid -o res/bgm.it

# A PNG -> an 8bpp background (tiles + map + palette)
python3 graphics/png2bg8.py title.png -n title -d res

# A font -> an 8x8 text font for your HUD / menus
python3 graphics/ttf2font.py myfont.ttf 14 -o res/font.pic

Each audio tool also drops a *_preview.wav next to its output so you can audition the result before it ever goes in the ROM.


The tools

Audio — chiptune .it modules

All three converters produce an Impulse Tracker .it module that snesmod's smconv can compile, built from the same bank of small synthesised voices (triangle / square / pulse / saw leads + kick / snare / hat / crash drums). They keep your song's real pitches and timing; the timbre is a chiptune approximation, so always check the preview WAV.

audio/mid2it.py — MIDI → .it

A General MIDI file is pure composition (notes + timing, no audio), which makes it the ideal SNES music source. Each MIDI channel becomes a chiptune voice; channel 10 (GM percussion) is routed to drum one-shots.

python3 audio/mid2it.py song.mid                  # -> ./song.it + ./song_preview.wav
python3 audio/mid2it.py song.mid -o res/bgm.it    # explicit output path
python3 audio/mid2it.py song.mid --bpm 130        # force the whole song to 130 BPM

Combine mode — merge two MIDIs into one module as two independently looping sections. In-game you switch between them with a single snesmod position jump (spcPlay) — no blocking module reload — perfect for calm/intense variants of a level theme:

python3 audio/mid2it.py --combine calm.mid intense.mid -o res/level.it --bpm 130
# spcPlay(0) plays section A (calm); spcPlay(<b_start>) plays section B (intense)
# the script prints the b_start order index to use.
Option Default Meaning
src input .mid (first section in --combine)
second second .mid (only with --combine)
-o, --out <src>.it output .it path
-d, --outdir . output directory when -o is omitted
-t, --title filename IT song title (max 25 chars)
--bpm N keep MIDI's own force one tempo for the whole song
--combine off merge two MIDIs into two looping sections

audio/mml2it.py — AddmusicK MML → .it

Many SNES music archives ship AddmusicK MML scores (the text source behind a .spc). This parses the MML directly — notes, lengths, ties, octaves, tempo, volume, instruments, loops [..]n (incl. nesting), labelled-loop recalls (N)[..], and NAME=... macros — and renders the same chiptune module.

python3 audio/mml2it.py score.txt -o res/bgm.it
python3 audio/mml2it.py score.txt --amk-scale 2.0    # retune AMK-tempo -> BPM
python3 audio/mml2it.py score.txt --bpm 150          # force an exact tempo
Option Default Meaning
src input MML score (.txt / .mml)
-o, --out <src>.it output .it path
-d, --outdir . output directory when -o is omitted
-t, --title filename IT song title
--bpm N from score force this exact tempo, bypassing the AMK conversion
--amk-scale F 2.385 AddmusicK-tempo → BPM scale factor

audio/mod2it.py — ProTracker .mod.it

For when you have real sampled music. Parses a 4/6/8-channel ProTracker .mod and re-emits it as .it, translating samples, notes, volumes and the structural effects. An optional KB budget shrinks sample-heavy modules to fit ARAM by decimating samples (pitch preserved).

python3 audio/mod2it.py song.mod res/song.it          # straight conversion
python3 audio/mod2it.py song.mod res/song.it 32       # fit samples into ~32 KB
python3 audio/mod2it.py --selftest                    # build + convert a tiny test mod
Argument Meaning
in.mod source ProTracker module
out.it output Impulse Tracker module
KB (optional) sample-data budget in KB; downsamples to fit

audio/make_it.py — Impulse Tracker writer (library)

The minimal .it writer the three converters share. You can also import it directly to build a sound-effects bank — an .it whose samples are your SFX, played in-game by index via spcLoadEffect / spcEffect:

from make_it import write_it
write_it("res/sfx.it", "SFX", samples)   # samples = list of {name, pcm, rate, loop}

See the docstring at the top of the file for the sample-dict format.


Graphics

graphics/png2bg8.py — PNG → 8bpp background

Converts an indexed PNG into a Mode 3, 8bpp background. 8bpp means 256 colours with no per-tile palette limit, so your source colours are preserved exactly 1:1 (a 4bpp/Mode-1 background is capped at 16 colours per 8×8 tile). Identical tiles — including horizontal/vertical mirrors — are deduplicated into a 32×32 tilemap.

python3 graphics/png2bg8.py title.png -n title -d res

Produces three files in the output directory:

File Contents
<name>.pic deduplicated 8bpp tiles (64 bytes each)
<name>.map 32×32 tilemap (16-bit little-endian tile indices)
<name>.pal palette, BGR555 little-endian
Option Default Meaning
image source image (any Pillow-readable format)
-n, --name image stem output basename
-d, --outdir . output directory

graphics/ttf2font.py — TTF/OTF → 8×8 text font

Rasterises a TrueType/OpenType font into a flat run of 8×8, 2bpp glyph tiles — the layout most SNES text engines (including PVSnesLib's consoleDrawText) expect. Each glyph is baseline-aligned and ink-centred, so a proportional font still lines up as a tidy fixed-width run.

python3 graphics/ttf2font.py myfont.ttf 14 -o res/font.pic        # transparent bg
python3 graphics/ttf2font.py myfont.ttf 14 --bg 2 -o res/hud.pic  # opaque bg (solid HUD)
python3 graphics/ttf2font.py myfont.ttf 14 --preview "HELLO 0123" # ASCII-art, no file
Option Default Meaning
font TrueType/OpenType font file
size 14 pixel size to rasterise at
-o, --out <font>.pic output .pic path
--bg N 0 background colour index: 0 transparent, 2 opaque
--first N 32 first code point (32 = space)
--count N 64 number of glyphs (64 → ASCII 32..95)
--preview [TEXT] off print the font as ASCII art instead of writing a file

The letter is colour 1; the cell background is --bg. Glyphs the font lacks are emitted blank and reported, and glyphs wider than 8px are reported as overflow (lower size if too many overflow).


Toolchain (macOS)

toolchain/bin/sed — portable sed -i shim

PVSnesLib's snes_rules uses GNU-style sed -i SCRIPT FILE, which BSD/macOS sed rejects (it reads the next argument as a backup suffix). This shim intercepts only the -i in-place form and passes everything else straight through to the system sed, so you never have to modify the installed toolchain. Put it on PATH ahead of /usr/bin in your build (see below).

toolchain/install_pvsneslib.sh — one-shot installer

Downloads the latest PVSnesLib release, extracts it to ~/pvsneslib, clears the macOS quarantine bit on the binaries, detects and persists PVSNESLIB_HOME to your ~/.zshrc, and writes a .pvsneslib_home marker into your project so its build can find the toolchain.

bash toolchain/install_pvsneslib.sh [PROJECT_DIR]    # PROJECT_DIR default: $PWD

Using these in a PVSnesLib project

A typical project keeps converted assets in res/ and .incbins them. Two practical tips:

1. The macOS sed shim. Run your build with the shim on PATH so snes_rules works:

all:
	PATH="$(CURDIR)/toolchain/bin:$$PATH" $(MAKE) rom

2. snesmod module ordering. smconv assigns the MOD_* indices you pass to spcPlay() in the order modules appear in your AUDIOFILES list, and the SFX bank must be first. Keep that list stable, or your in-game music indices will shift.

A regeneration target might look like:

res/bgm.it: music/song.mid
	python3 tools/mid2it.py $< -o $@

res/title.pic: art/title.png
	python3 tools/png2bg8.py $< -n title -d res

Notes & gotchas

Hard-won lessons baked into these tools:

  • numpy uint8 overflow when bit-packing. When packing palettes/pixels into BGR555 or shifting bytes, cast numpy uint8 to int before the math. uint8 arithmetic wraps silently and produces garbage — the classic symptom is an all-red palette. (The tools already handle this; worth knowing if you extend them.)
  • Chiptune timbre is an approximation. The audio tools reproduce the real notes and rhythm but synthesise the instruments, so they won't sound like the source's patches. Always listen to the *_preview.wav.
  • Font baseline clipping. ttf2font aligns glyphs to a baseline inside the 8×8 cell, so deep descenders and the underscore can clip, and wide glyphs may overflow 8px. Both are reported; reduce --size if needed.
  • ARAM is small. SNES audio RAM is 64 KB shared by the driver, samples and patterns. If a .mod won't fit, use mod2it's KB-budget argument; chiptune modules from mid2it/mml2it are tiny by design.

FAQ

Does this depend on Deadfall or any specific game? No. The tools were extracted from Deadfall but have no game-specific assumptions — output paths, titles and filenames are all command-line options.

Do I have to use the whole toolkit? No — every script is standalone. Grab just the one you need.

Will the music sound exactly like my MIDI/MML? The notes and timing will match; the instruments are synthesised chiptune voices, so the timbre differs. That's the trade-off for fitting in ARAM alongside your sound effects. Preview WAVs let you check before building.

Windows / Linux? The Python converters (audio/*, graphics/*) are cross-platform. The toolchain/ helpers are macOS-specific — Linux users generally don't need the sed shim, and the installer targets macOS.

Can I use this in a commercial game? Yes. It's public domain — do anything you like, no permission or credit required.


License

Released into the public domain under The Unlicense. You may copy, modify, publish, use, compile, sell, or distribute it — for any purpose, commercial or not — with no attribution and no permission required. See the LICENSE file for the full text.

Support

These tools are, and always will be, completely free. If they helped you ship something and you'd like to support more work like this, a coffee is hugely appreciated — but never expected:

Support me on Ko-fi

➡️ ko-fi.com/aianddesign

Credits

Built for and extracted from Deadfall. Targets PVSnesLib by alekmaul — the open-source SDK that makes SNES homebrew in C possible.

About

Free, public-domain asset-conversion tools for SNES homebrew with PVSnesLib: MIDI/MML/MOD to chiptune .it, PNG to 8bpp backgrounds, TTF to fonts.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors