一、背景
看到microbit edit有旋律编辑器,就在scratch块中也写了一个,如下图所示
二、功能配置说明
支持8个音符8拍旋律控制
三、使用说明
(1)引入添加field_tone.js到core文件夹中,代码在下文
(2)添加css样式到core/css.js中,代码在下文
(3)封装音频播放组件Blockly.AudioContext,主要实现音频播放API,基于Web AudioContext实现音频播放,在core/audio_context.js,代码在下文
(4)将field_tone注册到Blockly中,这样在任意地方都可以使用,如果不想注入,直接用script标签引入也行,在core/blockly.js写如下代码:
goog.require('Blockly.FieldTone');
(5)block定义代码如下,下面代码是直接集成到一个完整block中
// ZE3P蜂鸣器 旋律播放
Blockly.Blocks['ZE3P_play_melody'] = {
init: function () {
this.jsonInit({
"message0": "%1%2",
"args0": [
{
"type": "field_image",
"src": Blockly.mainWorkspace.options.pathToMedia + "/extensions/ZE3P.png",
"width": 24,
"height": 24
},
{
"type": "field_vertical_separator"
}
],
"message1": "播放旋律%1",
"args1": [
{
"type": "field_tone",
"name": "TONE",
}
],
"category": Blockly.Categories.sounds,
"extensions": ["colours_sounds", "shape_statement"]
});
}
}
(6)添加toolbox配置,代码如下:
<block type="ZE3P_play_melody" id="ZE3P_play_melody"></block>
(7)转码实现以python为例,块生成的value格式为:字符串"C5 - - G F E D C 200",以空格为分隔符,前八个为音符,每一个代表特定的频率,其中'-'为休止符,最后一个数字为BPM,和持续时间的关系为 time = 60000 / BPM,代码如下:
Blockly.Python['ZE3P_play_melody'] = function (block) {
const toneString = block.getFieldValue('TONE') || "";
const melody = toneString.split(" ");
const time = parseInt(melody.pop());
console.log(melody, time)
// 根据实际代码而定
return `music.play_melody("${melody.join(" ")}", ${time})\n`;
};
提示:如果采用注册方法,最好本地编译一下,使用javascript引入则不需要
四、效果展示
五、完整代码
完整field_tone.js代码如下:
'use strict';
goog.provide('Blockly.FieldTone');
goog.require('Blockly.DropDownDiv');
goog.require('Blockly.AudioContext');
/**
* 构造器
* @param music
* @constructor
*/
Blockly.FieldTone = function (music) {
music = "C5 B A G F E D C 200";
// 初始化
this.tones_ = [];
Blockly.FieldTone.superClass_.constructor.call(this, music);
this.addArgType('music');
// 缩略图节点按钮
this.buttonThumbNodes_ = [];
// 编辑节点按钮
this.buttonNodes_ = [];
// 播放状态
this.isPlaying = false;
// 播放定时器
this.playTimer = null;
// 事件句柄
this.bpmWrapper_ = null;
this.toneWrapper_ = null;
this.playWrapper_ = null;
this.finishWrapper_ = null;
};
goog.inherits(Blockly.FieldTone, Blockly.Field);
/**
* Json解析
*/
Blockly.FieldTone.fromJson = function (options) {
return new Blockly.FieldTone(options['music']);
};
/**
* 缩略图节点宽度
* @type {number}
* @const
*/
Blockly.FieldTone.THUMBNAIL_NODE_SIZE = 10;
/**
* 缩略图节点圆角
* @type {number}
* @const
*/
Blockly.FieldTone.THUMBNAIL_NODE_ROUND = 2;
/**
* 缩略图节点之间间距
* @type {number}
* @const
*/
Blockly.FieldTone.THUMBNAIL_NODE_GAP = 3;
/**
* 缩略图节点父块顶部距离
* @type {number}
* @const
*/
Blockly.FieldTone.THUMBNAIL_NODE_TOP = 5;
/**
* 节点宽度
* @type {number}
* @const
*/
Blockly.FieldTone.EDIT_NODE_SIZE = 20;
/**
* 节点圆角
* @type {number}
* @const
*/
Blockly.FieldTone.EDIT_NODE_ROUND = 3;
/**
* 节点之间间距
* @type {number}
* @const
*/
Blockly.FieldTone.EDIT_NODE_GAP = 6;
/**
* 编辑音频面板内边距
* @type {number}
* @const
*/
Blockly.FieldTone.EDIT_DROPDOWN_PAD = 10;
/**
* 节拍个数
* @type {number}
* @const
*/
Blockly.FieldTone.NODE_SIZE = 8;
/**
* 最大节拍
* @type {number}
* @const
*/
Blockly.FieldTone.MAX_BPM = 1000;
/**
* 最大节拍
* @type {number}
* @const
*/
Blockly.FieldTone.MIN_BPM = 1;
/**
* 默认值
* @type {string}
*/
Blockly.FieldTone.DEFAULT_NOTE = "- - - - - - - - " + Blockly.FieldTone.MIN_BPM;
/**
* 初始化
*/
Blockly.FieldTone.prototype.init = function () {
if (this.fieldGroup_) {
return;
}
// 重建DOM
this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
this.size_.width = Blockly.FieldTone.THUMBNAIL_NODE_SIZE * 11 +
Blockly.FieldTone.THUMBNAIL_NODE_GAP * 7 + 24;
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
if (!this.sourceBlock_.isShadow()) {
this.box_ = Blockly.utils.createSvgElement('rect', {
'rx': Blockly.BlockSvg.NUMBER_FIELD_CORNER_RADIUS,
'ry': Blockly.BlockSvg.NUMBER_FIELD_CORNER_RADIUS,
'x': 0,
'y': 0,
'width': this.size_.width,
'height': this.size_.height,
'stroke': this.sourceBlock_.getColourTertiary(),
'fill': this.sourceBlock_.getColour(),
'class': 'blocklyBlockBackground',
'fill-opacity': 1
}, null);
this.fieldGroup_.insertBefore(this.box_, this.textElement_);
}
const nodeSize = Blockly.FieldTone.THUMBNAIL_NODE_SIZE;
const nodeGap = Blockly.FieldTone.THUMBNAIL_NODE_GAP;
const nodeHeight = this.size_.height - Blockly.FieldTone.THUMBNAIL_NODE_TOP * 2;
const nodeRound = Blockly.FieldTone.THUMBNAIL_NODE_ROUND;
// 创建图标
this.iconElement_ = Blockly.utils.createSvgElement('image', {
'height': '24px', 'width': '24px',
'x': nodeSize, 'y': "4px",
}, this.fieldGroup_);
this.iconElement_.setAttributeNS('http://www.w3.org/1999/xlink',
'xlink:href', Blockly.mainWorkspace.options.pathToMedia + '/extensions/music-block-icon.svg');
// 创建缩略按键节点
for (let i = 0; i < Blockly.FieldTone.NODE_SIZE; i++) {
const x = nodeSize * 1.5 + (nodeSize + nodeGap) * i + 24;
const attr = {
'x': x, 'y': Blockly.FieldTone.THUMBNAIL_NODE_TOP,
'width': nodeSize, 'height': nodeHeight,
'rx': nodeRound, 'ry': nodeRound,
'fill': '#dcdcdc',
'stroke': '#f0f0f0'
};
this.buttonThumbNodes_.push(Blockly.utils.createSvgElement('rect', attr, this.fieldGroup_));
}
this.updateTone_();
this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(
this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
};
/**
* 设置值
*/
Blockly.FieldTone.prototype.setValue = function (newValue) {
// 解析值
newValue = this.parseValue(newValue);
// 无改变
if (newValue === null || newValue === this.value_) {
return;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_, 'field', this.name, this.value_, newValue));
}
// 更新值
this.value_ = newValue;
this.tones_ = this.valueToTones(newValue);
// 更新缩略图
this.updateTone_();
};
/**
* 获取值
*/
Blockly.FieldTone.prototype.getValue = function () {
return this.value_;
};
/**
* 更新
* @private
*/
Blockly.FieldTone.prototype.updateTone_ = function () {
if (!this.buttonThumbNodes_) {
return;
}
for (let i = 0; i < this.buttonThumbNodes_.length; i++) {
// 更新缩略
this.buttonThumbNodes_[i].setAttribute("fill", this.getToneColor(this.tones_[i]));
// 更新按钮
if (this.buttonNodes_ && this.buttonNodes_.length === 64) {
for (let j = 0; j < Blockly.FieldTone.NODE_SIZE; j++) {
const color = (this.getToneIndex(this.tones_[i]) === j) ?
this.getToneColor(this.tones_[i]) : '#dcdcdc';
this.buttonNodes_[j * 8 + i].setAttribute("fill", color);
}
}
}
}
/**
* 解析值
* @param value
*/
Blockly.FieldTone.prototype.parseValue = function (value) {
if (!value) {
return Blockly.FieldTone.DEFAULT_NOTE;
}
// 删除边界空白
value = value.trim();
// 分割提取值
let tones = value.split(" ");
if (tones.length !== 9) {
return Blockly.FieldTone.DEFAULT_NOTE;
}
// 异常音符值
for (let i = 0; i < 8; i++) {
if (!this.isValidNote(tones[i])) {
return Blockly.FieldTone.DEFAULT_NOTE;
}
}
// 异常节拍
const bpm = parseInt(tones[8]);
if (isNaN(bpm) || bpm > Blockly.FieldTone.MAX_BPM || bpm < Blockly.FieldTone.MIN_BPM) {
return Blockly.FieldTone.DEFAULT_NOTE;
}
return value;
}
/**
* 数据值转换位音符值
* @param value
*/
Blockly.FieldTone.prototype.valueToTones = function (value) {
value = this.parseValue(value);
let data = value.split(" ");
const tones = [];
for (let i = 0; i < 8; i++) {
tones.push(data[i]);
}
tones.push(parseInt(data[8]));
return tones;
}
/**
* 数据值转换位音符值
* @param tones
*/
Blockly.FieldTone.prototype.tonesToValue = function (tones = []) {
if (tones.length !== 9) {
return Blockly.FieldTone.DEFAULT_NOTE;
}
return this.tones_.join(" ");
}
/**
* 判断音符是否有效
* @param tone
* @returns {boolean}
*/
Blockly.FieldTone.prototype.isValidNote = function (tone) {
const tones = ["C", "D", "E", "F", "G", "A", "B", "C5", "-"];
return tones.includes(tone);
}
/**
* 获取音符颜色
* @param tone
* @returns {string}
*/
Blockly.FieldTone.prototype.getToneColor = function (tone) {
const toneColors = {
"C": "#A80000",
"D": "#D83B01",
"E": "#FFB900",
"F": "#107C10",
"G": "#008272",
"A": "#bb0dd5",
"B": "#5C2D91",
"C5": "#f88fe9",
}
return toneColors[tone] || "#DCDCDC";
}
/**
* 获取音符值
* @param index
* @returns {Number}
*/
Blockly.FieldTone.prototype.getToneValue = function (index) {
const tones = ["C", "D", "E", "F", "G", "A", "B", "C5"];
if (index >= 0 && index < 8) {
return tones[index];
}
return "-";
}
/**
* 获取颜色下标
* @param tone
* @returns {Number}
*/
Blockly.FieldTone.prototype.getToneIndex = function (tone) {
const tones = ["C", "D", "E", "F", "G", "A", "B", "C5"];
return tones.indexOf(tone);
}
/**
* 选择音符
* @param index
* @returns {number}
*/
Blockly.FieldTone.prototype.getToneCore = function (index) {
const toneValues = [523, 494, 440, 392, 349, 330, 294, 262];
if (index >= 0 && index < 8) {
return toneValues[index];
}
return 0;
}
/**
* 获取播放时长
* @returns {number}
*/
Blockly.FieldTone.prototype.getDuration = function () {
return 60000 / parseInt(this.tones_[8]);
}
/**
* 显示下拉
*/
Blockly.FieldTone.prototype.showEditor_ = function () {
Blockly.DropDownDiv.hideWithoutAnimation();
Blockly.DropDownDiv.clearContent();
// 创建下拉面板内容
const contentDiv = Blockly.DropDownDiv.getContentDiv();
const nodeSize = Blockly.FieldTone.NODE_SIZE;
const toneSize = Blockly.FieldTone.EDIT_NODE_SIZE * nodeSize +
Blockly.FieldTone.EDIT_NODE_GAP * (nodeSize - 1) +
Blockly.FieldTone.EDIT_DROPDOWN_PAD * 2;
this.toneStage_ = Blockly.utils.createSvgElement('svg', {
'xmlns': 'http://www.w3.org/2000/svg',
'height': toneSize + 'px',
'width': toneSize + 'px'
}, contentDiv);
// 清空面板按钮
this.buttonNodes_ = [];
// 创建音符按钮
for (let i = 0; i < nodeSize; i++) {
for (let j = 0; j < nodeSize; j++) {
// 计算按钮位置
const x = (Blockly.FieldTone.EDIT_NODE_SIZE * j) +
(Blockly.FieldTone.EDIT_NODE_GAP * j) +
Blockly.FieldTone.EDIT_DROPDOWN_PAD;
const y = (Blockly.FieldTone.EDIT_NODE_SIZE * i) +
(Blockly.FieldTone.EDIT_NODE_GAP * i) +
Blockly.FieldTone.EDIT_DROPDOWN_PAD;
// 设置显示颜色
const color = (this.getToneIndex(this.tones_[j]) === i) ?
this.getToneColor(this.tones_[j]) : '#dcdcdc';
const attr = {
'x': x, 'y': y,
'width': Blockly.FieldTone.EDIT_NODE_SIZE,
'height': Blockly.FieldTone.EDIT_NODE_SIZE,
'rx': Blockly.FieldTone.EDIT_NODE_ROUND,
'ry': Blockly.FieldTone.EDIT_NODE_ROUND,
'fill': color, 'stroke': 'white',
"cursor": "pointer"
};
const tone = Blockly.utils.createSvgElement('rect', attr, this.toneStage_);
this.toneStage_.appendChild(tone);
this.buttonNodes_.push(tone);
}
}
// 工具栏
const toolDiv = document.createElement('div');
toolDiv.className = "FieldToneTool";
contentDiv.appendChild(toolDiv);
// 左侧布局
const leftDiv = document.createElement('div');
toolDiv.appendChild(leftDiv);
this.bpmInput = document.createElement('input');
this.bpmInput.className = "blocklyHtmlInput";
this.bpmInput.value = this.tones_[8];
leftDiv.appendChild(this.bpmInput);
const bpmSpan = document.createElement('span');
bpmSpan.innerText = "BPM";
leftDiv.appendChild(bpmSpan);
// 按钮组
const rightDiv = document.createElement('div');
toolDiv.appendChild(rightDiv);
this.playButton = document.createElement('button');
this.playButton.innerText = "播放";
rightDiv.appendChild(this.playButton);
const finishButton = document.createElement('button');
finishButton.innerText = "完成"
rightDiv.appendChild(finishButton);
// 计算下拉面板位置
const scale = this.sourceBlock_.workspace.scale;
let bBox = {width: this.size_.width, height: this.size_.height};
bBox.width *= scale;
bBox.height *= scale;
const position = this.fieldGroup_.getBoundingClientRect();
const primaryX = position.left + bBox.width / 2;
const primaryY = position.top + bBox.height;
const secondaryX = primaryX;
const secondaryY = position.top;
// 设置颜色
Blockly.DropDownDiv.setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
Blockly.DropDownDiv.show(this, primaryX, primaryY, secondaryX, secondaryY, this.onHide_.bind(this));
const primaryColour = (this.sourceBlock_.isShadow()) ?
this.sourceBlock_.parentBlock_.getColour() : this.sourceBlock_.getColour();
Blockly.DropDownDiv.setColour(primaryColour, this.sourceBlock_.getColourTertiary());
const category = (this.sourceBlock_.isShadow()) ?
this.sourceBlock_.parentBlock_.getCategory() : this.sourceBlock_.getCategory();
Blockly.DropDownDiv.setCategory(category);
// 事件处理
this.bpmWrapper_ = Blockly.bindEvent_(this.bpmInput, 'input', this, this.onBpmChange);
this.toneWrapper_ = Blockly.bindEvent_(this.toneStage_, 'mousedown', this, this.onNoteClick);
this.playWrapper_ = Blockly.bindEvent_(this.playButton, 'click', this, this.onPlayClick);
this.finishWrapper_ = Blockly.bindEvent_(finishButton, 'click', this, this.onFinishClick);
};
/**
* 校验是否点击音符
* @param e
* @returns {number}
* @private
*/
Blockly.FieldTone.prototype.checkForNote_ = function (e) {
const bBox = this.toneStage_.getBoundingClientRect();
const size = Blockly.FieldTone.NODE_SIZE;
const nodeSize = Blockly.FieldTone.EDIT_NODE_SIZE;
const nodePad = Blockly.FieldTone.EDIT_NODE_GAP;
const dx = e.clientX - bBox.left;
const dy = e.clientY - bBox.top;
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const item = this.buttonNodes_[i * size + j];
const x = parseInt(item.getAttribute("x"));
const y = parseInt(item.getAttribute("y"));
if (x <= dx && dx < x + nodeSize && y <= dy && dy < y + nodeSize) {
return i * size + j;
}
}
}
return -1;
};
/**
* 节拍输入事件
*/
Blockly.FieldTone.prototype.onBpmChange = function () {
this.stopMelody_();
let value = this.bpmInput.value;
value = Number(parseInt(value));
if (isNaN(value) || value <= Blockly.FieldTone.MIN_BPM) {
value = Blockly.FieldTone.MIN_BPM;
} else if (value >= Blockly.FieldTone.MAX_BPM) {
value = Blockly.FieldTone.MAX_BPM;
}
this.tones_[8] = value;
this.bpmInput.value = value;
// 更新值
this.setValue(this.tonesToValue(this.tones_));
}
/**
* 点击音符按钮
*/
Blockly.FieldTone.prototype.onNoteClick = function (e) {
const index = this.checkForNote_(e);
if (index >= this.buttonNodes_.length || index === -1)
return;
const row = Math.trunc(index / Blockly.FieldTone.NODE_SIZE);
const col = index % Blockly.FieldTone.NODE_SIZE;
const tone = this.getToneValue(row);
if (this.tones_[col] === tone) {
this.tones_[col] = "-";
} else {
this.tones_[col] = this.getToneValue(row);
// 播放点击音符
this.playTone_(this.getToneCore(row));
}
// 更新值
this.setValue(this.tonesToValue(this.tones_));
}
/**
* 播放单个音符
* @param toneValue
*/
Blockly.FieldTone.prototype.playTone_ = function (toneValue) {
this.stopMelody_();
this.updateGrid_();
Blockly.AudioContext.tone(toneValue);
setTimeout(() => {
Blockly.AudioContext.stop();
}, this.getDuration());
}
/**
* 点击播放按钮
*/
Blockly.FieldTone.prototype.onPlayClick = function () {
this.isPlaying = !this.isPlaying;
this.playButton.innerText = this.isPlaying ? "暂停" : "播放";
if (this.isPlaying) {
this.playMelody_(0);
} else {
this.stopMelody_();
}
}
/**
* 停止旋律播放
*/
Blockly.FieldTone.prototype.stopMelody_ = function () {
clearTimeout(this.playTimer);
Blockly.AudioContext.stop();
this.playTimer = null;
this.playButton.innerText = "播放";
this.isPlaying = false;
}
/**
* 开始播放旋律
* @param index
*/
Blockly.FieldTone.prototype.playMelody_ = function (index) {
if (index >= 8) {
this.stopMelody_();
this.updateGrid_();
} else {
// 更新格子,显示出当前播放的列
this.updateGrid_(index);
// 播放当前音符
const tone = this.getToneCore(this.getToneIndex(this.tones_[index++]));
if (tone) {
Blockly.AudioContext.tone(tone);
} else {
Blockly.AudioContext.stop();
}
this.playTimer = setTimeout(() => {
this.playMelody_(index);
}, this.getDuration());
}
}
/**
* 更新格子样式
* @param col
*/
Blockly.FieldTone.prototype.updateGrid_ = function (col = -1) {
const nodeSize = Blockly.FieldTone.NODE_SIZE;
for (let i = 0; i < nodeSize; i++) {
for (let j = 0; j < nodeSize; j++) {
const index = j * nodeSize + i;
const fill = this.buttonNodes_[index].getAttribute("fill");
// 只更新休止符
if (fill === "#dcdcdc") {
if (i === col) {
this.buttonNodes_[index].setAttribute("fill-opacity", ".7");
} else {
this.buttonNodes_[index].setAttribute("fill-opacity", "1");
}
}
}
}
};
/**
* 点击完成按钮
*/
Blockly.FieldTone.prototype.onFinishClick = function () {
Blockly.DropDownDiv.hide();
this.stopMelody_();
}
/**
* 更新颜色
* @package
*/
Blockly.FieldTone.prototype.updateColour = function () {
if (this.fieldGroup_ && this.sourceBlock_) {
this.fieldGroup_.setAttribute('stroke', this.sourceBlock_.getColourTertiary());
if (this.sourceBlock_ && (this.sourceBlock_.disabled || this.sourceBlock_.getInheritedDisabled())) {
this.fieldGroup_.setAttribute('stroke-opacity', ".25");
this.fieldGroup_.setAttribute('fill-opacity', ".25");
} else {
this.fieldGroup_.setAttribute('stroke-opacity', "1");
this.fieldGroup_.setAttribute('fill-opacity', "1");
}
}
};
/**
* 隐藏时
*/
Blockly.FieldTone.prototype.onHide_ = function () {
if (this.bpmWrapper_)
Blockly.unbindEvent_(this.bpmWrapper_);
if (this.toneWrapper_)
Blockly.unbindEvent_(this.toneWrapper_);
if (this.playWrapper_)
Blockly.unbindEvent_(this.playWrapper_);
if (this.finishWrapper_)
Blockly.unbindEvent_(this.finishWrapper_);
};
Blockly.Field.register('field_tone', Blockly.FieldTone);
完成audio_context.js代码图下:
'use strict';
goog.provide('Blockly.AudioContext');
/**
* 音频管理
* @constructor
*/
Blockly.AudioContext = function () {
}
/**
* mute audio
* @type {boolean}
* @private
*/
Blockly.AudioContext._mute = false;
/**
* AudioContext
* @type {null}
* @private
*/
Blockly.AudioContext._context = null;
/**
* frequency
* @type {number}
* @private
*/
Blockly.AudioContext._frequency = 0;
/**
* gain
* @type {null}
* @private
*/
Blockly.AudioContext._gain = null;
/**
* OscillatorNode
* @type {null}
* @private
*/
Blockly.AudioContext._vco = null;
/**
* Create context
* @returns {null}
*/
Blockly.AudioContext.context = function () {
if (!Blockly.AudioContext._context)
Blockly.AudioContext._context = Blockly.AudioContext.freshContext();
return Blockly.AudioContext._context;
}
/**
* freshContext
* @returns {undefined|AudioContext}
*/
Blockly.AudioContext.freshContext = function () {
if (window.AudioContext) {
try {
return new window.AudioContext();
} catch (e) {
}
}
return undefined;
}
Blockly.AudioContext.mute = function (mute) {
if (!Blockly.AudioContext._context)
return;
Blockly.AudioContext._mute = mute;
Blockly.stop();
if (mute && Blockly.AudioContext._vco) {
Blockly.AudioContext._vco.disconnect();
Blockly.AudioContext.gain.disconnect();
Blockly.AudioContext._vco = undefined;
Blockly.AudioContext.gain = undefined;
}
}
/**
* Stop
*/
Blockly.AudioContext.stop = function () {
if (!Blockly.AudioContext._context)
return;
Blockly.AudioContext._gain.gain.setTargetAtTime(0, Blockly.AudioContext._context.currentTime, 0.015);
Blockly.AudioContext._frequency = 0;
}
/**
* frequency
* @returns {number|*}
*/
Blockly.AudioContext.frequency = function () {
return Blockly.AudioContext._frequency;
}
/**
* Play
* @param frequency
*/
Blockly.AudioContext.tone = function (frequency) {
if (Blockly.AudioContext._mute)
return;
if (isNaN(frequency) || frequency < 0)
return;
Blockly.AudioContext._frequency = frequency;
let ctx = Blockly.AudioContext.context();
if (!ctx)
return;
try {
if (!Blockly.AudioContext._vco) {
Blockly.AudioContext._vco = ctx.createOscillator();
Blockly.AudioContext._vco.type = 'triangle';
Blockly.AudioContext._gain = ctx.createGain();
Blockly.AudioContext._gain.gain.value = 0;
Blockly.AudioContext._gain.connect(ctx.destination);
Blockly.AudioContext._vco.connect(Blockly.AudioContext._gain);
Blockly.AudioContext._vco.start(0);
}
Blockly.AudioContext._vco.frequency.linearRampToValueAtTime(frequency, Blockly.AudioContext._context.currentTime);
Blockly.AudioContext._gain.gain.setTargetAtTime(.2, Blockly.AudioContext._context.currentTime, 0.015);
} catch (e) {
Blockly.AudioContext._vco = undefined;
}
}
完成css样式代码,可以添加到core/css.js中,也可以写在页面的html中,我是直接写在了css.js中,直接追加到Blockly.Css.CONTENT中就行,代码如下:
'.FieldToneTool{',
'display: flex;',
'margin-bottom: 8px;',
'padding: 0 10px;',
'align-items: center;',
'justify-content: space-between;',
'}',
'.FieldToneTool > div> input {',
'width: 42px;',
'height: 24px;',
'border-radius: 3px;',
"margin-right: 4px;",
'font-size: 12px;',
'}',
'.FieldToneTool > div> span {',
'color: white;',
'font-size: 12px;',
'}',
'.FieldToneTool >div> button {',
'padding: 0 8px;',
'border-radius: 3px;',
'cursor: pointer;',
'height: 24px;',
'outline: none;',
'transition: all .1s;',
'border: 1px solid rgba(0,0,0,.2);',
'background-color:rgba(0,0,0,.1);',
'font-size: 12px;',
'color: white;',
'margin-left: 8px;',
'}',
'.FieldToneTool >div> button:hover {',
'background-color:rgba(0,0,0,.2);',
'}',
'.FieldToneTool >div> button:active {',
'box-shadow: 0px 0px 0px 4px ' + Blockly.Colours.fieldShadow + ';',
'}',
六、关于我
作者:陆志敏
联系:761324428@qq.com