Scratch Blocks自定义组件之「旋律播放」

news2024/12/22 20:07:37

一、背景 

看到microbit edit有旋律编辑器,就在scratch块中也写了一个,如下图所示

这是我写的

这是Micro:bit的

二、功能配置说明

支持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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825471.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PPT密码】PPT编辑限制的设置与取消

PPT文件可以设置编辑限制吗&#xff1f;其实是可以的&#xff0c;只不过ppt文件不像word、excel一样有一个明确的设置按钮。今天我们一起来看一下&#xff0c;如何给PPT设置修改限制。 将PPT文件另存为操作&#xff0c;在设置保存路径时点击工具 – 常规选项功能&#xff0c;然…

【云原生】Kubernetes中deployment是什么?

目录 Deployments 更新 Deployment 回滚 Deployment 缩放 Deployment Deployment 状态 清理策略 金丝雀部署 编写 Deployment 规约 Deployments 一个 Deployment 为 Pod 和 ReplicaSet 提供声明式的更新能力。 你负责描述 Deployment 中的 目标状态&#xff0c;而 De…

Mysql中文乱码问题

问题&#xff1a; 解决&#xff1a; set names gbk;

Python的字典使用

今天做力扣上1207. 独一无二的出现次数添加链接描述时用到了python字典&#xff0c;于是把字典的用法整理了一下。 新建字典 iters {}检查字典中是否含有某一个键 iters.has_key(key)字典根据键访问值 iters[key]遍历字典的键和值 for key,value in iters.items():整体代码 c…

18. python从入门到精通——网络编程

Socket:提供给程序可以对外进程连接的接口&#xff0c;是对底层协议的封装。根据不同的的底层协议&#xff0c;Socket的实现是多样化的。每个socket都要绑定端口号和IP 优势&#xff1a;在用python进行编程的时候不用考虑三次握手等网络协议的具体实现&#xff0c;可以直接通过…

【Redis】内存数据库Redis进阶(Redis分片集群)

目录 分布式缓存 Redis 四大问题搭建Redis分片集群分片原理散列插槽&#xff08;插槽原理&#xff09;集群伸缩需求设定配置集群伸缩 故障转移自动故障转移手动故障转移 RedisTemplate访问分片集群 分布式缓存 Redis 四大问题 基于 Redis 集群解决单机 Redis 存在的四大问题&a…

如何在CSDN上转发别人的文章

很多小伙伴可能跟我一样&#xff0c;看到一些优秀发文章或内容&#xff0c;想转发到自己的CSDN账号上&#xff0c;但是在CSDN上找了半天没找到CSDN转发的功能。鉴于我成功转发文章到CSDN上后&#xff0c;网上关于转发文章到CSDN的教程写的比较简单&#xff0c;我整理了一份比较…

HTTP——五、与HTTP协作的Web服务器

HTTP 一、用单台虚拟主机实现多个域名二、通信数据转发程序 &#xff1a;代理、网关、隧道1、代理2、网关3、隧道 三、保存资源的缓存1、缓存的有效期限2、客户端的缓存 一台 Web 服务器可搭建多个独立域名的 Web 网站&#xff0c;也可作为通信路径上的中转服务器提升传输效率。…

如何制作Windows10安装U盘

如何制作Windows10安装U盘 有新电脑的时候&#xff0c;我们会用安装U盘装系统&#xff0c;可是要怎么制作&#xff1f; 工具/原料 一台可以使用并且能上网的电脑 一个至少4.7GB的U盘 方法/步骤 1 2 选择官网 3 点击“立即下载工具”下载制作工具 4 插入U盘&#xff…

Linux常用命令——dpkg-divert命令

在线Linux命令查询工具 dpkg-divert Debian Linux中创建并管理一个转向列表 补充说明 dpkg-divert命令是Debian Linux中创建并管理一个转向&#xff08;diversion&#xff09;列表&#xff0c;其使得安装文件的默认位置失效的工具。 语法 dpkg-divert(选项)(参数)选项 -…

C#使用libmodbus库与工业设备进行读写测试

一.编译libmodbus库供C#使用 如何编译&#xff1f;请移步&#xff1a;https://blog.csdn.net/weixin_42205408/article/details/119530811 上面博主的文章除了所写的modbus.cs内的代码有点问题外&#xff08;可能上面博主和我的Win 10 64位 Visual Studio 2019平台不一样吧&a…

IDEA社区版插件汇总

1. Smart Tomcat 顾名思义就是配置tomcat的&#xff0c;跟专业版配置小猫类似。 2. Database Navigator 类似专业版的数据库管理工具。 3. Spring Boot Assistant SpringBoot开发插件。&#xff08;可以识别springboot主配置文件&#xff0c;以及代码提示&#xff0c;我这个版本…

深度学习与计算机相结合:直播实时美颜SDK的创新之路

时下&#xff0c;实时美颜技术就成为了直播主们的得力工具&#xff0c;它可以在直播过程中即时处理视频画面。而支持实时美颜功能的SDK更是推动了这项技术的发展&#xff0c;让直播主和普通用户都能轻松使用美颜功能。 一、美颜技术的演进 早期的美颜技术主要依赖于简单的图…

TPC-DS 标准介绍、工具下载地址

目录 一、TPC-DS标准介绍 1. DMS介绍 2. TCP-DS概念 二、数据库模型 1. 数据库模型介绍 2. 数据库模型包含内容 三、数据生成器 1. 数据生成器介绍 2. 数据生成器包含内容 四、查询集合 1. 查询集合介绍 2. 查询集合包含的88个标准化查询和17个基准统计函数 五、性…

外卖多门店小程序开源版开发

外卖多门店小程序开源版开发 外卖多门店小程序开源版的开发可以按照以下步骤进行&#xff1a; 确定需求&#xff1a;明确外卖多门店小程序的功能和特点&#xff0c;包括用户注册登录、浏览菜单、下单支付、订单管理等。技术选型&#xff1a;选择适合开发小程序的技术框架&…

Red Hat 安装MySQL 8.0与 Navicat

目录 Red Hat 安装 MySQL 8.0 1、更新软件包列表 2、安装MySQL服务器和客户端 3、启动MySQL服务 4、确保MySQL服务器正在运行 5、root 用户的密码 6、登录MySQL&#xff0c;输入mysql密码 7、MySQL默认位置 Red Hat 安装 Navicat 1、下载 Navicat 2、执行命令 Red H…

Django笔记之使用原生SQL查询数据库

Django 提供了两种方式来执行原生 SQL 代码。 一种是使用 raw() 函数&#xff0c;一种是 使用 connection.cursor()。 但是官方还是推荐在使用原生 SQL 之前&#xff0c;尽量的先去探索一下 QuerySet 提供的各种 API。 目前而言&#xff0c;官方文档提供的各种 API 是能够满…

修改cuda软链接(实操演示)

文章目录 1 找到已存在的CUDA软链接2 确认当前软链接真实路径3 删除现有软链接4 创建新的软链接5 验证新的软链接 要修改CUDA的软链接&#xff0c;需要找到已经存在的软链接并重新创建它指向新的目录。 1 找到已存在的CUDA软链接 首先&#xff0c;需要找到之前创建的CUDA软链…