在 Python 中播放期间更改音频的音高(和速度)

发布于 2024-09-28 01:34:42 字数 403 浏览 3 评论 0原文

我正在开发一个播放音乐的 Python 程序。其中一项功能是一个滑块,用户可以向上或向下拖动滑块来改变音乐播放时的音高。

例如,如果音高设置为 2,那么音乐听起来会高一个八度,播放速度会加快两倍,持续时间也会缩短一半。我真正改变的是播放速度,但我需要实时交互地进行。

此处可以找到在 Flash 中实现此功能的一个很好的示例。 (加载需要一点时间,请耐心等待。)

我已经研究了许多 python 音频包,但我还没有找到一个可以改变当前正在播放的声音的音调的包。我有多个版本的Python,所以对包支持什么版本没有要求。我正在 Windows 7 上开发这个。

有什么建议吗?

I'm working on a Python program that plays music. One feature will be a slider that the user can drag up or down to change the pitch of the music as it plays.

For example, if the pitch is set to 2, then the music will sound one octave higher, it will play twice as fast, and it will last half as long. All I'm really changing is the playback speed, but I need to do so interactively in real-time.

A good example of this functionality implemented in flash can be found here. (It takes a little bit to load, be patient.)

I've looked into many python audio packages, but I haven't found one that can change the pitch of a sound that is currently playing. I have multiple versions of Python, so there is no requirement for what version the package supports. I'm developing this on Windows 7.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

飞烟轻若梦 2024-10-05 01:34:42

Craig McQueen 的帮助下,我创建了一个概念验证程序。

该程序播放一个名为“music.wav”的单声道 wav 文件(位于与该程序相同的文件夹中)并显示一个短而宽的窗口。当您在窗口中单击并拖动时,音乐的音调会发生变化。窗口的左侧低两个八度,右侧高两个八度。

这里有一些奇怪的行为,我不知道如何解决。如果当前音调较低,则音调改变之前会有大约 2 秒的延迟。然而,高音的音高会实时变化。 (随着音调变低,延迟会平滑增加)。如果 soundOutput.getLeft() soundOutput.getLeft() ,我只会向缓冲区添加更多声音。 0.2 。也就是说,如果缓冲区中剩余的声音量小于0.2秒。因此,不应有任何延误。为了进行故障排除,我添加了将 soundOutput.getLeft() 写入文件的代码。它往往始终保持在或非常接近 0。

减少读取到 waveRead.readframes(100) 的帧会减少延迟,但也会使声音断断续续。增加读取的帧会显着增加延迟。

import os, sys, wave, pygame, numpy, pymedia.audio.sound, scikits.samplerate

class Window:
    def __init__(self, width, height, minOctave, maxOctave):
        """
        width, height: the width and height of the screen.
        minOctave, maxOctave: the highest and lowest pitch changes. 0 is no change.
        """
        self.minOctave = minOctave
        self.maxOctave = maxOctave
        self.width = width
        self.mouseDown = False
        self.ratio = 1.0 # The resampling ratio
        waveRead = wave.open(os.path.join(sys.path[0], "music.wav"), 'rb')
        sampleRate = waveRead.getframerate()
        channels = waveRead.getnchannels()
        soundFormat = pymedia.audio.sound.AFMT_S16_LE
        soundOutput = pymedia.audio.sound.Output(sampleRate, channels, soundFormat)
        pygame.init()
        screen = pygame.display.set_mode((width, height), 0)
        screen.fill((255, 255, 255))
        pygame.display.flip()
        fout = open(os.path.join(sys.path[0], "musicdata.txt"), 'w') # For troubleshooting
        byteString = waveRead.readframes(1000) # Read at most 1000 samples from the file.
        while len(byteString) != 0:
            self.handleEvent(pygame.event.poll()) # This does not wait for an event.
            fout.write(str(soundOutput.getLeft()) + "\n") # For troubleshooting
            if soundOutput.getLeft() < 0.2: # If there is less than 0.2 seconds left in the sound buffer.
                array = numpy.fromstring(byteString, dtype=numpy.int16)
                byteString = scikits.samplerate.resample(array, self.ratio, "sinc_fastest").astype(numpy.int16).tostring()
                soundOutput.play(byteString)
                byteString = waveRead.readframes(500) # Read at most 500 samples from the file.
        waveRead.close()
        return

    def handleEvent(self, event):
        if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
            sys.exit()
        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            self.mouseDown = True
            self.setRatio(event.pos)
        if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
            self.mouseDown = False
        if event.type == pygame.MOUSEMOTION and self.mouseDown:
            self.setRatio(event.pos)
        return None

    def setRatio(self, point):
        self.ratio = 2 ** -(self.minOctave + point[0] * (self.maxOctave - self.minOctave) / float(self.width))
        print(self.ratio)

def main():
    Window(768, 100, -2.0, 2.0)

if __name__ == '__main__':
    main()

尝试让我使用的所有软件包都能很好地协同工作是一件很痛苦的事情。我正在使用 Python 2.6.6PyGame 1.9.1 for python 2.6, NumPy 1.3.0 for python 2.6, PyMedia 1.3.7.3 适用于 python 2.6,以及 scikits.samplerate 0.3.1 适用于蟒蛇2.6。请注意,scikits.samplerate 与 NumPy 1.4 或更高版本冲突,并且其中一个包(我忘记是哪一个)需要 安装工具

With Craig McQueen's help, I have created a proof-of-concept program.

This program plays a mono wav file called "music.wav" (located in the same folder as the program) and displays a short and wide window. The pitch of the music changes when you click and drag in the window. The left side of the window is two octaves lower, and the right side is two octaves higher.

There is some strange behavior here that I'm not sure how to fix. If the pitch is currently low, then there's about a 2 second delay before the pitch changes. However, the pitch changes in real-time for high pitches. (The delay increases smoothly as the pitch gets lower). I only add more sound to the buffer if soundOutput.getLeft() < 0.2. That is to say, if the amount of sound left on the buffer is less than 0.2 seconds. Therefore there should be no delay. For troubleshooting, I included code that writes soundOutput.getLeft() to a file. It tends to stay at or very near 0 all the time.

Decreasing the frames read to waveRead.readframes(100) decreases the delay, but also makes the sound choppy. Increasing the frames read significantly increases the delay.

import os, sys, wave, pygame, numpy, pymedia.audio.sound, scikits.samplerate

class Window:
    def __init__(self, width, height, minOctave, maxOctave):
        """
        width, height: the width and height of the screen.
        minOctave, maxOctave: the highest and lowest pitch changes. 0 is no change.
        """
        self.minOctave = minOctave
        self.maxOctave = maxOctave
        self.width = width
        self.mouseDown = False
        self.ratio = 1.0 # The resampling ratio
        waveRead = wave.open(os.path.join(sys.path[0], "music.wav"), 'rb')
        sampleRate = waveRead.getframerate()
        channels = waveRead.getnchannels()
        soundFormat = pymedia.audio.sound.AFMT_S16_LE
        soundOutput = pymedia.audio.sound.Output(sampleRate, channels, soundFormat)
        pygame.init()
        screen = pygame.display.set_mode((width, height), 0)
        screen.fill((255, 255, 255))
        pygame.display.flip()
        fout = open(os.path.join(sys.path[0], "musicdata.txt"), 'w') # For troubleshooting
        byteString = waveRead.readframes(1000) # Read at most 1000 samples from the file.
        while len(byteString) != 0:
            self.handleEvent(pygame.event.poll()) # This does not wait for an event.
            fout.write(str(soundOutput.getLeft()) + "\n") # For troubleshooting
            if soundOutput.getLeft() < 0.2: # If there is less than 0.2 seconds left in the sound buffer.
                array = numpy.fromstring(byteString, dtype=numpy.int16)
                byteString = scikits.samplerate.resample(array, self.ratio, "sinc_fastest").astype(numpy.int16).tostring()
                soundOutput.play(byteString)
                byteString = waveRead.readframes(500) # Read at most 500 samples from the file.
        waveRead.close()
        return

    def handleEvent(self, event):
        if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
            sys.exit()
        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            self.mouseDown = True
            self.setRatio(event.pos)
        if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
            self.mouseDown = False
        if event.type == pygame.MOUSEMOTION and self.mouseDown:
            self.setRatio(event.pos)
        return None

    def setRatio(self, point):
        self.ratio = 2 ** -(self.minOctave + point[0] * (self.maxOctave - self.minOctave) / float(self.width))
        print(self.ratio)

def main():
    Window(768, 100, -2.0, 2.0)

if __name__ == '__main__':
    main()

It's a pain to try to get all the packages I use to work well together. I'm using Python 2.6.6, PyGame 1.9.1 for python 2.6, NumPy 1.3.0 for python 2.6, PyMedia 1.3.7.3 for python 2.6, and scikits.samplerate 0.3.1 for python 2.6. Note that scikits.samplerate conflicts with NumPy 1.4 or greater, and one of the packages (I forget which one) requires setuptools

享受孤独 2024-10-05 01:34:42

听起来好像您想即时重新采样音频。

也许您可以尝试使用 scikits.samplerate 模块。它使用Secret Rabbit Code 库

It sounds as though you want to resample the audio on-the-fly.

Perhaps you could try using the scikits.samplerate module. It uses the Secret Rabbit Code library.

月寒剑心 2024-10-05 01:34:42

您可能想看看使用 wxPython创建一个媒体播放器,并调查 SetPlaybackRate() 函数。 wxWidget 文档

并非所有平台都支持 SetPlaybackRate() 函数,我自己也没有尝试过它是否完全符合您的要求,以及它的效果如何。

You might want to look at using wxPython to create a media player, and investigate the SetPlaybackRate() function. wxWidget docs here.

That SetPlaybackRate() function is not supported on all platforms, and I've not tried it myself to see whether it does exactly what you want, and how well it works or not.

靖瑶 2024-10-05 01:34:42

scikits.samplerate 0.3.1 需要安装工具,

如果不这样做,您将不断收到错误 ImportError: No module named pkg_resources

setup tools is required for scikits.samplerate 0.3.1

if you dont do that you will keep getting an error ImportError: No module named pkg_resources

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文