mz700-js
Version:
MZ-700 Full JavaScript Emulator
162 lines (145 loc) • 4.76 kB
text/typescript
"use strict";
/* tslint:disable: no-string-literal no-console */
/**
* The beep-sound emulator for Sharp MZ-Serias.
* @constructor
*/
export default class MZBeep {
/**
* Minimum frequency
* @type {number}
*/
static FREQ_MIN = -24000;
/**
* Maximum frequency
* @type {number}
*/
static FREQ_MAX = 24000;
attackTime = 0.010;
decayTime = 0.010;
sustainLebel = 0.8;
releaseTime = 0.050;
audio:AudioContext = null;
totalGainNode:GainNode = null;
gain = 0;
poly = 128;
indexOsc = 0;
oscNodes:OscillatorNode[] = null;
oscGainNodes:GainNode[] = null;
constructor() {
window.AudioContext = window.AudioContext || window["webkitAudioContext"];
const audioContext = (window.AudioContext ? new AudioContext() : null);
this.oscNodes = new Array(this.poly);
this.oscGainNodes = new Array(this.poly);
if(audioContext) {
this.audio = audioContext;
this.totalGainNode = this.audio.createGain();
this.totalGainNode.gain.setValueAtTime(
this.gain, this.audio.currentTime);
this.totalGainNode.connect(this.audio.destination);
} else {
console.warn("NO AudioContext API supported by this browser.");
this.setGain = ()=>{ /* empty */ };
this.startSound = ()=>{ /* empty */ };
this.stopSound = ()=>{ /* empty */ };
}
}
allowToPlaySound():void {
if(!this.resumed()) {
this.resume();
}
}
/**
* Check the audio API is resumed (available).
* @returns {boolean} true if the API is resumed and available,
* otherwise false.
*/
resumed():boolean {
return this.audio.state === "running";
}
/**
* Resume the Audio API that was blocked by its platform.
* @async
* @returns {Promise<undefined>} that will be resolved.
*/
async resume():Promise<undefined> {
await this.audio.resume();
return;
}
/**
* Set beep gain.
* @param {number} gain The beep sound gain.
* @returns {undefined}
*/
setGain(gain:number):void {
if(gain < 0) {
gain = 0;
}
if(gain > 1.0) {
gain = 1.0;
}
this.gain = gain;
if(this.totalGainNode) {
this.totalGainNode.gain.setValueAtTime(
this.gain, this.audio.currentTime);
}
}
/**
* Start the oscillator beeping with the frequency.
* @param {number} freq The frequency.
* @returns {undefined}
*/
startSound(freq:number):void {
const indexOsc = this.indexOsc;
let oscGainNode = this.oscGainNodes[indexOsc];
if(oscGainNode != null) {
oscGainNode.gain.linearRampToValueAtTime(
0.0, this.audio.currentTime + this.releaseTime);
oscGainNode.disconnect();
}
this.oscGainNodes[indexOsc] = this.audio.createGain();
oscGainNode = this.oscGainNodes[indexOsc];
oscGainNode.gain.setValueAtTime(0.0, this.audio.currentTime);
oscGainNode.gain.linearRampToValueAtTime(
1.0, this.audio.currentTime + this.attackTime);
oscGainNode.gain.linearRampToValueAtTime(
this.sustainLebel,
this.audio.currentTime + this.attackTime + this.decayTime);
oscGainNode.connect(this.totalGainNode);
const lastOscNode = this.oscNodes[indexOsc];
if(lastOscNode != null) {
lastOscNode.stop();
lastOscNode.disconnect();
}
this.oscNodes[indexOsc] = this.audio.createOscillator();
const oscNode = this.oscNodes[indexOsc];
oscNode.type = "square";
if(isFinite(freq)) {
if(freq < MZBeep.FREQ_MIN) {
freq = MZBeep.FREQ_MIN;
} else if(freq > MZBeep.FREQ_MAX) {
freq = MZBeep.FREQ_MAX;
}
oscNode.frequency.setValueAtTime(
freq, this.audio.currentTime);
}
oscNode.start = oscNode.start || oscNode["noteOn"];
oscNode.connect(oscGainNode);
oscNode.start();
}
/**
* Stop the oscillator beeping.
* @param {number} freq The frequency.
* @returns {undefined}
*/
stopSound():void {
const indexOsc = this.indexOsc;
const oscGainNode = this.oscGainNodes[indexOsc];
if(oscGainNode != null) {
oscGainNode.gain.linearRampToValueAtTime(
0.0, this.audio.currentTime + this.releaseTime);
}
this.indexOsc = (indexOsc + 1) % this.poly;
}
}
module.exports = MZBeep;