Skip to content

Commit

Permalink
more stable version of step writer
Browse files Browse the repository at this point in the history
  • Loading branch information
shicks committed Jan 8, 2016
1 parent 9d53e49 commit d92f6cb
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 38 deletions.
33 changes: 19 additions & 14 deletions apu/apu.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,7 @@ export default class Apu {
this.pulse1_.clockSequencer();
this.pulse2_.clockSequencer();

const pulse1 = this.pulse1_.volume();
const pulse2 = this.pulse2_.volume();
const pulseOut = (pulse1 || pulse2) &&
95.88 / (8128 / (pulse1 + pulse2) + 100);

const triangle = 0; // this.triangle_.volume();
const noise = 0; // this.noise_.volume();
const dmc = 0; // this.dmc_.volume();
const tndOut = (triangle || noise || dmc) &&
159.79 / (1 / (triangle / 8227 + noise / 12241 + dmc / 22638) + 100);

const volume = pulseOut + tndOut;

const volume = this.volume();
if (volume != this.last_) {
this.steps_.push([this.clock_.time, volume]);
this.last_ = volume;
Expand All @@ -103,9 +91,26 @@ export default class Apu {

steps() {
const steps = this.steps_;
this.steps_ = [];
const volume = this.volume();
steps.push([this.clock_.time, volume]); // always return at least one.
this.steps_ = [[this.clock_.time, volume]];
return steps;
}

volume() {
const pulse1 = this.pulse1_.volume();
const pulse2 = this.pulse2_.volume();
const pulseOut = (pulse1 || pulse2) &&
95.88 / (8128 / (pulse1 + pulse2) + 100);

const triangle = 0; // this.triangle_.volume();
const noise = 0; // this.noise_.volume();
const dmc = 0; // this.dmc_.volume();
const tndOut = (triangle || noise || dmc) &&
159.79 / (1 / (triangle / 8227 + noise / 12241 + dmc / 22638) + 100);

return pulseOut + tndOut;
}
}

const FRAME_CYCLES = [3728.5 * 2, 7456.5 * 2, 11185.5 * 2, 14914.5 * 2];
Expand Down
3 changes: 1 addition & 2 deletions audio/bufferedaudionode.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export default class BufferedAudioNode {
Object.seal(this);
}


get context() { return this.ac_; }
get numberOfInputs() { return 0; }
get numberOfOutputs() { return 1; }
Expand All @@ -66,7 +65,7 @@ export default class BufferedAudioNode {
/** Resets everything to an empty buffer. */
reset() {
if (this.writeResolver_) this.writeResolver_();
this.s_.stop(0);
if (this.started_ >= 0) this.s_.stop(0);
this.s_.disconnect();

this.s_ = this.ac_.createBufferSource();
Expand Down
96 changes: 96 additions & 0 deletions audio/stepbufferwriter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict';

import BufferedAudioNode from './bufferedaudionode';
import {StepKernel,lanczosKernel} from './kernel';

/** A class to write steps to a buffer. */
export default class StepBufferWriter {
/**
* @param {!BufferedAudioNode} buffer
*/
constructor(buffer) {
/** @private @const {!BufferedAudioNode} */
this.buffer_ = buffer;
/** @private @const {number} */
this.sampleRate_ = buffer.context.sampleRate;

/** @private {number} Current sample index. */
this.sampleIndex_ = 0;
/** @private {number} Last sample value. */
this.lastSample_ = 0;
/** @private {!Array<!Array<number>>} Steps we currently care about. */
this.steps_ = [];

// this.kernel_ = new StepKernel([[.5, .5], [.5, .5]]);
// this.kernel_ = new StepKernel([[1, 0], [0, 1]]);

/** @private @const {!StepKernel} */
this.kernel_ = lanczosKernel(5, 32);
}


/**
* @param {!Array<!Array<number>>} steps Array of [time (s), sample]
* indicating transition times between steps.
* @param {number} time "Current" ending time.
* @return {!Promise}
*/
write(steps, time) {
console.log('WRITE: [' + steps + '], ' + time);

const samples = [];
const endTime = time - 2 * this.kernel_.radius * this.sampleRate_;
// Add a final (zero) step.
if (steps.length == 0 || steps[steps.length - 1][0] < endTime) {

// TODO - why aren't we outputting anything for empty steps?!?
// ....?!?
// -- consider separating steps from time, and having
// time just output regardless? but how to know when ready?

steps = steps.slice();
steps.push([
endTime,
steps.length ? steps[steps.length - 1][1] : this.lastSample_]);
}
for (let step of steps) {
// console.log('step: ' + step[0] + ', ' + step[1]);
const s = step[0] * this.sampleRate_;
const v = step[1] - this.lastSample_;
//console.log('step: [' + s + ', ' + v + ']');

if (s <= this.sampleIndex_) {
this.lastSample_ = step[1];
this.steps_ = [];
console.log('past value: resetting');
continue;
}
this.lastSample_ += v;

// Compute samples until s - kernel.radius
const endSample = Math.floor(s - this.kernel_.radius);
if (endSample > this.sampleIndex_) {
const deltas =
this.kernel_.convolve(this.sampleIndex_, endSample, this.steps_)
for (let delta of /** @type {!Iterable<number>} */ (deltas)) {
// TODO(sdh): can we remove the floating-point error drift?!?
//this.lastSample_ *= 0.9999995;
samples.push(this.lastSample_ += delta);
}
this.sampleIndex_ = endSample;

const done = Math.floor(this.sampleIndex_ - this.kernel_.radius);
let i = 0;
while (i < this.steps_.length && this.steps_[i][0] < done) i++;
if (i > 0) this.steps_.splice(0, i);
}
// console.log('step push: ' + s + ', ' + v);
this.steps_.push([s, v]);
}
// now write the buffer.
console.log(`Writing ${samples.length} samples`);
return this.buffer_.write(samples);
}
}


44 changes: 22 additions & 22 deletions nsfplayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import Clock from './clock';
import Cpu from './cpu';
import Memory from './mem';
import Nsf from './nsf';
import StepGeneratorNode from './audio/stepgeneratornode';
import StepBufferWriter from './audio/stepbufferwriter';
import BufferedAudioNode from './audio/bufferedaudionode';

export default class NsfPlayer {

Expand All @@ -18,41 +19,40 @@ export default class NsfPlayer {
this.apu = new Apu(this.mem, this.clock);
this.cpu = new Cpu(this.mem);
this.banks = new BankSwitcher(this.mem);
this.node = new StepGeneratorNode(ac, 2);
this.node = new BufferedAudioNode(ac, 2);
this.node.connect(ac.destination);
this.writer = new StepBufferWriter(this.node);
this.promise = null;
}

start(song = 0) {
this.nsf.init(this.cpu, this.mem, song, this.banks);
this.node.generator = this.play();
this.node.reset();
this.promise = null;
this.play(null);
}

*play() {
//yield [];
let frameCounter = this.cyclesPerFrame;
// console.log('Starting frame counter at ' + frameCounter);
for (let i = 0; /*i < 6 * this.cyclesPerFrame*/; i++) {
if (frameCounter != frameCounter) throw new Error('NaN');
if (--frameCounter <= 0) {
frameCounter = this.cyclesPerFrame;
if (this.cpu.PC == 0xFFFF) {
// console.log('New frame');
this.nsf.frame(this.cpu);
}
// Yield a single frame worth of steps
let data = this.apu.steps();
if (!data.length) data = [[this.clock.time, 0]];
// console.log('Yield data', data);
yield data;
}
play(promise) {
if (this.promise != promise) return;
for (let frameCycle = this.cyclesPerFrame; frameCycle >= 0; frameCycle--) {
if (frameCycle != frameCycle) throw new Error('NaN');
if (this.cpu.PC != 0xFFFF) this.cpu.clock();
this.apu.clock();
this.clock.tick();
}
if (this.cpu.PC == 0xFFFF) {
// console.log('New frame');
this.nsf.frame(this.cpu);
}
// Yield a single frame worth of steps
promise = this.promise =
this.writer.write(this.apu.steps(), this.clock.time)
.then(() => this.play(promise));
// console.log('Yield data', data);
}

stop() {
this.node.generator = null;
this.promise = null;
}
}

Expand Down

0 comments on commit d92f6cb

Please sign in to comment.