Wii Guitar MIDI Bridge
A Guitar Hero controller, a Raspberry Pi Zero 2 W, and a short cable turn into a real MIDI instrument. Fret combinations pick chords, the strum bar plays them, the touch bar picks individual strings, and the whammy bends or drives intensity. Four instruments — guitar, bass, synth and drums — each on its own MIDI channel.
The Pi appears to a phone or computer as a class-compliant USB MIDI device, so GarageBand, Loopy, a DAW, or anything else that takes MIDI sees an instrument with no drivers and no configuration.
What you need
- A Wii Guitar Hero guitar and the Wii Remote it plugs into. Built and tested against a World Tour neck (five frets plus the touch bar).
- A Raspberry Pi Zero 2 W. The USB gadget mode needs a board with USB OTG — a Zero, Zero 2, A-series or Compute Module. A 3B or 4B model B will not work; those are host-only.
- A Waveshare 1.44″ LCD HAT (ST7735S, 128×128, joystick and
three keys) for the on-device interface. Optional — run with
--no-hatwithout it. - A micro-USB cable to the host, plus separate power into the Pi’s
inner
PWRport.
Install
On a fresh Raspberry Pi OS or DietPi install:
curl -fsSLO https://jacksonmicro.com/wiiguitar/wiiguitar.tar.gz
tar xzf wiiguitar.tar.gz
cd wiiguitar
less install.sh # it wants root, so read it first
sudo ./install.sh
It installs packages, enables SPI and USB gadget mode, applies the Bluetooth
settings a Wii Remote needs, and sets up three systemd services. Re-running it is
safe — every step checks before changing anything, and
--dry-run shows exactly what it would do without touching the
system.
Then reboot, pair, and plug in:
sudo systemctl start reboot.target # DietPi masks logind
cd ~/wiiguitar && sudo ./pair.sh # press 1+2 on the Wii Remote
Download wiiguitar.tar.gz View install.sh SHA256SUMS
How playing works
Hold a combination of the five fret buttons to select a chord; hit the strum bar to play it. That much is the guitar. On top:
- The touch bar is the strings of whatever chord you are holding. Each zone picks one note of the voicing, low to high, so sliding along it arpeggiates. A pluck re-articulates just that string and leaves the rest of the chord ringing.
- The whammy bends pitch in guitar and bass, and drives intensity in synth and drums.
- Lifting all five frets mutes, the way taking your hand off the neck does.
Instrument modes
KEY1 on the HAT cycles modes. Switching sends CC123
(all notes off) to the channel you are leaving, so the previous
instrument is never left holding notes.
| Mode | MIDI ch | Frets | Whammy | Touch bar | Default voice |
|---|---|---|---|---|---|
| Guitar | 1 | chord | bend | pluck | clean |
| Bass | 2 | note | bend | notes | finger |
| Synth | 3 | chord | intensity | pluck | piano |
| Drums | 10 | drum | intensity | cymbals | kit |
Drums sit on channel 10 because that is where General MIDI puts percussion. No program change is ever sent there — one would swap the entire kit.
Configuring the notes
Everything below is plain Python or JSON in the install directory. Edit,
restart with sudo systemctl restart wiiguitar, and it takes effect.
Chords — guitar and synth
All 31 fret combinations are mapped in chords.py. Each entry is
bitmask: (semitones above the key root, quality, label), with
GREEN=1 RED=2 YELLOW=4 BLUE=8 ORANGE=16:
CHORD_MAP = {
GREEN: (0, "maj", "I"), # C in the key of C
RED: (7, "maj", "V"), # G
YELLOW: (9, "min", "vi"), # Am
BLUE: (5, "maj", "IV"), # F
ORANGE: (2, "min", "ii"), # Dm
GREEN | RED: (4, "min", "iii"), # Em
...
}
The four singles run I–V–vi–IV left to right, which is the most common progression in popular music — so you play it as a plain sweep across the neck.
One fret — the workhorse chords
| Frets | Degree | Key of C | Key of G |
|---|---|---|---|
G | I | C | G |
R | V | G | D |
Y | vi | Am | Em |
B | IV | F | C |
O | ii | Dm | Am |
Two frets — 7ths, colour, the remaining degrees
| Frets | Degree | Key of C | Key of G |
|---|---|---|---|
G+R | iii | Em | Bm |
G+Y | Isus4 | Csus4 | Gsus4 |
R+Y | V7 | G7 | D7 |
G+B | I7 | C7 | G7 |
R+B | ii7 | Dm7 | Am7 |
Y+B | vi7 | Am7 | Em7 |
G+O | Imaj7 | Cmaj7 | Gmaj7 |
R+O | Vsus4 | Gsus4 | Dsus4 |
Y+O | IVmaj7 | Fmaj7 | Cmaj7 |
B+O | bVII | A# | F |
Three frets — power chords
| Frets | Degree | Key of C | Key of G |
|---|---|---|---|
G+R+Y | I5 | C5 | G5 |
G+R+B | vi5 | A5 | E5 |
G+Y+B | ii5 | D5 | A5 |
R+Y+B | V5 | G5 | D5 |
G+R+O | bVII5 | A#5 | F5 |
G+Y+O | iii5 | E5 | B5 |
R+Y+O | bVI | G# | D# |
G+B+O | bIII | D# | A# |
R+B+O | vii | Bdim | F#dim |
Y+B+O | IV5 | F5 | C5 |
Four frets — suspensions and extensions
| Frets | Degree | Key of C | Key of G |
|---|---|---|---|
G+R+Y+B | Iadd9 | Cadd9 | Gadd9 |
G+R+Y+O | IVsus2 | Fsus2 | Csus2 |
G+R+B+O | Vadd9 | Gadd9 | Dadd9 |
G+Y+B+O | iisus4 | Dsus4 | Asus4 |
R+Y+B+O | vi7 | Am7 | Em7 |
All five
| Frets | Degree | Key of C | Key of G |
|---|---|---|---|
G+R+Y+B+O | Imaj7* | Cmaj7 | Gmaj7 |
Chord qualities
Semitone intervals above the root, from QUALITIES. Add your own
by putting a new tuple in that dictionary.
| Name | Intervals |
|---|---|
maj | 0, 4, 7 |
min | 0, 3, 7 |
dom7 | 0, 4, 7, 10 |
min7 | 0, 3, 7, 10 |
maj7 | 0, 4, 7, 11 |
sus4 | 0, 5, 7 |
sus2 | 0, 2, 7 |
power | 0, 7 |
dim | 0, 3, 6 |
add9 | 0, 4, 7, 14 |
Songs — naming real chords
setlist.json holds a setlist. A song names actual chords per
fret, so it reads the way songs are written down rather than in roman
numerals:
{"name": "Wonderwall", "key": "Em", "patch": "acoustic",
"frets": {"G": "Em7", "R": "G", "Y": "Dsus4",
"B": "A7sus4", "O": "Cadd9"}}
Keys use combination names like "G+R" for two frets. Anything a
song does not define falls back to the diatonic chord for its key, so no fret is
ever dead. Minor keys correctly borrow their relative major’s chord set
— Em gets G major’s chords, not E major’s.
You can also edit chords on the device: KEY2 opens an editor where the joystick picks the fret slot and the root, KEY1 cycles the quality, and KEY2 saves. Chords preview audibly as you dial them, and saves are atomic, so pulling the power mid-write cannot corrupt the setlist.
| Song | Key | Frets |
|---|---|---|
| Key of C | C | diatonic default for the key |
| Key of G | G | diatonic default for the key |
| Wonderwall | Em | Em7 G Dsus4 A7sus4 Cadd9 |
| Let It Be | C | C G Am F Em |
| Zombie | Em | Em C G D Am |
| Knockin on Heaven | G | G D Am C Em |
| House of Rising Sun | Am | Am C D F E |
| Blues in E | E | E7 A7 B7 E5 A5 |
| Sweet Home Alabama | D | D C G Em Bm |
Bass — ten single notes
The five frets are scale degrees 1–5 and the five touch-bar zones continue 6–10, giving just over an octave without leaving the neck. Minor keys use the natural minor scale. The root is placed once for the whole layout so the top notes cannot wrap around and collide with the bottom.
| Key | Frets — G R Y B O | Touch zones 1–5 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| C | C2 | D2 | E2 | F2 | G2 | A2 | B2 | C3 | D3 | E3 |
| Am | A1 | B1 | C2 | D2 | E2 | F2 | G2 | A2 | B2 | C3 |
| Em | E1 | F#1 | G1 | A1 | B1 | C2 | D2 | E2 | F#2 | G2 |
Change the scales or the register in modes.py:
MAJOR = (0, 2, 4, 5, 7, 9, 11)
MINOR = (0, 2, 3, 5, 7, 8, 10)
BASS_LOW, BASS_HIGH = 28, 55 # keep inside a bass guitar's range
Drums — General MIDI kit
Frets fire the instant they are pressed; waiting for a strum makes no sense for percussion. The strum bar becomes the kick and open hat instead.
| Control | Sound | GM note |
|---|---|---|
| Strum down | Kick | 36 |
| Strum up | Open hat | 46 |
G | Snare | 38 |
R | Hi-hat | 42 |
Y | Hi tom | 50 |
B | Low tom | 45 |
O | Crash | 49 |
| zone 1 | Ride | 51 |
| zone 2 | Tambourine | 54 |
| zone 3 | Cowbell | 56 |
| zone 4 | Clap | 39 |
| zone 5 | Splash | 55 |
Remap in modes.py by editing DRUM_FRETS,
DRUM_STRUM_DOWN, DRUM_STRUM_UP and
DRUM_TOUCH — each is a GM note number and a label.
Voices
Each mode cycles its own instrument list, so a bass line is never played on a guitar patch. Open the setlist menu with the joystick and press KEY1 to cycle the voice for the mode you are in. Numbers are GM programs.
| Mode | Voices |
|---|---|
| Guitar | clean 27, overdrive 29, distortion 30, acoustic 25, nylon 24, muted 28, jazz 26, piano 0, organ 16, strings 48 |
| Bass | finger 33, pick 34, fretless 35, slap 36, synth bs 38, upright 32 |
| Synth | piano 0, e.piano 4, saw 81, square 80, warm pad 89, organ 16, strings 48, bell 11 |
| Drums | kit 0 |
A song’s patch names a guitar voice, so it applies to
guitar mode only. To move a mode’s register, give it an octave:
Mode("Synth", ..., octave=1) raises it twelve semitones across
chords, plucks and bass notes alike.
Whammy and touch bar
On a World Tour neck these two axes are the opposite way round from what their names suggest. Measured, not assumed:
| Control | Axis | Range |
|---|---|---|
| Whammy bar | ABS_HAT1X | 0–15, rests at 0 |
| Touch bar | ABS_HAT0X | 4–31, and 15 means not touched |
If your controller differs, run the bundled test rather than guessing — it prompts on the LCD and reports which axis each control actually moved:
sudo systemctl stop wiiguitar
python3 axistest.py
sudo systemctl start wiiguitar
Intensity is floored so releasing the whammy returns to normal, never to silence: velocity runs from your configured value to 127, CC11 expression from 100 to 127, and CC74 brightness from 64 (neutral) to 127. CC11 is deliberately floored — it is MIDI Expression, where 0 mutes the channel outright.
Controls
| Control | Play view | Setlist menu | Chord editor |
|---|---|---|---|
| Joystick up / down | Previous / next song | Scroll | Select fret slot |
| Joystick left / right | Transpose a semitone | — | Change chord root |
| Joystick press | Open setlist | Pick song | Cycle quality |
| KEY1 | Cycle instrument mode | Cycle voice | Cycle quality |
| KEY2 | Open chord editor | Pick song | Save |
| KEY3 | Panic — all notes off | Back | Revert / back |
On the guitar itself, + and − transpose while
you play.
Things that will bite you
Three Bluetooth problems each independently stop a Wii Remote from working, and none of them give an obvious error. The installer handles all three; they are listed here because they are miserable to rediscover.
bluez-firmwareis not optional. Without it the BCM43430A1 runs unpatched and reports a placeholder MAC ofAA:AA:AA:AA:AA:AA. Confirm withdmesg | grep BCMthat it shows….hcd’ Patch.ClassicBondedOnly=falsein/etc/bluetooth/input.conf. A Wii Remote paired with 1+2 never bonds — its PIN is a raw binary value the standard BlueZ agent cannot supply — and BlueZ ≥5.65 then refuses the HID connection. The symptom is maddening: the controller reads as connected and no input device ever appears.- A Wii Remote never reconnects on its own. Because it does not bond, the
host has to dial it. That is what
wiiguitar-connect.serviceis for — press 1+2 and it connects within about five seconds.
Also worth knowing: hid-wiimote exposes five input devices
for one controller, and the base “Nintendo Wii Remote” also has five
or more buttons. Matching the wrong one gives you a bridge that looks connected
and does nothing.
How it fits together
bridge.py the application: reads the guitar, drives the screen, sends MIDI
modes.py instrument modes, drum map, bass scales, voices
chords.py chord table, qualities, voicings
songs.py setlist loading and per-song fret assignments
ui.py the 128x128 screen
hat.py ST7735S display driver and joystick/button input
backends.py output: raw USB MIDI, a local FluidSynth, or silent
axistest.py identifies whammy vs touch bar on your controller
pair.sh Bluetooth pairing, including the fixes above
The bridge idles at about 0.3% CPU on a Zero 2 W. Reading the HAT’s eight buttons through libgpiod edge events rather than a polling library is most of why — the obvious alternatives cost 7–8% forever.