219 lines
7.1 KiB
Python
219 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Copyright (C) 2020 harrysentonbury
|
|
# GNU General Public License v3.0
|
|
|
|
import numpy as np
|
|
import os
|
|
import simpleaudio as sa
|
|
import scipy.io.wavfile as wf
|
|
import sys
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
import time
|
|
import tkinter as tk
|
|
from tkinter import messagebox
|
|
|
|
|
|
def play_note(event):
|
|
if stop_flag.get() is False:
|
|
sa.stop_all()
|
|
play_obj = sa.play_buffer(key_notes.get(event.char), 2, 2, sample_rate)
|
|
|
|
|
|
def stop_play():
|
|
sa.stop_all()
|
|
|
|
|
|
def on_quitting():
|
|
if messagebox.askokcancel("Quit?", "Shure You Want To Quit"):
|
|
closing()
|
|
|
|
|
|
def closing():
|
|
sa.stop_all()
|
|
master.destroy()
|
|
|
|
|
|
def do_it_int16(place_holder=0):
|
|
|
|
def wave_maker(a):
|
|
factor = (2**(a / 12.0))
|
|
waveform_mod = (sine_wave(freq_octave * factor) * sm) + \
|
|
(triangle(freq3 * factor, freq_detune) * tm)
|
|
waveform = (sine_wave(freq_octave * factor) * sm) + \
|
|
(triangle(freq3 * factor) * tm)
|
|
waveform_detune = (sine_wave(freq_octave * factor, freq_detune)
|
|
* sm) + (triangle(freq3 * factor) * tm)
|
|
|
|
waveform = ((waveform + waveform_detune) *
|
|
(waveform_mod / 2 + 0.5)) * 0.1
|
|
|
|
waveform[-fade_amount:] *= fade
|
|
waveform = np.int16(waveform * 32767)
|
|
waveform2 = np.roll(waveform, roll_amount, axis=None)
|
|
waveform3 = np.vstack((waveform2, waveform)).T
|
|
waveform3 = (waveform3.copy(order='C'))
|
|
return waveform3
|
|
|
|
def sine_wave(f, detune=0.0):
|
|
y = np.sin((f + detune) * x + ramp_0 *
|
|
np.sin(((f + detune) * 0.5) * x + (np.sin(((f + detune) * fm) * x) * 0.5)))
|
|
return y
|
|
|
|
def triangle(f, detune=0.0):
|
|
y = 2 / np.pi * np.arcsin(np.sin((f + detune) * x + ramp_0 *
|
|
np.sin(((f + detune) * 0.5) * x + (np.sin(((f + detune) * fm) * x) * 0.5))))
|
|
return y * 0.8
|
|
|
|
global notes
|
|
fm = 0.25
|
|
freq3 = 440.0
|
|
freq_detune = float(scale_detune.get())
|
|
duration = float(scale_duration.get())
|
|
freq_octave = (2**float(scale_octave.get())) * 440
|
|
ramp_amount = float(scale_ramp.get())
|
|
roll_amount = int(scale_roll.get())
|
|
st = float(scale_st.get())
|
|
tm = st * 1.2
|
|
sm = 1 - st
|
|
|
|
x = np.linspace(0, 2 * np.pi * duration, int(duration * sample_rate))
|
|
ramp_0 = np.logspace(1, 0, np.size(x), base=10) * ramp_amount
|
|
|
|
notes = []
|
|
with ThreadPoolExecutor() as executor:
|
|
notes = list(executor.map(wave_maker, range(-5, 10)))
|
|
|
|
global key_notes
|
|
key_notes = dict(zip(keys, notes))
|
|
return key_notes
|
|
|
|
|
|
def save_wav():
|
|
print(str(sample_rate))
|
|
dir_name = "./audio{}-{}Hz".format(time.strftime("%d-%m-%Y-%H-%M-%S", time.gmtime()), sample_rate)
|
|
try:
|
|
print(f"Making directory{dir_name}")
|
|
os.mkdir(dir_name)
|
|
except FileExistsError:
|
|
print("Aborted. Directory already exists")
|
|
return
|
|
else:
|
|
for e, i in enumerate(notes, start=1):
|
|
if len(str(e)) < 2:
|
|
wf.write(f"{dir_name}/a0{e}.wav", sample_rate, i)
|
|
else:
|
|
wf.write(f"{dir_name}/a{e}.wav", sample_rate, i)
|
|
print(f"Wav files saved in directory{dir_name}")
|
|
|
|
|
|
def toggle_flag():
|
|
sa.stop_all()
|
|
stop_flag.set(not stop_flag.get())
|
|
if stop_flag.get() is True:
|
|
toggle_button.config(bg="#728C00", fg="white", text="Poly")
|
|
else:
|
|
toggle_button.config(bg="#000000", fg="white", text="Mono")
|
|
|
|
|
|
def binders(la):
|
|
master.bind(f"<{la}>", play_note)
|
|
|
|
|
|
try:
|
|
keys = ['a', 's', 'e', 'd', 'r', 'f', 't',
|
|
'g', 'h', 'u', 'j', 'i', 'k', 'l', 'p']
|
|
|
|
if len(sys.argv) < 2:
|
|
sample_rate = 48000
|
|
else:
|
|
if sys.argv[1] == "44100":
|
|
sample_rate = 44100
|
|
elif sys.argv[1] == "96000":
|
|
sample_rate = 96000
|
|
elif sys.argv[1] == "88200":
|
|
sample_rate = 88200
|
|
elif sys.argv[1] == "192000":
|
|
sample_rate = 192000
|
|
else:
|
|
sample_rate = 48000
|
|
print(f"Samplerate: {sample_rate}")
|
|
|
|
fade_amount = 8000
|
|
fade = np.linspace(1, 0, fade_amount)
|
|
|
|
master = tk.Tk()
|
|
master.geometry('700x500')
|
|
master.configure(padx=20, pady=20)
|
|
master.title("1/4 Dead Synth")
|
|
|
|
stop_flag = tk.BooleanVar()
|
|
stop_flag.set(True)
|
|
|
|
for key in keys:
|
|
binders(f'{key}')
|
|
master.bind("<ButtonRelease-1>", do_it_int16)
|
|
master.bind("<Control-q>", lambda event=None: closing())
|
|
master.bind("<c>", lambda event=None: stop_play())
|
|
|
|
duration_label = tk.Label(master, text='Duration')
|
|
detune_label = tk.Label(master, text='Detune')
|
|
octave_label = tk.Label(master, text='Sine Octave')
|
|
ramp_label = tk.Label(master, text='Ramp')
|
|
roll_label = tk.Label(master, text='Delay')
|
|
sm_label = tk.Label(master, text='Sine')
|
|
tm_label = tk.Label(master, text='Triangle')
|
|
|
|
scale_duration = tk.Scale(master, from_=0.2, to=5.0, resolution=0.2,
|
|
orient=tk.HORIZONTAL, length=200, takefocus=True)
|
|
scale_detune = tk.Scale(master, from_=0.0, to=13.0,
|
|
resolution=0.2, orient=tk.HORIZONTAL, length=200, takefocus=True)
|
|
scale_octave = tk.Scale(master, from_=-1, to=2,
|
|
resolution=1, orient=tk.HORIZONTAL, length=70, takefocus=True)
|
|
scale_ramp = tk.Scale(master, from_=0.0, to=2.0,
|
|
resolution=0.01, orient=tk.HORIZONTAL, length=200, takefocus=True)
|
|
scale_roll = tk.Scale(master, from_=0, to=4000,
|
|
resolution=50, orient=tk.HORIZONTAL, length=200, takefocus=True)
|
|
scale_st = tk.Scale(master, from_=0.0, to=1.0,
|
|
resolution=0.005, orient=tk.HORIZONTAL, length=200, showvalue=0, takefocus=True)
|
|
|
|
stop_it_button = tk.Button(
|
|
master, text='Stop', width=7, command=stop_play)
|
|
toggle_button = tk.Button(master, text='Poly',
|
|
bg="#728C00", fg="white", width=7, command=toggle_flag)
|
|
save_wav_button = tk.Button(master, text="Save as .wav", command=save_wav)
|
|
close_button = tk.Button(master, text='Close', width=7, command=closing)
|
|
scale_duration.set(1.0)
|
|
scale_detune.set(2.2)
|
|
scale_octave.set(0)
|
|
scale_ramp.set(0.25)
|
|
scale_roll.set(600)
|
|
|
|
duration_label.grid(row=0, column=0)
|
|
detune_label.grid(row=1, column=0)
|
|
octave_label.grid(row=2, column=0)
|
|
ramp_label.grid(row=3, column=0)
|
|
roll_label.grid(row=4, column=0)
|
|
sm_label.grid(row=5, column=0)
|
|
tm_label.grid(row=5, column=2, sticky='w')
|
|
|
|
scale_duration.grid(row=0, column=1)
|
|
scale_detune.grid(row=1, column=1)
|
|
scale_octave.grid(row=2, column=1, sticky='w')
|
|
scale_ramp.grid(row=3, column=1)
|
|
scale_roll.grid(row=4, column=1)
|
|
scale_st.grid(row=5, column=1, pady=20)
|
|
stop_it_button.grid(row=0, column=2, padx=20)
|
|
toggle_button.grid(row=2, column=2, padx=20)
|
|
save_wav_button.grid(row=6, column=1, padx=20)
|
|
close_button.grid(row=6, column=2, padx=20)
|
|
|
|
key_notes = do_it_int16()
|
|
|
|
master.protocol("WM_DELETE_WINDOW", on_quitting)
|
|
master.mainloop()
|
|
except KeyboardInterrupt:
|
|
print(' The End')
|
|
except Exception as e:
|
|
print(f'{type(e).__name__}: {str(e)}')
|