😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔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;
}
@Override
public 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;
}
@Override
public 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上,欢迎大家玩!传送门