使用Python实现的音符生成节拍器程序
推荐超级课程:
@TOC
摘要
节拍器和随机音符生成器各有用途,但单独使用时功能有限。本文将使用 Python 将这两种功能结合起来,创建一个定制的节拍器,用于您的练习或探索。 如果您想练习在吉他或键盘上找到音符位置,同时受到节拍器的压力,网络上和线下的资源可以帮助您进行这些活动。然而,它们通常是分开的 - 一个节拍器,一个随机音符生成器。本文将使用 Python 将这两种功能结合起来,创建一个您可以使用的程序。
先决条件
您应该安装 Python 和您选择的文本编辑器 - 本文假设您使用 VSCode,但任何其他编辑器都适用。 一些 Python 经验会有帮助,但并非必需。
设置
打开您的文本编辑器,创建一个新的 notes.py
文件并打开它。您应该在编辑器窗口底部看到一个终端 - 如果它不在那里,请按 ctrl+shift+`
。在终端中键入以下行:
pip install simpleaudio
pip install numpy
simpleaudio
是我们将用于播放声音的包,numpy
是我们将用于生成可听音符的包。 现在,让我们通过在notes.py
文件的顶部键入以下行来将这些包导入到我们的 Python 文件中:
import simpleaudio
import numpy
import random
import time
- 我们导入额外的 random 和 time 模块,以便从列表中选择随机音符并为我们的节拍器计时。
生成音符频率
使用自下而上的方法,让我们首先创建我们的音符字典。为此,我们将创建一个名为 notes
的 Python 字典。在字典的每个索引处是一个包含音符和其频率的列表。在您的 Python 文件中键入以下内容:
# 音符频率
notes = {
1: ["A" ,0],
2: ["A#",0],
3: ["B" ,0],
4: ["C" ,0],
5: ["C#",0],
6: ["D" ,0],
7: ["D#",0],
8: ["E" ,0],
9: ["F" ,0],
10:["F#",0],
11:["G" ,0],
12:["G#",0],
}
# 生成音符频率
for i,note in enumerate(notes.keys()):
notes[note] = round(440 * 2 ** (i / 12), 1)
print(notes[i])`
- 我们使用 0 作为频率值初始化我们的字典,以便我们可以使用循环生成音符频率。 - 使用 440 Hz 作为基础频率,此循环根据它们之间的数学间隔计算每个音符的频率。然后我们更新字典中每个音符的频率,并在继续时将其打印到我们的终端上。
如果您使用
ctrl + f5
或按窗口右上角的绿色运行按钮运行代码,您应该在终端上看到正确的音符和频率显示如下:
['A', 440.0]
['A#', 466.2]
['B', 493.9]
['C', 523.3]
['C#', 554.4]
['D', 587.3]
['D#', 622.3]
['E', 659.3]
['F', 698.5]
['F#', 740.0]
['G', 784.0]
['G#', 830.6]
如果正确的音符和频率被打印出来,您可以删除 print(notes[i])
行。
播放音符
现在,让我们创建音符播放函数。为此,我们将使用 numpy
和 simpleaudio
。在您的 Python 文件中键入以下函数:
def play_note(note, octave=4):
if octave >= 8:
octave = 8
frequency = note[1] * 2 ** (((octave*12)-48) / 12)
fs = 44100 # 44100 个样本/秒
seconds = 1 # 音符持续时间 - 整数
# 生成具有秒*样本率步骤的数组,范围在 0 到秒之间
t = numpy.linspace(0, seconds, seconds * fs, False)
# 从频率生成正弦波
note = numpy.sin(frequency * t * 2 * numpy.pi)
# 确保最高值在 16 位范围内
audio = note * (2**15 - 1) / numpy.max(numpy.abs(note))
# 转换为 16 位数据
audio = audio.astype(numpy.int16)
# 开始播放
play_obj = simpleaudio.play_buffer(audio, 1, 2, fs)
# 测试后删除此行
play_note(notes[1], octave = 4)
time.sleep(1)
- 在这里,我们创建一个接受两个参数的函数:包含音符名称和频率的列表,以及一个八度数 - 用于计算音符的新频率,使我们能够在多个八度上播放音符。
- 然后,我们使用各种
numpy
函数最终从我们提供的频率生成可听音符。simpleaudio
然后播放这个生成的音符,持续一秒钟。 如果您现在运行程序,您应该会听到您的扬声器上播放了一个中音 A。
节拍器
使用 Python 时需要注意的一件事是,您的代码不会直接访问您的硬件,而是通过很多层进行。因此,我们的节拍器可能不会完全准确,并且在一段时间后会漂移。创建节拍器的通常方法是使用 Python 的内置 time.sleep()
方法。然而,由于此方法在很大程度上取决于您的计算机处理器在那一刻可能正在运行的其他任务,我们将使用一种略有不同的方法,以便我们的节拍器可以具有相对更高的精度。
现在,让我们构建我们的节拍器 - 您需要下载以下两个节拍器声音文件并将它们放在与您的 notes.py
文件相同的文件夹中:
这两个文件之间的唯一区别是,当强调新节拍的开始时,将播放其中一个。
在您的 Python 文件中添加以下代码:
def metronome(bpm):
print(float(bpm), "bpm")
delay = 60/bpm
count = 0
beat = 0
mode = 4
multiple = 8
while True:
wait(delay)
# 每次等待后增加计数,并在每 4 次计数后增加节拍
count += 1
if count > mode:
count = 1
beat += 1
# 根据节拍计数设置节拍器音频
wave_obj = simpleaudio.WaveObject.from_wave_file('metronome.wav')
if count == 1:
wave_obj = simpleaudio.WaveObject.from_wave_file('metronomeup.wav')
# 播放节拍器音频
play_obj = wave_obj.play()
play_obj
# 测试后删除此行
print(beat, count)
让我们分解一下这段代码。 - 我们创建了一个接受 bpm 数字的节拍器函数。此函数首先定义每分钟节拍之间的时间延迟,并初始化节拍器计数和节拍。我们将在接下来的部分中查看 mode
和 multiple
变量。 - 然后,我们创建一个无限循环,在每次迭代之前等待指定的延迟时间,然后再执行循环中其余的代码。您会注意到我们使用了一个 wait()
函数 - 这是我们在节拍器函数之后单独编写的另一个函数。 - 我们循环中的第二部分在每次延迟后增加计数,并在每 4 次计数(模式变量)后重置为 1。每 4 次计数后,节拍号也增加 1。您可以将模式变量从 4 更改为其他数字,例如 3 或 6,以更改节拍器的节奏。 - 我们有了计数系统后,我们使用 simpleaudio
准备我们的节拍器声音文件。每当计数为 1 时,我们选择不同的节拍器声音以强调新节拍的开始。 - 我们循环的最后一部分播放所选的声音文件。在这种情况下,simpleaudio
在主程序线程之外的不同线程上播放声音,因此主程序可以继续运行。
现在,让我们创建一个函数,作为节拍器每次滴答之间的延迟。将此代码复制到您的文件中:
def wait(delay):
end_time = time.time() + delay
while end_time > time.time():
continue
# 测试后删除此行
metronome(95)
- 此函数接受我们在上一个函数中定义的延迟时间。 - 我们创建了一个名为
end_time
的变量,该变量通过将自 *epoch 过去的时间数与 bpm 的延迟时间相加来定义。 - 然后,我们创建一个循环,该循环继续运行(换句话说,等待),直到 epoch 时间大于我们定义的end_time
。一旦此循环完成,我们的等待函数终止,程序继续运行。
*
"epoch 是时间开始的地方,并且平台相关。对于 Unix,epoch 是 1970 年 1 月 1 日 00:00:00 (UTC)。要查找给定平台上的 epoch,请查看 time.gmtime(0)。"
*"......各种实时函数可能不如其值或参数表示的单位所暗示的那样准确。例如,在大多数 Unix 系统上,时钟“滴答”仅为每秒 50 次或 100 次。"
请记住,如本节概述中所述,此方法可能不会完全准确。运行代码,您应该会听到您的节拍器以及节拍计数器在控制台上打印如下:
95.0 bpm
0 1
0 2
0 3
0 4
1 1
1 2
1 3
1 4
2 1
2 2
2 3
2 4
3 1
...
如果您不需要查看它们的输出,请记得删除 print(beat, count)
和 metronome(95)
行。
您可以使用终端上的快捷键 ctrl+c
,或单击屏幕右上角附近的红色停止按钮来停止程序。
合并
现在,节拍器和音符播放功能已经完成,让我们将它们合并以完成此程序。将以下函数添加到您的文件中:
def generate_notes(sound = True):
random_note = random.choice(list(notes.values()))
print("nnFind: ",random_note[0])
if sound == True:
play_note(random_note, octave = 3)
- 此函数从我们的音符字典中选择一个随机音符。我们将字典中的所有值合并成一个列表,然后使用
random.choice()
选择一个随机音符。 - 然后,我们在控制台上打印一条消息,指示已选择哪个音符。 - 由于我们创建此函数以接受一个sound
变量,该变量可以是 True 或 False。我们使用该变量来确定程序是否应该播放音符声音。如果播放音符,我们将其设置为在第三八度上播放。在某些扬声器上,更高八度可能会变得有点不舒服 正如它现在所做的那样,我们刚刚创建的函数在任何地方都没有被调用,所以它还没有工作。让我们回到我们的节拍器函数并添加调用此函数的代码。在您的节拍器函数的# 播放节拍器音频
部分下面添加以下代码:
# 生成音符()
if beat % 8 == 0 and count == 4:
generate_notes()
您的代码应该看起来像这样:
# 播放节拍器音频
play_obj = wave_obj.play()
play_obj
# 生成音符()
if beat % multiple == 0 and count == 1:
generate_notes()
- 我们仅在节拍号是 8 的倍数(我们之前定义的变量),并且计数为 1 时调用
generate_notes()
函数。这个 8 的值将给您足够的时间在您的乐器上找到音符。根据您的需要随意调整此值。较低的数字将更频繁地生成音符(低于 1 的值将无法很好地工作)。 您现在可以通过将此行添加到您的文件中来运行代码:metronome(90)
结论
您可以使用此功能练习找到音符,或者使用它为您生成要在其上演奏的根音符。如果您有更多的编码经验,也许您可以扩展功能以包括音阶、midi 输入或任何其他可能的功能。 在本教程中,您学习了如何制作节拍器系统和随机音符生成器。我们还研究了如何使用 simpleaudio 播放外部声音或使用 numpy 的帮助生成音符。我希望这以某种方式增强了您使用乐器的练习。