main.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. const LINE_SPACE = 20;
  2. const START_SPACE = 50;
  3. const END_SPACE = 50;
  4. const RGB_WHITE = "rgb(255, 255, 255)";
  5. const RGB_BLACK = "rgb(0, 0, 0)";
  6. const RGB_GREEN = "rgb(0, 255, 0)";
  7. const RGB_BLUE = "rgba(0, 0, 255)";
  8. const RGB_BLUE_LIGHT = "rgba(0, 155, 255)";
  9. const RGB_RED = "rgba(255, 0, 0)";
  10. const RGB_YELLOW = "rgba(255, 255, 0)";
  11. const RGB_GRAY = "rgba(55, 55, 55)";
  12. var menu_right = document.getElementById("menu-right");
  13. var btn_add = menu_right.children[0];
  14. var btn_delete = menu_right.children[1];
  15. var btn_import = menu_right.children[2];
  16. var btn_export = menu_right.children[3];
  17. var audio = new Audio("000.mp3");
  18. var canvas = document.querySelector("canvas")
  19. var ctx = canvas.getContext("2d");
  20. var music_progress_bar = document.getElementById("music-progress-bar");
  21. var file_name = "tempo";
  22. var beats = [0.267,1.013,1.765,2.245,2.784,3.515,3.776,4.256,4.784,5.765,6.267,6.768,7.515,7.765,8.267,8.768,9.765,10.267,10.795,11.509,11.765,12.251,12.779,13.765,14.256,14.768,15.259,16.261,16.768,17.312,17.787,18.267,18.768,19.515,19.765,20.267,20.768,21.765,22.267,22.768,23.509,23.781,24.261,24.789,25.765,26.245,26.779,27.509,27.765,28.261,28.768,29.771,30.267,30.752,31.259,31.771,32.261,32.768,33.259,33.765,34.24,34.768,35.28,35.787,36.256,36.768,37.259,37.765,38.267,38.779,39.275,39.765,40.256,40.517,40.768,41.269,41.765,42.261,42.768,43.28,43.787,44.267,44.517,44.768,45.019,45.28,45.781,46.267,46.763,47.269,47.728,48.245,48.768,49.024,49.771,50.027,50.763,51.264,51.76,52.027,52.773,53.253,53.765,54.027,54.768,55.243,55.76,56.027,56.768,57.253,57.765,58.251,58.763,59.259,59.765,60.021,60.768,61.259,61.765,62.245,62.768,63.264,63.765,64.245,64.768,65.259,65.765,66.027,66.763,67.264,67.76,68.24,68.768,69.264,69.765,70.021,70.763,71.264,71.749,72.021,72.768,73.264,73.771,74.256,74.763,75.259,75.765,76.021,76.517,76.768,77.264,77.771,78.256,78.768,79.264,79.765,80.245,80.768,81.243,81.765,82.245,82.768,83.264,83.765,84.251,84.763,85.259,85.765,86.261,86.763,87.248,87.765,88.251,88.763,89.259,89.765,90.261,90.763,91.269,91.765,92.251,92.763,93.259,93.792,94.256,94.763,95.269,95.771,96.261,96.763,97.248,97.771,98.251,98.768,99.259,99.765,100.256,100.773,101.248,101.765,102.272,102.768,103.269,103.765,104.261,104.768,105.259,105.771,106.245,106.768,107.259,107.765,108.256,108.763,109.248,109.765,110.267,110.768,111.253,111.781,112.245,112.768,113.269,113.771,114.021,114.763,115.264,115.76,116.021,116.763,117.253,117.765,118.027,118.768,119.243,119.76,120.027,120.768,121.269,121.765,122.251,122.768,123.259,123.765,124.021,124.768,125.259,125.771,126.245,126.768,127.264,127.765,128.245,128.768,129.259,129.765,130.027,130.763,131.264,131.76,132.24,132.768,133.264,133.771,134.021,134.768,135.259,135.749,136.021,136.763,137.259,137.765,138.256,138.763,139.264,139.765,140.021,140.517,140.768,141.264,141.771,142.261,142.768,143.264,143.765,144.261,145.269,145.669,146.24,146.992,147.333,148.267,149.52,150.213,150.997,152.267,153.285,153.749,154.704,155.579,156.277,157.445,157.733,158.256,158.528,159.344,159.813,160.251,160.768,161.253,161.765,162.245,162.768,163.509,163.781,164.256,164.768,165.264,165.765,166.272,166.784,167.515,167.771,168.256,168.768,169.019,169.771,170.245,170.768,171.509,171.765,172.256,172.992,173.765,174.267,174.768,175.264,175.781,176.256,176.768,177.248,177.765,178.261,178.768,179.253,179.749,180.261,180.763,181.253,181.765,182.261,182.763,183.248,183.749,184.256,184.768,185.259,185.765,186.245,186.763,187.253,187.76,188.261,188.768,189.259,189.765,190.245,190.768,191.264,191.765,192.245,192.763,193.253,193.765,194.261,194.768,195.259,195.76,196.245,196.757,197.248,197.765,198.261,198.763,199.264,199.744,200.261,200.768,201.253,201.765,202.261,202.757,203.259,203.765,204.261,204.517,204.768,205.253,205.765,206.251,206.768,207.264,207.765,211.968];
  23. var duration = 213.1;
  24. var time_table_width = duration * LINE_SPACE * 10;
  25. var offsetX = 0;
  26. var btn_control = {x: START_SPACE - 40, y: 300, width: 80, height: 40, on: false};
  27. var last_right_mouse_btn_x = null;//记录最后一次右击canvas的x坐标
  28. function drawBG(){
  29. ctx.clearRect(0, 0, canvas.width, canvas.height);
  30. canvas.width = innerWidth;
  31. canvas.height = innerHeight;
  32. ctx.fillStyle = RGB_BLACK;
  33. ctx.fillRect(0,0,canvas.width,canvas.height);
  34. ctx.lineWidth = 1;
  35. ctx.strokeStyle = RGB_WHITE;
  36. ctx.beginPath();
  37. for (let x = 0; x <= time_table_width; x += LINE_SPACE){
  38. let actualX = x + START_SPACE + offsetX;
  39. if (actualX < 0) {
  40. continue;
  41. }
  42. else if (actualX > innerWidth) {
  43. break;
  44. }
  45. ctx.moveTo(actualX, 0);
  46. if (x % (LINE_SPACE * 10) == 0) {
  47. ctx.lineTo(actualX, 100);
  48. } else {
  49. ctx.lineTo(actualX, 60);
  50. }
  51. }
  52. ctx.closePath();
  53. ctx.stroke();
  54. ctx.fillStyle = RGB_WHITE;
  55. ctx.textBaseline = "top";
  56. ctx.textAlign = "center";
  57. ctx.font = "24px Arial"
  58. for (let t = 0; t <= duration; t++){
  59. ctx.fillText(t.toString(), t * LINE_SPACE * 10 + START_SPACE + offsetX, 110);
  60. if (t + START_SPACE + offsetX > innerWidth) {
  61. break;
  62. }
  63. }
  64. let progressX = audio.currentTime * LINE_SPACE * 10 + START_SPACE + offsetX;
  65. btn_control.x = progressX - btn_control.width / 2;
  66. if (audio.currentTime * LINE_SPACE * 10 + START_SPACE > innerWidth / 2) {
  67. offsetX = innerWidth / 2 - (audio.currentTime * LINE_SPACE * 10 + START_SPACE);
  68. } else {
  69. offsetX = 0;
  70. }
  71. if (audio.paused) {
  72. ctx.strokeStyle = RGB_YELLOW;
  73. } else {
  74. ctx.strokeStyle = RGB_GREEN;
  75. }
  76. ctx.beginPath();
  77. ctx.moveTo(progressX, 0);
  78. ctx.lineTo(progressX, btn_control.y);
  79. ctx.closePath();
  80. ctx.stroke();
  81. ctx.font = "24px Arial"
  82. if (audio.paused) {
  83. ctx.fillStyle = RGB_YELLOW;
  84. ctx.fillRect(progressX - btn_control.width / 2, btn_control.y, btn_control.width, btn_control.height);
  85. ctx.fillStyle = RGB_GRAY;
  86. ctx.fillText("暂停", progressX, btn_control.y + (btn_control.height - 22) / 2);
  87. } else {
  88. ctx.fillStyle = RGB_GREEN;
  89. ctx.fillRect(progressX - btn_control.width / 2, btn_control.y, btn_control.width, btn_control.height);
  90. ctx.fillStyle = RGB_GRAY;
  91. ctx.fillText("播放", progressX, btn_control.y + (btn_control.height - 22) / 2);
  92. }
  93. ctx.strokeStyle = RGB_BLUE_LIGHT;
  94. ctx.beginPath();
  95. for (let beat of beats) {
  96. let x = beat * LINE_SPACE * 10 + START_SPACE + offsetX;
  97. if (x < 0) {
  98. continue;
  99. } else if (x > innerWidth) {
  100. break;
  101. }
  102. ctx.moveTo(x, 0);
  103. ctx.lineTo(x, 200);
  104. if (Math.abs(beat - audio.currentTime) < 0.1) {
  105. ctx.fillStyle = RGB_GREEN;
  106. } else {
  107. ctx.fillStyle = RGB_BLUE_LIGHT;
  108. }
  109. ctx.fillRect(x - 28, 200, 56, 20);
  110. ctx.font = "14px Arial"
  111. ctx.fillStyle = RGB_WHITE;
  112. ctx.fillText(beat.toString(), x, 204);
  113. }
  114. ctx.closePath();
  115. ctx.stroke();
  116. }
  117. function download(filename, text) {
  118. var aTag = document.createElement("a");
  119. aTag.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
  120. aTag.setAttribute("download", filename);
  121. aTag.style.display = "none";
  122. document.body.appendChild(aTag);
  123. aTag.click();
  124. document.body.removeChild(aTag);
  125. }
  126. canvas.oncontextmenu = function(e){
  127. e.preventDefault();
  128. menu_right.style.display = "block";
  129. menu_right.style.left = e.offsetX+"px";
  130. menu_right.style.top = e.offsetY+"px";
  131. }
  132. canvas.onclick = function(){
  133. menu_right.style.display = "none";
  134. }
  135. menu_right.onclick = function() {
  136. menu_right.style.display = "none";
  137. }
  138. canvas.onmousemove = function(e) {
  139. if (btn_control.on) {
  140. audio.currentTime += e.movementX / (LINE_SPACE * 10);
  141. }
  142. }
  143. var select_beat_index = undefined;
  144. canvas.onmousedown = function(e) {
  145. if (
  146. e.clientX > btn_control.x &&
  147. e.clientX < btn_control.x + btn_control.width &&
  148. e.clientY > btn_control.y &&
  149. e.clientY < btn_control.y + btn_control.height
  150. ) {
  151. btn_control.on = true;
  152. }
  153. if (e.offsetY >= 200 && e.offsetY <= 220) {
  154. for (let i in beats) {
  155. let beat = beats[i];
  156. let x = beat * LINE_SPACE * 10 + START_SPACE + offsetX;
  157. if (Math.abs(e.offsetX - x) <= 20) {
  158. select_beat_index = parseInt(i);
  159. break;
  160. }
  161. }
  162. }
  163. if (e.button == 2) {
  164. last_right_mouse_btn_x = e.offsetX;
  165. }
  166. }
  167. canvas.onmouseup = function(e) {
  168. if (btn_control.on) {
  169. if (
  170. e.clientX > btn_control.x &&
  171. e.clientX < btn_control.x + btn_control.width &&
  172. e.clientY > btn_control.y &&
  173. e.clientY < btn_control.y + btn_control.height
  174. ) {
  175. if (audio.paused) {
  176. audio.play();
  177. } else {
  178. audio.pause();
  179. }
  180. }
  181. btn_control.on = false;
  182. }
  183. }
  184. btn_import.onclick = function() {
  185. //创建导入按钮的临时标签
  186. let inputTag = document.createElement("input");
  187. inputTag.setAttribute("type","file");
  188. inputTag.setAttribute("multiple", "");
  189. inputTag.style.display = "none";
  190. //监听传入文件
  191. inputTag.onchange = function() {
  192. //确保导入的文件有音乐文件和节奏文件
  193. if (inputTag.files.length != 2) {
  194. alert("请同时导入音乐的MP3和JSON");
  195. return;
  196. }
  197. let musicFile = null;
  198. let rhythmFile = null;
  199. for (let i = 0; i < inputTag.files.length; i++) {
  200. let file = inputTag.files[i];
  201. if (file.name.endsWith(".mp3")) {
  202. musicFile = file;
  203. } else if (file.name.endsWith(".json")) {
  204. rhythmFile = file;
  205. }
  206. }
  207. if (!musicFile || !rhythmFile) {
  208. alert("请同时导入音乐的MP3和JSON");
  209. return;
  210. }
  211. //检测导入进度并提示
  212. let imortProgress = 0;
  213. let checkImortProgress = () => {
  214. imortProgress++;
  215. if (imortProgress == 2) {
  216. alert("导入完成");
  217. }
  218. }
  219. //记录音乐名称和设置音乐播放的本地地址
  220. file_name = musicFile.name.split('.')[0];
  221. audio.src = URL.createObjectURL(musicFile);
  222. //读取节拍文件内容
  223. let rhythmReader = new FileReader();
  224. rhythmReader.onloadend = (e) => {
  225. beats = JSON.parse(e.target.result);
  226. checkImortProgress();
  227. }
  228. rhythmReader.readAsText(rhythmFile, "utf-8");
  229. //读取音乐文件的信息
  230. let musicReader = new FileReader();
  231. musicReader.onload = function(e) {
  232. let audioContext = new AudioContext({
  233. sampleRate: 48000,
  234. });
  235. audioContext.decodeAudioData(e.target.result, function(audioBuffer) {
  236. duration = parseFloat(audioBuffer.duration.toFixed(1));
  237. time_table_width = duration * LINE_SPACE * 10;
  238. checkImortProgress();
  239. });
  240. }
  241. musicReader.readAsArrayBuffer(musicFile);
  242. }
  243. //触发并销毁导入按钮的临时标签
  244. document.body.appendChild(inputTag);
  245. inputTag.click();
  246. document.body.removeChild(inputTag);
  247. }
  248. btn_add.onclick = function() {
  249. let beat = (last_right_mouse_btn_x - START_SPACE - offsetX) / (LINE_SPACE * 10);
  250. beat = parseFloat(beat.toFixed(3));
  251. if (beat < 0 || beat > duration) {
  252. return;
  253. }
  254. let beatsLength = beats.length;
  255. for (let i = beatsLength - 1; i >= 0; i--) {
  256. let currentBeat = beats[i];
  257. if (currentBeat > beat) {
  258. beats[i + 1] = currentBeat;
  259. beats[i] = null;
  260. } else {
  261. beats[i + 1] = beat;
  262. break;
  263. }
  264. if (i == 0 && beats[i] == null) {
  265. beats[i] = beat;
  266. }
  267. }
  268. }
  269. btn_delete.onclick = function() {
  270. if (typeof select_beat_index == "number") {
  271. beats.splice(select_beat_index, 1);
  272. select_beat_index = undefined;
  273. }
  274. }
  275. btn_export.onclick = function() {
  276. download(file_name + ".json", JSON.stringify(beats));
  277. }
  278. music_progress_bar.onclick = function(e) {
  279. audio.currentTime = e.offsetX / music_progress_bar.clientWidth * audio.duration;
  280. }
  281. function draw() {
  282. requestAnimationFrame(draw);
  283. drawBG();
  284. try {
  285. music_progress_bar.max = audio.duration;
  286. music_progress_bar.value = audio.currentTime;
  287. } catch(e) {}
  288. }
  289. draw();