如果想把我的帽子扔进擂台,我建议结合使用PyOpenGL(创建你的圆圈/球体并制作精美的实时 2D/3D 图形)和PyQt(创建一个 OpenGL 上下文容纳您的可视化,并提供输入用户界面)。 请注意,如果您选择走这条路,虽然这将非常灵活,但请准备好从头开始编写大量的东西。您可能还可以看看这两个库的现有组合: @Jeronimo 在 cmets 中建议,PyQtGraph,这可能更直接。
让我们从可视化技术中退后一步,从数学上思考如何我们将在屏幕上绘制它。从您的问题来看,您的目标如下:
我想在屏幕上查看各个频率 F1 到 Fn 以及由此产生的拍频在时空中的移动。
这有点模糊,您应该在您的问题中明确您期望绘制的内容。为了我自己的理智,让我们看一下具有可变半径的圆的“更简单”情况。让圆圈有一个radius r,它的幅度可以变化+/- a。这种变化可以向我们展示波形。从 0 到 2*pi 弧度开始,逆时针绕圆的周长,我们可以沿着圆的周长模拟时间范围 t0 到 t1。然后我们可以实时增加t0(和t1)的值,改变我们的模拟值范围。
玩弄this demo 我看到的一个问题是,只需“环绕”你的线条并创建一个完美的ouroboros,就是在环绕点处会有不连续性,除非你改变周长,这样它就可以完全被你的拍频周期整除。
此外,我得到的结果(见下面的代码)看起来相当令人失望。为什么?增加时间会使圆圈旋转(把它想象成我们的波只是沿着圆圈的表面传播)。无论如何,这就是(凌乱的)代码的样子;希望它能提供一些关于如何从头开始创建自己的可视化的想法:
from OpenGL.GL import *
from OpenGL.GLU import *
from PyQt4 import QtGui
from PyQt4.QtCore import QTimer
from PyQt4.QtGui import QColor, QStatusBar, QSizePolicy
from PyQt4.QtOpenGL import *
import math
import numpy as np
import sys
import time
class Waveform(object):
"""
Defines a class Waveform where waves are of the form: y = sin(2*pi*f*t).
"""
def __init__(self, frequency):
self.f = frequency
def calc_points(self, t0, t1, n):
"""
Samples at n points from [t0, t1] and calculates the appropriate amplitude "a" value.
Returns the data as a list of tuples.
"""
data = []
for i in range(n):
frac = (t1 - t0)/(n - 1)
t = i * frac + t0
a = math.sin(2 * math.pi * self.f * t)
data.append((i, t, a))
return data
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.widget = GLWidget(self)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.widget)
layout.setContentsMargins(5, 5, 5, 5)
self.setLayout(layout)
class GLWidget(QGLWidget):
def __init__(self, parent):
QGLWidget.__init__(self, parent)
self.setMinimumSize(800, 600)
self.start_time = time.time()
self.timer = QTimer()
self.timer.timeout.connect(self.update)
self.timer.start(1000/60)#so-called "60 fps"
def initializeGL(self):
glClearColor(0, 0, 0, 1)
glClearDepth(1.0)
glEnable(GL_DEPTH_TEST)
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, 0, height, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def paintGL(self):#Important part of the code (circle drawing stuff) here!
curr_time = time.time()
delta = (curr_time - self.start_time)
t0, t1 = 0 + delta, 0.05 + delta
f1 = 500
f2 = 490
num_slices = 1001
radius = 200
amplitude = 5
a = Waveform(f1)
b = Waveform(f2)
data1 = a.calc_points(t0, t1, num_slices)
data2 = b.calc_points(t0, t1, num_slices)
w, h = self.width(), self.height()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glBegin(GL_TRIANGLE_FAN)
glColor(1.0, 0.0, 0.0, 1.0)
glVertex3f(w/2.0, h/2.0, 0)
for point in range(len(data1)):
i1, p1, a1 = data1[point]
i2, p2, a2 = data2[point]
t = p1
a = a1 + a2#adding the waveforms together
frac = (t - t0)/(t1 - t0)
angle = 2 * math.pi * frac
r = radius + amplitude * a
x = r * math.cos(angle) + (w/2)
y = r * math.sin(angle) + (h/2)
glVertex3f(x, y, 0)
glEnd()
def update(self):
self.updateGL()#seems odd, but calling this causes a paintGL() call behind the scenes
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.setWindowTitle("Beat Frequency App")
window.show()
app.exec_()