adding sound emulation in browser

This commit is contained in:
Peli de Halleux 2016-03-13 21:24:11 -07:00
parent a751be2e97
commit d323995196
2 changed files with 149 additions and 93 deletions

View File

@ -331,6 +331,58 @@ namespace ks.rt.micro_bit {
export function servoSetPulse(pin: Pin, micros:number) { export function servoSetPulse(pin: Pin, micros:number) {
} }
module AudioContextManager {
var _context : any; // AudioContext
var _vco : any; //OscillatorNode;
var _vca: any; // GainNode;
function context() : any {
if (!_context) _context = freshContext();
return _context;
}
function freshContext() : any {
(<any>window).AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
if ((<any>window).AudioContext) {
try {
// this call my crash.
// SyntaxError: audio resources unavailable for AudioContext construction
return new (<any>window).AudioContext();
} catch(e) {}
}
return undefined;
}
export function stop() {
if (_vca) _vca.gain.value = 0;
}
export function tone(frequency: number, gain: number) {
if (frequency <= 0) return;
var ctx = context();
if (!ctx) return;
gain = Math.max(0, Math.min(1, gain));
if (!_vco) {
try {
_vco = ctx.createOscillator();
_vca = ctx.createGain();
_vco.connect(_vca);
_vca.connect(ctx.destination);
_vca.gain.value = gain;
_vco.start(0);
} catch(e) {
_vco = undefined;
_vca = undefined;
return;
}
}
_vco.frequency.value = frequency;
_vca.gain.value = gain;
}
}
export function enablePitch(pin: Pin) { export function enablePitch(pin: Pin) {
pin.mode = PinMode.Analog | PinMode.Output; pin.mode = PinMode.Analog | PinMode.Output;
@ -338,7 +390,11 @@ namespace ks.rt.micro_bit {
export function pitch(frequency: number, ms: number) { export function pitch(frequency: number, ms: number) {
let cb = getResume(); let cb = getResume();
setTimeout(() => { cb() }, ms); AudioContextManager.tone(frequency, 1);
setTimeout(() => {
if (ms > 0) AudioContextManager.stop();
cb()
}, ms);
} }

View File

@ -2,141 +2,141 @@ namespace ks.rt.micro_bit {
export interface RuntimeOptions { export interface RuntimeOptions {
theme: string; theme: string;
} }
export enum DisplayMode { export enum DisplayMode {
bw, bw,
greyscale greyscale
} }
export enum PinMode { export enum PinMode {
Unused = 0, Unused = 0,
Digital = 0x0001, Digital = 0x0001,
Analog = 0x0002, Analog = 0x0002,
Input = 0x0004, Input = 0x0004,
Output = 0x0008, Output = 0x0008,
Touch = 0x0010 Touch = 0x0010
} }
export class Pin { export class Pin {
constructor(public id: number) {} constructor(public id: number) { }
touched = false; touched = false;
value = 0; value = 0;
mode = PinMode.Unused; mode = PinMode.Unused;
isTouched() : boolean { isTouched(): boolean {
this.mode = PinMode.Touch; this.mode = PinMode.Touch;
return this.touched; return this.touched;
} }
} }
export class Button { export class Button {
constructor(public id : number) {} constructor(public id: number) { }
pressed: boolean; pressed: boolean;
} }
export class EventBus { export class EventBus {
private queues : Map<EventQueue<number>> = {}; private queues: Map<EventQueue<number>> = {};
constructor(private runtime : Runtime) { } constructor(private runtime: Runtime) { }
listen(id:number, evid:number, handler: RefAction) { listen(id: number, evid: number, handler: RefAction) {
let k = id + ':' + evid; let k = id + ':' + evid;
let queue = this.queues[k]; let queue = this.queues[k];
if (!queue) queue = this.queues[k] = new EventQueue<number>(this.runtime); if (!queue) queue = this.queues[k] = new EventQueue<number>(this.runtime);
queue.handler = handler; queue.handler = handler;
} }
queue(id: number, evid: number, value: number = 0) { queue(id: number, evid: number, value: number = 0) {
let k = id + ':' + evid; let k = id + ':' + evid;
let queue = this.queues[k]; let queue = this.queues[k];
if (queue) queue.push(value); if (queue) queue.push(value);
} }
} }
export interface PacketBuffer { export interface PacketBuffer {
data:number[]; data: number[];
rssi?:number; rssi?: number;
} }
export class RadioDatagram { export class RadioDatagram {
datagram: PacketBuffer[] = []; datagram: PacketBuffer[] = [];
lastReceived: PacketBuffer = { lastReceived: PacketBuffer = {
data:[0,0,0,0], data: [0, 0, 0, 0],
rssi: -1 rssi: -1
}; };
constructor(private runtime : Runtime) { constructor(private runtime: Runtime) {
} }
queue(packet : PacketBuffer) { queue(packet: PacketBuffer) {
if (this.datagram.length < 5) { if (this.datagram.length < 5) {
this.datagram.push(packet); this.datagram.push(packet);
let ens = enums(); let ens = enums();
(<Board>runtime.board).bus.queue(ens.MICROBIT_ID_RADIO, ens.MICROBIT_RADIO_EVT_DATAGRAM); (<Board>runtime.board).bus.queue(ens.MICROBIT_ID_RADIO, ens.MICROBIT_RADIO_EVT_DATAGRAM);
} }
} }
send(buffer : number[]) { send(buffer: number[]) {
Runtime.postMessage(<SimulatorRadioPacketMessage>{ Runtime.postMessage(<SimulatorRadioPacketMessage>{
type:'radiopacket', type: 'radiopacket',
data: buffer.slice(0, 8) data: buffer.slice(0, 8)
}) })
} }
recv() : PacketBuffer { recv(): PacketBuffer {
var r = this.datagram.shift(); var r = this.datagram.shift();
if (!r) r = { if (!r) r = {
data:[0,0,0,0], data: [0, 0, 0, 0],
rssi: -1 rssi: -1
}; };
return this.lastReceived = r; return this.lastReceived = r;
} }
} }
export class RadioBus { export class RadioBus {
// uint8_t radioDefaultGroup = MICROBIT_RADIO_DEFAULT_GROUP; // uint8_t radioDefaultGroup = MICROBIT_RADIO_DEFAULT_GROUP;
groupId = 0; // todo groupId = 0; // todo
datagram : RadioDatagram; datagram: RadioDatagram;
constructor(private runtime : Runtime) { constructor(private runtime: Runtime) {
this.datagram = new RadioDatagram(runtime); this.datagram = new RadioDatagram(runtime);
} }
setGroup(id: number) { setGroup(id: number) {
this.groupId = id & 0xff; // byte only this.groupId = id & 0xff; // byte only
} }
broadcast(msg: number) { broadcast(msg: number) {
let ens = enums(); let ens = enums();
Runtime.postMessage(<SimulatorEventBusMessage>{ Runtime.postMessage(<SimulatorEventBusMessage>{
type:'eventbus', type: 'eventbus',
id: ens.MES_BROADCAST_GENERAL_ID, id: ens.MES_BROADCAST_GENERAL_ID,
eventid: msg eventid: msg
}) })
} }
} }
export interface SimulatorEventBusMessage extends SimulatorMessage { export interface SimulatorEventBusMessage extends SimulatorMessage {
id: number; id: number;
eventid: number; eventid: number;
value?: number; value?: number;
} }
export interface SimulatorSerialMessage extends SimulatorMessage { export interface SimulatorSerialMessage extends SimulatorMessage {
id:string; id: string;
data: string; data: string;
} }
export interface SimulatorRadioPacketMessage extends SimulatorMessage { export interface SimulatorRadioPacketMessage extends SimulatorMessage {
data: number[]; data: number[];
rssi?: number; rssi?: number;
} }
export class Board extends BaseBoard { export class Board extends BaseBoard {
id: string; id: string;
// the bus // the bus
bus : EventBus; bus: EventBus;
radio: RadioBus; radio: RadioBus;
// display // display
@ -147,28 +147,28 @@ namespace ks.rt.micro_bit {
// buttons // buttons
usesButtonAB: boolean = false; usesButtonAB: boolean = false;
buttons : Button[]; buttons: Button[];
// pins // pins
pins : Pin[]; pins: Pin[];
// serial // serial
serialIn: string[] = []; serialIn: string[] = [];
// sensors // sensors
usesAcceleration = false; usesAcceleration = false;
acceleration = [0, 0, -1023]; acceleration = [0, 0, -1023];
usesHeading = false; usesHeading = false;
heading = 90; heading = 90;
temperature = 21; temperature = 21;
usesLightLevel = false; usesLightLevel = false;
lightLevel = 128; lightLevel = 128;
animationQ: AnimationQueue; animationQ: AnimationQueue;
constructor() { constructor() {
super() super()
this.id = "b" + Math.random(); this.id = "b" + Math.random();
@ -199,25 +199,25 @@ namespace ks.rt.micro_bit {
new Pin(ens.MICROBIT_ID_IO_P14), new Pin(ens.MICROBIT_ID_IO_P14),
new Pin(ens.MICROBIT_ID_IO_P15), new Pin(ens.MICROBIT_ID_IO_P15),
new Pin(ens.MICROBIT_ID_IO_P16), new Pin(ens.MICROBIT_ID_IO_P16),
null, null,
null, null,
new Pin(ens.MICROBIT_ID_IO_P19), new Pin(ens.MICROBIT_ID_IO_P19),
new Pin(ens.MICROBIT_ID_IO_P20) new Pin(ens.MICROBIT_ID_IO_P20)
]; ];
} }
initAsync(msg : SimulatorRunMessage) : Promise<void> { initAsync(msg: SimulatorRunMessage): Promise<void> {
let options = (msg.options || {}) as RuntimeOptions; let options = (msg.options || {}) as RuntimeOptions;
let theme : micro_bit.IBoardTheme; let theme: micro_bit.IBoardTheme;
switch(options.theme) { switch (options.theme) {
case 'blue': theme = micro_bit.themes[0]; break; case 'blue': theme = micro_bit.themes[0]; break;
case 'yellow': theme = micro_bit.themes[1]; break; case 'yellow': theme = micro_bit.themes[1]; break;
case 'green': theme = micro_bit.themes[2]; break; case 'green': theme = micro_bit.themes[2]; break;
case 'red': theme = micro_bit.themes[3]; break; case 'red': theme = micro_bit.themes[3]; break;
default: theme = ks.rt.micro_bit.randomTheme(); default: theme = ks.rt.micro_bit.randomTheme();
} }
console.log('setting up microbit simulator') console.log('setting up microbit simulator')
let view = new ks.rt.micro_bit.MicrobitBoardSvg({ let view = new ks.rt.micro_bit.MicrobitBoardSvg({
theme: theme, theme: theme,
@ -225,56 +225,56 @@ namespace ks.rt.micro_bit {
}) })
document.body.innerHTML = ''; // clear children document.body.innerHTML = ''; // clear children
document.body.appendChild(view.element); document.body.appendChild(view.element);
return Promise.resolve(); return Promise.resolve();
} }
receiveMessage(msg: SimulatorMessage) { receiveMessage(msg: SimulatorMessage) {
if (!runtime || runtime.dead) return; if (!runtime || runtime.dead) return;
switch(msg.type || "") { switch (msg.type || "") {
case 'eventbus': case 'eventbus':
let ev = <SimulatorEventBusMessage>msg; let ev = <SimulatorEventBusMessage>msg;
this.bus.queue(ev.id, ev.eventid, ev.value); this.bus.queue(ev.id, ev.eventid, ev.value);
break; break;
case 'serial': case 'serial':
this.serialIn.push((<SimulatorSerialMessage>msg).data || ''); this.serialIn.push((<SimulatorSerialMessage>msg).data || '');
break; break;
case 'radiopacket': case 'radiopacket':
let packet = <SimulatorRadioPacketMessage>msg; let packet = <SimulatorRadioPacketMessage>msg;
this.radio.datagram.queue({ data: packet.data || [], rssi: packet.rssi || 0}) this.radio.datagram.queue({ data: packet.data || [], rssi: packet.rssi || 0 })
break; break;
} }
} }
readSerial() { readSerial() {
let v = this.serialIn.shift() || ''; let v = this.serialIn.shift() || '';
return v; return v;
} }
serialOutBuffer: string = ''; serialOutBuffer: string = '';
writeSerial(s : string) { writeSerial(s: string) {
for(let i = 0; i < s.length;++i) { for (let i = 0; i < s.length; ++i) {
let c = s[i]; let c = s[i];
switch(c) { switch (c) {
case '\n': case '\n':
Runtime.postMessage(<SimulatorSerialMessage>{ Runtime.postMessage(<SimulatorSerialMessage>{
type: 'serial', type: 'serial',
data: this.serialOutBuffer, data: this.serialOutBuffer,
id: runtime.id id: runtime.id
}) })
this.serialOutBuffer = '' this.serialOutBuffer = ''
break; break;
case '\r': continue; case '\r': continue;
default: this.serialOutBuffer += c; default: this.serialOutBuffer += c;
} }
} }
} }
} }
export class Image { export class Image {
public width: number; public width: number;
public data: number[]; public data: number[];
constructor(width: number, data: number[]) { constructor(width: number, data: number[]) {
this.width = width; this.width = width;
this.data = data; this.data = data;
@ -295,8 +295,8 @@ namespace ks.rt.micro_bit {
} }
} }
} }
public clear() : void { public clear(): void {
for(var i = 0;i < this.data.length; ++i) for (var i = 0; i < this.data.length; ++i)
this.data[i] = 0; this.data[i] = 0;
} }
} }