Skip to content

Commit

Permalink
wip bgm encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
bates64 committed Sep 23, 2022
1 parent 5d10d2e commit 0b9e3a6
Show file tree
Hide file tree
Showing 14 changed files with 56,547 additions and 72 deletions.
17 changes: 17 additions & 0 deletions mamar-wasm-bridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ pub fn bgm_decode(data: &[u8]) -> JsValue {
}
}

#[wasm_bindgen]
pub fn bgm_encode(bgm: &JsValue) -> JsValue {
let bgm: Bgm = from_js(bgm);
let mut f = Cursor::new(Vec::new());
match bgm.encode(&mut f) {
Ok(_) => {
let data: Vec<u8> = f.into_inner();
let arr = js_sys::Uint8Array::new_with_length(data.len() as u32);
for (i, v) in data.into_iter().enumerate() {
arr.set_index(i as u32, v);
}
arr.into()
},
Err(e) => e.to_string().into(),
}
}

#[wasm_bindgen]
pub fn sbn_decode(rom: &[u8]) -> JsValue {
const SBN_START: usize = 0xF00000;
Expand Down
2 changes: 2 additions & 0 deletions mamar-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"@adobe/react-spectrum": "^3.21.2",
"@spectrum-icons/workflow": "^4.0.2",
"@types/stats.js": "^0.17.0",
"browser-fs-access": "^0.31.0",
"idb-keyval": "^6.2.0",
"mamar-wasm-bridge": "*",
Expand All @@ -24,6 +25,7 @@
"react-dom": "^18.2.0",
"react-stately": "^3.17.0",
"react-tracked": "^1.7.10",
"stats.js": "^0.17.0",
"use-undoable": "^3.3.11"
},
"devDependencies": {
Expand Down
193 changes: 170 additions & 23 deletions mamar-web/src/app/emu/PlaybackControls.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,215 @@
import { ActionButton, Flex, ToggleButton, View } from "@adobe/react-spectrum"
import Play from "@spectrum-icons/workflow/Play"
import { bgm_encode } from "mamar-wasm-bridge"
import { EmulatorControls } from "mupen64plus-web"
import * as patches from "patches"
import { Bgm } from "pm64-typegen"
import { useEffect, useMemo, useState } from "react"
import { useEffect, useRef, useState } from "react"

import { useBgm } from "../store"
import useMupen from "../util/hooks/useMupen"
import useRomData from "../util/hooks/useRomData"
import Patcher from "../util/Patcher"

import "./PlaybackControls.scss"

function usePatchedRom(bgm: Bgm | undefined): ArrayBuffer | undefined {
const cleanRom = useRomData()
class DramView {
u8: Uint8Array

return useMemo(() => {
if (!bgm) {
return undefined
constructor(mupen: EmulatorControls) {
this.u8 = mupen.getDram()
}

readU8(address: number) {
address = address & 0x00FFFFFF
return this.u8[address]
}

writeU8(address: number, data: Uint8Array | number) {
address = address & 0x00FFFFFF
if (typeof data === "number") {
this.u8[address] = data
} else {
for (let i = 0; i < data.length; i++) {
this.u8[address + i] = data[i]
}
}
}

readU32(address: number) {
address = address & 0x00FFFFFF
return this.u8[address] | (this.u8[address + 1] << 8) | (this.u8[address + 2] << 16) | (this.u8[address + 3] << 24)
}

readU32Byteswapped(address: number) {
address = address & 0x00FFFFFF
return (this.u8[address] << 24) | (this.u8[address + 1] << 16) | (this.u8[address + 2] << 8) | this.u8[address + 3]
}

writeU32Byteswapped(address: number, data: Uint8Array | number) {
address = address & 0x00FFFFFF

if (typeof data === "number") {
this.u8[address] = (data >> 24) & 0xFF
this.u8[address + 1] = (data >> 16) & 0xFF
this.u8[address + 2] = (data >> 8) & 0xFF
this.u8[address + 3] = data & 0xFF
} else {
// Convert to Uint32Array
if (data.length % 4 !== 0) {
throw new Error("data length must be a multiple of 4")
}
const u32 = new Uint32Array(data.length / 4)

for (let i = 0; i < u32.length; i++) {
u32[i] = (data[i * 4] << 24)
| (data[i * 4 + 1] << 16)
| (data[i * 4 + 2] << 8)
| data[i * 4 + 3]
}

// Byte swap
for (let i = 0; i < u32.length; i++) {
u32[i] = ((u32[i] & 0xff000000) >> 24) | ((u32[i] & 0x00ff0000) >> 8) | ((u32[i] & 0x0000ff00) << 8) | ((u32[i] & 0x000000ff) << 24)
}

// Write u32
for (let i = 0; i < u32.length; i++) {
this.u8[address + i * 4] = (u32[i] >> 24) & 0xFF
this.u8[address + i * 4 + 1] = (u32[i] >> 16) & 0xFF
this.u8[address + i * 4 + 2] = (u32[i] >> 8) & 0xFF
this.u8[address + i * 4 + 3] = u32[i] & 0xFF
}
}
}

writeU32(address: number, data: Uint32Array | number) {
address = address & 0x00FFFFFF
if (typeof data === "number") {
this.u8[address] = data & 0xFF
this.u8[address + 1] = (data >> 8) & 0xFF
this.u8[address + 2] = (data >> 16) & 0xFF
this.u8[address + 3] = (data >> 24) & 0xFF
} else {
for (let i = 0; i < data.length; i++) {
this.u8[address + i * 4] = data[i] & 0xFF
this.u8[address + i * 4 + 1] = (data[i] >> 8) & 0xFF
this.u8[address + i * 4 + 2] = (data[i] >> 16) & 0xFF
this.u8[address + i * 4 + 3] = (data[i] >> 24) & 0xFF
}
}
}
}

function writePatches(mupen: EmulatorControls) {
const dram = new DramView(mupen)

dram.writeU32(patches.RAM_state_step_logos, patches.ASM_PATCH_state_step_logos)
dram.writeU32(patches.RAM_PATCH_state_step_logos, patches.ASM_PATCH_state_step_logos)

dram.writeU32(patches.RAM_state_step_title_screen, patches.ASM_PATCH_state_step_title_screen)
dram.writeU32(patches.RAM_PATCH_state_step_title_screen, patches.ASM_PATCH_state_step_title_screen)

dram.writeU32(patches.RAM_appendGfx_title_screen, patches.ASM_PATCH_appendGfx_title_screen)
dram.writeU32(patches.RAM_PATCH_appendGfx_title_screen, patches.ASM_PATCH_appendGfx_title_screen)

dram.writeU32(patches.RAM_au_load_song_files, patches.ASM_PATCH_au_load_song_files)
dram.writeU32(patches.RAM_PATCH_au_load_song_files, patches.ASM_PATCH_au_load_song_files)

dram.writeU32(patches.RAM_MAMAR_au_load_song_files, patches.ASM_MAMAR_au_load_song_files)
}

const rom = cleanRom.slice(0)
//const patcher = new Patcher(rom)
//patcher.overwriteFunction(0xF4A4, patches.skipIntroLogos)
//patcher.overwriteFunction(0x10, [0x1B99678D, 0xE109577C])
let songId = 0

// TODO: encode bgm, write over Toad Town in SBN
function writeBgm(mupen: EmulatorControls, bgm: Bgm) {
const bgmBin: Uint8Array | string = bgm_encode(bgm)

return rom
}, [cleanRom, bgm])
if (bgmBin instanceof Uint8Array) {
const dram = new DramView(mupen)

if (bgmBin.length > 0x20000) {
throw new Error(`Encoded BGM too large, ${bgmBin.length} > 0x20000 bytes`)
}

console.log(`Writing BGM to ${patches.RAM_MAMAR_bgm.toString(16)}`)
dram.writeU32Byteswapped(patches.RAM_MAMAR_bgm, bgmBin)
dram.writeU32(patches.RAM_MAMAR_bgm_size, bgmBin.length)
dram.writeU32(patches.RAM_MAMAR_bk_files, new Uint32Array([0, 0, 0]))
dram.writeU32(patches.RAM_MAMAR_song_id, songId++)
dram.writeU32(patches.RAM_MAMAR_song_variation, 0)
dram.writeU32(patches.RAM_MAMAR_ambient_sounds, 6) // AMBIENT_SILENCE
} else {
throw new Error(bgmBin)
}
}

export default function PlaybackControls() {
const [bgm] = useBgm()
const [isPlaying, setIsPlaying] = useState(false)
const romData = usePatchedRom(isPlaying ? bgm : undefined)
const mupen = useMupen(romData)
const romData = useRomData()
const bpmRef = useRef<HTMLSpanElement | null>(null)
const mupen = useMupen(bgm ? romData : undefined, () => {
if (!mupen || !bgm)
return

const ram = new DramView(mupen)

if (bpmRef.current) {
const bpm = ram.readU32(patches.RAM_MAMAR_out_masterTempo) / 100
bpmRef.current.innerText = `${bpm} BPM`
}
})

useEffect(() => {
if (!mupen || !bgm)
return

if (isPlaying) {
mupen?.resume?.()
writePatches(mupen)
//writeBgm(mupen, bgm)
mupen.resume()
} else {
mupen?.pause?.()
mupen.pause()
}
}, [mupen, bgm, isPlaying])

useEffect(() => {
const onKeydown = (event: KeyboardEvent) => {
if (event.key === " ") {
setIsPlaying(!isPlaying)
setIsPlaying(p => !p)
}
}
document.addEventListener("keydown", onKeydown)
return () => document.removeEventListener("keydown", onKeydown)
}, [mupen, isPlaying])
}, [])

/*useEffect(() => {
if (!mupen || !bgm)
return
const i = setInterval(() => {
writeBgm(mupen, bgm)
}, 4000)
return () => clearInterval(i)
}, [mupen, bgm])*/

if (!bgm) {
return <View />
}

return <Flex alignItems="center">
return <Flex alignItems="center" gap="size-50">
<span ref={bpmRef}></span>

<ActionButton
onPress={() => {
mupen?.softReset()
if (mupen) {
mupen.pause()
writePatches(mupen)
writeBgm(mupen, bgm)
mupen.resume()
}
}}
>
{"<-" /* TODO: icon */}
{"Compile"}
</ActionButton>

<ToggleButton
Expand Down
2 changes: 1 addition & 1 deletion mamar-web/src/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<link rel="icon" href="../mamar-flat.svg" />
</head>
<body class="no-scroll">
<canvas id="canvas"></canvas>
<canvas id="canvas" width="1" height="1"></canvas>

<div id="root">
<main class="initial-load-container" aria-label="Loading">
Expand Down
19 changes: 16 additions & 3 deletions mamar-web/src/app/util/hooks/useMupen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import createMupen64PlusWeb, { EmulatorControls } from "mupen64plus-web"
import { useEffect, useRef, useState } from "react"
import Stats from "stats.js"

enum State {
MOUNTING,
Expand All @@ -9,11 +10,19 @@ enum State {
RELOADING,
}

export default function useMupen(romData: ArrayBuffer | undefined): EmulatorControls | undefined {
const stats = new Stats()
stats.showPanel(0)
stats.dom.style.top = "auto"
stats.dom.style.bottom = "0"

export default function useMupen(romData: ArrayBuffer | undefined, vi: () => void): EmulatorControls | undefined {
const [mupen, setMupen] = useState<EmulatorControls>()
const [error, setError] = useState<any>()
const state = useRef(State.MOUNTING)

const viRef = useRef(vi)
viRef.current = vi

useEffect(() => {
if (!romData) {
mupen?.pause?.()
Expand All @@ -27,8 +36,11 @@ export default function useMupen(romData: ArrayBuffer | undefined): EmulatorCont
createMupen64PlusWeb({
canvas: document.getElementById("canvas") as HTMLCanvasElement,
romData,
beginStats: () => {},
endStats: () => {},
beginStats: () => stats.begin(),
endStats: () => {
stats.end()
viRef.current()
},
coreConfig: {
emuMode: 0,
},
Expand All @@ -48,6 +60,7 @@ export default function useMupen(romData: ArrayBuffer | undefined): EmulatorCont
if (mupen) {
await mupen.start()
state.current = State.STARTED
document.body.appendChild(stats.dom)
setMupen(mupen)
}
}).catch(error => {
Expand Down
2 changes: 1 addition & 1 deletion mamar-web/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ body {
}

#canvas {
//display: none;
display: none;
}

.flex-grow {
Expand Down
3 changes: 2 additions & 1 deletion mamar-web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"jsx": "preserve",
"moduleResolution": "node",
"module": "ES2020",
"resolveJsonModule": true
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true
},
}
Loading

0 comments on commit 0b9e3a6

Please sign in to comment.