Skip to content

Commit 00e8b0d

Browse files
authored
Fixes Audio from Sound Optimizations (#277)
relates to #270 Fixes some bugs introduced by the PR, that caused no audio to play, or just spurt random noise. And Also, fixed channel 1 from not fading out from the envelope. Also, fixes up logging from the `importObject`, and other general cleanup. **EDIT:** This adds an audio golden test 😄 # Examples <img width="1280" alt="screen shot 2019-02-28 at 12 01 50 am" src="https://user-images.githubusercontent.com/1448289/53550747-643e2d00-3aec-11e9-816d-9dab1e78733e.png"> <img width="1280" alt="screen shot 2019-02-28 at 12 02 04 am" src="https://user-images.githubusercontent.com/1448289/53550748-64d6c380-3aec-11e9-9cae-afba258e6ee8.png">
1 parent c84d075 commit 00e8b0d

File tree

13 files changed

+131225
-114
lines changed

13 files changed

+131225
-114
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ node_js:
77
install:
88
- npm install
99
script:
10-
- npm run prettier:lint && npm run demo:build:apps && npm test
10+
- npm run prettier:lint && npm run demo:build:apps && npm run test:accuracy:nobuild

core/debug/debug-graphics.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '../graphics/index';
1717
import { Cpu } from '../cpu/index';
1818
import { eightBitLoadFromGBMemory, Memory } from '../memory/index';
19-
import { checkBitOnByte, hexLog } from '../helpers/index';
19+
import { checkBitOnByte } from '../helpers/index';
2020

2121
// Some Simple internal getters
2222
export function getLY(): i32 {

core/helpers/index.ts

+7-21
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,14 @@ export function checkBitOnByte(bitPosition: i32, byte: i32): boolean {
5858
return (byte & (1 << bitPosition)) != 0;
5959
}
6060

61-
namespace env {
62-
export declare function log(message: string, arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32, arg5: i32): void;
63-
export declare function hexLog(arg0: i32, arg1: i32): void;
64-
export declare function performanceTimestamp(id: i32, value: i32): void;
65-
}
66-
67-
export function log(
68-
message: string,
69-
arg0: i32 = -9999,
70-
arg1: i32 = -9999,
71-
arg2: i32 = -9999,
72-
arg3: i32 = -9999,
73-
arg4: i32 = -9999,
74-
arg5: i32 = -9999
75-
): void {
76-
env.log(message, arg0, arg1, arg2, arg3, arg4, arg5);
77-
}
61+
// Declared importObject functions
62+
declare function consoleLog(arg0: i32, arg1: i32): void;
63+
declare function consoleLogTimeout(arg0: i32, arg1: i32, timeout: i32): void;
7864

79-
export function hexLog(arg0: i32, arg1: i32): void {
80-
env.hexLog(arg0, arg1);
65+
export function log(arg0: i32, arg1: i32): void {
66+
consoleLog(arg0, arg1);
8167
}
8268

83-
export function performanceTimestamp(id: i32 = -9999, value: i32 = -9999): void {
84-
env.performanceTimestamp(id, value);
69+
export function logTimeout(arg0: i32, arg1: i32, timeout: i32): void {
70+
consoleLogTimeout(arg0, arg1, timeout);
8571
}

core/interrupts/interrupts.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
loadBooleanDirectlyFromWasmMemory,
88
storeBooleanDirectlyToWasmMemory
99
} from '../memory/index';
10-
import { setBitOnByte, resetBitOnByte, checkBitOnByte, hexLog } from '../helpers/index';
10+
import { setBitOnByte, resetBitOnByte, checkBitOnByte } from '../helpers/index';
1111

1212
export class Interrupts {
1313
static masterInterruptSwitch: boolean = false;

core/portable/importObject.js

+21-31
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,31 @@
33
// Log throttling for our core
44
// The same log can't be output more than once every half second
55
let logRequest = {};
6-
let logThrottleLength = 100;
76

8-
const wasmImportObject = {
9-
env: {
10-
log: (message, arg0, arg1, arg2, arg3, arg4, arg5) => {
11-
// Grab our string
12-
var len = new Uint32Array(wasmInstance.exports.memory.buffer, message, 1)[0];
13-
var str = String.fromCharCode.apply(null, new Uint16Array(wasmInstance.exports.memory.buffer, message + 4, len));
14-
if (arg0 !== -9999) str = str.replace('$0', arg0);
15-
if (arg1 !== -9999) str = str.replace('$1', arg1);
16-
if (arg2 !== -9999) str = str.replace('$2', arg2);
17-
if (arg3 !== -9999) str = str.replace('$3', arg3);
18-
if (arg4 !== -9999) str = str.replace('$4', arg4);
19-
if (arg5 !== -9999) str = str.replace('$5', arg5);
7+
const logTimeout = (id, message, timeout) => {
8+
if (!logRequest[id]) {
9+
logRequest[id] = true;
10+
log(id, message);
11+
setTimeout(() => {
12+
delete logRequest[id];
13+
}, timeout);
14+
}
15+
};
2016

21-
console.log('[WasmBoy] ' + str);
22-
},
23-
hexLog: (arg0, arg1) => {
24-
if (!logRequest[arg0]) {
25-
// Grab our arguments, and log as hex
26-
let logString = '[WasmBoy]';
27-
if (arg0 !== -9999) logString += ` 0x${arg0.toString(16)} `;
28-
if (arg1 !== -9999) logString += ` 0x${arg1.toString(16)} `;
17+
const log = (arg0, arg1) => {
18+
// Grab our arguments, and log as hex
19+
let logString = '[WasmBoy]';
20+
if (arg0 !== -9999) logString += ` 0x${arg0.toString(16)} `;
21+
if (arg1 !== -9999) logString += ` 0x${arg1.toString(16)} `;
2922

30-
// Uncomment to unthrottle
31-
console.log(logString);
23+
console.log(logString);
24+
};
3225

33-
// Comment the lines below to disable throttle
34-
/*logRequest[arg0] = true;
35-
setTimeout(() => {
36-
console.log(logString);
37-
logRequest[arg0] = false;
38-
}, logThrottleLength);*/
39-
}
40-
}
26+
// https://github.com/AssemblyScript/assemblyscript/issues/384
27+
const wasmImportObject = {
28+
index: {
29+
consoleLog: log,
30+
consoleLogTimeout: logTimeout
4131
}
4232
};
4333

core/sound/channel1.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ export class Channel1 {
301301
// TODO: The volume envelope and sweep timers treat a period of 0 as 8.
302302
let envelopeCounter = Channel1.envelopeCounter - 1;
303303
if (envelopeCounter <= 0) {
304-
Channel1.envelopeCounter = Channel1.NRx2EnvelopePeriod;
304+
envelopeCounter = Channel1.NRx2EnvelopePeriod;
305305

306306
// When the timer generates a clock and the envelope period is NOT zero, a new volume is calculated
307307
// NOTE: There is some weiirrdd obscure behavior where zero can equal 8, so watch out for that
@@ -315,9 +315,8 @@ export class Channel1 {
315315
}
316316
Channel1.volume = volume;
317317
}
318-
} else {
319-
Channel1.envelopeCounter = envelopeCounter;
320318
}
319+
Channel1.envelopeCounter = envelopeCounter;
321320
}
322321

323322
static setFrequency(frequency: i32): void {

core/sound/sound.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ function calculateSound(numberOfCycles: i32): void {
228228
// Reset the downsample counter
229229
// Don't set to zero to catch overflowed cycles
230230
downSampleCycleCounter -= Sound.maxDownSampleCycles();
231-
Sound.downSampleCycleCounter = downSampleCycleCounter;
232231

233232
// Mix our samples
234233
let mixedSample = mixChannelSamples(channel1Sample, channel2Sample, channel3Sample, channel4Sample);
@@ -274,6 +273,8 @@ function calculateSound(numberOfCycles: i32): void {
274273
}
275274
Sound.audioQueueIndex = audioQueueIndex;
276275
}
276+
277+
Sound.downSampleCycleCounter = downSampleCycleCounter;
277278
}
278279

279280
// Inlined because closure compiler inlines

lib/audio/gbchannel.js

+4
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ export default class GbChannelWebAudio {
287287
return `data:audio/wav;base64,${base64String}`;
288288
}
289289

290+
getRecordingAsAudioBuffer() {
291+
return this.recordingAudioBuffer;
292+
}
293+
290294
_libMute() {
291295
this._setGain(0);
292296
this.libMuted = true;

lib/worker/smartworker.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export class SmartWorker {
9999
console.warn('Message dropped', message);
100100
this.removeMessageListener(messageId);
101101
reject();
102-
}, 500);
102+
}, 1000);
103103

104104
// Listen for a message with the same message id to be returned
105105
this.addMessageListener((responseMessage, messageListener) => {

rollup.getcore.js

-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ if (process.env.GET_CORE_CLOSURE) {
4141
let closureCompilerOptions = {};
4242

4343
if (process.env.CLOSURE_DEBUG) {
44-
console.log('Yoooo');
4544
closureCompilerOptions = {
4645
...closureCompilerOptions,
4746
debug: true

test/accuracy/accuracy-test.js

+59-53
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
const commonTest = require('../common-test');
33

44
// Wasm Boy library
5-
const WasmBoy = require('../../dist/wasmboy.wasm.cjs.js').WasmBoy;
5+
const WasmBoy = require('../../dist/wasmboy.wasm.cjs').WasmBoy;
66

77
// File management
88
const fs = require('fs');
99

1010
// Assertion
1111
const assert = require('assert');
1212

13+
// Golden file handling
14+
const { goldenFileCompareOrCreate, goldenImageDataArrayCompare } = require('./goldenCompare');
15+
1316
// Some Timeouts for specified test roms
1417
const TEST_ROM_DEFAULT_TIMEOUT = 7500;
1518
const TEST_ROM_TIMEOUT = {};
@@ -25,6 +28,55 @@ WasmBoy.config({
2528
isGbcEnabled: true
2629
});
2730

31+
// Audio Golden Test
32+
// TODO: Remove this with an actual accuracy
33+
// sound test
34+
describe('audio golden test', () => {
35+
// Define our wasmboy instance
36+
// Not using arrow functions, as arrow function timeouts were acting up
37+
beforeEach(function(done) {
38+
// Set a timeout of 7500, takes a while for wasm module to parse
39+
this.timeout(7500);
40+
41+
// Read the test rom a a Uint8Array and pass to wasmBoy
42+
const testRomArray = new Uint8Array(fs.readFileSync('./test/performance/testroms/back-to-color/back-to-color.gbc'));
43+
44+
WasmBoy.loadROM(testRomArray).then(done);
45+
});
46+
47+
it('should have the same audio buffer', function(done) {
48+
// Set our timeout
49+
this.timeout(TEST_ROM_DEFAULT_TIMEOUT + 2000);
50+
51+
const asyncTask = async () => {
52+
// Run some frames
53+
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
54+
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
55+
await WasmBoy._runWasmExport('clearAudioBuffer');
56+
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
57+
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
58+
59+
// Execute a few frames
60+
const memoryStart = await WasmBoy._getWasmConstant('AUDIO_BUFFER_LOCATION');
61+
const memorySize = await WasmBoy._getWasmConstant('AUDIO_BUFFER_SIZE');
62+
// - 20 to not include the overrun in the audio buffer
63+
const memory = await WasmBoy._getWasmMemorySection(memoryStart, memoryStart + memorySize - 20);
64+
65+
// Get the memory as a normal array
66+
const audioArray = [];
67+
for (let i = 0; i < memory.length; i++) {
68+
audioArray.push(memory[i]);
69+
}
70+
71+
goldenFileCompareOrCreate('./test/accuracy/sound-test.golden.output.json', audioArray);
72+
done();
73+
};
74+
asyncTask();
75+
});
76+
});
77+
78+
// Graphical Golden Test(s)
79+
// Simply screenshot the end result of the accuracy test
2880
const testRomsPath = './test/accuracy/testroms';
2981

3082
commonTest.getDirectories(testRomsPath).forEach(directory => {
@@ -74,61 +126,15 @@ commonTest.getDirectories(testRomsPath).forEach(directory => {
74126
const wasmboyOutputImageTest = async () => {
75127
await WasmBoy.pause();
76128

77-
console.log(`Checking results for the following test rom: ${directory}/${testRom}`);
129+
const testDataPath = testRom.replace('.gb', '.golden.output');
130+
const goldenFile = `${directory}/${testDataPath}`;
131+
132+
console.log(`Checking results for the following test rom: ${goldenFile}`);
78133

79134
const imageDataArray = await commonTest.getImageDataFromFrame();
80135

81-
// Output a gitignored image of the current tests
82-
const testImagePath = testRom.replace('.gb', '.current.png');
83-
await commonTest.createImageFromFrame(imageDataArray, `${directory}/${testImagePath}`);
84-
85-
// Now compare with the current array if we have it
86-
const testDataPath = testRom.replace('.gb', '.golden.output');
87-
if (fs.existsSync(`${directory}/${testDataPath}`)) {
88-
// Compare the file
89-
const goldenOuput = fs.readFileSync(`${directory}/${testDataPath}`);
90-
91-
const goldenImageDataArray = JSON.parse(goldenOuput);
92-
93-
if (goldenImageDataArray.length !== imageDataArray.length) {
94-
assert.equal(goldenImageDataArray.length === imageDataArray.length, true);
95-
} else {
96-
// Find the differences between the two arrays
97-
const arrayDiff = [];
98-
99-
for (let i = 0; i < goldenImageDataArray.length; i++) {
100-
if (goldenImageDataArray[i] !== imageDataArray[i]) {
101-
arrayDiff.push({
102-
index: i,
103-
goldenElement: goldenImageDataArray[i],
104-
imageDataElement: imageDataArray[i]
105-
});
106-
}
107-
}
108-
109-
// Check if we found differences
110-
if (arrayDiff.length > 0) {
111-
console.log('Differences found in expected (golden) output:');
112-
console.log(arrayDiff);
113-
}
114-
assert.equal(arrayDiff.length, 0);
115-
}
116-
117-
done();
118-
} else {
119-
// Either we didn't have it because this is the first time running this test rom,
120-
// or we wanted to update expected output, so we deleted the file
121-
console.warn(`No output found in: ${directory}/${testDataPath}, Creating expected (golden) output...`);
122-
123-
// Create the output file
124-
// Stringify our image data
125-
const imageDataStringified = JSON.stringify(imageDataArray);
126-
fs.writeFileSync(`${directory}/${testDataPath}`, imageDataStringified);
127-
128-
const testImagePath = testRom.replace('.gb', '.golden.png');
129-
await commonTest.createImageFromFrame(imageDataArray, `${directory}/${testImagePath}`);
130-
done();
131-
}
136+
await goldenImageDataArrayCompare(goldenFile, imageDataArray, directory, testRom);
137+
done();
132138
};
133139
wasmboyOutputImageTest();
134140
}, timeToWaitForTestRom);

0 commit comments

Comments
 (0)