第一问
Use the techniques you learned in this chapter to create a method that replicates the sound of two strings of different frequencies vibrating together. Remember, the Karplus-Strong algorithm produces sound amplitudes that can be added together (before scaling to 16-bit values for WAV file creation). Now add a time delay between the first and second string plucks.
import math
import wave
import numpy as np
def together(a, b, t=0):
sRate = 44100
nSamples = sRate * 5
x = np.arange(nSamples)/float(sRate)
vals1 = np.sin(2.0*math.pi*a*x + t)
vals2 = np.sin(2.0*math.pi*b*x)
vals = vals1 + vals2
data = np.array(vals * 32767, 'int16').tobytes()
file = wave.open('together.wav', 'wb')
file.setparams((1, 2, sRate, nSamples, 'NONE', 'uncompressed'))
file.writeframes(data)
file.close()
if __name__ == '__main__':
together(200, 400, 0.01)
第二问
- Write a method to read music from a text file and generate musical notes. Then play the music using these notes. You can use a format where the note names are followed by integer rest time intervals, like this: C4 1 F4 2 G4 1 . . . .
import random
import time
import pygame
# play a wav file
class NotePlayer:
# constr
def __init__(self):
pygame.mixer.pre_init(44100, -16, 1, 2048)
pygame.init()
# dictionary of notes
self.notes = {}
# add a note
def add(self, fileName):
self.notes[fileName] = pygame.mixer.Sound(fileName)
# play a note
def play(self, fileName):
try:
self.notes[fileName].play()
except:
print(fileName + ' not found!')
def playRandom(self):
"""play a random note"""
index = random.randint(0, len(self.notes) - 1)
note = list(self.notes.values())[index]
note.play()
if __name__ == '__main__':
# create note player
nplayer = NotePlayer()
# piano C4-E(b)-F-G-B(b)-C5
pmNotes = {'C': 262, 'E': 311, 'F': 349, 'G': 391, 'B': 466}
for name, freq in list(pmNotes.items()):
nplayer.add(name + '.wav')
fp = open("music.txt")
wav = []
rest = []
while True:
eof = fp.read(1)
if eof == '':
break
wav.append(eof)
eof = fp.read(1)
if eof == '':
break
rest.append(int(eof))
fp.close()
for i in range(len(wav)):
nplayer.play(wav[i] + '.wav')
time.sleep(0.25 * rest[i])
第三问
- Add a --piano command line option to the project. When the project is run with this option, the user should be able to press the A, S, D, F, and G keys on a keyboard to play the five musical notes. (Hint: use pygame.event.get and pygame.event.type.)
import sys, os
import time, random
import wave, argparse, pygame
import numpy as np
from collections import deque
from matplotlib import pyplot as plt
# show plot of algorithm in action?
gShowPlot = False
# notes of a Pentatonic Minor scale
# piano C4-E(b)-F-G-B(b)-C5
pmNotes = {'C4': 262, 'Eb': 311, 'F': 349, 'G':391, 'Bb':466}
# write out WAVE file
def writeWAVE(fname, data):
# open file
file = wave.open(fname, 'wb')
# WAV file parameters
nChannels = 1
sampleWidth = 2
frameRate = 44100
nFrames = 44100
# set parameters
file.setparams((nChannels, sampleWidth, frameRate, nFrames,
'NONE', 'noncompressed'))
file.writeframes(data)
file.close()
# generate note of given frequency
def generateNote(freq):
nSamples = 44100
sampleRate = 44100
N = int(sampleRate/freq)
# initialize ring buffer
buf = deque([random.random() - 0.5 for i in range(N)])
# plot of flag set
if gShowPlot:
axline, = plt.plot(buf)
# init sample buffer
samples = np.array([0]*nSamples, 'float32')
for i in range(nSamples):
samples[i] = buf[0]
avg = 0.995*0.5*(buf[0] + buf[1])
buf.append(avg)
buf.popleft()
# plot of flag set
if gShowPlot:
if i % 1000 == 0:
axline.set_ydata(buf)
plt.draw()
# samples to 16-bit to string
# max value is 32767 for 16-bit
samples = np.array(samples * 32767, 'int16')
return samples.tobytes()
# play a wav file
class NotePlayer:
# constr
def __init__(self):
pygame.mixer.pre_init(44100, -16, 1, 2048)
pygame.init()
# dictionary of notes
self.notes = {}
# add a note
def add(self, fileName):
self.notes[fileName] = pygame.mixer.Sound(fileName)
# play a note
def play(self, fileName):
try:
self.notes[fileName].play()
except:
print(fileName + ' not found!')
def playRandom(self):
"""play a random note"""
index = random.randint(0, len(self.notes)-1)
note = list(self.notes.values())[index]
note.play()
# main() function
def main():
# declare global var
global gShowPlot
parser = argparse.ArgumentParser(description="Generating sounds with Karplus String Algorithm.")
# add arguments
parser.add_argument('--display', action='store_true', required=False)
parser.add_argument('--play', action='store_true', required=False)
parser.add_argument('--piano', action='store_true', required=False)
args = parser.parse_args()
# show plot if flag set
if args.display:
gShowPlot = True
plt.ion()
# create note player
nplayer = NotePlayer()
print('creating notes...')
for name, freq in list(pmNotes.items()):
fileName = name + '.wav'
if not os.path.exists(fileName) or args.display:
data = generateNote(freq)
print('creating ' + fileName + '...')
writeWAVE(fileName, data)
else:
print('fileName already created. skipping...')
# add note to player
nplayer.add(name + '.wav')
# play note if display flag set
if args.display:
nplayer.play(name + '.wav')
time.sleep(0.5)
# play a random tune
if args.play:
while True:
try:
nplayer.playRandom()
# rest - 1 to 8 beats
rest = np.random.choice([1, 2, 4, 8], 1,
p=[0.15, 0.7, 0.1, 0.05])
time.sleep(0.25*rest[0])
except KeyboardInterrupt:
exit()
# random piano mode
if args.piano:
pygame.init()
file = {'a': 'C4.wav', 's': 'Eb.wav', 'd': 'C4.wav', 'f': 'C4.wav', 'g': 'C4.wav'}
size = width, height = 600, 400
bg = (255, 255, 255)
img = pygame.image.load("logo.jpg")
position = img.get_rect()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("piano")
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
cr = chr(event.key)
if cr in ['a', 's', 'd', 'f', 'g']:
nplayer.play(file[cr])
pygame.display.flip()
if __name__ == '__main__':
main()
第三问运行截图