experimentations/quarter_dead_synth.py

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)}')