问题
My son and I are trying to write a program that will allow a user to input a sequence of musical notes, and save them into a list to be played back. We've come up with the following:
import math #import needed modules
import pyaudio #sudo apt-get install python-pyaudio
def playnote(char):
octave = int(char[1])
if char[0] == 'c':
frequency = 16.35*octave+1
elif char[0] =='C':
frequency = 17.32*octave+1
elif char[0] =='d':
frequency = 18.35*octave+1
elif char[0] == 'D':
frequency = 19.45*octave+1
elif char[0] =='e':
frequency = 20.6*octave+1
elif char[0] == 'f':
frequency = 21.83*octave+1
elif char[0] =='F':
frequency = 23.12*octave+1
elif char[0] == 'g':
frequency = 24.5*octave+1
elif char[0] == 'G':
frequency = 25.96*octave+1
elif char[0] == 'a':
frequency = 27.5*octave+1
elif char[0] == 'A':
frequency = 29.14*octave+1
elif char[0] == 'b':
frequency = 30.87*octave+1
elif char[0] == 'p':
del song[-1]
PyAudio = pyaudio.PyAudio #initialize pyaudio
#See https://en.wikipedia.org/wiki/Bit_rate#Audio
bitrate = 256000 #number of frames per second/frameset.
# frequency = 220 #Hz, waves per second, 261.63=C4-note.
LENGTH = 1 #seconds to play sound
if frequency > bitrate:
bitrate = frequency+100
frames = int(bitrate * LENGTH)
# RESTFRAMES = frames % bitrate
wavedata = ''
#generating waves
for x in range(frames):
wavedata = wavedata+chr(int(math.sin(x/((bitrate/frequency)/math.pi))*127+128))
# for x in range(RESTFRAMES):
# wavedata = wavedata+chr(128)
p = PyAudio()
stream = p.open(format = p.get_format_from_width(1),
channels = 1,
rate = bitrate,
output = True)
stream.write(wavedata)
stream.stop_stream()
stream.close()
p.terminate()
song = []
while True:
try:
note = str(input('''Enter note (A-G) (capital for sharp)
and an octave (0-8) or any other key to play: '''))
playnote(note)
song.append(note)
except:
for note in song:
playnote(note)
break
It works fine as a starting point, but the notes don't sound very 'musical'.
From here, we're wondering:
are there any existing Python scripts or modules which do something similar?
is there a way to modify the waveforms to emulate different instruments?
回答1:
If you are looking for other work for producing music using Python, you might find the following program be a helpful inspiration. It uses the winsound
module on Windows to produce beeps of a certain duration and frequency. The program shown below is old and not maintained -- an experiment really but may give you and your son some ideas for further related work.
#! /usr/bin/env python3
import msvcrt
import random
import time
import winsound
# CONSTANTS
KEYS = 'zsxcfvgbnjmk,l./\'q2we4r5t6yu8i9op-[=]'
A4 = 440
AUTHOR = '.\',zb'
NEW_SONG = ('vm',
'zv',
'cn',
'vm',
'xb',
'cn',
'zv')
# CONFIGURATION
NS_SP = 1
SPEED = 5
HOLD_RATIO = 0.95
TRANSPOSE = 0
PAUSE_TIME = 2
SHOW_FREQU = False
# RANDOM
NEIGHBOR_RATIO = 0.9
ODD_RATIO = 0.05
SWITCH_RATIO = 0.01
WHITE_KEYS = 'zxcvbnm,./qwertyuiop[]'
BLACK_KEYS = 'sfgjkl\'245689-='
EXAMPLE_SONG_1 = [('x', 1),
('x', 2),
('x', 1),
('x', 1),
('f', 1),
('g', 1),
('b', 2),
('b', 1),
('g', 2),
('x', 1),
('k', 2),
('k', 1),
('j', 2),
('g', 1),
('f', 5),
('x', 1),
('k', 2),
('k', 1),
('l', 1),
('.', 1),
("'", 1),
('j', 2),
('j', 1),
('g', 2),
('g', 1),
('b', 2),
('g', 1),
('f', 1),
('x', 1),
('f', 1),
('x', 5)]
EXAMPLE_SONG_2 = [('j', 2),
('j', 1),
('j', 2),
('.', 1),
('b', 2),
('j', 1),
('b', 1),
('g', 1.5),
('f', 0.5),
('g', 2),
('g', 1),
('g', 1),
('f', 1),
('x', 1),
('f', 6),
('j', 2),
('j', 1),
('j', 2),
('.', 1),
('b', 2),
('j', 1),
('b', 1),
('g', 1.5),
('f', 0.5),
('g', 2),
('g', 1),
('f', 1),
('x', 1),
('f', 1),
('x', 5),
('x', 1),
('k', 2),
('k', 1),
('l', 3),
('l', 2),
("'", 1),
('.', 2),
('.', 1),
('.', 2),
('.', 1),
('2', 1),
("'", 1),
('.', 1),
('j', 6),
('j', 2),
('j', 1),
('j', 2),
('.', 1),
('b', 2),
('j', 1),
('b', 1),
('g', 1.5),
('f', 0.5),
('g', 2),
('g', 1),
('f', 1),
('x', 1),
('f', 1),
('x', 6)]
EXAMPLE_SONG_3 = [(' ', 1),
('x', 0.5),
('f', 0.5),
('g', 0.5),
('b', 0.5),
('j', 1),
('.', 3),
('.', 0.5),
('l', 0.5),
('k', 0.5),
('l', 0.5),
('j', 3),
(' ', 1),
('k', 0.5),
('j', 0.5),
('b', 0.5),
('k', 0.5),
('j', 1),
('x', 3),
('f', 0.5),
('g', 0.5),
('b', 0.5),
('g', 0.5),
('f', 3),
(' ', 1),
('x', 0.5),
('f', 0.5),
('g', 0.5),
('b', 0.5),
('j', 1),
('.', 3),
('.', 0.5),
('l', 0.5),
('k', 0.5),
('l', 0.5),
('j', 3),
(' ', 1),
('k', 0.5),
('j', 0.5),
('b', 0.5),
('k', 0.5),
('j', 1),
('x', 2.5),
('x', 0.5),
('b', 0.5),
('g', 0.5),
('f', 0.5),
('g', 0.5),
('x', 3),
('z', 0.5),
('x', 0.5),
('f', 0.5),
('g', 0.5),
('b', 0.5),
('j', 0.5),
('k', 1),
('.', 0.5),
('l', 0.5),
('k', 0.5),
('l', 0.5),
('.', 1),
('k', 2),
(' ', 1),
('l', 0.5),
('k', 0.5),
('j', 0.5),
('k', 0.5),
('l', 3),
(' ', 1),
('k', 0.5),
('j', 0.5),
('b', 0.5),
('j', 0.5),
('k', 1),
('b', 2),
(' ', 1),
('j', 0.5),
('b', 0.5),
('g', 0.5),
('b', 0.5),
('j', 3),
(' ', 1),
('.', 0.5),
('l', 0.5),
('k', 0.5),
('l', 0.5),
('.', 1),
('k', 2),
("'", 0.5),
('.', 0.5),
('l', 0.5),
('.', 0.5),
('j', 3),
(' ', 1),
("'", 0.5),
('.', 0.5),
('l', 0.5),
('.', 0.5),
('j', 3),
(' ', 1),
('k', 0.5),
('j', 0.5),
('b', 0.5),
('k', 0.5),
('j', 1),
('x', 2),
(' ', 1),
('b', 0.5),
('g', 0.5),
('f', 0.5),
('g', 0.5),
('x', 3)]
EXAMPLE_SONG_4 = [('j', 1.5),
('j', 0.5),
('j', 0.75),
('b', 0.25),
('g', 0.75),
('b', 0.25),
('j', 1),
('k', 1),
('j', 2),
('l', 1.5),
('l', 0.5),
('l', 0.75),
('k', 0.25),
('j', 0.75),
('b', 0.25),
('g', 1),
('k', 1),
('j', 2),
('j', 1.5),
('j', 0.5),
('j', 0.75),
('b', 0.25),
('g', 0.75),
('b', 0.25),
('j', 1),
('k', 1),
('j', 1),
('.', 1),
("'", 2),
('l', 2),
('.', 4),
('.', 1.5),
('l', 0.5),
('.', 0.75),
('l', 0.25),
('.', 0.75),
('k', 0.25),
('k', 1),
('j', 1),
('j', 2),
('l', 1.5),
('k', 0.5),
('l', 0.75),
('k', 0.25),
('l', 0.75),
('k', 0.25),
('j', 1),
('.', 1),
('.', 2),
('.', 1.5),
('l', 0.5),
('.', 0.75),
('l', 0.25),
('.', 0.75),
('k', 0.25),
('k', 1),
('j', 1),
('j', 1),
('.', 1),
("'", 2),
('l', 2),
('.', 4)]
EXAMPLE_SONG_5 = [('g', 0.5),
('g', 0.5),
('g', 0.5),
('g', 0.5),
('b', 0.5),
('b', 0.5),
('b', 0.5),
('g', 0.5),
('f', 0.5),
('f', 0.5),
('j', 0.5),
('f', 0.5),
('g', 2),
('b', 0.5),
('b', 0.5),
('j', 0.5),
('k', 0.5),
('j', 0.5),
('x', 0.5),
('b', 0.5),
('g', 0.5),
('f', 4),
('g', 0.5),
('g', 0.5),
('g', 0.5),
('g', 0.5),
('b', 0.5),
('g', 0.5),
('g', 0.5),
('g', 0.5),
('k', 0.5),
('k', 0.5),
('l', 0.5),
('k', 0.5),
('g', 1),
('g', 1),
('b', 0.5),
('b', 0.5),
('j', 0.5),
('k', 0.5),
('j', 0.5),
('x', 0.5),
('b', 0.5),
('g', 0.5),
('f', 5),
('j', 1),
('k', 1),
('l', 1),
('l', 0.5),
('.', 0.5),
('.', 0.5),
('j', 0.5),
('j', 1.5),
('g', 0.5),
('b', 0.5),
('g', 0.5),
('x', 0.5),
('f', 0.5),
('g', 1.5),
('g', 0.5),
('b', 0.5),
('b', 0.5),
('j', 0.5),
('k', 0.5),
('j', 0.5),
('.', 0.5),
('.', 0.5),
('l', 0.5),
('k', 0.5),
('k', 0.5),
('l', 0.5),
('.', 0.5),
("'", 1.5),
("'", 0.5),
('2', 0.5),
('2', 0.5),
('2', 0.5),
("'", 0.5),
("'", 0.5),
('.', 0.5),
('l', 0.5),
('.', 0.5),
('k', 0.5),
('k', 0.5),
('k', 0.5),
('j', 0.5),
('b', 1.5),
('f', 0.5),
('g', 0.5),
('j', 0.5),
('j', 0.5),
('g', 0.5),
('b', 0.5),
('k', 0.5),
('k', 0.5),
('j', 0.5),
('k', 2),
('l', 2),
('.', 4)]
EXAMPLE_SONG_6 = [('j', 2),
('k', 1),
('j', 2),
('j', 1),
('b', 2),
('g', 1),
('b', 0.5),
('g', 0.5),
('f', 2),
('g', 2),
('j', 1),
('.', 2),
('j', 1),
('b', 2),
('f', 1),
('j', 3),
('j', 2),
('k', 1),
('j', 2),
('j', 1),
('k', 2),
('l', 1),
('.', 1),
('k', 2),
('j', 2),
('g', 1),
('x', 2),
('g', 1),
('f', 2),
('x', 1),
('x', 3),
('.', 2),
("'", 1),
('.', 2),
('l', 1),
('.', 2),
("'", 1),
('.', 1),
('k', 2),
('j', 2),
('.', 1),
('2', 2),
('.', 1),
("'", 2),
('k', 1),
('j', 3),
('j', 1),
('k', 1),
('l', 1),
('.', 2),
('l', 1),
('k', 2),
('j', 1),
('j', 1),
('g', 2),
('g', 2),
('j', 1),
('x', 2),
('g', 1),
('f', 2),
('x', 1),
('x', 3)]
# These next few songs were contributed by Mike Sperry.
TWINKLE_TWINKLE = [('c', 1),
('c', 1),
('m', 1),
('m', 1),
(',', 1),
(',', 1),
('m', 2),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 1),
('v', 1),
('c', 2),
('m', 1),
('m', 1),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 2),
('m', 1),
('m', 1),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 2),
('c', 1),
('c', 1),
('m', 1),
('m', 1),
(',', 1),
(',', 1),
('m', 2),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 1),
('v', 1),
('c', 2)]
ABCS = [('c', 1),
('c', 1),
('m', 1),
('m', 1),
(',', 1),
(',', 1),
('m', 2),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 0.5),
('v', 0.5),
('v', 0.5),
('v', 0.5),
('c', 2),
('m', 1),
('m', 1),
('n', 2),
('b', 1),
('b', 1),
('v', 2),
('m', 1),
('m', 1),
('n', 2),
('b', 1),
('b', 1),
('v', 2),
('c', 1),
('c', 1),
('m', 1),
('m', 1),
(',', 1),
(',', 1),
('m', 2),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 1),
('v', 1),
('c', 2)]
BAH_BAH_BLACK_SHEEP = [('c', 1),
('c', 1),
('m', 1),
('m', 1),
(',', 0.5),
(',', 0.5),
(',', 0.5),
(',', 0.5),
('m', 2),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 1),
('v', 1),
('c', 2),
('m', 1),
('m', 0.5),
('m', 0.5),
('n', 1),
('n', 1),
('b', 1),
('b', 0.5),
('b', 0.5),
('v', 2),
('m', 1),
('m', 0.5),
('m', 0.5),
('n', 0.5),
('n', 0.5),
('n', 0.5),
('n', 0.5),
('b', 1),
('b', 0.5),
('b', 0.5),
('v', 2),
('c', 1),
('c', 1),
('m', 1),
('m', 1),
(',', 0.5),
(',', 0.5),
(',', 0.5),
(',', 0.5),
('m', 2),
('n', 1),
('n', 1),
('b', 1),
('b', 1),
('v', 1),
('v', 1),
('c', 2)]
HAPPY_BIRTHDAY = [('m', 0.75),
('m', 0.25),
(',', 1),
('m', 1),
('/', 1),
('.', 2),
('m', 0.75),
('m', 0.25),
(',', 1),
('m', 1),
('q', 1),
('/', 2),
('m', 0.75),
('m', 0.5),
('r', 1),
('w', 1),
('/', 1),
('.', 1),
(',', 1),
('n', 0.75),
('n', 0.25),
('b', 1),
('c', 1),
('v', 1),
('c', 3)]
# KNOWN MUSIC
SONGS = EXAMPLE_SONG_1, \
EXAMPLE_SONG_2, \
EXAMPLE_SONG_3, \
EXAMPLE_SONG_4, \
EXAMPLE_SONG_5, \
EXAMPLE_SONG_6, \
TWINKLE_TWINKLE, \
ABCS, \
BAH_BAH_BLACK_SHEEP, \
HAPPY_BIRTHDAY
def main():
print('''
MENU
====
(R)andom
(S)huffle
(P)lay
(K)eyboard
(A)uthor
(N)ew Song''')
while True:
key = msvcrt.getwch()
if key in 'rspk': print()
if key == 'r': menu_help(random.random)
if key == 's': menu_help(random.shuffle)
if key == 'p': select_song()
if key == 'k': menu_help()
if key == 'a': author()
if key == 'n': new_song()
def new_song():
while True:
sig = 0
for notes in NEW_SONG:
sig *= 2
for note in random.sample(notes, 2):
try:
winsound.Beep(get_frequency(note), int(100 / float(NS_SP)))
except:
pass
if notes[1] == note:
sig += 1
time.sleep((1.0 / 30) / NS_SP)
if not SHOW_FREQU:
print(sig + 1)
def select_song():
songs = (('EXAMPLE_SONG_1', EXAMPLE_SONG_1),
('EXAMPLE_SONG_2', EXAMPLE_SONG_2),
('EXAMPLE_SONG_3', EXAMPLE_SONG_3),
('EXAMPLE_SONG_4', EXAMPLE_SONG_4),
('EXAMPLE_SONG_5', EXAMPLE_SONG_5),
('EXAMPLE_SONG_6', EXAMPLE_SONG_6),
('TWINKLE_TWINKLE', TWINKLE_TWINKLE),
('ABCS', ABCS),
('BAH_BAH_BLACK_SHEEP', BAH_BAH_BLACK_SHEEP),
('HAPPY_BIRTHDAY', HAPPY_BIRTHDAY))
for index, data in enumerate(songs):
print('(%s) %s' % (index + 1, data[0].replace('_', ' ').lower().title()))
while True:
try:
index = int(input('\nSelect: '))
assert 0 < index <= len(songs)
play(songs[index - 1][1])
except:
pass
def menu_help(score=None):
if isinstance(score, list):
play(score)
elif score is random.random:
play_random()
elif score is random.shuffle:
play_songs()
keyboard()
def play(score):
for key, duration in score:
duration /= float(SPEED)
bd = int(duration * HOLD_RATIO * 1000)
sd = duration * (1 - HOLD_RATIO)
try:
winsound.Beep(get_frequency(key), bd)
except:
time.sleep(duration * HOLD_RATIO)
time.sleep(sd)
def keyboard():
while msvcrt.kbhit():
msvcrt.getwch()
while True:
try:
winsound.Beep(get_frequency(msvcrt.getwch()), 1000)
except:
pass
def get_frequency(key):
assert key[0] in KEYS
if SHOW_FREQU:
frequ = int((A4 * 2 ** ((KEYS.find(key[0]) + key.count('+') - (0 if key[0] == '-' else key.count('-')) + TRANSPOSE) / 12.0)) + 0.5)
print(frequ)
return frequ
else:
print(key, end=' ')
return int((A4 * 2 ** ((KEYS.find(key[0]) + key.count('+') - (0 if key[0] == '-' else key.count('-')) + TRANSPOSE) / 12.0)) + 0.5)
def play_random():
key = 'c'
RANDOM_KEYS = WHITE_KEYS
while not msvcrt.kbhit():
if random.random() < SWITCH_RATIO:
if RANDOM_KEYS is WHITE_KEYS:
RANDOM_KEYS = BLACK_KEYS
else:
RANDOM_KEYS = WHITE_KEYS
key = RANDOM_KEYS[random.randrange(len(RANDOM_KEYS))]
if random.random() < NEIGHBOR_RATIO:
index = RANDOM_KEYS.index(key[0]) + key.count('+') - key.count('-') + random.randrange(2) * 2 - 1
if index < 0:
key = RANDOM_KEYS[0] + '-' * (index * -1)
elif index >= len(RANDOM_KEYS):
key = RANDOM_KEYS[-1] + '+' * (index - len(RANDOM_KEYS) + 1)
else:
key = RANDOM_KEYS[index]
else:
key = RANDOM_KEYS[random.randrange(len(RANDOM_KEYS))]
if random.random() < ODD_RATIO:
if random.randrange(2):
key += '+'
else:
key += '-'
neg = key.count('-')
pos = key.count('+')
trans = pos - neg
if trans > 0:
key = key[0] + '+' * trans
elif trans < 0:
key = key[0] + '-' * (trans * -1)
else:
key = key[0]
winsound.Beep(get_frequency(key), 100)
def play_songs():
songs = list(SONGS)
while True:
random.shuffle(songs)
for song in songs:
play(song)
time.sleep(PAUSE_TIME)
def author():
for note in AUTHOR:
winsound.Beep(get_frequency(note), 1000)
time.sleep(1)
while msvcrt.kbhit():
msvcrt.getwch()
author = random.sample(AUTHOR, len(AUTHOR))
while not msvcrt.kbhit():
for note in author:
winsound.Beep(get_frequency(note), 100)
last_note = author[-1]
author = random.sample(AUTHOR, len(AUTHOR))
while author[0] == last_note:
author = random.sample(AUTHOR, len(AUTHOR))
if __name__ == '__main__':
main()
回答2:
There are almost certainly plenty of synths or programs out there that can do various things like this. However, there is lots of fun and value in doing this on your own, and I honestly couldn't point you to anything specific. For your task, you can modify the wave form by doing things like adding additional harmonics to create square and sine waves, adding additional harmonics based on patters (as I did in some of the below cases) or doing things to vary the onset, phase, amplitude, or any other aspect you'd like.
import math # import needed modules
import pyaudio # sudo apt-get install python-pyaudio
scale_notes = {
# pitch standard A440 ie a4 = 440Hz
'c': 16.35,
'C': 17.32,
'd': 18.35,
'D': 19.45,
'e': 20.6,
'f': 21.83,
'F': 23.12,
'g': 24.5,
'G': 25.96,
'a': 27.5,
'A': 29.14,
'b': 30.87
}
def playnote(note, note_style):
octave = int(note[1])
frequency = scale_notes[note[0]] * (2**(octave + 1))
p = pyaudio.PyAudio() # initialize pyaudio
# sampling rate
sample_rate = 22050
LENGTH = 1 # seconds to play sound
frames = int(sample_rate * LENGTH)
wavedata = ''
# generating waves
stream = p.open(
format=p.get_format_from_width(1),
channels=1,
rate=sample_rate,
output=True)
for x in range(frames):
wave = math.sin(x / ((sample_rate / frequency) / math.pi)) * 127 + 128
if note_style == 'bytwos':
for i in range(3):
wave += math.sin((2 + 2**i) * x /
((sample_rate / frequency) / math.pi)) * 127 + 128
wavedata = (chr(int(wave / 4)
))
elif note_style == 'even':
for i in range(3):
wave += math.sin((2 * (i + 1)) * x /
((sample_rate / frequency) / math.pi)) * 127 + 128
wavedata = (chr(int(wave / 4)
))
elif note_style == 'odd':
for i in range(3):
wave += math.sin(((2 * i) + 1) * x /
((sample_rate / frequency) / math.pi)) * 127 + 128
wavedata = (chr(int(wave / 4)
))
elif note_style == 'trem':
wave = wave * (1 + 0.5 * math.sin((1 / 10)
* x * math.pi / 180)) / 2
wavedata = (chr(int(wave)))
else:
wavedata = (chr(int(wave))
)
stream.write(wavedata)
stream.stop_stream()
stream.close()
p.terminate()
song = []
while True:
song_composing = True
note = ''
while note != 'p':
note = str(input(
'''Enter note (a-G) (capital for sharp) and an octave (0-8) or any other key to play: '''))
if note[0] in scale_notes:
note_style = str(
input('''Enter style (bytwos, even, odd, trem): '''))
song.append((note, note_style))
playnote(note, note_style)
for notes in song:
playnote(notes[0], notes[1])
break
Once you experiment with different sounds, you can start looking into how these go together to make real instrument sounds. For instance, guitar or piano decay differently, but not as different as they'd be to woodwinds. Drums generally lack much harmonic structure on purpose, a violin is designed to highlight very pleasant harmonic overtones. There is a good music stackexchange question on the characteristics of instruments.
One thing I would suggest is using a buffer instead of the one-off approach to calculating the next value. Being able to generate a good sound (and apply more complicated algorithms) will be impeded by the ability of your process to complete before the next audio sample is due. I think that's outside the scope of this particular question, but it would also probably be good to use the call back method from pyaudio for this application: https://people.csail.mit.edu/hubert/pyaudio/docs/#example-callback-mode-audio-i-o
回答3:
As an alternative, you and your son may be more interested in how sound waves are actually crafted and then written to a file. While my other answer focused on music, the code shown below is just about the generation of sound. It supports sine, square, triangle, and saw-tooth sound waves and includes the ability to adjust frequency, amplitude, mixing, and interpolation of sounds. Tests are included to generate wave files that can be played back using another program.
#! /usr/bin/env python3
import math
import wave
################################################################################
class Waves:
BUFF = 1 << 20
MAX = 127
MID = 128
def __init__(self, fps):
self.__fps = fps
self.__data = []
@staticmethod
def __sin(ratio):
return math.sin(ratio * math.pi * 2)
@staticmethod
def __squ(ratio):
return 1 - ratio // 0.5 * 2
@staticmethod
def __tri(ratio):
if ratio < 0.25:
return ratio / 0.25
elif ratio < 0.75:
return 1 - 4 * (ratio - 0.25)
else:
return (ratio - 0.75) * 4 - 1
@staticmethod
def __saw(ratio):
return ratio / 0.5 - ratio // 0.5 * 2
def add_sine(self, freq, amp):
self.__add(freq, amp, self.__sin)
def add_square(self, freq, amp):
self.__add(freq, amp, self.__squ)
def add_triangle(self, freq, amp):
self.__add(freq, amp, self.__tri)
def add_sawtooth(self, freq, amp):
self.__add(freq, amp, self.__saw)
def __add(self, freq, amp, func):
rate = int(self.__fps / freq)
self.__data.extend(func(pos / rate) * amp for pos in range(rate))
def interpolate_sine(self, freq_a, freq_b, amp_a, amp_b, seconds):
self.__lerp(freq_a, freq_b, amp_a, amp_b, seconds, self.add_sine)
def interpolate_square(self, freq_a, freq_b, amp_a, amp_b, seconds):
self.__lerp(freq_a, freq_b, amp_a, amp_b, seconds, self.add_square)
def interpolate_triangle(self, freq_a, freq_b, amp_a, amp_b, seconds):
self.__lerp(freq_a, freq_b, amp_a, amp_b, seconds, self.add_triangle)
def interpolate_sawtooth(self, freq_a, freq_b, amp_a, amp_b, seconds):
self.__lerp(freq_a, freq_b, amp_a, amp_b, seconds, self.add_sawtooth)
def __lerp(self, freq_a, freq_b, amp_a, amp_b, seconds, func):
samples = int(seconds * (freq_a + freq_b) / 2)
for position in range(samples):
b = position / samples
a = 1 - b
func(freq_a * a + freq_b * b, amp_a * a + amp_b * b)
def write(self, name):
file = wave.open(name, 'wb')
file.setnchannels(1)
file.setsampwidth(1)
file.setframerate(self.__fps)
self.__writeframes(file)
file.close()
def __writeframes(self, file):
parts = len(self.__data) // self.BUFF
parts += bool(len(self.__data) % self.BUFF)
for part in range(parts):
index = part * self.BUFF
buff = self.__data[index:index+self.BUFF]
byte = self.__transform(buff)
file.writeframes(byte)
@classmethod
def __transform(cls, buff):
return bytes(int(pos * cls.MAX) + cls.MID for pos in buff)
@classmethod
def add(cls, *waves):
sounds = len(waves)
assert sounds > 1, 'Must have two or more waves to add!'
fps = waves[0].__fps
for wave_instance in waves[1:]:
assert wave_instance.__fps == fps, 'Framerate is not the same!'
result = cls(fps)
package = map(lambda wave_instance: wave_instance.__data, waves)
result.__data = [sum(sound) / sounds for sound in zip(*package)]
return result
def __add__(self, other):
return Waves.add(self, other)
def __mul__(self, other):
result = Waves(self.__fps)
result.__data = [value * other for value in self.__data]
return result
def __imul__(self, other):
self.__data = [value * other for value in self.__data]
return self
def append(self, other):
assert self.__fps == other.__fps, 'Framerate is not the same!'
self.__data.extend(other.__data)
def average_amp(self):
total = count = 0
for value in self.__data:
total += abs(value)
count += 1
return total / count
def adjust_amp(self, value):
self *= value / self.average_amp()
################################################################################
def test_1():
test = Waves(96000)
# Standard Test
test.interpolate_sine(440, 440, 0.1, 0.1, 1)
test.interpolate_square(440, 440, 0.1, 0.1, 1)
test.interpolate_triangle(440, 440, 0.1, 0.1, 1)
# Frequency Test
test.interpolate_sine(440, 880, 0.1, 0.1, 1)
test.interpolate_square(440, 880, 0.1, 0.1, 1)
test.interpolate_triangle(440, 880, 0.1, 0.1, 1)
# Amplitude Test
test.interpolate_sine(440, 440, 0.1, 0.5, 1)
test.interpolate_square(440, 440, 0.1, 0.5, 1)
test.interpolate_triangle(440, 440, 0.1, 0.5, 1)
# Frequency & Amplitude Test
test.interpolate_sine(440, 880, 0.1, 0.5, 1)
test.interpolate_square(440, 880, 0.1, 0.5, 1)
test.interpolate_triangle(440, 880, 0.1, 0.5, 1)
# Finish Test
test.write('test_1.wav')
def test_2():
# Addition, Multiplication, & Append Test
test = Waves(96000)
# Sine -> Square
a = Waves(96000)
a.interpolate_sine(440, 440, 0.5, 0.0, 5)
a = a * (0.2 / a.average_amp())
b = Waves(96000)
b.interpolate_square(440, 440, 0.0, 0.5, 5)
b = b * (0.2 / b.average_amp())
c = a + b
test.append(c)
# Square -> Triangle
a = Waves(96000)
a.interpolate_square(440, 440, 0.5, 0.0, 5)
a = a * (0.2 / a.average_amp())
b = Waves(96000)
b.interpolate_triangle(440, 440, 0.0, 0.5, 5)
b = b * (0.2 / b.average_amp())
c = a + b
test.append(c)
# Triangle -> Sawtooth
a = Waves(96000)
a.interpolate_triangle(440, 440, 0.5, 0.0, 5)
a = a * (0.2 / a.average_amp())
b = Waves(96000)
b.interpolate_sawtooth(440, 440, 0.0, 0.5, 5)
b = b * (0.2 / b.average_amp())
c = a + b
test.append(c)
# Sawtooth -> Sine
a = Waves(96000)
a.interpolate_sawtooth(440, 440, 0.5, 0.0, 5)
a = a * (0.2 / a.average_amp())
b = Waves(96000)
b.interpolate_sine(440, 440, 0.0, 0.5, 5)
b = b * (0.2 / b.average_amp())
c = a + b
test.append(c)
# Finish Test
test.write('test_2.wav')
def test_3():
# Test Sample Mixing
sound = Waves(96000)
sample_1 = Waves(96000)
sample_1.interpolate_sine(220, 440, 0.5, 0.5, 10)
sample_2 = Waves(96000)
sample_2.interpolate_sine(330, 660, 0.2, 0.2, 10)
sample_3 = Waves(96000)
sample_3.interpolate_sine(440, 880, 0.2, 0.2, 10)
sound.append(sample_1)
sound.append(sample_1 + sample_2)
sound.append(sample_1 + sample_2 + sample_3)
sound.write('test_3.wav')
def test_4():
# Test Sound of Waveforms
sound = Waves(96000)
# Sine
sample = Waves(96000)
sample.interpolate_sine(440, 440, 0.1, 0.1, 2)
sample.adjust_amp(0.2)
sound.append(sample)
# Square
sample = Waves(96000)
sample.interpolate_square(440, 440, 0.1, 0.1, 2)
sample.adjust_amp(0.2)
sound.append(sample)
# Triangle
sample = Waves(96000)
sample.interpolate_triangle(440, 440, 0.1, 0.1, 2)
sample.adjust_amp(0.2)
sound.append(sample)
# Sawtooth
sample = Waves(96000)
sample.interpolate_sawtooth(440, 440, 0.1, 0.1, 2)
sample.adjust_amp(0.2)
sound.append(sample)
# Finish Test
sound.write('test_4.wav')
################################################################################
if __name__ == '__main__':
test_1()
test_2()
test_3()
test_4()
If you combine concepts from both answers, you could create a program that takes music you have created or encoded and generate music files other people can play on their devices.
回答4:
A fairly simple way to do this type of thing is the winsound
module. Here's a tutorial that I've excerpted a small section of to illustrate.
from winsound import Beep
# Beep(frequency, duration) Frequency in Hertz, duration in ms
Beep(480,200)
Beep(1568,200)
Beep(1568,200)
Beep(1568,200)
Beep(740,200)
Beep(784,200)
Beep(784,200)
Beep(784,200)
Beep(370,200)
Beep(392,200)
Beep(370,200)
Beep(392,200)
Beep(392,400)
Beep(196,400)
And here's a website with the frequency of notes. It lets you choose from a few different tuning standards.
来源:https://stackoverflow.com/questions/57422755/entering-a-sequence-of-notes-and-have-them-played