Skip to content

Commit

Permalink
Fixes Audio from Sound Optimizations (#277)
Browse files Browse the repository at this point in the history
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">
  • Loading branch information
torch2424 committed Feb 28, 2019
1 parent c84d075 commit 00e8b0d
Show file tree
Hide file tree
Showing 13 changed files with 131,225 additions and 114 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ node_js:
install:
- npm install
script:
- npm run prettier:lint && npm run demo:build:apps && npm test
- npm run prettier:lint && npm run demo:build:apps && npm run test:accuracy:nobuild
2 changes: 1 addition & 1 deletion core/debug/debug-graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '../graphics/index';
import { Cpu } from '../cpu/index';
import { eightBitLoadFromGBMemory, Memory } from '../memory/index';
import { checkBitOnByte, hexLog } from '../helpers/index';
import { checkBitOnByte } from '../helpers/index';

// Some Simple internal getters
export function getLY(): i32 {
Expand Down
28 changes: 7 additions & 21 deletions core/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,28 +58,14 @@ export function checkBitOnByte(bitPosition: i32, byte: i32): boolean {
return (byte & (1 << bitPosition)) != 0;
}

namespace env {
export declare function log(message: string, arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32, arg5: i32): void;
export declare function hexLog(arg0: i32, arg1: i32): void;
export declare function performanceTimestamp(id: i32, value: i32): void;
}

export function log(
message: string,
arg0: i32 = -9999,
arg1: i32 = -9999,
arg2: i32 = -9999,
arg3: i32 = -9999,
arg4: i32 = -9999,
arg5: i32 = -9999
): void {
env.log(message, arg0, arg1, arg2, arg3, arg4, arg5);
}
// Declared importObject functions
declare function consoleLog(arg0: i32, arg1: i32): void;
declare function consoleLogTimeout(arg0: i32, arg1: i32, timeout: i32): void;

export function hexLog(arg0: i32, arg1: i32): void {
env.hexLog(arg0, arg1);
export function log(arg0: i32, arg1: i32): void {
consoleLog(arg0, arg1);
}

export function performanceTimestamp(id: i32 = -9999, value: i32 = -9999): void {
env.performanceTimestamp(id, value);
export function logTimeout(arg0: i32, arg1: i32, timeout: i32): void {
consoleLogTimeout(arg0, arg1, timeout);
}
2 changes: 1 addition & 1 deletion core/interrupts/interrupts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
loadBooleanDirectlyFromWasmMemory,
storeBooleanDirectlyToWasmMemory
} from '../memory/index';
import { setBitOnByte, resetBitOnByte, checkBitOnByte, hexLog } from '../helpers/index';
import { setBitOnByte, resetBitOnByte, checkBitOnByte } from '../helpers/index';

export class Interrupts {
static masterInterruptSwitch: boolean = false;
Expand Down
52 changes: 21 additions & 31 deletions core/portable/importObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,31 @@
// Log throttling for our core
// The same log can't be output more than once every half second
let logRequest = {};
let logThrottleLength = 100;

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

console.log('[WasmBoy] ' + str);
},
hexLog: (arg0, arg1) => {
if (!logRequest[arg0]) {
// Grab our arguments, and log as hex
let logString = '[WasmBoy]';
if (arg0 !== -9999) logString += ` 0x${arg0.toString(16)} `;
if (arg1 !== -9999) logString += ` 0x${arg1.toString(16)} `;
const log = (arg0, arg1) => {
// Grab our arguments, and log as hex
let logString = '[WasmBoy]';
if (arg0 !== -9999) logString += ` 0x${arg0.toString(16)} `;
if (arg1 !== -9999) logString += ` 0x${arg1.toString(16)} `;

// Uncomment to unthrottle
console.log(logString);
console.log(logString);
};

// Comment the lines below to disable throttle
/*logRequest[arg0] = true;
setTimeout(() => {
console.log(logString);
logRequest[arg0] = false;
}, logThrottleLength);*/
}
}
// https://github.com/AssemblyScript/assemblyscript/issues/384
const wasmImportObject = {
index: {
consoleLog: log,
consoleLogTimeout: logTimeout
}
};

Expand Down
5 changes: 2 additions & 3 deletions core/sound/channel1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export class Channel1 {
// TODO: The volume envelope and sweep timers treat a period of 0 as 8.
let envelopeCounter = Channel1.envelopeCounter - 1;
if (envelopeCounter <= 0) {
Channel1.envelopeCounter = Channel1.NRx2EnvelopePeriod;
envelopeCounter = Channel1.NRx2EnvelopePeriod;

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

static setFrequency(frequency: i32): void {
Expand Down
3 changes: 2 additions & 1 deletion core/sound/sound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ function calculateSound(numberOfCycles: i32): void {
// Reset the downsample counter
// Don't set to zero to catch overflowed cycles
downSampleCycleCounter -= Sound.maxDownSampleCycles();
Sound.downSampleCycleCounter = downSampleCycleCounter;

// Mix our samples
let mixedSample = mixChannelSamples(channel1Sample, channel2Sample, channel3Sample, channel4Sample);
Expand Down Expand Up @@ -274,6 +273,8 @@ function calculateSound(numberOfCycles: i32): void {
}
Sound.audioQueueIndex = audioQueueIndex;
}

Sound.downSampleCycleCounter = downSampleCycleCounter;
}

// Inlined because closure compiler inlines
Expand Down
4 changes: 4 additions & 0 deletions lib/audio/gbchannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ export default class GbChannelWebAudio {
return `data:audio/wav;base64,${base64String}`;
}

getRecordingAsAudioBuffer() {
return this.recordingAudioBuffer;
}

_libMute() {
this._setGain(0);
this.libMuted = true;
Expand Down
2 changes: 1 addition & 1 deletion lib/worker/smartworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class SmartWorker {
console.warn('Message dropped', message);
this.removeMessageListener(messageId);
reject();
}, 500);
}, 1000);

// Listen for a message with the same message id to be returned
this.addMessageListener((responseMessage, messageListener) => {
Expand Down
1 change: 0 additions & 1 deletion rollup.getcore.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ if (process.env.GET_CORE_CLOSURE) {
let closureCompilerOptions = {};

if (process.env.CLOSURE_DEBUG) {
console.log('Yoooo');
closureCompilerOptions = {
...closureCompilerOptions,
debug: true
Expand Down
112 changes: 59 additions & 53 deletions test/accuracy/accuracy-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
const commonTest = require('../common-test');

// Wasm Boy library
const WasmBoy = require('../../dist/wasmboy.wasm.cjs.js').WasmBoy;
const WasmBoy = require('../../dist/wasmboy.wasm.cjs').WasmBoy;

// File management
const fs = require('fs');

// Assertion
const assert = require('assert');

// Golden file handling
const { goldenFileCompareOrCreate, goldenImageDataArrayCompare } = require('./goldenCompare');

// Some Timeouts for specified test roms
const TEST_ROM_DEFAULT_TIMEOUT = 7500;
const TEST_ROM_TIMEOUT = {};
Expand All @@ -25,6 +28,55 @@ WasmBoy.config({
isGbcEnabled: true
});

// Audio Golden Test
// TODO: Remove this with an actual accuracy
// sound test
describe('audio golden test', () => {
// Define our wasmboy instance
// Not using arrow functions, as arrow function timeouts were acting up
beforeEach(function(done) {
// Set a timeout of 7500, takes a while for wasm module to parse
this.timeout(7500);

// Read the test rom a a Uint8Array and pass to wasmBoy
const testRomArray = new Uint8Array(fs.readFileSync('./test/performance/testroms/back-to-color/back-to-color.gbc'));

WasmBoy.loadROM(testRomArray).then(done);
});

it('should have the same audio buffer', function(done) {
// Set our timeout
this.timeout(TEST_ROM_DEFAULT_TIMEOUT + 2000);

const asyncTask = async () => {
// Run some frames
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
await WasmBoy._runWasmExport('clearAudioBuffer');
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);
await WasmBoy._runWasmExport('executeMultipleFrames', [60]);

// Execute a few frames
const memoryStart = await WasmBoy._getWasmConstant('AUDIO_BUFFER_LOCATION');
const memorySize = await WasmBoy._getWasmConstant('AUDIO_BUFFER_SIZE');
// - 20 to not include the overrun in the audio buffer
const memory = await WasmBoy._getWasmMemorySection(memoryStart, memoryStart + memorySize - 20);

// Get the memory as a normal array
const audioArray = [];
for (let i = 0; i < memory.length; i++) {
audioArray.push(memory[i]);
}

goldenFileCompareOrCreate('./test/accuracy/sound-test.golden.output.json', audioArray);
done();
};
asyncTask();
});
});

// Graphical Golden Test(s)
// Simply screenshot the end result of the accuracy test
const testRomsPath = './test/accuracy/testroms';

commonTest.getDirectories(testRomsPath).forEach(directory => {
Expand Down Expand Up @@ -74,61 +126,15 @@ commonTest.getDirectories(testRomsPath).forEach(directory => {
const wasmboyOutputImageTest = async () => {
await WasmBoy.pause();

console.log(`Checking results for the following test rom: ${directory}/${testRom}`);
const testDataPath = testRom.replace('.gb', '.golden.output');
const goldenFile = `${directory}/${testDataPath}`;

console.log(`Checking results for the following test rom: ${goldenFile}`);

const imageDataArray = await commonTest.getImageDataFromFrame();

// Output a gitignored image of the current tests
const testImagePath = testRom.replace('.gb', '.current.png');
await commonTest.createImageFromFrame(imageDataArray, `${directory}/${testImagePath}`);

// Now compare with the current array if we have it
const testDataPath = testRom.replace('.gb', '.golden.output');
if (fs.existsSync(`${directory}/${testDataPath}`)) {
// Compare the file
const goldenOuput = fs.readFileSync(`${directory}/${testDataPath}`);

const goldenImageDataArray = JSON.parse(goldenOuput);

if (goldenImageDataArray.length !== imageDataArray.length) {
assert.equal(goldenImageDataArray.length === imageDataArray.length, true);
} else {
// Find the differences between the two arrays
const arrayDiff = [];

for (let i = 0; i < goldenImageDataArray.length; i++) {
if (goldenImageDataArray[i] !== imageDataArray[i]) {
arrayDiff.push({
index: i,
goldenElement: goldenImageDataArray[i],
imageDataElement: imageDataArray[i]
});
}
}

// Check if we found differences
if (arrayDiff.length > 0) {
console.log('Differences found in expected (golden) output:');
console.log(arrayDiff);
}
assert.equal(arrayDiff.length, 0);
}

done();
} else {
// Either we didn't have it because this is the first time running this test rom,
// or we wanted to update expected output, so we deleted the file
console.warn(`No output found in: ${directory}/${testDataPath}, Creating expected (golden) output...`);

// Create the output file
// Stringify our image data
const imageDataStringified = JSON.stringify(imageDataArray);
fs.writeFileSync(`${directory}/${testDataPath}`, imageDataStringified);

const testImagePath = testRom.replace('.gb', '.golden.png');
await commonTest.createImageFromFrame(imageDataArray, `${directory}/${testImagePath}`);
done();
}
await goldenImageDataArrayCompare(goldenFile, imageDataArray, directory, testRom);
done();
};
wasmboyOutputImageTest();
}, timeToWaitForTestRom);
Expand Down
Loading

0 comments on commit 00e8b0d

Please sign in to comment.