// 简谱实现地址: https://github.com/imiskolee/vexflow/blob/master/src/numbered_note.js
import { Vex } from "./vex";
import { StaveNote } from "./stavenote";
import { Flow } from "./tables";
import { Dot } from "./dot";
import { Glyph } from "./glyph";

function initGroup() {
  return {
    notes: [],
    finish: false,
    total: 0,
    staveId: ''
  }
}

let _text_width = null;
let tone = 'C';
let group = initGroup();
let _denominator = ''
let _numerator = ''
let ornamentGroup = ''
export class NumberedNote extends StaveNote {

  static get CATEGORY() {
    return "numberednotes"
  }

  // 固定调 需要升降调
  static accidentalAndKey(key) {
    switch (key) {

      case "G":
        return {
          "4": "#"
        }
      case "D":
        return {
          "1": "#",
          "4": "#"
        }
      case "A":
        return {
          "1": "#",
          "4": "#",
          "5": "#",
        }
      case "E":
        return {
          "1": "#",
          "2": "#",
          "4": "#",
          "5": "#"
        }
      case "B":
        return {
          "1": "#",
          "2": "#",
          "4": "#",
          "5": "#",
          "6": "#",
        }
      case "F#":
        return {
          "1": "#",
          "2": "#",
          "3": "#",
          "4": "#",
          "5": "#",
          "6": "#",
        }
      case "C#":
        return {
          "1": "#",
          "2": "#",
          "3": "#",
          "4": "#",
          "5": "#",
          "6": "#",
          "7": "#",
        }
      case "F":
        return {
          "7": "b"
        }
      case "Bb":
        return {
          "7": "b",
          "3": "b",
        }
      case "Eb":
        return {
          "7": "b",
          "3": "b",
          "6": "b",
        }
      case "Ab":
        return {
          "7": "b",
          "2": "b",
          "3": "b",
          "6": "b",
        }
      case "Db":
        return {
          "7": "b",
          "2": "b",
          "3": "b",
          "5": "b",
          "6": "b",
        }
      case "Gb":
        return {
          "7": "b",
          "1": "b",
          "2": "b",
          "3": "b",
          "5": "b",
          "6": "b",
        }
      case "Cb":
        return {
          "7": "b",
          "1": "b",
          "2": "b",
          "3": "b",
          "4": "b",
          "5": "b",
          "6": "b",
        }

      default:
        return {}
    }
  }
  /**
     * 临时升降调 简谱符号映射
     */
  static accidentalMap(key) {
    //
    switch (key) {
      case "C":
        return ''

      case "F":
      case "Bb":
      case "Eb":
      case "Ab":
      case "Db":
      case "Gb":
      case "Cb":
        return {
          "bb": "b",
          "b": "",
          "n": "#",
          "#": "##",
        }
      default:
        return {
          "b": "bb",
          "n": "b",
          "#": "",
          "##": "#",
        }

    }
  }
  /**
   * 升降记号特殊音符
   */

  static get noteLift() {
    switch (tone) {
      case "G":
        return ['7']
      case "D":
        return ['7', '3']
      case "A":
        return ['7', '3', '6']
      case "E":
        return ['2', '6', '3', '7']
      case "B":
        return ['5', '2', '6', '3', '7']
      case "F#":
        return ['1', '5', '2', '6', '3', '7']
      case "C#":
        return ['4', '1', '5', '2', '6', '3', '7']
      case "F":
        return ['4']
      case "Bb":
        return ['1', '4']
      case "Eb":
        return ['5', '1', '4']
      case "Ab":
        return ['2', '5', '1', '4']
      case "Db":
        return ['6', '2', '5', '1', '4']
      case "Gb":
        return ['3', '6', '2', '5', '1', '4']
      case "Cb":
        return ['7', '3', '6', '2', '5', '1', '4']
    }
  }

  static get longDurationLine() {
    return {
      '1': 3,
      '2': 1,
      '6': 2,
    }
  }

  static get numberedDurationLine() {
    return {
      '8': 1,
      '16': 2,
      '32': 3,
      '64': 4,
      '128': 5
    }
  }

  static get durationTime() {
    return {
      8: 6144,
      4: 4096
    }
  }

  constructor(options) {
    super(options);
    this.setAttribute("type", "NumberedNote")
    tone = options.keyString
    // 装饰音
    this.ornament = options.ornament ? true : false
    this.line = 0;
    this.keys = options.keys || [];
    this.fontSize = 20;
    this.offset_x = 0;
    this.offset_y = 0;
    this.note_tones = [];
    this.duration_lines = 0;
    this.long_duration_lines = 0;
    this.note_size = options.note_size || 16;
    const duration = Flow.sanitizeDuration(this.duration)
    var lines = NumberedNote.numberedDurationLine[duration.toString()]
    if (lines) {
      this.duration_lines = lines;
    }

    lines = NumberedNote.longDurationLine[duration.toString()]
    if (lines) {
      this.long_duration_lines = lines;
    }
    //todo: hack for get text width;
    if (!_text_width) {
      var id = "__$text_wraper$$__"
      let canvas_ele = document.createElement("canvas");
      canvas_ele.id = id;
      document.body.append(canvas_ele)
      const canvas = document.getElementById(id);
      const ctx = canvas.getContext('2d');
      ctx.font = "Arial 22px"
      var text = ctx.measureText("5");
      _text_width = text.width + 2;
      document.body.removeChild(canvas);
    }
    // this.buildToneHeads();
  }

  getCategory() {
    return NumberedNote.CATEGORY;
  }

  setKeySignature(key) {
    this.keySignature = key;
  }

  preFormat() {
    //this.checkContext();
    if (this.preFormatted) return;
    if (this.modifierContext) this.modifierContext.preFormat();

    this.setWidth(this.full_width)
    this.setPreFormatted(true);
  }

  // 处理升降记号特殊音符
  filterNote() {
    // 固定调不处理升降调
    if (window && window.sett && window.sett.keySignature) return
    if (!this.keys || !this.keys.length) return
    const accidentalMap = NumberedNote.accidentalMap(tone)
    if (!accidentalMap) return
    const noteLift = NumberedNote.noteLift
    if (!noteLift) return
    var spl = this.keys[0].split("/")
    var k = spl[0];
    if (k === "b") {
      k = "R"
    }
    k = k.replace(new RegExp('#', 'g'), "");
    k = k.replace(new RegExp("n", 'g'), "");
    if (k.length > 1) {
      k = k.replace(new RegExp("b", 'g'), "");
    }
    const key = NumberedNoteHead.numberedNotationMapping[k.toUpperCase()]

    if (noteLift.includes(key)) {
      for (let i = 0; i < this.modifiers.length; i++) {
        const modifier = this.modifiers[i]
        if (modifier.getAttribute('type') === 'Accidental') {
          if (modifier.accidental) {
            const code = accidentalMap[modifier.type]
            if (code) {
              modifier.accidental = Flow.accidentalCodes(code)
            } else {
              this.modifiers.splice(i, 1)
            }
          }
        }
      }
    }


  }

  preFormatModifier() {
    let offset_modifier = 0;
    let idx = 0;
    this.filterNote()
    this.modifiers.forEach((modifier, index) => {
      let changed = false;
      switch (modifier.getAttribute("type")) {
        case 'Accidental':
          modifier.render_options.font_scale = this.note_width * 1.4;
          modifier.x_shift = 2;
          modifier.y_shift = this.unit_height / 2;
          changed = true;
          // 装饰音时,位置修改
          if (this.ornament) {
            modifier.render_options.font_scale = this.note_width
            modifier.x_shift = 14
            modifier.y_shift = -10
          }
          break
        case 'Stroke':
          modifier.x_shift = -1 * offset_modifier;
          modifier.y_shift = 1 * this.unit_height;
          changed = true;
          break
        case 'Dot':
          modifier.x_shift = idx * this.duration_dot_width + 7;
          modifier.y_shift = 1 * this.unit_height
          idx++;
          break
        case 'Ornament':
          modifier.y_shift = -3;
          break;
        default:
          modifier.y_shift = 1 * this.unit_height;
      }
      modifier.reset && modifier.reset()
      offset_modifier += modifier.getWidth() / 2;
    })
  }

  /** 单个音符时值超过2拍的附点音符去掉附点 */
  handleDeleteDot() {
    for (let i = 0; i < this.modifiers.length; i++) {
      if (this.modifiers[i].attrs.type == 'Dot') {
        this.modifiers.splice(i, 1)
      }
    }
  }

  /**
   * 计算拍子
   */
  calcGroup() {
    /**
     * 1. 按照拍号获取单个拍子的时值,
     * 2. 计算音符的时值是否达到单个拍子的时值
     */
    //判断是否是弱起 每个小节重新开始
    if (group.staveId != this.stave.attrs.id) {
      this.drawDurationLoneLine()
      group = initGroup()
    }

    // 设置拍子的默认时值
    if (this.stave.timeSignature.denominator) {
      _denominator = this.stave.timeSignature.denominator
      _numerator = this.stave.timeSignature.numerator
    }
    if (!_denominator || !_numerator) return
    let singleTime = NumberedNote.durationTime[_denominator] // 单个拍子的时值
    let duration = this.formatterMetrics.duration.split('/').filter(n => n)
    let time = duration.length ? (duration[0] / duration[1]) : 0
    // 为装饰音时,直接跳过不计算时值
    if (this.ornament) {
      return
    }
    //为整小节 全音符和全休止符
    if (time === 0) {
      // 根据拍号计算时值，并绘制增时线的数量
      this.long_duration_lines = this.intrinsicTicks / singleTime - 1
      if (this.long_duration_lines >= _numerator) this.long_duration_lines = _numerator - 1

      // 全休止符 拍号为8拍时，单独处理
      if (_denominator == 8) {
        this.long_duration_lines = 0
        // 时值为 4096 ，增时线数量为0，并绘制附点
        if (_numerator == 3) {
          this.draw_dot(this.context, this.bottom_x + 25, this.bottom_y - 30, this.dot_width)
        }
        // 时值为 2048， 绘制 | 0 . 0 .|
        if (_numerator == 6) {
          this.draw_dot(this.context, this.bottom_x + 25, this.bottom_y - 30, this.dot_width)
          let g = new Glyph('v0', 40)
          g.render(this.context, this.bottom_x + 40, this.top_y)
          this.draw_dot(this.context, this.bottom_x + 65, this.bottom_y - 30, this.dot_width)
        }
      }
      return group = initGroup()
    }
    let n = {
      lines: this.duration_lines,
      duration: time,
      id: this.attrs.id,
      x: this.x - this.duration_line_space / 2 - this.left_width,
      y: this.y,
      width: this.full_width
    }

    // 附点音符,时值超过2拍
    if (time > singleTime * 2) {
      this.long_duration_lines = parseInt(time / singleTime) - 1
      this.handleDeleteDot()
    }

    //处理前一个音符为装饰音时,当前音符的x位置
    if (ornamentGroup) {
      n.x += this.note_width
    }
    group.notes.push(n)
    // group.total += time
    let total = group.notes.reduce((total, n) => {
      return total + n.duration
    }, 0)
    total = parseInt(total)
    //单个音符时值超过单个拍子的时值,减去单个拍子时值,匹配后面的
    if (total > singleTime) {
      total -= singleTime
    }
    // 因为js 小数精度问题, 条件改为 在单个拍子时值的正负10范围内
    if (total > (singleTime - 10) && total < (singleTime + 10)) {
      group.finish = true
      //单个音符已到达单个拍子时长
      if (group.notes.length === 1) {
        group = initGroup()
      }

      //过滤一拍里面没有下划线的抱团
      let isExceed = group.notes.filter(n => n.lines === 0)

      if (isExceed.length) {
        this.drawDurationLoneLine()
        group = initGroup()
      }
    }

  }

  draw() {
    this.checkContext()
    if (!this.stave) {
      throw new Vex.RERR('NoStave', "Can't draw without a stave.");
    }
    let ctx = this.context;
    ctx.save()
    ctx.setFont("Arial", this.note_height, "normal")
    this.setAttribute('el', ctx.openGroup('stavenote', this.getAttribute('id')));
    ctx.openGroup('note', null, { pointerBBox: true });

    let x = this.getAbsoluteX() + 2.5;
    this.x = x;
    let y = this.stave.getYForLine(2) + this.note_height / 2;
    this.y = y;
    this.buildToneHeads();
    this.preFormatModifier();
    this.applyStyle();
    this.note_tones.forEach((head) => {
      head.draw(ctx);
    });
    ctx.openGroup('modifier', null, { pointerBBox: true });
    this.drawModifiers();
    ctx.closeGroup();
    ctx.closeGroup();
    ctx.closeGroup();
    this.calcGroup();
    // 记录每次的五线谱小节 ID
    group.staveId = this.stave.attrs.id
    this.drawDurationLine(ctx, {});
    this.drawLongDurationLines(ctx);
    this.restoreStyle();
    this.setRendered();

    if (this.ornament) {
      if (ornamentGroup) {
        ornamentGroup = Object.assign(ornamentGroup, {
          lines: this.duration_lines,
          id: this.attrs.id,
          x: this.x - this.duration_line_space / 2 - this.left_width,
          width: this.full_width
        })
      } else {
        ornamentGroup = {
          lines: this.duration_lines,
          id: this.attrs.id,
          x: this.x - this.duration_line_space / 2 - this.left_width,
          width: this.full_width
        }
      }

    } else {
      ornamentGroup = ''
    }
  }

  // 绘制抱团连线方法
  drawDurationLoneLine(ctx, y) {
    ctx = ctx ? ctx : this.context;
    let startY = y ? y : (this.y + this.duration_line_space)
    // console.log(group.notes.length)
    for (let i = 0; i < 5; i++) {
      let notes = [];
      let index = 0;
      // 判断 音符是否连续, 遇到不连续,将音符数量1个以上的连接,然后重置
      for (let j = 0; j < group.notes.length; j++) {
        let n = group.notes[j]
        if (index == j && n.lines > i) {
          notes.push(n)
          index++
        } else {
          if (notes.length > 1) {
            let _index = notes.length - 1;
            startY = notes[0].y + this.duration_line_space
            ctx.rect(notes[0].x, startY + (i * 3), (notes[_index].x - notes[0].x + notes[_index].width), 0.5)
          }
          notes = []
          index = j + 1
        }
      }
      // group.notes.filter(n => n.lines > i)

      if (notes.length > 1) {
        let index = notes.length - 1;
        startY = notes[0].y + this.duration_line_space
        ctx.rect(notes[0].x, startY + (i * 3), (notes[index].x - notes[0].x + notes[index].width), 0.5)
      }
    }
  }

  // 绘制附点
  draw_dot(ctx, x, y, width) {
    ctx.openGroup("numbered-note-dot")
    ctx.beginPath()
    ctx.arc(x, y, width, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.fill()
    ctx.closeGroup()
  }

  drawDurationLine(ctx, opts) {
    var id = this.attrs.id + '-lines'
    ctx.openGroup('numbered_note_lines', id)
    var startX, startY;
    //todo 分离left modifier 与 right modifier

    if (this.duration_lines > 0) {
      const spacing = this.duration_line_space;
      //todo 这里和字体有很大的关系
      var w = this.full_width// + spacing;
      if (opts && opts.width) {
        w = opts.width
      }
      if (w < 0) {
        w = this.note_width
      }
      if (!opts || 'undefined' === typeof opts.startX) {
        startX = this.x - spacing / 2 - this.left_width;
      } else {
        startX = opts.startX
      }
      if (!opts || 'undefined' === typeof opts.startY) {
        startY = this.y + this.duration_line_space;
      } else {
        startY = opts.startY
      }

      // 绘制抱团
      if (group.finish) {
        this.drawDurationLoneLine(ctx, startY)
        group = initGroup()
      }

      //当前音符为装饰音时,处理大小位置
      if (this.ornament) {
        startX += 12
        startY -= 22
        w = 15
        if (ornamentGroup) {
          startX -= 10
          w += 10
        }
      } else {
        // 为装饰音时,处理多出的连线
        if (ornamentGroup) {
          startX += this.note_width
          w -= this.note_width
        }
      }


      if (w > 0) {
        for (var i = 0; i < this.duration_lines; i++) {
          ctx.rect(startX, startY, w, 0.5)
          startY += 3
        }

        //记录装饰音最后下划线的x, y
        if (this.ornament) {
          if (ornamentGroup) {
            ornamentGroup.startX = startX
            ornamentGroup.startY = startY
          } else {
            ornamentGroup = {
              startX,
              startY
            }
          }
        }

      }
    }
    if (!this.ornament && ornamentGroup) {
      startX = ornamentGroup.startX
      startY = ornamentGroup.startY
      startX += 5
      startY -= 3
      //绘制折线
      ctx.beginPath();
      ctx.moveTo(startX, startY);
      ctx.lineTo(startX, startY + 5);
      ctx.lineTo(startX + 5, startY + 5);
      ctx.stroke();
    }
    ctx.closeGroup()
  }

  removeDurationLines() {
    //todo bug, not work now.
    var id = 'vf-' + this.attrs.id + '-lines'
    var ele = document.getElementById(id);
    if (ele) {
      ele.remove()
    }
  }

  drawLongDurationLines(ctx) {
    let start_x = this.x + this.note_width + this.right_width + 10;
    for (let i = 1; i <= this.long_duration_lines; i++) {
      ctx.fillRect(start_x + 2, this.y - this.full_height / 2, this.long_duration_width, 2);
      start_x += (this.long_duration_space + this.long_duration_width)
    }
  }

  setStave(stave) {
    super.setStave(stave);
    return this;
  }

  buildToneHeads() {
    this.offset_x = this.x;
    this.offset_y = this.y;
    let ys = [];
    this.note_tones = [];
    for (let i = 0; i < this.keys.length; i++) {
      const head = new NumberedNoteHead({
        key: this.keys[i],
        x: this.ornament ? this.note_x + 12 : this.note_x,
        y: this.ornament ? this.offset_y - this.note_height : this.offset_y,
        idx: i,
        note: this,
      });
      this.offset_y -= (head.height + this.head_space);
      this.note_tones.push(head);
      ys.push(this.offset_y);
      try {
        if (i === 0 && window && window.sett && window.sett.keySignature) {
          const code = NumberedNote.accidentalAndKey(tone)[head.meta.key]
          const acc = this.modifiers.find(n => n.getAttribute('type') === 'Accidental')
          const accIndex = this.modifiers.findIndex(n => n.getAttribute('type') === 'Accidental')
          if (code && accIndex > -1){
            this.modifiers[accIndex].accidental.code = code === '#' ? 'v18' : 'v44'
          } else {
            this.modifiers.splice(accIndex, 1)
          }
        }
      } catch (error) {
        console.log("🚀 ~ error", error)

      }

    }
    this.setYs(ys);
  }

  getFullWidth() {
    return this.full_width
  }

  getFullHeight() {
    return this.full_height;
  }

  getLine() {
    return this.keyProps[0].line
  }



  get full_width() {
    return this.note_width + this.left_width + this.right_width + (this.long_duration_line_width)
  }

  get long_duration_line_width() {
    return (this.long_duration_width + this.long_duration_space) * this.long_duration_lines;
  }

  get full_height() {

    return this.bottom_y - this.top_y;
  }

  get note_width() {
    return this.note_size || 0;
  }
  get note_dot_width() {
    return 6;
  }

  get note_height() {
    return this.unit_height;
  }
  get unit_height() {
    return this.note_size || 0;
  }
  get note_x() {
    return this.x || 0;
  }

  get top_x() {
    return this.x || 0;
  }

  get top_y() {
    var _self = this;
    const y = this.y || 0;
    return y - (_self.note_tones.map((head) => {
      return (head.td + head.bd) * (_self.dot_space + _self.dot_width);
    }).reduce((p, v) => { return p + v }, 0) + (this.note_height * this.note_tones.length) - this.unit_height -
      this.note_tones[0].bd * (_self.dot_space + _self.dot_width) + (this.note_tones.length - 1) * this.head_space);
  }

  get bottom_x() {
    return this.x || 0;
  }

  get bottom_y() {
    const y = this.y || 0;
    return y +
      this.unit_height + this.duration_lines * (this.duration_line_height + this.duration_line_space)
      + ((this.note_tones[0].meta.bd) * (this.dot_width + this.dot_space))
  }


  get left_width() {
    return this.modifiers_width
  }

  get right_width() {
    return this.full_duration_dot_width;
  }
  get duration_dot_width() {
    return 6;
  }

  get full_duration_dot_width() {
    return this.duration_dot_width * this.getModifiersByCategory(Dot.CATEGORY).length;
  }

  getModifiersByCategory(category) {
    var lst = [];
    this.modifiers.forEach((v) => {
      if (v.getCategory() === category) {
        lst.push(v);
      }
    })
    return lst;
  }


  get head_space() {
    return 6;
  }

  get duration_line_height() {
    return 2;
  }

  get duration_line_space() {
    return 4;
  }

  get dot_width() {
    return 2;
  }

  get dot_space() {
    return 2;
  }

  get long_duration_space() {
    return 10;
  }

  get long_duration_width() {
    return 20;
  }

  getWidth() {
    //basic font size + left modifiers width + right modifier width + spacing
    return this.full_width;
  }

  get modifiers_width() {
    let w = 0;
    this.modifiers.forEach((modifier) => {
      w += modifier.getWidth()// / 2;
    })
    return w;
  }
}

class NumberedNoteHead {
  static get numberedNotationMapping() {
    let _key = tone
    if (window && window.sett && window.sett.keySignature) {
      _key = "C"
    }
    switch (_key) {
      case "A":
        return {
          "C": "3",
          "D": "4",
          "E": "5",
          "F": "6",
          "G": "7",
          "A": "1",
          "B": "2",
          "R": "0",
          min: 2.5,
          max: 5.5
        }
      case "B":
        return {
          "C": "2",
          "D": "3",
          "E": "4",
          "F": "5",
          "G": "6",
          "A": "7",
          "B": "1",
          "R": "0",
          min: -0.5,
          max: 2.5
        }
      case "D":
        return {
          "C": "7",
          "D": "1",
          "E": "2",
          "F": "3",
          "G": "4",
          "A": "5",
          "B": "6",
          "R": "0",
          min: 0.5,
          max: 3.5
        }
      case "E":
        return {
          "C": "6",
          "D": "7",
          "E": "1",
          "F": "2",
          "G": "3",
          "A": "4",
          "B": "5",
          "R": "0",
          min: 1,
          max: 4
        }
      case "F":
        return {
          "C": "5",
          "D": "6",
          "E": "7",
          "F": "1",
          "G": "2",
          "A": "3",
          "B": "4",
          "R": "0",
          min: 1.5,
          max: 4.5
        }
      case "G":
      case "Gb":
        return {
          "C": "4",
          "D": "5",
          "E": "6",
          "F": "7",
          "G": "1",
          "A": "2",
          "B": "3",
          "R": "0",
          min: 2,
          max: 5
        }

      case "Eb":
        return {
          "C": "6",
          "D": "7",
          "E": "1",
          "F": "2",
          "G": "3",
          "A": "4",
          "B": "5",
          "R": "0",
          min: 1,
          max: 4
        }
      case "D#":
      case "Db":
        return {
          "C": "7",
          "D": "1",
          "E": "2",
          "F": "3",
          "G": "4",
          "A": "5",
          "B": "6",
          "R": "0",
          min: 0.5,
          max: 3.5
        }

      case "F#":
        return {
          "C": "5",
          "D": "6",
          "E": "7",
          "F": "1",
          "G": "2",
          "A": "3",
          "B": "4",
          "R": "0",
          min: 1.5,
          max: 4.5
        }
      case "Ab":
      case "G#":
        return {
          "C": "3",
          "D": "4",
          "E": "5",
          "F": "6",
          "G": "7",
          "A": "1",
          "B": "2",
          "R": "0",
          min: -1,
          max: 2
        }
      case "Bb":
      case "A#":
        return {
          "C": "2",
          "D": "3",
          "E": "4",
          "F": "5",
          "G": "6",
          "A": "7",
          "B": "1",
          "R": "0",
          min: -0.5,
          max: 2.5
        }

      case "C#":
      case "Cb":
      default:
        return {
          "C": "1",
          "D": "2",
          "E": "3",
          "F": "4",
          "G": "5",
          "A": "6",
          "B": "7",
          "R": "0",
          min: 0,
          max: 3
        }
    }
  }
  tone_to_numbered_key() {
    var spl = this.key.split("/")
    var k = spl[0];
    if (k === "b") {
      k = "R"
    }
    k = k.replace(new RegExp("#", 'g'), "");
    k = k.replace(new RegExp("n", 'g'), "");
    if (k.length > 1) {
      k = k.replace(new RegExp("b", 'g'), "");
    }
    // var h = parseInt(spl[1]);
    // var t = h - 4 - 1;
    let bd = 0;
    let td = 0;

    // if (t < 0) {
    //   bd = Math.abs(t)
    // } else if (h > 0) {
    //   td = t
    // }
    // 根据音符在五线谱上的位置，计算简谱的八度
    if (this.note.stave && this.note.stave.options && this.note.stave.options.keySignature) {
      tone = this.note.stave.options.keySignature
    }
    const line = this.note.getLine()
    //低音
    // console.log(this.note, line)
    if (line < NumberedNoteHead.numberedNotationMapping.min) {
      bd = Math.ceil(Math.abs((line - NumberedNoteHead.numberedNotationMapping.min) / 3.5))
    }
    //高音
    if (line > NumberedNoteHead.numberedNotationMapping.max) {
      td = Math.ceil((line - NumberedNoteHead.numberedNotationMapping.max) / 3.5)
    }
    if (k.toUpperCase() === 'R') {
      bd = 0;
      td = 0;
    }
    // console.log([
    //   tone,
    //   this.note.minLine,
    //   NumberedNoteHead.numberedNotationMapping[k.toUpperCase()],
    //   "td: " + td + ', bd: ' + bd,
    //   spl
    // ])
    return {
      key: NumberedNoteHead.numberedNotationMapping[k.toUpperCase()],
      td: td,
      bd: bd,
    }
  }

  constructor(options) {
    this.key = options.key;
    this.x = options.x || 0;
    this.y = options.y || 0;
    this.idx = options.idx || 0;
    this.note = options.note || 0;
    this.meta = this.tone_to_numbered_key();
    this.noteSize = options.noteSize || 16;
    this.td = this.meta.td;
    this.bd = this.meta.bd;
  }

  draw(ctx) {
    let cy = this.y;
    ctx.openGroup("numbered-note-head")
    var basic_x = this.x + (this.note.ornament ? -2 : 0);

    let start_top_cy = cy - this.note.unit_height - this.note.dot_space - (this.note.ornament ? -4 : 2);

    for (let i = 0; i < this.meta.td; i++) {
      this.draw_dot(ctx, basic_x + _text_width - 1, start_top_cy, this.note.dot_width);
      start_top_cy -= (this.note.dot_width + this.note.dot_space) + 4;
    }
    let g = new Glyph('v' + this.meta.key, this.note.ornament ? 30 : 40)
    g.render(ctx, basic_x + 1, cy)
    // ctx.fillText(this.meta.key, basic_x, cy, this.note.note_height);
    cy += this.note.dot_space + this.note.dot_width - (this.note.ornament ? 2 : 0);
    for (let i = 0; i < this.meta.bd; i++) {
      if (this.idx === 0) {
        let lines_height = (this.note.duration_lines *
          (this.note.duration_line_height + this.note.duration_line_space))
        if (this.note.duration_lines > 1) {
          lines_height -= this.note.duration_line_space
        }
        cy += lines_height
      }
      this.draw_dot(ctx, basic_x + _text_width, cy, this.note.dot_width);
      cy += (this.note.dot_width + this.note.dot_space);
    }
    ctx.closeGroup();
  }

  get height() {
    return this.note.note_height + (this.meta.bd + this.meta.td) * (this.note.dot_width + this.note.dot_space);
  }

  draw_dot(ctx, x, y, width) {
    ctx.openGroup("numbered-note-dot")
    ctx.beginPath()
    ctx.arc(x, y, width, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.fill()
    ctx.closeGroup()
  }
}
