在PyGame上播放和弦,Python 3

时间:2016-03-12 03:27:17

标签: 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()并未改变任何内容。

1 个答案:

答案 0 :(得分:1)

我设法用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()