- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
在过去的几周里,我一直在从事一个对我来说非常新的项目,并且我正在边做边学。我正在使用 Raspberry Pi 2 构建一个合成器,并使用 Python3 对其进行编码,因为我对该语言有一些基本知识,但没有太多实际经验。到目前为止,我已经应付得很好,但我现在已经碰上了我知道我最终会碰上的墙:性能。
我一直在使用 Pygame 及其声音模块来创建我想要的声音,然后使用我自己的数学算法来计算每个声音的 ADS(H)R 音量包络。我使用 8 个电位器调整这个包络。其中 3 个控制 Attack、Decay、Release 的长度(以秒为单位),另一个用于设置 Sustain 级别。然后我又添加了 4 个电位器来控制包络每个部分的曲率(除了其中一个电位器为延音设置了保持值)。我还连接了一个 PiTFT 屏幕,它可以绘制整个信封的当前形状和长度,并打印出 ADSR 的当前值。
为了播放声音,我使用 4x4 Adafruit Trellis 板,通过不同的按钮组合,我可以播放 C0 和 C8 之间的每个音符。
我使用 SciPy 和 NumPy 创建不同类型的声波,如正弦波、方波、三角波、锯齿波、脉冲波和噪声波。
由于我一直使用常规 for 循环根据 ADSR 包络来更改声音的音量,因此运行 PlaySound 函数需要一段时间才能完成(当然取决于我的 ADSR 设置)。这促使我尝试使用线程。我不知道我是否以最好的方式使用它,或者我是否应该使用它,但这是我能想到的实现复调的唯一方法。否则它必须等到声音完成才能恢复主循环。所以现在我可以同时演奏几个音符。好吧,至少有两个注释。之后,它就会滞后,并且直到前面的声音之一结束后,第三个声音似乎才播放。
我已经做了一些测试和检查,我应该能够同时运行最多 4 个线程,但我可能会遗漏一些东西。一种猜测是系统本身预留了两个线程(核心)用于其他用途。
我还意识到 Python 并不是最有效的语言,我也一直在研究纯数据,但我很难理解它(我更喜欢代码而不是点击-拖动 GUI)。我想尽可能长时间地继续使用Python。我可能会考虑使用 pyo,但我认为我必须从头开始编写我的代码(我愿意这样做,但我还不想放弃当前的代码)。
所以。这是我的问题:如何优化它以使其成为真正的复调?两个笔记还不够。我应该完全跳过线程吗?我能否以更好、成本更低的方式实现 ADSR 包络?我怎样才能清理我凌乱的数学?还有哪些我忽略的其他性能瓶颈?目前,Pygame 绘制到屏幕上的效果似乎可以忽略不计,因为如果我完全禁用它,几乎没有任何区别。这是到目前为止我的代码:
import pygame
from pygame.mixer import Sound, get_init, pre_init, get_num_channels
from array import array
import RPi.GPIO as GPIO
import alsaaudio
import time
import Adafruit_Trellis
import Adafruit_MCP3008
import math
import _thread
import os
import multiprocessing
import numpy as np
from scipy import signal as sg
import struct
#print(str(multiprocessing.cpu_count()))
os.putenv('SDL_FBDEV','/dev/fb1')
fps = pygame.time.Clock()
FRAMERATE = 100
MINSEC = 1/FRAMERATE
BLUE = ( 0, 0, 255)
WHITE = (255, 255, 255)
DARKRED = (128, 0, 0)
DARKBLUE = ( 0, 0, 128)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
DARKGREEN = ( 0, 128, 0)
YELLOW = (255, 255, 0)
DARKYELLOW = (128, 128, 0)
BLACK = ( 0, 0, 0)
PTCH = [ 1.00, 1.059633027522936, 1.122324159021407, 1.18960244648318,
1.259938837920489, 1.335168195718654, 1.414067278287462,
1.498470948012232, 1.587767584097859, 1.681957186544343,
1.782262996941896, 1.888073394495413, 2.00 ]
FREQ = { # Parsed from http://www.phy.mtu.edu/~suits/notefreqs.html
'C0': 16.35, 'Cs0': 17.32, 'D0': 18.35, 'Ds0': 19.45, 'E0': 20.60,
'F0': 21.83, 'Fs0': 23.12, 'G0': 24.50, 'Gs0': 25.96, 'A0': 27.50,
'As0': 29.14, 'B0': 30.87, 'C1': 32.70, 'Cs1': 34.65, 'D1': 36.71,
'Ds1': 38.89, 'E1': 41.20, 'F1': 43.65, 'Fs1': 46.25, 'G1': 49.00,
'Gs1': 51.91, 'A1': 55.00, 'As1': 58.27, 'B1': 61.74, 'C2': 65.41,
'Cs2': 69.30, 'D2': 73.42, 'Ds2': 77.78, 'E2': 82.41, 'F2': 87.31,
'Fs2': 92.50, 'G2': 98.00, 'Gs2': 103.83, 'A2': 110.00, 'As2': 116.54,
'B2': 123.47, 'C3': 130.81, 'Cs3': 138.59, 'D3': 146.83, 'Ds3': 155.56,
'E3': 164.81, 'F3': 174.61, 'Fs3': 185.00, 'G3': 196.00, 'Gs3': 207.65,
'A3': 220.00, 'As3': 233.08, 'B3': 246.94, 'C4': 261.63, 'Cs4': 277.18,
'D4': 293.66, 'Ds4': 311.13, 'E4': 329.63, 'F4': 349.23, 'Fs4': 369.99,
'G4': 392.00, 'Gs4': 415.30, 'A4': 440.00, 'As4': 466.16, 'B4': 493.88,
'C5': 523.25, 'Cs5': 554.37, 'D5': 587.33, 'Ds5': 622.25, 'E5': 659.26,
'F5': 698.46, 'Fs5': 739.99, 'G5': 783.99, 'Gs5': 830.61, 'A5': 880.00,
'As5': 932.33, 'B5': 987.77, 'C6': 1046.50, 'Cs6': 1108.73, 'D6': 1174.66,
'Ds6': 1244.51, 'E6': 1318.51, 'F6': 1396.91, 'Fs6': 1479.98, 'G6': 1567.98,
'Gs6': 1661.22, 'A6': 1760.00, 'As6': 1864.66, 'B6': 1975.53, 'C7': 2093.00,
'Cs7': 2217.46, 'D7': 2349.32, 'Ds7': 2489.02, 'E7': 2637.02, 'F7': 2793.83,
'Fs7': 2959.96, 'G7': 3135.96, 'Gs7': 3322.44, 'A7': 3520.00,
'As7': 3729.31, 'B7': 3951.07,
'C8': 4186.01, 'Cs8': 4434.92, 'D8': 4698.64, 'Ds8': 4978.03,
}
buttons = ['A',PTCH[9],PTCH[10],PTCH[11],'B',PTCH[6],PTCH[7],PTCH[8],'C',PTCH[3],PTCH[4],PTCH[5],PTCH[12],PTCH[0],PTCH[1],PTCH[2] ]
octaves = { 'BASE':'0', 'A':'1', 'B':'2', 'C':'3', 'AB':'4', 'AC':'5', 'BC':'6', 'ABC':'7' }
class Note(pygame.mixer.Sound):
def __init__(self, frequency, volume=.1):
self.frequency = frequency
self.oktostop = False
Sound.__init__(self, self.build_samples())
self.set_volume(volume)
def playSound(self, Aval, Dval, Sval, Rval, Acurve, Dcurve, Shold, Rcurve, fps):
self.set_volume(0)
self.play(-1)
if Aval >= MINSEC:
Alength = round(Aval*FRAMERATE)
for num in range(0,Alength+1):
fps.tick_busy_loop(FRAMERATE)
volume = (Acurve[1]*pow(num*MINSEC,Acurve[0]))/100
self.set_volume(volume)
#print(fps.get_time()," ",str(volume))
else:
self.set_volume(100)
if Sval <= 1 and Sval > 0 and Dval >= MINSEC:
Dlength = round(Dval*FRAMERATE)
for num in range(0,Dlength+1):
fps.tick_busy_loop(FRAMERATE)
volume = (Dcurve[1]*pow(num*MINSEC,Dcurve[0])+100)/100
self.set_volume(volume)
#print(fps.get_time()," ",str(volume))
elif Sval <= 1 and Sval > 0 and Dval < MINSEC:
self.set_volume(Sval)
else:
self.set_volume(0)
if Shold >= MINSEC:
Slength = round(Shold*FRAMERATE)
for num in range(0,Slength+1):
fps.tick_busy_loop(FRAMERATE)
while True:
if self.oktostop:
if Sval > 0 and Rval >= MINSEC:
Rlength = round(Rval*FRAMERATE)
for num in range(0,Rlength+1):
fps.tick_busy_loop(FRAMERATE)
volume = (Rcurve[1]*pow(num*MINSEC,Rcurve[0])+(Sval*100))/100
self.set_volume(volume)
#print(fps.get_time()," ",str(volume))
self.stop()
break
def stopSound(self):
self.oktostop = True
def build_samples(self):
Fs = get_init()[0]
f = self.frequency
sample = Fs/f
x = np.arange(sample)
# Sine wave
#y = 0.5*np.sin(2*np.pi*f*x/Fs)
# Square wave
y = 0.5*sg.square(2*np.pi*f*x/Fs)
# Pulse wave
#sig = np.sin(2 * np.pi * x)
#y = 0.5*sg.square(2*np.pi*f*x/Fs, duty=(sig + 1)/2)
# Sawtooth wave
#y = 0.5*sg.sawtooth(2*np.pi*f*x/Fs)
# Triangle wave
#y = 0.5*sg.sawtooth(2*np.pi*f*x/Fs,0.5)
# White noise
#y = 0.5*np.random.uniform(-1.000,1.000,sample)
return y
pre_init(44100, -16, 2, 2048)
pygame.init()
screen = pygame.display.set_mode((480, 320))
pygame.mouse.set_visible(False)
CLK = 5
MISO = 6
MOSI = 13
CS = 12
mcp = Adafruit_MCP3008.MCP3008(clk=CLK, cs=CS, miso=MISO, mosi=MOSI)
Asec = 1.0
Dsec = 1.0
Ssec = 1.0
Rsec = 1.0
matrix0 = Adafruit_Trellis.Adafruit_Trellis()
trellis = Adafruit_Trellis.Adafruit_TrellisSet(matrix0)
NUMTRELLIS = 1
numKeys = NUMTRELLIS * 16
I2C_BUS = 1
trellis.begin((0x70, I2C_BUS))
# light up all the LEDs in order
for i in range(int(numKeys)):
trellis.setLED(i)
trellis.writeDisplay()
time.sleep(0.05)
# then turn them off
for i in range(int(numKeys)):
trellis.clrLED(i)
trellis.writeDisplay()
time.sleep(0.05)
posRecord = {'attack': [], 'decay': [], 'sustain': [], 'release': []}
octaval = {'A':False,'B':False,'C':False}
pitch = 0
tone = None
old_tone = None
note = None
volume = 0
#m = alsaaudio.Mixer('PCM')
#mastervol = m.getvolume()
sounds = {}
values = [0]*8
oldvalues = [0]*8
font = pygame.font.SysFont("comicsansms", 22)
while True:
fps.tick_busy_loop(FRAMERATE)
#print(fps.get_time())
update = False
#m.setvolume(int(round(MCP3008(4).value*100)))
#mastervol = m.getvolume()
values = [0]*8
for i in range(8):
# The read_adc function will get the value of the specified channel (0-7).
values[i] = mcp.read_adc(i)/1000
if values[i] >= 1:
values[i] = 1
# Print the ADC values.
#print('| {0:>4} | {1:>4} | {2:>4} | {3:>4} | {4:>4} | {5:>4} | {6:>4} | {7:>4} |'.format(*values))
#print(str(pygame.mixer.Channel(0).get_busy())+" "+str(pygame.mixer.Channel(1).get_busy())+" "+str(pygame.mixer.Channel(2).get_busy())+" "+str(pygame.mixer.Channel(3).get_busy())+" "+str(pygame.mixer.Channel(4).get_busy())+" "+str(pygame.mixer.Channel(5).get_busy())+" "+str(pygame.mixer.Channel(6).get_busy())+" "+str(pygame.mixer.Channel(7).get_busy()))
Sval = values[2]*Ssec
Aval = values[0]*Asec
if Sval == 1:
Dval = 0
else:
Dval = values[1]*Dsec
if Sval < MINSEC:
Rval = 0
else:
Rval = values[3]*Rsec
if Aval > 0:
if values[4] <= MINSEC: values[4] = MINSEC
Acurve = [round(values[4]*4,3),round(100/pow(Aval,(values[4]*4)),3)]
else:
Acurve = False
if Dval > 0:
if values[5] <= MINSEC: values[5] = MINSEC
Dcurve = [round(values[5]*4,3),round(((Sval*100)-100)/pow(Dval,(values[5]*4)),3)]
else:
Dcurve = False
Shold = values[6]*4*Ssec
if Rval > 0 and Sval > 0:
if values[7] <= MINSEC: values[7] = MINSEC
Rcurve = [round(values[7]*4,3),round(-Sval*100/pow(Rval,(values[7]*4)),3)]
else:
Rcurve = False
if update:
screen.fill((0, 0, 0))
scrnvals = ["A: "+str(round(Aval,2))+"s","D: "+str(round(Dval,2))+"s","S: "+str(round(Sval,2)),"R: "+str(round(Rval,2))+"s","H: "+str(round(Shold,2))+"s","ENV: "+str(round(Aval,2)+round(Dval,2)+round(Shold,2)+round(Rval,2))+"s"]
for line in range(len(scrnvals)):
text = font.render(scrnvals[line], True, (0, 128, 0))
screen.blit(text,(60*line+40, 250))
# Width of one second in number of pixels
ASCALE = 20
DSCALE = 20
SSCALE = 20
RSCALE = 20
if Aval >= MINSEC:
if Aval <= 1:
ASCALE = 80
else:
ASCALE = 20
# Attack
for yPos in range(0,101):
xPos = round(pow((yPos/Acurve[1]),(1/Acurve[0]))*ASCALE)
posRecord['attack'].append((int(xPos) + 40, int(-yPos) + 130))
if len(posRecord['attack']) > 1:
pygame.draw.lines(screen, DARKRED, False, posRecord['attack'], 2)
if Dval >= MINSEC:
if Dval <= 1:
DSCALE = 80
else:
DSCALE = 20
# Decay
for yPos in range(100,round(Sval*100)-1,-1):
xPos = round(pow(((yPos-100)/Dcurve[1]),(1/Dcurve[0]))*DSCALE)
#print(str(yPos)+" = "+str(Dcurve[1])+"*"+str(xPos)+"^"+str(Dcurve[0])+"+100")
posRecord['decay'].append((int(xPos) + 40 + round(Aval*ASCALE), int(-yPos) + 130))
if len(posRecord['decay']) > 1:
pygame.draw.lines(screen, DARKGREEN, False, posRecord['decay'], 2)
# Sustain
if Shold >= MINSEC:
for xPos in range(0,round(Shold*SSCALE)):
posRecord['sustain'].append((int(xPos) + 40 + round(Aval*ASCALE) + round(Dval*DSCALE), int(100-Sval*100) + 30))
if len(posRecord['sustain']) > 1:
pygame.draw.lines(screen, DARKYELLOW, False, posRecord['sustain'], 2)
if Rval >= MINSEC:
if Rval <= 1:
RSCALE = 80
else:
RSCALE = 20
# Release
for yPos in range(round(Sval*100),-1,-1):
xPos = round(pow(((yPos-round(Sval*100))/Rcurve[1]),(1/Rcurve[0]))*RSCALE)
#print(str(xPos)+" = (("+str(yPos)+"-"+str(round(Sval*100))+")/"+str(Rcurve[1])+")^(1/"+str(Rcurve[0])+")")
posRecord['release'].append((int(xPos) + 40 + round(Aval*ASCALE) + round(Dval*DSCALE) + round(Shold*SSCALE), int(-yPos) + 130))
if len(posRecord['release']) > 1:
pygame.draw.lines(screen, DARKBLUE, False, posRecord['release'], 2)
posRecord = {'attack': [], 'decay': [], 'sustain': [], 'release': []}
pygame.display.update()
tone = None
pitch = 0
time.sleep(MINSEC)
# If a button was just pressed or released...
if trellis.readSwitches():
# go through every button
for i in range(numKeys):
# if it was pressed, turn it on
if trellis.justPressed(i):
print('v{0}'.format(i))
trellis.setLED(i)
if i == 0:
octaval['A'] = True
elif i == 4:
octaval['B'] = True
elif i == 8:
octaval['C'] = True
else:
pitch = buttons[i]
button = i
# if it was released, turn it off
if trellis.justReleased(i):
print('^{0}'.format(i))
trellis.clrLED(i)
if i == 0:
octaval['A'] = False
elif i == 4:
octaval['B'] = False
elif i == 8:
octaval['C'] = False
else:
sounds[i].stopSound()
# tell the trellis to set the LEDs we requested
trellis.writeDisplay()
octa = ''
if octaval['A']:
octa += 'A'
if octaval['B']:
octa += 'B'
if octaval['C']:
octa += 'C'
if octa == '':
octa = 'BASE'
if pitch > 0:
tone = FREQ['C0']*pow(2,int(octaves[octa]))*pitch
if tone:
sounds[button] = Note(tone)
_thread.start_new_thread(sounds[button].playSound,(Aval, Dval, Sval, Rval, Acurve, Dcurve, Shold, Rcurve, fps))
print(str(tone))
GPIO.cleanup()
最佳答案
你现在正在做的就是发出声音并放弃所有控制,直到声音播放完毕。这里的一般方法是更改它并一次处理一个样本并将其推送到定期播放的缓冲区。该样本将是您所有声音/信号的总和。这样,您可以为每个样本决定是否要触发新的声音,并且您可以决定在已经播放的音符时播放该音符多长时间。一种方法是安装一个计时器,如果您想要 48kHz 的采样率,该计时器每 1/48000 秒触发一次回调函数。
如果您需要处理大量声音,但不是一个线程处理一个声音,您仍然可以使用多线程进行并行处理,在我看来,这有点过分了。是否有必要取决于您进行了多少过滤/处理以及您的程序的有效/无效程度。
例如
sample_counter = 0
output_buffer = list()
def callback_fct():
pitch_0 = 2
pitch_1 = 4
sample_counter += 1 #time in ms
signal_0 = waveform(sample_counter * pitch_0)
signal_1 = waveform(sample_counter * pitch_1)
signal_out = signal_0 * 0.5 + signal_1 *0.5
output_buffer.append(signal_out)
return 0
if __name__ == "__main__":
call_this_function_every_ms(callback_fct)
play_sound_from_outputbuffer() #plays sound from outputbuffer by popping samples from the beginning of the list.
类似这样的事情。 waveform() 函数会根据实际时间乘以所需的音调为您提供样本值。在C中,你可以用指针来完成所有这些操作,在波表末尾溢出,所以当你应该重置你的sample_counter而不会在波形中出现毛刺时,你不必处理这个问题(它会变得非常大)很快)。但我确信,还有更多“Pythonic”的方法。使用较低级语言执行此操作的另一个充分理由是速度。一旦涉及到真正的 DSP,您就会计算处理器时钟周期。到那时,Python 可能会有太多的开销。
关于python - 在 Raspberry Pi 上优化 Python 合成器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45183844/
我正在处理一组标记为 160 个组的 173k 点。我想通过合并最接近的(到 9 或 10 个组)来减少组/集群的数量。我搜索过 sklearn 或类似的库,但没有成功。 我猜它只是通过 knn 聚类
我有一个扁平数字列表,这些数字逻辑上以 3 为一组,其中每个三元组是 (number, __ignored, flag[0 or 1]),例如: [7,56,1, 8,0,0, 2,0,0, 6,1,
我正在使用 pipenv 来管理我的包。我想编写一个 python 脚本来调用另一个使用不同虚拟环境(VE)的 python 脚本。 如何运行使用 VE1 的 python 脚本 1 并调用另一个 p
假设我有一个文件 script.py 位于 path = "foo/bar/script.py"。我正在寻找一种在 Python 中通过函数 execute_script() 从我的主要 Python
这听起来像是谜语或笑话,但实际上我还没有找到这个问题的答案。 问题到底是什么? 我想运行 2 个脚本。在第一个脚本中,我调用另一个脚本,但我希望它们继续并行,而不是在两个单独的线程中。主要是我不希望第
我有一个带有 python 2.5.5 的软件。我想发送一个命令,该命令将在 python 2.7.5 中启动一个脚本,然后继续执行该脚本。 我试过用 #!python2.7.5 和http://re
我在 python 命令行(使用 python 2.7)中,并尝试运行 Python 脚本。我的操作系统是 Windows 7。我已将我的目录设置为包含我所有脚本的文件夹,使用: os.chdir("
剧透:部分解决(见最后)。 以下是使用 Python 嵌入的代码示例: #include int main(int argc, char** argv) { Py_SetPythonHome
假设我有以下列表,对应于及时的股票价格: prices = [1, 3, 7, 10, 9, 8, 5, 3, 6, 8, 12, 9, 6, 10, 13, 8, 4, 11] 我想确定以下总体上最
所以我试图在选择某个单选按钮时更改此框架的背景。 我的框架位于一个类中,并且单选按钮的功能位于该类之外。 (这样我就可以在所有其他框架上调用它们。) 问题是每当我选择单选按钮时都会出现以下错误: co
我正在尝试将字符串与 python 中的正则表达式进行比较,如下所示, #!/usr/bin/env python3 import re str1 = "Expecting property name
考虑以下原型(prototype) Boost.Python 模块,该模块从单独的 C++ 头文件中引入类“D”。 /* file: a/b.cpp */ BOOST_PYTHON_MODULE(c)
如何编写一个程序来“识别函数调用的行号?” python 检查模块提供了定位行号的选项,但是, def di(): return inspect.currentframe().f_back.f_l
我已经使用 macports 安装了 Python 2.7,并且由于我的 $PATH 变量,这就是我输入 $ python 时得到的变量。然而,virtualenv 默认使用 Python 2.6,除
我只想问如何加快 python 上的 re.search 速度。 我有一个很长的字符串行,长度为 176861(即带有一些符号的字母数字字符),我使用此函数测试了该行以进行研究: def getExe
list1= [u'%app%%General%%Council%', u'%people%', u'%people%%Regional%%Council%%Mandate%', u'%ppp%%Ge
这个问题在这里已经有了答案: Is it Pythonic to use list comprehensions for just side effects? (7 个答案) 关闭 4 个月前。 告
我想用 Python 将两个列表组合成一个列表,方法如下: a = [1,1,1,2,2,2,3,3,3,3] b= ["Sun", "is", "bright", "June","and" ,"Ju
我正在运行带有最新 Boost 发行版 (1.55.0) 的 Mac OS X 10.8.4 (Darwin 12.4.0)。我正在按照说明 here构建包含在我的发行版中的教程 Boost-Pyth
学习 Python,我正在尝试制作一个没有任何第 3 方库的网络抓取工具,这样过程对我来说并没有简化,而且我知道我在做什么。我浏览了一些在线资源,但所有这些都让我对某些事情感到困惑。 html 看起来
我是一名优秀的程序员,十分优秀!