スクラッチブロックカスタムコンポーネント「メロディープレイ」

1. 背景 

microbit edit にメロディーエディターがあるのを見て、以下に示すように、スクラッチブロックにもメロディーエディターを作成しました。

これは私が書いたことです

これがマイクロビットです

2. 機能構成説明

8音と8ビートのメロディーコントロールをサポート

3. 使用上の注意

(1) field_tone.js を導入してコアフォルダーに追加します。コードは以下のとおりです。

(2) CSS スタイルを core/css.js に追加します。コードは以下のとおりです。

(3) 主にオーディオ再生 API を実装し、Web AudioContext に基づいてオーディオ再生を実装するオーディオ再生コンポーネント Blockly.AudioContext をカプセル化します。core/audio_context.js のコードは次のとおりです。

(4) field_tone を Blockly に登録してどこでも使えるようにする 注入したくない場合は script タグで直接導入することもできるので core/blockly.js に以下のコードを記述します。

goog.require('Blockly.FieldTone');

(5) ブロック定義コードは次のとおりであり、以下のコードが直接完全なブロックに組み込まれます。

// 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) ツールボックス構成を追加します。コードは次のとおりです。

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

(7) トランスコーディングの実装は Python を例にとります。ブロックによって生成される値の形式は次のとおりです: 文字列 "C5 -- GFEDC 200"、区切り文字としてスペース、最初の 8 つは音符で、それぞれが特定の周波数を表します。 - ' は休符文字、最後の数字は BPM、長さとの関係は時間 = 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 を使用する必要はありません。 

4.エフェクト表示

 5. 完全なコード

完全な 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 + ';',
  '}',

 6. 私について

著者: 陸志民

連絡先: [email protected]

おすすめ

転載: blog.csdn.net/weixin_43532890/article/details/132056642