音乐波谱生成

音乐波谱生成

实现一个由代码生成,《送别》的音频文件. 本文设涉及编码、音乐以及一些数学公式。实现从底层波函数生成到音乐音频的生成。不依赖第三方API。其中的数学、音乐知识点,具体细节不展开讨论,可以私聊。

一.根据频率生成波谱

def generate_wave_func(v, c=0):
    # v 频率 单位 HZ, c 相位。  生成正弦波
    a = 2 * numpy.pi / (1 / v)
    def inner(x):
        return numpy.sin(a * x + c)

    return inner


def superelipse_points(n=1.0, a=1.0, b=1.0, lins=1000):
    # 超椭圆方程, 返回第一象限的数值
    X1 = numpy.linspace(0, a, lins)
    Y1 = (1 / b) * numpy.power((1 - numpy.power(X1 / a, n)), 1 / n)
    return X1, Y1


def wave_decay(data, i0=0, i1=-1, decay=0.8):
    # 波谱衰减函数
    # i0, i1 不包含 i1
    # print(data.shape)
    if i1 == -1:
        i1 = data.shape[0] - 1
    n = (i1 - i0)
    x, y = superelipse_points(n=decay, a=1, b=1, lins=n)
    for ch in range(data.shape[1]):
        data[i0: i1, ch] = data[i0: i1, ch] * y
    return data

##
import wave
import numpy
import matplotlib.pyplot as plt

"""
file_path moonlight.wav
nchannels 2 # 声道
sampwidth 2 # 采样深度 字节
framerate 44100 # 采样速率 HZ
nframes 16052015 # 音频总帧数
comptype NONE # 压缩类型
compname not compressed # 压缩类型名称
data.shape (16052015, 2)
"""


class WaveObj(object):
    # 对 wave模块 封装
    def __init__(self, nchannels=None, sampwidth=None,
                 framerate=None, nframes=None, data=None, **kwargs):
        """
        nchannels: 声道 int
        sampwidth: 采样字节长度(音频强度) bytes
        framerate: 采样率 HZ
        nframes: 总帧数 int
        data: 音频数据 numpy 数组 shape = (nframes, nchannels)
        """
        self.nchannels = nchannels
        self.sampwidth = sampwidth
        self.framerate = framerate
        self.nframes = nframes

        self.comptype = kwargs.get("comptype", 'NONE')
        self.compname = kwargs.get("compname", 'not compressed')
        self.data = data
        self.file_path = ""
        # 第一帧时间标记为0, 所以总时间需要减少一帧计算
        if self.nframes and self.framerate:
            # self.total_time = (nframes - 1) / framerate
            self.total_time = nframes / framerate
        # if self.nframes and self.framerate:
        #     self.times = numpy.arange(0, nframes) * (1 / framerate)
        #     self.total_time = self.times[-1]

    def init_from_file(self, path):
        self.file_path = path
        with wave.open(path, "rb") as w:
            self.nchannels = w.getnchannels()
            self.sampwidth = w.getsampwidth()
            self.framerate = w.getframerate()
            self.nframes = w.getnframes()
            self.comptype = w.getcomptype()
            self.compname = w.getcompname()
            data = w.readframes(self.nframes)  # bytes
            data = numpy.frombuffer(data, dtype=numpy.int16)
            data = numpy.reshape(data, (self.nframes, self.nchannels))
            self.data = data
            # self.times = numpy.arange(0, self.nframes) * (1 / self.framerate)
            self.total_time = self.nframes / self.framerate

    @property
    def times(self):
        if not hasattr(self, "_times"):
            self._times = numpy.arange(0, self.nframes) * (1 / self.framerate)
            return self._times

        return self._times

    def save(self, path):
        if not path.endswith(".wav"):
            path += path + ".wav"
        with wave.open(path, mode="wb") as w:
            w.setparams((self.nchannels,
                         self.sampwidth,
                         self.framerate,
                         self.nframes,
                         self.comptype,
                         self.compname))
            w.writeframes(self.data.tobytes())

    def print_params(self):
        print("file_path", self.file_path)
        print("nchannels", self.nchannels),
        print("sampwidth", self.sampwidth),
        print("framerate", self.framerate),
        print("nframes", self.nframes),
        print("comptype", self.comptype),
        print("compname", self.compname)
        print("data.shape", self.data.shape)

    def show_wave(self):
        plt.plot(self.times, self.data[:, ...])
        # plt.scatter(d.times, d.data[:, 0])
        plt.show()


def create_wave_data_by_func(func, t0, t1, framerate, channels=2, batch=True):
    # 生成波谱
    # t0, t1, 单位 s   framerate 单位 n/s
    n = int((t1 - t0) * framerate)
    x = numpy.linspace(t0, t1, n)
    if not batch:
        y = numpy.array([func(i) for i in x])
    else:
        y = func(x)

    y = y[..., numpy.newaxis].repeat(channels, axis=1)

    return y


二.计算小字一组音的频率

# 大调音阶频率关系
ionian = [0, 2, 4, 5, 7, 9, 11, 12] # c1, d1, e1, f1, g1, a1, b1
du = 2 ** (1 / 12) # 一度音程倍率
a1 = 440 # 标准音 a1
c1 = 440 / (du ** 11) # 小字一组 c1 频率
c1toc2 = [int(c1 * (du ** i)) for i in ionian]  # 小字一组音阶 + c2
print(c1toc2)
[233, 261, 293, 311, 349, 391, 440, 466]

三.根据简谱生成波谱数据

以下是送别的简谱
在这里插入图片描述

T = 0.5 # 一拍的时间
# 根据简谱,做一些简单变换,方便处理,比如 1*表示 高八度的 1, 第二个是以数值标注的节拍时值
data = [
    "5-1",
    "3-0.5",
    "5-0.5",
    "1*-2",
    
    "6-1",
    "1*-1",
    "5-2",
    
    "5-1",
    "1-0.5",
    "2-0.5",
    "3-1",
    "2-0.5",
    "1-0.5",
    
    "2-4",
    
    
    "5-1",
    "3-0.5",
    "5-0.5",
    "1*-1.5",
    "7-0.5",
    
    "6-1",
    "1*-1",
    "5-2",
    
    "5-1",
    "2-0.5",
    "3-0.5",
    "4-1.5",
    "*7-0.5",
    "1-4",
    
    "6-1",
    "1*-1",
    "1*-2",
    
    "7-1",
    "6-0.5",
    "7-0.5",
    "1*-2",
    
    "6-0.5",
    "7-0.5",
    "1*-0.5",
    "6-0.5",
    "6-0.5",
    "5-0.5",
    "3-0.5",
    "1-0.5",
    
    "2-4",
    
    "5-1",
    "3-0.5",
    "5-0.5",
    "1*-1.5",
    "7-0.5",
    
    "6-1",
    "1*-1",
    "5-2",
    
    
    "5-1",
    "2-0.5",
    "3-0.5",
    "4-1.5",
    "*7-0.5",
    "1-4",
    
]
def create_music_wave(data):
    T = 0.5 # 一拍时间 单位 s
    framerate = 44100
    sampwidth = 2
    v_list = [i * 2 for i in c1toc2]
    music = []
    for i in data:
        key, time = i.split("-")
        key_id = int(key.replace("*", "")) - 1
        time = float(time)
        
        starts = key.count("*")
        mark = -1 if key.startswith("*") else 1
        v = v_list[key_id] * (2 ** (starts * mark))
        print(f"音符:{key} 频率:{v}, 时间:{time}")
        func = generate_wave_func(v)
        nots_wave = create_wave_data_by_func(func, 0, time, framerate,
                                 channels=2
                                )
        nots_wave =  nots_wave * (2 ** (sampwidth * 8 - 1))
        # 做音高衰减
        nots_wave = wave_decay(nots_wave, decay=1.1)
        nots_wave = nots_wave.astype("int16")
        music.append(nots_wave)
        
    return numpy.concatenate(music)

music_wave = create_music_wave(data)
音符:5 频率:698, 时间:1.0
音符:3 频率:586, 时间:0.5
音符:5 频率:698, 时间:0.5

#    def __init__(self, nchannels=None, sampwidth=None,
#                  framerate=None, nframes=None, data=None, **kwargs):
wave_obj = WaveObj(data=music_wave,nframes=music_wave.shape[0],
               nchannels=music_wave.shape[1],
               framerate = 44100,
               sampwidth = 2
              )
wave_obj.show_wave()

在这里插入图片描述

wave_obj.save("送别.wav")
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值