python - 在 PyGame、Python 3 上演奏和弦

标签 python python-3.x audio pygame

我目前正在 Python 3 上开发一个音乐播放程序。我设法用下面的代码让它播放单个音符(它被撕掉了音高完美 0.3.2):

import pygame, time
import numpy as np

notes_dct = {
        'c': -9.0, 'c#': -8.0, 'db': -8.0, 'd': -7.0, 'd#': -6.0, 'eb': -6.0,
        'e': -5.0, 'f': -4.0, 'f#': -3.0, 'gb': -3.0, 'g': -2.0, 'g#': -1.0,
        'ab': -1.0, 'a': 0.0, 'a#': 1.0, 'bb': 1.0, 'b': 2.0,
        }

def getExponent(note):
    """ Returns a float needed to obtain the frequency in Hz based on
        'note', which is a string with note name defaulting to 'A', and
        an optional trailing octave value, defaulting to 4; each octave
        begins at the C tone.

        Examples:
            # np is short for numpy
            GetExponent('A4') returns a value 'v' where
                2 ** (np.log2(440) + v) == 440.0  # in Hz

            GetExponent('C') (or C4) returns v where
                2 ** (np.log2(440) + v) == 261.6  # approximate;
                                                  # note that C4 is below A4

            GetExponent('Gb-1') (or G flat, octave -1) returns v where
                2 ** (np.log2(440) + v) == 11.6  # below usual hearing range
    """

    i = 0
    while i < len(note) and note[i] not in '1234567890-':
        i += 1

    if i == 0:
        name = 'a'
    else:
        name = note[: i].lower()

    if i == len(note):
        octave = 4
    else:
        octave = int(note[i: ])

    return notes_dct[name] / 12.0 + octave - 4


def generateTone(freq=440.0, vol=1.0, shape='sine'):
    """ GenerateTone( shape='sine', freq=440.0, vol=1.0 )
            returns pygame.mixer.Sound object

        shape:  string designating waveform type returned; one of
                'sine', 'sawtooth', or 'square'
        freq:  frequency; can be passed in as int or float (in Hz),
               or a string (see GetExponent documentation above for
               string usage)
        vol:  relative volume of returned sound; will be clipped into
              range 0.0 to 1.0
    """

    # Get playback values that mixer was initialized with.
    (pb_freq, pb_bits, pb_chns) = pygame.mixer.get_init()

    if type(freq) == str:
        # Set freq to frequency in Hz; GetExponent(freq) is exponential
        # difference from the exponent of note A4: log2(440.0).
        freq = 2.0 ** (np.log2(440.0) + getExponent(freq))

    # Clip range of volume.
    vol = np.clip(vol, 0.0, 1.0)

    # multiplier and length pan out the size of the sample to help
    # keep the mixer busy between calls to channel.queue()
    multiplier = int(freq / 24.0)
    length = max(1, int(float(pb_freq) / freq * multiplier))
    # Create a one-dimensional array with linear values.
    lin = np.linspace(0.0, multiplier, num=length, endpoint=False)
    if shape == 'sine':
        # Apply a sine wave to lin.
        ary = np.sin(lin * 2.0 * np.pi)
    elif shape == 'sawtooth':
        # sawtooth keeps the linear shape in a modded fashion.
        ary = 2.0 * ((lin + 0.5) % 1.0) - 1.0
    elif shape == 'square':
        # round off lin and adjust to alternate between -1 and +1.
        ary = 1.0 - np.round(lin % 1.0) * 2.0
    else:
        print("shape param should be one of 'sine', 'sawtooth', 'square'.")
        print()
        return None

    # If mixer is in stereo mode, double up the array information for
    # each channel.
    if pb_chns == 2:
        ary = np.repeat(ary[..., np.newaxis], 2, axis=1)

    if pb_bits == 8:
        # Adjust for volume and 8-bit range.
        snd_ary = ary * vol * 127.0
        return pygame.sndarray.make_sound(snd_ary.astype(np.uint8) + 128)
    elif pb_bits == -16:
        # Adjust for 16-bit range.
        snd_ary = ary * vol * float((1 << 15) - 1)
        return pygame.sndarray.make_sound(snd_ary.astype(np.int16))
    else:
        print("pygame.mixer playback bit-size unsupported.")
        print("Should be either 8 or -16.")
        print()
        return None

但是我在让程序同时播放多个音符(一个和弦)时遇到了问题。同时运行多个 generateTone 是一次难忘的经历,所以我在网上找到了这段代码:

import math
import wave
import struct
def synthComplex(freq=[440],coef=[1], datasize=10000, fname="test.wav"):
    frate = 44100.00  
    amp=8000.0 
    sine_list=[]
    for x in range(datasize):
        samp = 0
        for k in range(len(freq)):
            samp = samp + coef[k] * math.sin(2*math.pi*freq[k]*(x/frate))
        sine_list.append(samp)
    wav_file=wave.open(fname,"w")
    nchannels = 1
    sampwidth = 2
    framerate = int(frate)
    nframes=datasize
    comptype= "NONE"
    compname= "not compressed"
    wav_file.setparams((nchannels, sampwidth, framerate, nframes, comptype, compname))
    for s in sine_list:
        wav_file.writeframes(struct.pack('h', int(s*amp/2)))
    wav_file.close()

然后我可以用 winsoundpygame 播放声音文件。然而,编译声音文件需要大约一秒钟的时间(这对我来说太长了),而且制作几千个预制声音文件似乎相当低效。

有没有简单的方法可以解决这个问题?

在此先感谢您的帮助!

编辑:

我试过这样做:

pygame.mixer.init(frequency=22050,size=-16,channels=4)
chan1 = pygame.mixer.Channel(0)
chan1.play(generateTone('C4'), 10)
chan2 = pygame.mixer.Channel(1)
chan2.play(generateTone('G5'), 10)

但这与简单地玩游戏的效果相同:

generateTone('C4').play(10)
generateTone('G5').play(10)

chan1.play 更改为 chan1.queue 或将 chan1 = pygame.mixer.Channel(0) 更改为 chan1 = pygame.mixer.find_channel() 没有改变任何东西。

最佳答案

我设法用 pygame.midi 模块解决了这个问题:

import pygame.midi
import time

pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(0)
player.note_on(60, 127)
player.note_on(64, 127)
player.note_on(67, 127)
time.sleep(1)
player.note_off(60, 127)
player.note_off(64, 127)
player.note_off(67, 127)
del player
pygame.midi.quit()

关于python - 在 PyGame、Python 3 上演奏和弦,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35953399/

相关文章:

python - wxgrid 单元格渲染器设置列大小

python - Flask, flask 登录 - 我不明白

python - 在 QTableWidget PyQt5 中突出显示单元格

python - 有人可以帮助我理解这个埃拉托色尼筛法脚本吗?最后几行让我难过

java - 如何用Java实现Karplus-Strong算法?

c# - 如何获得默认音频设备?

python - set_contents_from_string 在 S3 中上传时出现 AWS lambda_handler 错误

python - django:胖模型和瘦 Controller ?

python - 语法错误: 'await' outside function

javascript - jQuery:支持几乎所有格式的音频播放器