in editor/extension.ts [89:387]
export function initExtensionsAsync(opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
pxt.debug("loading chibitronics target extensions...")
let logicBlockWeight = 85.9;
let logicMonacoBlockWeight = 40;
const res: pxt.editor.ExtensionResult = {
toolboxOptions: {
blocklyToolbox: {
loops: {
name: lf("{id:category}Control"),
icon: "logic",
blocks:
opts.blocklyToolbox.loops.blocks.concat(
opts.blocklyToolbox.logic.blocks
.filter(block => block.name == 'controls_if'
|| block.name == 'controls_if_else')
.map(b => {
b.weight = 49;
b.group = undefined;
return b;
}))
},
logic: {
},
variables: {
weight: 50.06
},
maths: {
weight: 50.07,
blocks:
opts.blocklyToolbox.maths.blocks
.filter(block => block.name != 'math_js_op')
.concat(
opts.blocklyToolbox.logic.blocks
.filter(block => block.name != 'controls_if'
&& block.name != 'controls_if_else')
.map(b => {
b.weight = logicBlockWeight;
logicBlockWeight -= 0.1;
b.group = undefined;
return b;
}))
},
text: {
advanced: true
},
functions: {
advanced: true
}
},
monacoToolbox: {
loops: {
name: lf("{id:category}Control"),
icon: "logic",
blocks:
opts.monacoToolbox.loops.blocks.concat(
opts.monacoToolbox.logic.blocks
.filter(block => block.name == 'logic_if'
|| block.name == 'logic_if_else')
.map(block => {
block.weight = logicMonacoBlockWeight--;
return block;
}))
},
logic: {
},
variables: {
weight: 50.06
},
maths: {
weight: 50.07,
blocks:
opts.monacoToolbox.maths.blocks
.concat(
opts.monacoToolbox.logic.blocks
.filter(block => block.name != 'logic_if'
&& block.name != 'logic_if_else'
&& block.name != 'logic_switch'))
},
text: {
advanced: true
},
functions: {
advanced: true
}
}
},
beforeCompile: () => {
function createModulatorDOM() {
let modulatorOutput = document.getElementById('modulatorAudioOutput');
if (!modulatorOutput) {
const customContent = document.getElementById('custom-content');
const modulatorOutput = document.createElement('audio') as HTMLAudioElement;
modulatorOutput.id = 'modulatorAudioOutput';
customContent.appendChild(modulatorOutput);
const modulatorView = document.createElement('div') as HTMLDivElement;
modulatorView.id = 'modulatorWrapper';
const modulatorBubble = document.createElement('div') as HTMLDivElement;
modulatorBubble.id = 'modulatorBubble';
const modulatorCanvas = document.createElement('canvas') as HTMLCanvasElement;
modulatorCanvas.id = 'modulatorWavStrip';
modulatorBubble.appendChild(modulatorCanvas);
modulatorView.appendChild(modulatorBubble);
customContent.appendChild(modulatorView);
}
}
createModulatorDOM();
// Play silence, in order to unblock audio.
let audioTag = document.getElementById("modulatorAudioOutput") as HTMLAudioElement;
audioTag.src = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQAAAAAAA==";
audioTag.play();
},
deployCoreAsync: (resp: pxtc.CompileResult) => {
//core.infoNotification(lf("Here's a tune..."));
let lbrEnable = false;
let modulationVersion = 2;
let audioFormat = "wav";
let bin = ltcIhexToBinary(resp.outfiles[pxtc.BINARY_HEX]);
let hex = resp.outfiles[pxtc.BINARY_HEX];
function getCanvas(): HTMLCanvasElement {
return document.getElementById("modulatorWavStrip") as HTMLCanvasElement;
}
function getAudioElement(): HTMLAudioElement {
return document.getElementById("modulatorAudioOutput") as HTMLAudioElement;
}
function getWaveFooter() {
return document.getElementById("modulatorWrapper");
}
function ltcIhexToBinary(ihex: string): Uint8Array {
let hex = ihex.split("\n");
let hexOutput: string[] = [];
// Figure out what the base offset is
var startAddr = parseInt(/^:..(....)00(.{4,})/.exec(hex[0])[1], 16);
for (let i = 0; i < hex.length; ++i) {
let m = /^:(..)(....)00(.{4,})/.exec(hex[i]);
if (!m) continue;
// Ensure we're padded properly. Add "0xff" if needed to fill in gaps in the hexfile.
var currentOffset = parseInt(m[2], 16);
while (startAddr + hexOutput.length < currentOffset) {
hexOutput.push("FF");
}
// Skip past the :, count, address, and record type fields, and chop off the checksum
let s = hex[i].slice(9, hex[i].length - 2);
let step = 2;
while (s.length >= step) {
let hexb = s.slice(0, step);
hexOutput.push(hexb);
s = s.slice(step)
}
}
console.log("Resulting binary is " + hexOutput.length + " bytes");
let output = new Uint8Array(hexOutput.length);
for (let i = 0; i < hexOutput.length; i++) {
output[i] = parseInt(hexOutput[i], 16);
}
return output;
}
function renderWave(e: any) {
let aud = e.target;
let current = aud.currentTime;
let end = aud.duration;
let canvas = getCanvas();
if (!canvas || !canvas.getContext || !modController || !end) {
return;
}
let strip = canvas.getContext("2d");
// Resize the canvas to be the window size.
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let h = strip.canvas.height;
let w = strip.canvas.width;
strip.clearRect(0, 0, w, h);
let y: number;
// Draw scale lines at 10% interval
strip.lineWidth = 1.0;
strip.strokeStyle = "#55a";
strip.beginPath();
y = 1 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 2 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 3 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 4 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 5 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 6 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 7 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 8 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
y = 9 * (h / 10);
strip.moveTo(0, y);
strip.lineTo(w, y);
strip.stroke();
strip.strokeStyle = "#fff";
strip.lineWidth = 1.0;
let buffer = modController.getPcmData();
let b = Math.floor(buffer.length * ((current * 1.0) / end));
let lastSample = (buffer[b++] + 32768) / 65536.0; // map -32768..32768 to 0..1
for (let x = 1; x < canvas.width; x++) {
let sample = (buffer[b++] + 32768) / 65536.0;
if (b > buffer.length) {
break;
}
strip.beginPath();
strip.moveTo(x - 1, h - lastSample * h);
strip.lineTo(x, h - sample * h);
strip.stroke();
lastSample = sample;
}
}
if (modController) {
modController.stop();
}
let resolve: (thenableOrResult?: void | PromiseLike<void>) => void;
let reject: (error: any) => void;
const deferred = new Promise<void>((res, rej) => {
resolve = res;
reject = rej;
});
modController = new ModControllerConstructor({
canvas: getCanvas(),
uriType: 'blob',
repeat: 2,
endCallback: function () {
getWaveFooter().style.visibility = "hidden";
getWaveFooter().style.opacity = "0";
pxt.log("Completed audio modulation");
resolve();
}
});
let audio = getAudioElement();
if (pxt.BrowserUtils.isIE()) {
// For IE, download the raw WAV data to a .wav file
const data = new Uint8Array(modController.getRawWavData(bin, lbrEnable, modulationVersion));
const fn = resp.downloadFileBaseName + ".wav";
pxt.debug('saving ' + fn)
const url = pxt.BrowserUtils.browserDownloadUInt8Array(
data,
fn,
"audio/wav",
resp.userContextWindow,
reject
);
showIEUploadInstructionsAsync(resp.confirmAsync, fn, url)
.then(() => resolve());
return deferred;
} else {
showUploadInstructionsAsync(resp.confirmAsync)
.then((confirm) => {
// For all other browsers, play the sound directly in the browser
modController.transcodeToAudioTag(bin, audio);
resp.saveOnly = true;
audio.ontimeupdate = renderWave;
getWaveFooter().style.visibility = "visible";
getWaveFooter().style.opacity = "1";
});
return deferred;
}
}
};
return Promise.resolve<pxt.editor.ExtensionResult>(res);
}