Componente personalizado de Scratch Blocks "Melody Play"

1. Antecedentes 

Al ver que la edición de microbits tiene un editor de melodías, también escribí uno en el bloque temporal, como se muestra a continuación.

esto es lo que escribí

Esto es Micro:bit

2. Descripción de la configuración de funciones

Admite control de melodía de 8 notas y 8 tiempos

3. Instrucciones de uso

(1) Introduzca y agregue field_tone.js a la carpeta principal, el código se encuentra a continuación

(2) Agregue estilo CSS a core/css.js, el código se encuentra a continuación

(3) Encapsule el componente de reproducción de audio Blockly.AudioContext, que implementa principalmente la API de reproducción de audio y la reproducción de audio basada en Web AudioContext. En core/audio_context.js, el código se encuentra a continuación

(4) Registre field_tone en Blockly para que pueda usarse en cualquier lugar. Si no desea inyectarlo, puede introducirlo directamente con la etiqueta script. Escriba el siguiente código en core/blockly.js:

goog.require('Blockly.FieldTone');

(5) El código de definición del bloque es el siguiente: el siguiente código se integra directamente en un bloque completo.

// 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) Agregue la configuración de la caja de herramientas, el código es el siguiente:

<block type="ZE3P_play_melody" id="ZE3P_play_melody"></block>

(7) La implementación de transcodificación toma Python como ejemplo. El formato de valor generado por el bloque es: cadena "C5 - - GFEDC 200", con espacios como separadores, y las primeras ocho son notas musicales, cada una de las cuales representa una frecuencia específica, donde ' - ' es el carácter de resto, el último número es BPM, y la relación con la duración es tiempo = 60000/BPM, el código es el siguiente:

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`;
};

Consejo: Si utiliza el método de registro, es mejor compilarlo localmente, no es necesario utilizar JavaScript para introducirlo. 

4. Visualización de efectos

 5. Código completo

El código completo de field_tone.js es el siguiente:

'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);

 Complete la imagen del código audio_context.js a continuación:

'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;
  }
}

Complete el código de estilo CSS, que puede agregarse a core/css.js o escribirse en el html de la página. Lo escribí directamente en css.js y lo agregué directamente a Blockly.Css.CONTENT. El código es el siguiente:

'.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 + ';',
  '}',

 6. Acerca de mí

Autor: Lu Zhimin

Contacto: [email protected]

Supongo que te gusta

Origin blog.csdn.net/weixin_43532890/article/details/132056642
Recomendado
Clasificación