Skip to content
This repository has been archived by the owner on Dec 24, 2020. It is now read-only.

Commit

Permalink
Robustify AudioContext code (thanks for the surprise, Chrome!)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffpar committed Mar 7, 2015
1 parent 0ea63a7 commit ddc9124
Show file tree
Hide file tree
Showing 6 changed files with 1,628 additions and 1,597 deletions.
849 changes: 425 additions & 424 deletions docs/pcjs/demos/pc-dbg.js

Large diffs are not rendered by default.

700 changes: 351 additions & 349 deletions docs/pcjs/demos/pc.js

Large diffs are not rendered by default.

42 changes: 22 additions & 20 deletions modules/pcjs/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ PCjs Sources

Structure
---
All the code for PCjs is contained in the following JavaScript files, which roughly divide the
functionality into major PC components, aka "devices". However, not every file implements a device,
and "component" is an overloaded term, since *[Component](/docs/pcjs/component/)* is also the name of
the shared base class used for most PCjs devices (see [component.js](../../shared/lib/component.js)).
So it's best to refer to these files generically as "modules", and more specifically as "device modules"
whenever they implement a specific device (or set of devices, in the case of [*ChipSet*](/docs/pcjs/chipset/)).
These JavaScript files divide PCjs functionality into major PC components. Most of the files are device
components, implementing a specific device (or set of devices, in the case of [chipset.js](chipset.js)).

Examples of non-device modules include UI modules like [panel.js](panel.js) and [debugger.js](debugger.js),
and sub-modules like [x86opxx.js](x86opxx.js) and [x86func.js](x86func.js) that separate the CPU functionality
of [x86.js](x86.js) into more manageable pieces.
Be aware that *component* is an overloaded term, since **Component** is also the name of the
shared base class in [component.js](../../shared/lib/component.js) used by most machine components.
A few low-level components (eg, the **Memory** and **State** components, the Card class of the **Video**
component, the Color and Rectangle classes of the **Panel** component, etc) do not extend **Component**,
so don't assume that every PCjs object has access to [component.js](../../shared/lib/component.js) methods.

These modules should always be loaded or compiled in the order listed by the *pcJSFiles* property in
[package.json](../../../package.json), which includes all the necessary *shared* modules as well.
At the time of this writing, the order is:
Examples of non-device components include UI components like [panel.js](panel.js) and [debugger.js](debugger.js),
and sub-components like [x86opxx.js](x86opxx.js) and [x86func.js](x86func.js) that separate the CPU
functionality of [x86.js](x86.js) into more manageable pieces.

These components should always be loaded or compiled in the order listed by the *pcJSFiles* property in
[package.json](../../../package.json), which includes all the necessary *shared* components as well.
At the time of this writing, the recommended order is:

* [shared/defines.js](../../shared/lib/defines.js)
* [shared/diskapi.js](../../shared/lib/diskapi.js)
Expand All @@ -38,15 +40,15 @@ At the time of this writing, the order is:
* [pcjs/x86seg.js](x86seg.js)
* [pcjs/x86cpu.js](x86cpu.js)
* [pcjs/x86func.js](x86func.js)
* [pcjs/x86opxx.js](x86opxx.js)
* [pcjs/x86op0f.js](x86op0f.js)
* [pcjs/x86modb.js](x86modb.js)
* [pcjs/x86modw.js](x86modw.js)
* [pcjs/x86modb16.js](x86modb16.js)
* [pcjs/x86modw16.js](x86modw16.js)
* [pcjs/x86modb32.js](x86modb32.js)
* [pcjs/x86modw32.js](x86modw32.js)
* [pcjs/x86modsib.js](x86modsib.js)
* [pcjs/x86opxx.js](x86opxx.js)
* [pcjs/x86op0f.js](x86op0f.js)
* [pcjs/chipset.js](chipset.js)
* [pcjs/rom.js](rom.js)
* [pcjs/ram.js](ram.js)
Expand All @@ -62,16 +64,16 @@ At the time of this writing, the order is:
* [pcjs/computer.js](computer.js)
* [shared/embed.js](../../shared/lib/embed.js)

Some of the modules *can* be reordered or even omitted (eg, [debugger.js](debugger.js) or
Some of the components *can* be reordered or even omitted (eg, [debugger.js](debugger.js) or
[embed.js](../../shared/lib/embed.js)), but you should observe the following:

* [component.js](../../shared/lib/component.js) must be listed before any module that extends [*Component*](/docs/pcjs/component/)
* [component.js](../../shared/lib/component.js) must be listed before any component that extends **Component**
* [panel.js](panel.js) should be loaded early to initialize the Control Panel (if any) as soon as possible
* [computer.js](computer.js) should be the last device module, as it supervises and notifies all the other device modules
* [computer.js](computer.js) should be the last device component, as it supervises and notifies all the other device components

To minimize ordering requirements, the init() handlers and constructors of all modules should avoid
referencing other modules. Device modules should define an initBus() notification handler, which the
[*Computer*](/docs/pcjs/computer/) will call after it has created/initialized the *Bus* object.
To minimize ordering requirements, the init() handlers and constructors of all components should avoid
referencing other components. Device components should define an initBus() notification handler, which the
*Computer* component will call after it has created/initialized the *Bus* component.

Features
---
Expand Down
85 changes: 54 additions & 31 deletions modules/pcjs/lib/chipset.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,14 @@ function ChipSet(parmsChipSet)
*/
this.fSpeaker = false;
if (parmsChipSet['sound']) {
if (window && 'webkitAudioContext' in window) {
this.contextAudio = new window['webkitAudioContext']();
this.classAudio = this.contextAudio = null;
if (window) {
this.classAudio = window['AudioContext'] || window['webkitAudioContext'];
}
if (this.classAudio) {
this.contextAudio = new this.classAudio();
} else {
if (DEBUG) this.log("webkitAudioContext not available");
if (DEBUG) this.log("AudioContext not available");
}
}

Expand Down Expand Up @@ -4771,37 +4775,56 @@ ChipSet.prototype.parseSwitches = function(s, def)
ChipSet.prototype.setSpeaker = function(fOn)
{
if (this.contextAudio) {
if (fOn !== undefined) {
this.fSpeaker = fOn;
} else {
fOn = this.fSpeaker && this.cpu && this.cpu.isRunning();
}
var freq = Math.round(ChipSet.TIMER_TICKS_PER_SEC / this.getTimerInit(ChipSet.TIMER2.INDEX));
/*
* Treat frequencies outside the normal hearing range (below 20hz or above 20Khz) as a clever attempt
* to turn sound off; we have to explicitly turn the sound off in those cases, to prevent the Audio API
* from "easing" the audio to the target frequency and creating odd sound effects.
*/
if (freq < 20 || freq > 20000) fOn = false;
if (fOn) {
if (this.sourceAudio) {
this.sourceAudio['frequency']['value'] = freq;
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker set to " + freq + "hz", true);
try {
if (fOn !== undefined) {
this.fSpeaker = fOn;
} else {
this.sourceAudio = this.contextAudio['createOscillator']();
this.sourceAudio['type'] = "square"; // any of: "sine", "square", "sawtooth", "triangle", "custom"
this.sourceAudio['connect'](this.contextAudio['destination']);
this.sourceAudio['frequency']['value'] = freq;
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker on at " + freq + "hz", true);
this.sourceAudio['start'](0);
fOn = this.fSpeaker && this.cpu && this.cpu.isRunning();
}
} else {
if (this.sourceAudio) {
this.sourceAudio['stop'](0);
this.sourceAudio['disconnect'](); // QUESTION: is this automatic following a stop(), since this particular source cannot be started again?
delete this.sourceAudio; // QUESTION: ditto?
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker off at " + freq + "hz", true);
var freq = Math.round(ChipSet.TIMER_TICKS_PER_SEC / this.getTimerInit(ChipSet.TIMER2.INDEX));
/*
* Treat frequencies outside the normal hearing range (below 20hz or above 20Khz) as a clever attempt
* to turn sound off; we have to explicitly turn the sound off in those cases, to prevent the Audio API
* from "easing" the audio to the target frequency and creating odd sound effects.
*/
if (freq < 20 || freq > 20000) fOn = false;
if (fOn) {
if (this.sourceAudio) {
this.sourceAudio['frequency']['value'] = freq;
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker set to " + freq + "hz", true);
} else {
this.sourceAudio = this.contextAudio['createOscillator']();
if (this.sourceAudio) {
if (typeof this.sourceAudio['type'] == "number") {
this.sourceAudio['type'] = 1; // deprecated: 0: "sine", 1: "square", 2: "sawtooth", 3: "triangle"
} else {
this.sourceAudio['type'] = "square";
}
this.sourceAudio['connect'](this.contextAudio['destination']);
this.sourceAudio['frequency']['value'] = freq;
if ('start' in this.sourceAudio) {
this.sourceAudio['start'](0);
} else {
this.sourceAudio['noteOn'](0); // deprecated: this.sourceAudio['noteOn'](0)
}
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker on at " + freq + "hz", true);
}
}
} else {
if (this.sourceAudio) {
if ('stop' in this.sourceAudio) {
this.sourceAudio['stop'](0);
} else {
this.sourceAudio['noteOff'](0); // deprecated: this.sourceAudio['noteOff'](0)
}
this.sourceAudio['disconnect'](); // QUESTION: is this automatic following a stop(), since this particular source cannot be started again?
delete this.sourceAudio; // QUESTION: ditto?
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker off at " + freq + "hz", true);
}
}
} catch(e) {
this.notice("AudioContext exception: " + e.message);
this.contextAudio = null;
}
} else if (fOn) {
this.printMessage("BEEP", Messages.SPEAKER);
Expand Down
Loading

0 comments on commit ddc9124

Please sign in to comment.