😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔2023年快要到来啦,再此祝大家诸事顺遂,所见所盼皆如愿。
🔔本文讲解如何使用Java演奏一首歌曲,一起卷起来叭!
众所周知,语言=算法+数据结构,那么音乐=曲谱+音质,曲谱定义了音的高低、长短,力度,我们常常又根据这些对歌曲常分为主奏和伴奏。因为一切皆文件嘛,凭借这些规则,我们通过编程将曲谱读取得到对应的音符的高低、长短、力度,然后依次调用音质文件,最后组成音乐。
先上视频演示,代码在文末:
演奏视频
目录
- 1、音质
- 2、编写主奏、伴奏曲谱
- 3、编写项目
1、音质
主奏与伴奏中支持输入的35个音符:
“1–” ~ “7–”, “1-” ~ “7-”, “1” ~ “7”, “1+” ~ “7+”, “1++” ~ “7++” 分别代表倍低音、低音、中音、高音、倍高音一共35个音符
这里我们提前找到对应的音(音色可以自己找,上网一搜八十八个高音音频即可):
2、编写主奏、伴奏曲谱
2、分别在主奏(.note)与伴奏(.accompaniments)中输入需要自动弹奏的音符,定义规则如下:
-
每个音符之间用空格隔开(任意多个空格,推荐每个音符包括空格共占用4个占位符,以便主奏和伴奏音符对齐)
-
输入字符"0",则会使音长额外延长一倍;
-
输入除了上面35个音符以及“0”以外的任意字符不会对弹奏起任何作用;
-
如果需要换行填写,则需在上一行的末尾以及下一行的开头都加上空格;
-
音长里输入每两个音符之间的间隔时长,单位是毫秒(ms)
这里我找了一份蜜雪冰城
的谱子:
然后将它转化为咱们的规则:
主旋律:
0 0 3+ 4+ 5+ 0 5+ 0 5+ 0 0 6+ 5+ 0 3+ 0 1+ 0 1+ 2+ 3+ 0 3+ 0 2+ 0 2+ 0 1+ 0 0 0 1++ 0 0 0 3+ 0 5+ 0 5+ 0 0 6+ 5+ 0 3+ 0 1+ 0 1+ 2+ 3+ 0 3+ 0 2+ 0 1+ 0 2+ 0 0 0 0 0 0 0 3+ 0 5+ 0 5+ 0 0 6+ 5+ 0 3+ 0 1+ 0 1+ 2+ 3+ 0 3+ 0 2+ 0 2+ 0 1+ 0 0 0 0 0 0 0 4+ 0 0 0 4+ 0 0 0 4+ 0 6+ 0 0 0 0 0 5+ 0 0 0 5+ 0 3+ 0 2+ 0 0 0 0 0 0 0 3+ 0 5+ 0 5+ 0 0 6+ 5+ 0 3+ 0 1+ 0 1+ 2+ 3+ 0 3+ 0 2+ 0 2+ 0 1+ 0 0 0 0 0 0 0
伴奏:
0 0 0 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 5-- 0 2- 0 1- 0 3- 0 3- 0 0 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 5-- 0 2- 0 5-- 0 2- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 5-- 0 2- 0 1- 0 3- 0 1- 0 3- 0 4-- 0 1- 0 4-- 0 1- 0 4-- 0 1- 0 4-- 0 1- 0 1- 0 3- 0 1- 0 3- 0 5-- 0 2- 0 5-- 0 2- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 1- 0 3- 0 5-- 0 2- 0 1- 0 3- 0 1++ 0 0 0
3、编写项目
我们新建Maven项目并将上述文件分别放到resoucres目录下:
导入需要的依赖:
<dependency><groupId>javazoom</groupId><artifactId>jlayer</artifactId><version>1.0.1</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.6.5</version></dependency>
编写启动类,类名叫Happpy
叭:
public class Happy {public static void main(String[] args) {String musicName = "蜜雪冰城";FileUtils.play(musicName);}
}
编写FileUtils(实现读取曲谱的功能)
public class FileUtils {static void play(String musicName) {System.out.println("载入歌曲:" + musicName);String path =new File("").getAbsolutePath() + File.separator + "src/main/resources/notes" + File.separator;String notesPath = path + musicName + ".notes";String accompanimentsPath = path + musicName + ".accompaniments";String notes = fileToStr(notesPath);String accompaniments = fileToStr(accompanimentsPath);new AudioPlay(180).loadNotes(notes).start();new AudioPlay(180).loadNotes(accompaniments).start();new Animation(180).loadNotes(notes).start();}static String fileToStr(String musicPath) {StringBuilder stringBuilder = new StringBuilder();try {BufferedReader reader = new BufferedReader(new FileReader(musicPath));String line = null;String ls = System.getProperty("line.separator");while ((line = reader.readLine()) != null) {stringBuilder.append(line);stringBuilder.append(ls);}// 删除最后一个新行分隔符stringBuilder.deleteCharAt(stringBuilder.length() - 1);reader.close();} catch (Exception ignored) {}return stringBuilder.toString();}
}
规则编写:
新建AudioPlay类,将每个音符对应每个读取到的字符:
package com.example.demo.play;import cn.hutool.core.io.FileUtil;/*** @author xh* @Date 2022/12/27*/
public class AudioPlay extends Thread{/** 音符 */private String[] notes;/** 间隔时间(单位:毫秒) */private int times;public AudioPlay(String[] notes, int times){this.notes = notes;this.times = times;}public AudioPlay(String filePath, int times){String content = FileUtil.readString(filePath,"UTF-8");this.notes = content.split(" ");this.times = times;}public AudioPlay(int times){this.times = times;}public String[] getNotes(){return this.notes;}public void setNotes(String[] notes){this.notes = notes;}public AudioPlay loadNotes(String notes){this.notes = notes.split(" ");return this;}public int getTimes(){return this.times;}public void setTimes(int times){this.times = times;}@Overridepublic void run(){try{int times = this.times;new Audio("audio/test.mp3").start();sleep(1000);for (String note : notes) {if (note.length() < 1) {continue;}switch (note) {case "1--":new Audio("audio/ll1.mp3").start();sleep(times / 2);break;case "2--":new Audio("audio/ll2.mp3").start();sleep(times / 2);break;case "3--":new Audio("audio/ll3.mp3").start();sleep(times / 2);break;case "4--":new Audio("audio/ll4.mp3").start();sleep(times / 2);break;case "5--":new Audio("audio/ll5.mp3").start();sleep(times / 2);break;case "6--":new Audio("audio/ll6.mp3").start();sleep(times / 2);break;case "7--":new Audio("audio/ll7.mp3").start();sleep(times / 2);break;case "1-":new Audio("audio/l1.mp3").start();sleep(times / 2);break;case "2-":new Audio("audio/l2.mp3").start();sleep(times / 2);break;case "3-":new Audio("audio/l3.mp3").start();sleep(times / 2);break;case "4-":new Audio("audio/l4.mp3").start();sleep(times / 2);break;case "5-":new Audio("audio/l5.mp3").start();sleep(times / 2);break;case "6-":new Audio("audio/l6.mp3").start();sleep(times / 2);break;case "7-":new Audio("audio/l7.mp3").start();sleep(times / 2);break;case "1":new Audio("audio/m1.mp3").start();sleep(times / 2);break;case "2":new Audio("audio/m2.mp3").start();sleep(times / 2);break;case "3":new Audio("audio/m3.mp3").start();sleep(times / 2);break;case "4":new Audio("audio/m4.mp3").start();sleep(times / 2);break;case "5":new Audio("audio/m5.mp3").start();sleep(times / 2);break;case "6":new Audio("audio/m6.mp3").start();sleep(times / 2);break;case "7":new Audio("audio/m7.mp3").start();sleep(times / 2);break;case "1+":new Audio("audio/h1.mp3").start();sleep(times / 2);break;case "2+":new Audio("audio/h2.mp3").start();sleep(times / 2);break;case "3+":new Audio("audio/h3.mp3").start();sleep(times / 2);break;case "4+":new Audio("audio/h4.mp3").start();sleep(times / 2);break;case "5+":new Audio("audio/h5.mp3").start();sleep(times / 2);break;case "6+":new Audio("audio/h6.mp3").start();sleep(times / 2);break;case "7+":new Audio("audio/h7.mp3").start();sleep(times / 2);break;case "1++":new Audio("audio/hh1.mp3").start();sleep(times / 2);break;case "2++":new Audio("audio/hh2.mp3").start();sleep(times / 2);break;case "3++":new Audio("audio/hh3.mp3").start();sleep(times / 2);break;case "4++":new Audio("audio/hh4.mp3").start();sleep(times / 2);break;case "5++":new Audio("audio/hh5.mp3").start();sleep(times / 2);break;case "6++":new Audio("audio/hh6.mp3").start();sleep(times / 2);break;case "7++":new Audio("audio/hh7.mp3").start();sleep(times / 2);break;case "0":sleep(times / 2);break;default:continue;}sleep(times / 2);times = this.times;}}catch (Exception e){throw new RuntimeException(e);}}}
读取曲谱文件并打印曲谱:
package com.example.demo.play;/*** @author xh* @Date 2022/12/27*/
public class Animation extends Thread{/** 音符 */private String[] notes;/** 间隔时间(单位:毫秒) */private int times;public Animation(int times) {this.times = times;}public Animation(String[] notes, int times) {this.notes = notes;this.times = times;}public String[] getNotes() {return this.notes;}public void setNotes(String[] notes) {this.notes = notes;}public int getTimes() {return this.times;}public void setTimes(int times) {this.times = times;}public Animation loadNotes(String notes) {this.notes = notes.split(" ");return this;}@Overridepublic void run() {try {int times = this.times;new Audio("audio/test.mp3").start();sleep(1000);int no = 1;for (String note : this.notes) {if (note.length() < 1) {continue;}// 3+ 0 3+ 3+ 3+ 3+ 0 3+ 3+ 3+ 4+ 2+ 0 2+ 2+ 2+ 2+ 0 2+ 2+ 2+ 3+ 1+String n = note.replace("+", "").replace("-", "");if ("\n".equals(n) || "\r".equals(n) || "\r\n".equals(n)) {System.out.print("\n");no++;continue;}switch (n) {case "0":System.out.print("_");break;case "1":System.out.print("▁");break;case "2":System.out.print("▂");break;case "3":System.out.print("▃");break;case "4":System.out.print("▄");break;case "5":System.out.print("▅");break;case "6":System.out.print("▆");break;case "7":System.out.print("▇");break;}System.out.print(" ");sleep(times);}} catch (InterruptedException e) {throw new RuntimeException(e);}}}
控制音频播放Audio类:
package com.example.demo.play;import cn.hutool.core.io.resource.ResourceUtil;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author xh* @Date 2022/12/27*/
public class Audio {private static InputStream is;private Player player;ExecutorService service = Executors.newCachedThreadPool();public Audio(String path){is = ResourceUtil.getStream(path);try{player = new Player(is);}catch (JavaLayerException e){e.printStackTrace();}}public void start(){service.submit(() -> {try{player.play();}catch (JavaLayerException ignored){}});}
}
项目整体结构:
代码已经开源到Github上,欢迎大家玩!传送门