Wasm improvments
Former-commit-id: f51fc18327f6adb0011ff2aff2787d513fb6aa37
This commit is contained in:
parent
0d8a5467e0
commit
90032373a8
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1046,6 +1046,7 @@ dependencies = [
|
||||||
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustyline 6.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustyline 6.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1060,7 +1061,6 @@ dependencies = [
|
||||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jni 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"jni 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rustboyadvance-core 0.1.0",
|
"rustboyadvance-core 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ crate-type = ["staticlib", "cdylib"]
|
||||||
rustboyadvance-core = {path = "../../rustboyadvance-core/"}
|
rustboyadvance-core = {path = "../../rustboyadvance-core/"}
|
||||||
jni = { version = "0.16", default-features = false }
|
jni = { version = "0.16", default-features = false }
|
||||||
log = {version = "0.4.8", features = ["release_max_level_info", "max_level_debug"]}
|
log = {version = "0.4.8", features = ["release_max_level_info", "max_level_debug"]}
|
||||||
ringbuf = "0.2.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os="android")'.dependencies]
|
[target.'cfg(target_os="android")'.dependencies]
|
||||||
android_log = "0.1.3"
|
android_log = "0.1.3"
|
||||||
|
|
|
@ -20,24 +20,9 @@ use android_log;
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
use env_logger;
|
use env_logger;
|
||||||
|
|
||||||
use ringbuf::{Consumer, Producer, RingBuffer};
|
|
||||||
|
|
||||||
use rustboyadvance_core::prelude::*;
|
use rustboyadvance_core::prelude::*;
|
||||||
use rustboyadvance_core::StereoSample;
|
use rustboyadvance_core::StereoSample;
|
||||||
|
use rustboyadvance_core::util::audio::AudioRingBuffer;
|
||||||
struct AudioRingBuffer {
|
|
||||||
pub prod: Producer<i16>,
|
|
||||||
pub cons: Consumer<i16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AudioRingBuffer {
|
|
||||||
fn new() -> AudioRingBuffer {
|
|
||||||
let rb = RingBuffer::new(4096 * 2);
|
|
||||||
let (prod, cons) = rb.split();
|
|
||||||
|
|
||||||
AudioRingBuffer { prod, cons }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Hardware {
|
struct Hardware {
|
||||||
jvm: JavaVM,
|
jvm: JavaVM,
|
||||||
|
|
|
@ -45,6 +45,7 @@ features = [
|
||||||
'WebGlProgram',
|
'WebGlProgram',
|
||||||
'WebGlShader',
|
'WebGlShader',
|
||||||
'Window',
|
'Window',
|
||||||
|
'AudioContext',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,93 +7,120 @@
|
||||||
<title>RustBoyAdvance</title>
|
<title>RustBoyAdvance</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: #675ea7;
|
background-color: #2c2946;
|
||||||
font-family: "Courier New", Courier, monospace;
|
font-family: Source Code, Roboto, Monospace;
|
||||||
|
margin: 0;
|
||||||
|
color: azure;
|
||||||
}
|
}
|
||||||
#logo {
|
header {
|
||||||
text-align: center;
|
padding: 1em;
|
||||||
align-items: center;
|
padding-top: 6em;
|
||||||
justify-content: center;
|
background-color: #13121d;
|
||||||
|
background-image: url("icon.svg");
|
||||||
}
|
background-repeat: no-repeat;
|
||||||
#menu {
|
background-position: center;
|
||||||
background-color: #423c6c;
|
background-size: 400px;
|
||||||
display: flexbox;
|
display: flex;
|
||||||
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
|
|
||||||
top: 0.2em;
|
|
||||||
left: 0.2em;
|
|
||||||
padding: .1em;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
#menu .fileInput {
|
|
||||||
background-color: #675ea7;
|
|
||||||
margin: .4em;
|
|
||||||
}
|
|
||||||
#menu button {
|
|
||||||
background-color: #736ab1;
|
|
||||||
box-shadow: 1px 1px 2px rgba(255, 18, 148, 0.2);
|
|
||||||
font-size: .8em;
|
|
||||||
border-radius: .15em;
|
|
||||||
margin: .4em;
|
|
||||||
}
|
|
||||||
#canvas-container {
|
|
||||||
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position:fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 70%;
|
|
||||||
height: 70%;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: #423c6c;
|
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
#canvas-container.hover {
|
header h1 {
|
||||||
|
color: azure;
|
||||||
|
padding: .1em;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
#menu {
|
||||||
|
padding: .1em;
|
||||||
|
}
|
||||||
|
#menu ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
#menu li {
|
||||||
|
margin: 0.2em;
|
||||||
|
}
|
||||||
|
#playarea {
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
display: flexbox;
|
||||||
|
}
|
||||||
|
#playarea.hover {
|
||||||
border: rgba(9, 250, 0, 0.671);
|
border: rgba(9, 250, 0, 0.671);
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
border-width: 0.1em;
|
border-width: 0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#game-options {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#game-options > * {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.2em;
|
||||||
|
}
|
||||||
#screen {
|
#screen {
|
||||||
text-align: center;
|
display: block;
|
||||||
margin: auto;
|
max-width: 960px;
|
||||||
width: 70%;
|
width: 80%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
image-rendering: optimizeSpeed;
|
image-rendering: optimizeSpeed;
|
||||||
image-rendering: -moz-crisp-edges;
|
image-rendering: -moz-crisp-edges;
|
||||||
image-rendering: -webkit-crisp-edges;
|
image-rendering: -webkit-crisp-edges;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
image-rendering: crisp-edges;
|
image-rendering: crisp-edges;
|
||||||
|
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
|
||||||
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
.hidden {
|
|
||||||
display: none,
|
#screen.hover {
|
||||||
|
border: rgba(9, 250, 0, 0.671);
|
||||||
|
border-style: dashed;
|
||||||
|
border-width: 0.1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
|
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
|
||||||
<div id="menu">
|
<header>
|
||||||
<div class="fileInput">
|
<div id="logo">
|
||||||
<label> BIOS </label>
|
<h1>RustBoyAdvance</h1>
|
||||||
<input type="file" id="bios-file-input" />
|
|
||||||
</div>
|
</div>
|
||||||
<button id="reloadBios" class="hidden">Reload Bios</button>
|
</header>
|
||||||
<div class="fileInput">
|
<section id="menu">
|
||||||
<label> ROM </label>
|
<ul>
|
||||||
<input type="file" id="rom-file-input" />
|
<li class="fileInput">
|
||||||
</div>
|
<label> BIOS </label>
|
||||||
<button id="startEmulator">Start</button>
|
<input type="file" id="bios-file-input" />
|
||||||
<button id="fps-test">FPS Test</button>
|
</li>
|
||||||
<label>Skip Bios</label>
|
<li><button id="reloadBios" class="hidden">Reload Bios</button></li>
|
||||||
<input type="checkbox" id="skipBios">
|
<li class="fileInput">
|
||||||
</div>
|
<label> ROM </label>
|
||||||
<div id="canvas-container">
|
<input type="file" id="rom-file-input" />
|
||||||
<pre id="fps"></pre>
|
</li>
|
||||||
|
<li><button id="startEmulator">Start</button></li>
|
||||||
|
<li><button id="fps-test">FPS Test</button></li>
|
||||||
|
<li>
|
||||||
|
<label>Skip Bios</label>
|
||||||
|
<input type="checkbox" id="skipBios">
|
||||||
|
</li>
|
||||||
|
<pre> You can also drag rom files directly into the screen </pre>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section id="playarea">
|
||||||
|
<pre> Built with WASM</pre>
|
||||||
<canvas id="screen" width="240px" , height="160px"></canvas>
|
<canvas id="screen" width="240px" , height="160px"></canvas>
|
||||||
<img id="logo" width="240px" src="icon.svg"></img>
|
<section id="game-options">
|
||||||
</div>
|
<span id="fps">FPS</span>
|
||||||
|
<label>
|
||||||
|
<label>Max FPS</label>
|
||||||
|
<input type="checkbox" id="maxFps">
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<footer></footer>
|
||||||
<script src="./bootstrap.js"></script>
|
<script src="./bootstrap.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as wasm from "rustboyadvance-wasm";
|
import * as wasm from "rustboyadvance-wasm";
|
||||||
|
|
||||||
|
var fps_text = document.getElementById('fps');
|
||||||
var canvas = document.getElementById("screen");
|
var canvas = document.getElementById("screen");
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d');
|
||||||
var intervalId = 0;
|
var intervalId = 0;
|
||||||
|
@ -37,6 +38,68 @@ function ensureFilesLoaded() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const convertAudioBuffer = buffer => {
|
||||||
|
let length = buffer.length;
|
||||||
|
const floatArray = new Float32Array(length);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
floatArray[i] = (buffer[i] - 32767) / 32767;
|
||||||
|
}
|
||||||
|
return floatArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fpsCounter = (function() {
|
||||||
|
var lastLoop = (new Date).getMilliseconds();
|
||||||
|
var count = 0;
|
||||||
|
var fps = 0;
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
var currentLoop = (new Date).getMilliseconds();
|
||||||
|
if (lastLoop > currentLoop) {
|
||||||
|
fps = count;
|
||||||
|
count = 0;
|
||||||
|
} else {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
lastLoop = currentLoop;
|
||||||
|
return fps;
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
// Create our audio context
|
||||||
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
console.log("audio context " + audioContext);
|
||||||
|
|
||||||
|
const playAudio = emulator => {
|
||||||
|
let audioData = emulator.collect_audio_samples();
|
||||||
|
|
||||||
|
let frameCount = audioData.length / 2;
|
||||||
|
const audioBuffer = audioContext.createBuffer(
|
||||||
|
2,
|
||||||
|
frameCount,
|
||||||
|
audioContext.sampleRate
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let channel = 0; channel < 2; channel++) {
|
||||||
|
let nowBuffering = audioBuffer.getChannelData(channel);
|
||||||
|
for (let i = 0; i < frameCount; i++) {
|
||||||
|
// audio data frames are interleaved
|
||||||
|
nowBuffering[i] = audioData[i*2 + channel];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioSource = audioContext.createBufferSource();
|
||||||
|
audioSource.buffer = audioBuffer;
|
||||||
|
|
||||||
|
audioSource.connect(audioContext.destination);
|
||||||
|
audioSource.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
const emulatorLoop = function() {
|
||||||
|
emulator.run_frame(ctx);
|
||||||
|
fps_text.innerHTML = fpsCounter();
|
||||||
|
playAudio(emulator);
|
||||||
|
}
|
||||||
|
|
||||||
function startEmulator() {
|
function startEmulator() {
|
||||||
if (!ensureFilesLoaded()) {
|
if (!ensureFilesLoaded()) {
|
||||||
return;
|
return;
|
||||||
|
@ -55,29 +118,7 @@ function startEmulator() {
|
||||||
emulator.skip_bios();
|
emulator.skip_bios();
|
||||||
}
|
}
|
||||||
|
|
||||||
var fpsCounter = (function() {
|
intervalId = setInterval(emulatorLoop, 16);
|
||||||
var lastLoop = (new Date).getMilliseconds();
|
|
||||||
var count = 0;
|
|
||||||
var fps = 0;
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
var currentLoop = (new Date).getMilliseconds();
|
|
||||||
if (lastLoop > currentLoop) {
|
|
||||||
fps = count;
|
|
||||||
count = 0;
|
|
||||||
} else {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
lastLoop = currentLoop;
|
|
||||||
return fps;
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
|
|
||||||
let fps_text = document.getElementById('fps')
|
|
||||||
intervalId = setInterval(function() {
|
|
||||||
emulator.run_frame(ctx);
|
|
||||||
fps_text.innerHTML = fpsCounter();
|
|
||||||
}, 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const biosCached = localStorage.getItem("biosCached");
|
const biosCached = localStorage.getItem("biosCached");
|
||||||
|
@ -129,7 +170,7 @@ function loadRom(romFile) {
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
let dropArea = document.getElementById('canvas-container');
|
let dropArea = document.getElementById('screen');
|
||||||
|
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||||
dropArea.addEventListener(eventName,
|
dropArea.addEventListener(eventName,
|
||||||
|
@ -169,22 +210,26 @@ document.getElementById("startEmulator").addEventListener('click', e => {
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
['keydown', 'keyup'].forEach(eventName => {
|
document.getElementById("maxFps").addEventListener('change', e => {
|
||||||
window.addEventListener(eventName,
|
if (intervalId != 0) {
|
||||||
e => {
|
let checked = e.target.checked;
|
||||||
// prevent default events
|
clearInterval(intervalId);
|
||||||
e.preventDefault();
|
if (checked) {
|
||||||
e.stopPropagation();
|
intervalId = setInterval(emulatorLoop, 0);
|
||||||
}, false)
|
} else {
|
||||||
});
|
intervalId = setInterval(emulatorLoop, 16);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", e => {
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener("keydown", e => {
|
||||||
if (null != emulator) {
|
if (null != emulator) {
|
||||||
emulator.key_down(e.key)
|
emulator.key_down(e.key)
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
window.addEventListener("keyup", e => {
|
document.addEventListener("keyup", e => {
|
||||||
if (null != emulator) {
|
if (null != emulator) {
|
||||||
emulator.key_up(e.key)
|
emulator.key_up(e.key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,15 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen::Clamped;
|
use wasm_bindgen::Clamped;
|
||||||
|
|
||||||
|
use js_sys::Float32Array;
|
||||||
|
|
||||||
use web_sys::CanvasRenderingContext2d;
|
use web_sys::CanvasRenderingContext2d;
|
||||||
|
use web_sys::AudioContext;
|
||||||
|
|
||||||
use rustboyadvance_core::core::keypad as gba_keypad;
|
use rustboyadvance_core::core::keypad as gba_keypad;
|
||||||
use rustboyadvance_core::prelude::*;
|
use rustboyadvance_core::prelude::*;
|
||||||
|
use rustboyadvance_core::util::audio::AudioRingBuffer;
|
||||||
|
|
||||||
use bit::BitIndex;
|
use bit::BitIndex;
|
||||||
|
|
||||||
|
@ -19,6 +24,29 @@ pub struct Emulator {
|
||||||
struct Interface {
|
struct Interface {
|
||||||
frame: Vec<u8>,
|
frame: Vec<u8>,
|
||||||
keyinput: u16,
|
keyinput: u16,
|
||||||
|
sample_rate: i32,
|
||||||
|
audio_ctx: AudioContext,
|
||||||
|
audio_ring_buffer: AudioRingBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Interface {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.audio_ctx.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interface {
|
||||||
|
fn new(audio_ctx: AudioContext) -> Result<Interface, JsValue> {
|
||||||
|
Ok(
|
||||||
|
Interface {
|
||||||
|
frame: vec![0; 240 * 160 * 4],
|
||||||
|
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
|
||||||
|
sample_rate: audio_ctx.sample_rate() as i32,
|
||||||
|
audio_ctx: audio_ctx,
|
||||||
|
audio_ring_buffer: AudioRingBuffer::new(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoInterface for Interface {
|
impl VideoInterface for Interface {
|
||||||
|
@ -34,7 +62,21 @@ impl VideoInterface for Interface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioInterface for Interface {}
|
fn convert_sample(s: i16) -> f32 {
|
||||||
|
((s as f32) / 32767_f32)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioInterface for Interface {
|
||||||
|
fn get_sample_rate(&self) -> i32 {
|
||||||
|
self.sample_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_sample(&mut self, samples: StereoSample<i16>) {
|
||||||
|
self.audio_ring_buffer.prod.push(samples.0).unwrap();
|
||||||
|
self.audio_ring_buffer.prod.push(samples.1).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl InputInterface for Interface {
|
impl InputInterface for Interface {
|
||||||
fn poll(&mut self) -> u16 {
|
fn poll(&mut self) -> u16 {
|
||||||
|
@ -45,18 +87,16 @@ impl InputInterface for Interface {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Emulator {
|
impl Emulator {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(bios: &[u8], rom: &[u8]) -> Emulator {
|
pub fn new(bios: &[u8], rom: &[u8]) -> Result<Emulator, JsValue> {
|
||||||
|
let audio_ctx = web_sys::AudioContext::new()?;
|
||||||
|
let interface = Rc::new(RefCell::new(Interface::new(audio_ctx)?));
|
||||||
|
|
||||||
let gamepak = GamepakBuilder::new()
|
let gamepak = GamepakBuilder::new()
|
||||||
.take_buffer(rom.to_vec().into_boxed_slice())
|
.take_buffer(rom.to_vec().into_boxed_slice())
|
||||||
.without_backup_to_file()
|
.without_backup_to_file()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let interface = Rc::new(RefCell::new(Interface {
|
|
||||||
frame: vec![0; 240 * 160 * 4],
|
|
||||||
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
|
|
||||||
}));
|
|
||||||
|
|
||||||
let gba = GameBoyAdvance::new(
|
let gba = GameBoyAdvance::new(
|
||||||
bios.to_vec().into_boxed_slice(),
|
bios.to_vec().into_boxed_slice(),
|
||||||
gamepak,
|
gamepak,
|
||||||
|
@ -65,7 +105,7 @@ impl Emulator {
|
||||||
interface.clone(),
|
interface.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Emulator { gba, interface }
|
Ok( Emulator { gba, interface } )
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_bios(&mut self) {
|
pub fn skip_bios(&mut self) {
|
||||||
|
@ -129,4 +169,16 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn collect_audio_samples(&self) -> Result<Float32Array, JsValue> {
|
||||||
|
let mut interface = self.interface.borrow_mut();
|
||||||
|
|
||||||
|
let consumer = &mut interface.audio_ring_buffer.cons;
|
||||||
|
let mut samples = Vec::with_capacity(consumer.len());
|
||||||
|
while let Some(sample) = consumer.pop() {
|
||||||
|
samples.push(convert_sample(sample));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Float32Array::from(samples.as_slice()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ arrayvec = "0.5.1"
|
||||||
rustyline = {version = "6.0.0", optional = true}
|
rustyline = {version = "6.0.0", optional = true}
|
||||||
nom = {version = "5.0.0", optional = true}
|
nom = {version = "5.0.0", optional = true}
|
||||||
gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
|
gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
|
||||||
|
ringbuf = "0.2.1"
|
||||||
|
|
||||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||||
instant = { version = "0.1.2", features = [ "wasm-bindgen" ] }
|
instant = { version = "0.1.2", features = [ "wasm-bindgen" ] }
|
||||||
|
|
|
@ -65,5 +65,5 @@ pub mod prelude {
|
||||||
#[cfg(feature = "debugger")]
|
#[cfg(feature = "debugger")]
|
||||||
pub use super::debugger::Debugger;
|
pub use super::debugger::Debugger;
|
||||||
pub use super::util::{read_bin_file, write_bin_file};
|
pub use super::util::{read_bin_file, write_bin_file};
|
||||||
pub use super::{AudioInterface, InputInterface, VideoInterface};
|
pub use super::{AudioInterface, StereoSample, InputInterface, VideoInterface};
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,3 +127,22 @@ macro_rules! host_breakpoint {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub mod audio {
|
||||||
|
use ringbuf::{Consumer, Producer, RingBuffer};
|
||||||
|
|
||||||
|
pub struct AudioRingBuffer {
|
||||||
|
pub prod: Producer<i16>,
|
||||||
|
pub cons: Consumer<i16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioRingBuffer {
|
||||||
|
pub fn new() -> AudioRingBuffer {
|
||||||
|
let rb = RingBuffer::new(4096 * 2);
|
||||||
|
let (prod, cons) = rb.split();
|
||||||
|
|
||||||
|
AudioRingBuffer { prod, cons }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue