Last active
December 8, 2025 20:42
-
-
Save windwakr/9a2c1d5a8f0806f00a2a67a02a1ae665 to your computer and use it in GitHub Desktop.
generates wav files for use with Wappy Dog (Wappy --> DS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Generates wav files for Wappy Dog for Wappy-->DS communication | |
| # Sources: US8814627B2 / Game RE | |
| # The game samples the mic at 33,513,982 / 1519 = ~22063Hz | |
| import itertools | |
| import math | |
| import wave | |
| import os | |
| path = "./wappy wavs" | |
| if not os.path.exists(path): | |
| os.makedirs(path) | |
| def genSquare(rate, freq, length): | |
| wave = [] | |
| for i in range(int(length * rate)): | |
| sample = math.sin(2.0 * freq * math.pi * i / rate) | |
| if sample > 0.0: | |
| sample = 128+32 | |
| else: | |
| sample = 128-32 | |
| wave.append(sample) | |
| return bytearray(wave) | |
| def genSilence(rate, length): | |
| wave = [0x80] * int(length * rate) | |
| return bytearray(wave) | |
| sampleRate = 22063 | |
| first = genSquare(sampleRate, 2375, 0.05) | |
| second = genSquare(sampleRate, 2475, 0.05) | |
| third = genSquare(sampleRate, 2675, 0.05) | |
| fourth = genSquare(sampleRate, 2775, 0.05) | |
| fifth = genSquare(sampleRate, 2975, 0.05) | |
| toneList = [first, second, third, fourth, fifth] | |
| permutations = list(itertools.product([1, 2, 3, 4, 5], repeat=3)) | |
| for perm in permutations: | |
| with wave.open("%s/%d_%d_%d.wav" % (path, perm[0], perm[1], perm[2]), "wb") as af: | |
| arr = bytearray() | |
| # Output is wrapped in short silence | |
| # short 0.1 second silence between the 0.05 second tones | |
| arr.extend(genSilence(sampleRate, 0.25)) | |
| arr.extend(toneList[perm[0]-1]) | |
| arr.extend(genSilence(sampleRate, 0.1)) | |
| arr.extend(toneList[perm[1]-1]) | |
| arr.extend(genSilence(sampleRate, 0.1)) | |
| arr.extend(toneList[perm[2]-1]) | |
| arr.extend(genSilence(sampleRate, 0.25)) | |
| af.setsampwidth(1) | |
| af.setnchannels(1) | |
| af.setframerate(sampleRate) | |
| af.writeframesraw(arr) |
Author
Author
Looking more at the game, it performs an FFT on the mic data, does something, then if it detects a valid signal it writes a 32-bit value(1-125) to 0x02088E60. If you use an emulator's memory viewer to pause the game and place a value there the game will act as if it detected input from Wappy.
Author
A Lua script for DeSmuME to play around with feeding the game inputs. Click the buttons and the game will think that command was sent from the Wappy Dog device.
2025-12-06.22-33-15.mp4
local command = 1
local cooldown = 0
local commandCooldown = 0
local downBox = {x = 03, y = 2-192, width = 13, height = 13}
local upBox = {x = 45, y = 2-192, width = 13, height = 13}
local okBox = {x = 62, y = 2-192, width = 13, height = 13}
function checkInside(x, y, box)
if x >= box.x and x < (box.x+box.width) then
if y >= box.y and y < (box.y+box.height) then
return true
end
end
return false
end
function main()
keys = input.get()
gui.box(0, 0-192, 77, 16-192, "#20202080")
gui.box(20, 2-192, 40, 14-192, "#FFFFFF", "#000000")
gui.text(21, 5-192, string.format("%3d", command))
gui.box(downBox.x, downBox.y, downBox.x+downBox.width-1, downBox.y+downBox.height-1, "#000000")
gui.line(downBox.x+2, downBox.y+2, downBox.x+6, downBox.y+downBox.height-3, "#FFFFFF")
gui.line(downBox.x+6, downBox.y+downBox.height-3, downBox.x+downBox.width-3, downBox.y+2, "#FFFFFF")
gui.box(upBox.x, upBox.y, upBox.x+upBox.width-1, upBox.y+upBox.height-1, "#000000")
gui.line(upBox.x+2, upBox.y+upBox.height-3, upBox.x+6, upBox.y+2, "#FFFFFF")
gui.line(upBox.x+6, upBox.y+2, upBox.x+upBox.width-3, upBox.y+upBox.height-3, "#FFFFFF")
gui.box(okBox.x, okBox.y, okBox.x+okBox.width-1, okBox.y+okBox.height-1, "#000000")
gui.text(okBox.x, okBox.y+3, "OK", "#FFFFFF")
if keys["leftclick"] and keys.xmouse and keys.ymouse then
if checkInside(keys.xmouse, keys.ymouse, downBox) then
if command > 1 and cooldown == 0 then
command = command - 1
cooldown = 6
end
end
if checkInside(keys.xmouse, keys.ymouse, upBox) then
if command < 125 and cooldown == 0 then
command = command + 1
cooldown = 6
end
end
if checkInside(keys.xmouse, keys.ymouse, okBox) then
if commandCooldown == 0 then
memory.writedword(0x02088E60, command)
commandCooldown = 60
end
end
end
if cooldown > 0 then cooldown = cooldown - 1 end
if commandCooldown > 0 then commandCooldown = commandCooldown - 1 end
end
emu.registerbefore(main)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Generates 125 permutations of Wappy-->DS outputs. According to the patent this is all possible commands?

Sometimes requires feeding the sample in multiple times before it's recognized. Not sure where the flaw lies. I've tried tweaking the frequencies and timings and it still happens. Real recordings seem to use 0.04 and 0.11 second timings, and XX50 frequencies.
Still need to organize and name the outputs.