我目前正在制作一个关于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()
然后我可以使用winsound
或pygame
播放声音文件。但是,编辑声音文件需要大约一秒钟(这对我来说太长了),并且制作数千个预先制作的声音文件似乎相当无效。
我有一个简单的方法来解决这个问题吗?
提前感谢您的帮助!
编辑:
我尝试过这样的事情:
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()
并未改变任何内容。
答案 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()