@TOC
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、背景与需求
前一段时间在B站看到一个视频,up主用matlab演奏出了《小星星》这首歌bilibili。当时我就在想,pythonGit能不能做到这一点呢?《小星星》的谱子太简单了,我决定尝试演奏《国际歌》
二、必备知识
2.1 Python生成音乐的原理
想要生成一段音乐,首变量英语先要了解声音产生的原理:
声音是由物体振动产生的声波。是通过介质(空气或固体、液体)传播并能被人或动物听觉器官所感知的波动现象。最初发出振动(震动)的物体叫声源。声音可视化管理以波的形式振动(震动)传播。声音字符是什么是声波通过任何介质传播形成的运动。 声音是变量之间的关系一种波。可以被人耳识别的声(运动品牌频率在20 Hz~gitlab20000 Hz之间),我们称之为声音。
想要用Python生成一段音乐,道理其实非常简单。只要我们想办法生成这段音乐的波形数据,再将其写入文件保存,就可以播放了。
怎样生成一段声波呢?这可以通过Numpy库实现。
下字符面的代码用于生成一段正弦波并可视化:
import numpy as np import matplotlib.pyplot as plt # 生成一条正弦波 x = np.linspace(0, 2*np.pi, 8192) y = np.sin(x) #可视化 plt.plot(x,y) plt.show()
可视化结果如图所示:

当然,如果我们把这样一段正弦波写入文件并播放,是不会听到任何声音的。这是因为人耳可以听到的声音的频率范围在20到2万赫兹(H运动完多久可以洗澡z可视化工具)之间,而上面这条正弦波的频率为1Hz变量,属于人耳听不到的次声波。

import numpy as np import matplotlib.pyplot as plt frequency = 440 x = np.linspace(0, 2*np.pi, 8192) y = np.sin(frequency * x) plt.plot(x,y) plt.show()
结果如下:


2.2 十二平均律
十二平均律十二平均律
(英语:Equ可视化管理al temperament),又称十二等程律,音乐律式的一种,也是当今最主流的律式。将一个八度平均分成十二等份,每等分称为半音,音高八度音指的是频率乘上二倍。八度音的频率分为十二等github永久回家地址分,即是分为十二项的等比数列,也就是每个音的频率为前一个音的2的12次方根:

2.3 简单可视化音乐软件乐理知识
想要用Python演奏国运动会加油稿际歌,首先要对琴谱进行编码,将五线谱输入计算机中。这要求我们必字符型变量须可视化对乐理知识具备一定的了解。这里不作详细介绍,可参考 乐理可视化是什么意思知识以及musicXml属性介绍 这篇文章。
三、实现
3.1 对琴谱进行编码
我手里现有的国际歌琴谱是五线谱形式的。


3.2 Music类
Music
类用运动完多久可以洗澡来实例化一首歌。其包含以下成员:
主要变量
name
: 一个字符串运动员,歌曲名称staff
: 一个Staff对象字符间距加宽2磅,歌曲的五线谱。wave
: n运动健康p.array()数组,五线谱对应的波形。Fs
: 采样率conver可视化图表ter
: 一个C字符间距加宽2磅怎么设置onverter对象,用来实现五线谱到波形的转换。
主要函数
load_staff(self,file_name)
: 读取字符串是什么意思编码后的五线谱。update_music(self)
: 根据读取的五线谱更新波形。save_music(self,file_name,file_type="wav")
: 保存音频文件。draw_wave(sel变量类型有哪些f)
: 画可视化数据图表出波形图。
class Music(): def __init__(self, name=""): """ Init an instance of MyMusic. Parameters ---------- name : str, [optional] Name of the music. Must be str. """ #Judge the type of input. assert name is None or isinstance(name, str) self.__name = name self.__staff = None self.__wave = np.array([]) self.__Fs = 8192 self.__converter = Converter(self.__Fs) def load_staff(self,file_name): """ Load the staff from a file. Parameters ---------- file_name : str The file to read from. Returns ------- state : bool Whether loaded the data successfully. """ #Judge the input. assert isinstance(file_name, str), "file_name must be a string" assert len(file_name.split(".")) == 2, "Input error. neg.'start.txt'" self.__staff = Staff(file_name) self.update_music() def save_staff(self,file_name): """ Save the staff to a file. Parameters ---------- file_name : str The file to save to. Returns ------- state: bool Whether saved the data successfully. """ def update_music(self): """ Update the music(wave) by self.__staff. """ self.__wave = self.__converter.gen_music(self.__staff) def save_music(self,file_name,file_type="wav"): """ Save the music as an audio. Parameters ---------- file_name : str The file to save to. file_type : str Type of the file to save to. Defaults to 'wav' and it means save the audio as "{file_name}.wav". Returns ------- state : bool Whether successfully saved the music as an audio. """ path = file_name.split(".")[0]+"."+file_type sf.write(path, self.__wave, self.__Fs, 'PCM_24') def play(self): """ Play the music. """ def draw_wave(self): """ Draw the waveform according to self.__wave. """ n = len(self.__wave) # Number of samples. t = n/self.__Fs # Range of t is [0,t] x = np.linspace(0, t, n) plt.plot(x,self.__wave) # Draw the wave. plt.show()
3.3 Staff类
Staff
类用于表示琴谱
主要变量
rythm
: 琴谱的节奏。loop
: 储存琴谱循环信息。
主要函数
read(self)
: 读取编码后的五线谱。
class Staff():
def __init__(self, file_name):
"""
Init an instance of a Staff.
Parameters
----------
f_name : str
The name of the file.
f_type : str
The type of the file.
"""
self.__sections = []
self.__name ,self.__type = file_name.split(".")
self.__text = ""
self.__rythm = 60
self.__loop = None
self.__supported_type = {"txt"} # The type of file supported.
self.read() # Read file.
def read(self):
"""
Init the staff from a file.
"""
assert self.__type in self.__supported_type, "Sorry, this type of file is not supported."
if self.__type == "txt":
self.read_from_txt()
def read_from_txt(self):
"""
Load the staff from a txt file.
Parameters
----------
file_name : str ("xxx.txt")
The full name of the file.
"""
file_name = self.__name + "." + self.__type
with open(file_name, "r") as file:
self.__text = file.read()
re_rythm = re.compile("rythm=([0-9]*)",re.S)
re_loop = re.compile("loop=(.*?))",re.S)
re_section = re.compile("(<section.*?>.*?</section>)",re.S)
re_section_att = re.compile("<section (.*?)>(.*)</section>",re.S)
self.__rythm = int(re_rythm.findall(self.__text)[0]) # Find the rythm.
self.__loop = eval(re_loop.findall(self.__text)[0])
sections = re_section.findall(self.__text) # Find all sections.
for section in sections:
# Create a temp dict to save the information of this section.
dict_att = {}
# Find the attributions and the notes of this section.
match = re_section_att.findall(section)
# Add every attribute to `dict_att`.
attributes = match[0][0].split()
for att in attributes:
key, value = att.split("=")
dict_att[key] = value
# Create a list `notes` and add every note to this list.
notes_temp = match[0][1].split("n")
notes = []
for i in range(len(notes_temp)):
note = notes_temp[i].strip(" ")
note = note.strip("t")
if note:
notes.append(note)
# Create a dict to save the information of this section, and add it to `self.__section`.
self.__sections.append({"attribute":dict_att, "notes":notes})
# print(self.__sections)
@property
def sections(self):
return self.__sections
@property
def rythm(self):
return self.__rythm
@property
def loop(self):
return self.__loop
3.4 Converter类
Converter
类用于实现五可视化线谱到波形的转换。
主要变量
Fs
: 采样率。
主要函数
regitiad(self)
:变量名的命名规则 读取编码字符串逆序输出后的五线谱。get_frequency(可视化大屏sel可视化图表f,major,scale)
:计算给定音符的频率。get_wave(self,major,scale,rythm=1)
: 生成给定音符的波形。gen字符常量_music(seGitlf,staff)
: 拼接各音符的波形,生成整首音乐的波形。
class Converter(): def __init__(self, Fs=8192): """ Init an instance of Converter. Parameters ---------- Fs : int [optional] The sampling rate. """ self.__Fs = Fs def get_frequency(self,major,scale): """ Calculate the Frequency. Parameters ---------- major : int The major. For example, when it takes 0, it means C major. scale : int, range[-1,12] The scale. -1 means a rest, and 1 means "do", 12 means "si". Returns ------- frequency : float The Frequncy calculated by the major and scale. """ frequency = 440*(2**major) frequency = frequency*2**((scale-1)/12) return frequency def get_wave(self,major,scale,rythm=1): """ Generate the wave of a note. Parameter --------- major : int The major. For example, when it takes 3, it means C major. scale : int, range[-1,12] The scale. -1 means a rest, and 1 means "do", 12 means "si". rythm : int The rythm of the note. When it takes 1.5, int means 1.5 unit-time per beat. Returns ------- y : np.array The wave generated. """ Pi = np.pi x = np.linspace(0, 2*Pi*rythm, int(self.__Fs*rythm), endpoint=False) # time variable # When scale==-1, it means a rest. if scale == -1: y = x*0 else: frequency = self.get_frequency(major, scale) y = np.sin(frequency*x)*(1-x/(rythm*2*Pi)) return y def gen_music(self,staff): """ Play a piece of music based on section. Parameters ---------- staff : class Staff. Returns ------- wave : np.array The wave of the staff. """ sections = staff.sections time = 60/staff.rythm loop_start, loop_end, loop_times, loop_sub = staff.loop wave = np.array([]) section_wave_ls = [] for section in sections: notes = section["notes"] wave_list = [] for line_str in notes: line = [eval(note) for note in line_str.split()] line_wave = np.array([]) for note in line: major, scale, rythm = note rythm *= time y = self.get_wave(major, scale, rythm) line_wave = np.concatenate((line_wave,y),axis=0) wave_list.append(line_wave) length = min([len(line_wave) for line_wave in wave_list]) section_wave = wave_list[0][:length] for i in range(1,len(wave_list)): section_wave += wave_list[i][:length] # wave = np.concatenate((wave,section_wave),axis=0) section_wave_ls.append(section_wave) temp = [w for w in section_wave_ls[:loop_start-1]] for i in range(loop_times): for w in section_wave_ls[loop_start-1:loop_end]: temp.append(w) if loop_sub: section_wave_ls = temp[:-1] + [w for w in section_wave_ls[loop_end:]] else: section_wave_ls = temp + [w for w in section_wave_ls[loop_end:]] for w in section_wave_ls: wave = np.concatenate((wave,w), axis=0) return wave
四、项目代码
完整代码已经上传变量英语至github,传送门: Python演奏国际歌运动会作文
五、运动完多久可以洗澡不足与展望
- 对琴谱的编码方式有待优化。我采用的方法太过粗糙,只提取了原谱最关键的少量信息。此外,由于编码规则是我自己指定的,这种编码也缺乏通用性。接下来可以尝试MusicXMLXML编码。
- 琴谱编字符是什么码需要人工进行,费时费力,后期可以采用图像识别技术自动识别并编码字符间距加宽2磅琴谱。
- 后期可以再写一个UI界面,提高用户体验度。
- 【Matlab番外篇】怎样用Matgiteelab生成一段音乐↩
- 维基百科:A(音名)↩
- 维基百科:十二变量泵平均律↩
- Songs and Sch运动会作文emas↩
评论(0)