Python 从零开始制作自己的声音 - wave模块读写wav文件详解

2023-11-06

计算机经常被用于处理音频这种真实世界中的数据。声音经过采样,量化和编码后,存储在音频文件,如wav文件中。
文章首先介绍wave模块的基础用法; 再通过生成一定频率声波的算法实现,来深入讲解wave库的使用。

wave模块

wave模块提供了一个处理 wav 声音格式的便利接口, 可获取wav文件头信息, 从文件读取数据, 也可直接将bytes格式的数据写入wav文件。

wave.open()

wave.open(file, mode=None)
类似于普通的打开文件,函数接收两个参数,file为文件名或文件对象,mode可取"r",“rb”,“w”,“wb"四个值,其中"r"和"rb”, "w"和"wb"效果完全相同。如下:

>>> wave.open('音乐.wav','r')
<wave.Wave_read object at 0x0355E810>
>>> wave.open('test.wav','w')
<wave.Wave_write object at 0x0355E810>

以读模式打开的文件会返回Wave_read 对象, 写模式打开时会返回Wave_write 对象。

Wave_read

Wave_read 对象通过wave.open() 函数创建。wave文件记录了二进制的音频数据,由许多帧组成,一个采样对应一个帧,每一帧长度为1或2字节。

Wave_read.getnchannels():返回声道数量(1 为单声道,2 为立体声)

Wave_read.getsampwidth():返回采样字节长度 (每一帧的字节长度)。

Wave_read.getframerate():返回采样频率。

Wave_read.getnframes():返回音频总帧数。

Wave_read.getcomptype()Wave_read.getcompname():返回压缩类型。

Wave_read.readframes(n)
读取并返回以 bytes 对象表示的最多 n 帧音频。

Wave_read.tell()
返回当前文件指针位置。

Wave_read.setpos(pos)
设置文件指针到指定位置。

Wave_write

Wave_write 对象也通过wave.open() 函数创建。

Wave_write.setnchannels(n):设置声道数。

Wave_write.setsampwidth(n):设置采样字节长度为 n。

Wave_write.setframerate(n):设置采样频率为 n。

Wave_write.setnframes(n):设置总帧数为 n。(后来发现调用writeframes()时,wave模块会自动更新总帧数,实际上不需要调用这个函数)

Wave_write.setcomptype(type, name):设置压缩格式。(目前只支持 NONE 即无压缩格式。)

Wave_write.tell()
返回当前文件指针,其指针含义和 Wave_read.tell() 以及 Wave_read.setpos() 是一致的。

Wave_write.writeframes(data)(或writeframesraw(data)
写入bytes格式的音频帧,并更新 nframes。

Wave_write.close()
确保 nframes 是正确的,并在文件被 wave 打开时关闭它。 此方法会在对象收集时被调用。 如果输出流不可查找且 nframes 与实际写入的帧数不匹配时引发异常。

初步: 拼接音频

程序先将两段音频中的数据读入data1data2中,再将读取的数据拼接,写入result.wav。注意两段音频的采样频率、采样字节长度需要一致。

import wave

sampwidth = 1
framerate = 22050

with wave.open('音乐1.wav','rb') as f1:
    sampwidth = f1.getsampwidth()
    framerate = f1.getframerate()
    nframes1=f1.getnframes()
    data1=f1.readframes(nframes1)

with wave.open('音乐2.wav','rb') as f2:
    nframes2=f2.getnframes()
    data2=f2.readframes(nframes2)

with wave.open('result.wav','wb') as fw:
    fw.setnchannels(1)
    fw.setsampwidth(sampwidth)
    fw.setframerate(framerate)
    #fw.setnframes(nframes1+nframes2)
    fw.writeframesraw(data1)
    fw.writeframesraw(data2)

初次实现

现在开始制作自己的声音。程序生成一段频率为200Hz, 长度为1.8秒的蜂鸣声。

import wave
from winsound import PlaySound,SND_FILENAME

file = 'test.wav'
len_= 1.8 # 秒
frequency = 200
sampwidth = 1 #每一帧宽度(采样字节长度)
framerate = 22050 # 采样频率 (越大音质越好)
length = int(framerate * len_ * sampwidth)
para = [0b00000000]*(framerate//frequency//2*sampwidth)\
       +[0b11111111]*(framerate//frequency//2*sampwidth) # 音频的一小段
data=bytes(para)

# 生成wav文件
with wave.open(file,'wb') as f:
    f.setnchannels(1)
    f.setsampwidth(sampwidth)
    f.setframerate(framerate)
    # f.setnframes(length) (可选)
    f.writeframes(data * (length // len(data)))

PlaySound(file,SND_FILENAME) # 播放生成的wav

再次实现

上述程序生成的是方波,并有一些缺陷,如para0b00000000b11111111的长度是整数且相同,导致生成的声音频率不精确,等等。
这里合成一段200Hz,长度为1.8秒的正弦波。

import wave,math
from winsound import PlaySound,SND_FILENAME

def generate(T,total,volume,sine=False):
    # T: 周期, total 总长度, 都以帧为单位
    if not sine:
        h = T / 2
        for i in range(total):
            if i % T >= h:
                yield volume
            else:
                yield 0
    else:
        # 计算方法: sin 的 T = 2*pi / w
        w = 2 * math.pi  / T; r = volume / 2
        for i in range(total):
            yield int(math.sin(w * i) * r + r)

file = 'test.wav'
len_= 1.8 # 秒
frequency = 200
sampwidth = 1
framerate = 22050
sine=True
volume = 255 # 音量, 0 - 255
data = bytes(generate(framerate / frequency, int(framerate*len_),
                              volume,sine)) # bytes能接收0-255整数型的迭代器

with wave.open(file,'wb') as f:
    f.setnchannels(1)
    f.setsampwidth(sampwidth)
    f.setframerate(framerate)
    f.writeframes(data)

PlaySound(file,SND_FILENAME)

运行程序会发现,正弦波听起来比方波更加柔和。
自己做的合成与Python内置的音频合成对比:

import winsound
# Beep(freq,duration),参数分别是频率和毫秒为单位的持续时间
winsound.Beep(200,1800)

发现, 前述程序很好地仿真了调用内置的Beep函数发声。
但音质有区别, 这是采样字节长度为1(只有8位)导致的, 还需要加大采样字节长度。
最终的程序如下:

import wave,math,struct
from winsound import PlaySound,SND_FILENAME
def generate(T,total,volume,sampwidth,sine=False):
    # T: 周期, total 总长度, 以帧为单位
    volume = min(volume * 2**(sampwidth*8),2**(sampwidth*8) - 1)
    if not sine:
        h = T / 2
        for i in range(total):
            if i % T >= h:
                yield volume
            else:
                yield 0
    else:
        w = 2 * math.pi  / T; r = volume / 2
        for i in range(total):
            # T = 2*pi / w
            yield int(math.sin(w * i) * r + r)

file = 'test.wav'
len_= 1.8 # 秒
frequency = 200
sampwidth = 2
framerate = 22050
sine=True
volume = 255
# 8位的wav文件的一帧是无符号8位整数, 而16位的一帧是有符号的整数(-32768至32767)。
if sampwidth == 1: # 8位
    lst = list(generate(framerate / frequency, int(framerate*len_),
                    volume,sampwidth,sine))
    data = bytes(lst)
elif sampwidth == 2:
    data = b'' # 16位
    lst = list(generate(framerate/frequency,
                        int(framerate*len_),
                        volume,sampwidth,sine))
    for digit in lst:
        data += struct.pack('<h',digit - 32768)

with wave.open(file,'wb') as f:
    # --snip-- (看前面)

PlaySound(file,SND_FILENAME)

使用matplotlib库查看生成的声波:

import matplotlib.pyplot as plt
# --snip--
plt.plot(range(len(lst)),lst)
plt.show()

使用matplotlib查看声波
写在最后:
程序还可再做改进, 例如模拟各种乐器的音色, 也就是细微改变生成的声波形状。如果程序中加入共振峰, 还可实现简单的语音合成?
但是, Windows系统已经自带了语音合成, 何必再开发一个呢?
下篇: Python 调用Windows内置的语音合成,并生成wav文件

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 从零开始制作自己的声音 - wave模块读写wav文件详解 的相关文章

随机推荐

  • BES(恒玄)单线 串口通讯实现

    本文介绍 恒玄方案 耳机和充电盒之间的单线 通讯和是实现 充电仓通讯有很多用途 a 充电盒基于霍尔senor的开关盒 配对等命令 b 获取修改耳机蓝牙地址 c 耳机当前电量获取等 一 单线通讯协议定义 双方TTL信号通过一根信号线 一根地线
  • 计算机C盘能扩展吗,为什么电脑c盘没有扩展卷(原因揭秘及扩大c盘空间的方式)...

    经常有人跟我说 自己的C盘越来越小了 或者C盘太小 怎么扩大 C盘空间越来越小 会导致电脑运行速度变慢 当C盘满了 估计开机都要开不了 究其原因 大部分都是在安装软件的时候 把软件安装到C盘了 还有就是直接在桌面上放很多大的文件 那么C盘空
  • ElasticSearch部分(增删改查)

    一 基本概念 1 index 索引 动词 相当于MySQL中的insert 名词 相当于MySQL中的Database 2 Type 类型 在index 索引 中 可以定义一个或多个类型 类似于MySQL中的Table 每一种类型的数据放在
  • 去水印源码,不是接口,可以支持接口,短视频去水印,源码,算法

    去水印源码 不是接口 python 直接给算法核心 短视频去水印 主流平台去水印 需要请私我或评论区留联系方式 主流短视频平台去水印 支持去水印平台 抖音 快手 图片 视频 火山 皮皮虾 微视 西瓜视频 全民搞笑 全民小视频 皮皮搞笑 轻视
  • [vue3] CreateApp实现

    div hollo vue div
  • python用updatecursor删除行

    arcpy env workspace C ArcpyBook Ch8 WildfireData WildlandFires mdb try with arcpy da UpdateCursor FireIncidents CONFID R
  • Linux(CentOS 7)虚拟机无法ping通网关和外网

    1 查看VMware的虚拟网络编辑器的设置 查看网关地址 2 设置虚拟机IP地址以及网关 DNS 以太网 TYPE Ethernet PROXY METHOD none BROWSER ONLY no static设置静态IP BOOTPR
  • IT项目管理第七次作业

    IT项目管理第七次作业 17343140 杨泽涛 第一题 WBS条目 数量或小时数 单位小时成本 美元 子层总和 美元 WBS第二层总和 美元 占总和的 1 项目管理 44 000 22 项目经理 320 100 32 000 项目其他成员
  • Avalonia-VS环境安装

    链接 https pan baidu com s 1T1TYf7 Q5T6hbpOyGCW2Vg 提取码 m8eu 安装SDK 没有这个 vs2019安装后Avalonia插件 无法选择netcore 生成项目 插件地址 上面为vs2019
  • MapReduce中使用Avro出现TaskAttemptContext异常

    打包上传Jar包到Hadoop环境下运行时 出现异常 Found interface org apache hadoop mapreduce TaskAttemptContext but class was expected 在网上找了很多
  • Luatos-Air001(合宙开发板)初步使用——点亮板载LED

    实物图片 实物图片 暗处 实物图片 亮处 可以看到盒子还是很酷炫的 插电后板子自带的红绿蓝流水灯便会启动 比较漂亮 相机一团糊就不放视频了XD 开发环境搭建与程序编写 开发环境的搭建主要参考官网 就在板子背面w 非常贴心 https lua
  • springcloud集成hystrix 实现服务的隔离,熔断,降级

    一 pom引入依赖
  • 关于如何将一个springcloud项目部署至服务器

    目录 1 部署形式 2 Alibaba Cloud Toolkit的使用 3 一个springcloud项目的部署 4 运行jar包 5 后台运行 1 部署形式 一般我们在后端开发中 常常将整个springboot项目打包成war包 或是打
  • 【数据库】窗口函数实战(三)

    窗口函数实战 三 本篇文章是笔者在牛客网上摘选的几道比较有挑战性的SQL窗口函数编程题 1 近三个月未完成试卷数为0的用户完成情况 中等 原题链接 首先来分析一下题目的查询要求 用户近三个有试卷作答记录的月份 可以用窗口函数解决 并且这三个
  • 20+ css高频实用片段,提高幸福感的小技能你可以拥有噢

    前言 修改input placeholder样式 多行文本溢出 隐藏滚动条 修改光标颜色 水平垂直居中 多么熟悉的功能呀 前端童鞋几乎每天都会和他们打交道 一起来总结我们的css幸福小片段吧 下次不用百度 不用谷歌 这里就是你的港湾 Git
  • 原地删除数组中的重复元素--双指针

    题目 原地 不增加任何额外的空间 删除数组中的重复元素 返回新数组长度 思路 参考链接 图片对算法的描述直观明了 利用双指针的概念 一个慢指针 一个快指针 i代表慢指针 初始值为0 j代表快指针 初始值为1 快指针先走 如果nums fas
  • MySQL-图形化界面工具 (上)

    作者 小刘在C站 个人主页 小刘主页 每天分享云计算网络运维课堂笔记 努力不一定有收获 但一定会有收获加油 一起努力 共赴美好人生 树高千尺 落叶归根人生不易 人间真情 目录 MySQL 主要存在以下两点问题 1 安装 1 找到资料中准备好
  • Qt中使用QSettings读取配置文件注意事项

    QSetting使用中的注意事项 QSetting为Windows Linux和MacOS系统提供了统一的配置文件 注册表读取方式 引用QtHelp的一句话 The QSettings class provides persistent p
  • 安徽高考少输入的6个字节猜想

    情报收集 第39题 主观题 网上评卷 6个字节 第39题是主观题 所以排除了机读卡 因为是网上评卷系统应是数据录入员或评卷人在一个web页面填写得分那么服务端就该是xxx request form xxx 接收的 然后保存到数据库中 所以
  • Python 从零开始制作自己的声音 - wave模块读写wav文件详解

    计算机经常被用于处理音频这种真实世界中的数据 声音经过采样 量化和编码后 存储在音频文件 如wav文件中 文章首先介绍wave模块的基础用法 再通过生成一定频率声波的算法实现 来深入讲解wave库的使用 目录 wave模块 wave ope