metronome.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /**
  2. * 曲谱节拍器
  3. * auth: lsq
  4. * time: 2022.11.14
  5. */
  6. import { reactive, watch } from "vue";
  7. import { Howl } from "howler";
  8. import tockAndTick from "/src/constant/tockAndTick.json";
  9. type IOptions = {
  10. speed: number;
  11. };
  12. export const metronomeData = reactive({
  13. disable: true,
  14. initPlayerState: false,
  15. lineShow: false,
  16. isClick: false,
  17. metro: null as unknown as Metronome,
  18. metroList: [] as number[],
  19. activeList: [] as number[],
  20. metroMeasure: [] as any[],
  21. activeIndex: null as unknown as number,
  22. activeMetro: {} as any,
  23. });
  24. class Metronome {
  25. playType = "tick";
  26. source = null as any; // 创建音频源头
  27. source1 = null as any;
  28. source2 = null as any;
  29. constructor(option?: IOptions) {}
  30. init(times: any[]) {
  31. this.calculation(times);
  32. metronomeData.activeList = [];
  33. }
  34. initPlayer() {
  35. if (!this.source) {
  36. this.source = this.loadAudio1();
  37. }
  38. this.source.volume(metronomeData.disable ? 0 : 1);
  39. // if (!this.source2) {
  40. // this.source2 = this.loadAudio2();
  41. // }
  42. metronomeData.initPlayerState = true;
  43. }
  44. // 播放
  45. sound = (currentTime: number) => {
  46. let index = -1;
  47. let activeMetro = -1;
  48. for (let i = 0; i < metronomeData.metroList.length; i++) {
  49. const item = metronomeData.metroList[i];
  50. if (currentTime >= item) {
  51. // console.log(currentTime , item)
  52. index = i;
  53. activeMetro = item;
  54. } else {
  55. break;
  56. }
  57. }
  58. if (index > -1 && metronomeData.activeIndex !== index) {
  59. metronomeData.activeIndex = index;
  60. // console.log("播放", index);
  61. // metronomeData.activeMetro = this.getStep(activeMetro);
  62. // console.log("🚀 ~ metronomeData.activeMetro",metronomeData.activeMetro.measureNumberIndex, metronomeData.activeMetro.index)
  63. this.playAudio();
  64. return;
  65. }
  66. };
  67. // 播放
  68. playAudio = () => {
  69. if (!metronomeData.initPlayerState) return;
  70. this.source.play();
  71. };
  72. // 切换
  73. selectPlay() {}
  74. loadAudio1 = () => {
  75. return new Howl({
  76. src: tockAndTick.tick,
  77. });
  78. };
  79. loadAudio2 = () => {
  80. return new Howl({
  81. src: tockAndTick.tock,
  82. });
  83. };
  84. getStep(time: number) {
  85. for (let i = 0; i < metronomeData.metroMeasure.length; i++) {
  86. const list = metronomeData.metroMeasure[i];
  87. const item = list.find((n: any) => n.time === time);
  88. if (item) {
  89. // console.log('index',item)
  90. return item;
  91. }
  92. }
  93. return {};
  94. }
  95. // 计算 所有的拍子的时间
  96. calculation(times: any[]) {
  97. // console.log("🚀 ~ times", times);
  98. // 1.统计有多少小节
  99. const measures: any[] = [];
  100. let xmlNumber = -1;
  101. let measureListIndex = -1;
  102. for (let i = 0; i < times.length; i++) {
  103. const note = times[i];
  104. const measureNumberXML = note?.timeNote?.measureNumber;
  105. // console.log("🚀 ~ note?.noteElement?.sourceMeasure", note?.noteElement?.sourceMeasure)
  106. // console.log("🚀 ~ measureNumberXML", measureNumberXML , xmlNumber)
  107. // console.log("🚀 ~ measureNumberXML", note)
  108. if (measureNumberXML > -1) {
  109. if (measureNumberXML != measureListIndex) {
  110. const m = {
  111. measureNumberXML: measureNumberXML,
  112. numerator: note?.measure?.numerator || 0,
  113. time: note?.timeNote?.millisecondsPerMeasure,
  114. stepList: [] as number[],
  115. notes: [note] as any[],
  116. };
  117. xmlNumber++;
  118. measures[xmlNumber] = m;
  119. measureListIndex = measureNumberXML;
  120. } else {
  121. measures[xmlNumber].notes.push(note);
  122. }
  123. }
  124. }
  125. // console.log(measures, measures.length);
  126. const metroList: number[] = [];
  127. const metroMeasure: any[] = [];
  128. // 4.按照拍数将时长平均分配
  129. try {
  130. for (let i = 0; i < measures.length; i++) {
  131. const measure = measures[i];
  132. const calp = 1 / measure.numerator;
  133. // console.log("🚀 ~ measure.numerator:", measure.numerator)
  134. const totalMeasureTime = measure.notes.reduce((total: number, note: any) => {
  135. return total + note?.abcNote?.duration;
  136. }, 0);
  137. const step = Math.floor(totalMeasureTime / calp);
  138. // console.log('🚀 ~ calp:', calp, 'total', totalMeasureTime, 'step', step)
  139. const startTime = measure.notes[0].timeNote.milliseconds;
  140. const stepTime = measure.notes[0].timeNote.millisecondsPerMeasure / measure.numerator;
  141. for (let j = 0; j < step; j++) {
  142. const time = stepTime * j + startTime;
  143. metroList.push(time);
  144. }
  145. }
  146. } catch (error) {
  147. console.log(error);
  148. }
  149. // console.log(metroList, metroList.length);
  150. // 5.得到所有的节拍时间
  151. metronomeData.metroList = metroList;
  152. metronomeData.metroMeasure = metroMeasure;
  153. metronomeData.activeMetro = metroMeasure[0]?.[0] || {};
  154. }
  155. }
  156. export default Metronome;