You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cutie-dead-synth/extra_thing_for_fun.py

691 lines
18 KiB

#!/usr/bin/env python3
# Copyright (C) 2020 Harry Sentonbury
# GNU General Public License v3.0
from concurrent.futures import ThreadPoolExecutor
import os
import scipy.io.wavfile as wf
import sys
import threading
import time
import mido
import numpy as np
import simpleaudio as sa
from PyQt5 import QtGui
from PyQt5.QtWidgets import (QApplication, QWidget, QSlider, QPushButton,
QListWidget, QLabel, QVBoxLayout, QShortcut)
from PyQt5.Qt import Qt
from PyQt5.QtGui import QIcon, QPixmap, QKeySequence
class ChoosePort(QWidget):
def __init__(self):
super().__init__()
midi_input_names = mido.get_input_names()
self.setGeometry(100, 520, 600, 200)
self.setWindowTitle('Choose Midi Input')
self.setWindowIcon(QIcon('images/knotperfect-icon.png'))
self.setObjectName("midi_win")
layout = QVBoxLayout()
self.setLayout(layout)
self.listwidget = QListWidget()
for i, s in enumerate(midi_input_names):
self.listwidget.insertItem(i, s)
self.listwidget.clicked.connect(self.clicked)
layout.addWidget(self.listwidget)
self.info_label = QLabel(self)
self.info_label.setText("""Select port and wait a few seconds.
port will be open until program is closed""")
layout.addWidget(self.info_label)
self.close_kp_button = QPushButton("Close", self)
self.close_kp_button.move(400, 140)
self.close_kp_button.clicked.connect(lambda: self.close())
layout.addWidget(self.close_kp_button)
def clicked(self, qmodelindex):
midi_on_flag[0] = True
item = self.listwidget.currentItem().text()
do_it_int16()
activate_midi(item)
self.close()
class CustomKeybinder(QWidget):
def __init__(self):
super().__init__()
def change_key_list():
self.is_e_list = not self.is_e_list
if self.is_e_list:
self.selection_label.setText("key of E")
else:
self.selection_label.setText("key of C")
self.custom_e_list = []
self.is_e_list = True
self.setGeometry(400, 450, 600, 200)
self.setWindowTitle('Set Custom Keybindings')
self.setWindowIcon(QtGui.QIcon('images/knotperfect-icon.png'))
self.setObjectName("ck_win")
self.blah_label = QLabel(self)
self.blah_label.setText("Type the keys in the order you want, to whatever scale\n"
"then go back and click the Change Key button")
self.blah_label.move(10, 10)
self.list_label = QLabel(self)
self.list_label.setText("")
self.list_label.resize(550, 25)
self.list_label.move(10, 50)
self.list_label.setObjectName("l_label")
self.key_button = QPushButton("Select Key", self)
self.key_button.move(11, 100)
self.key_button.clicked.connect(change_key_list)
self.selection_label = QLabel(self)
self.selection_label.setText("key of E")
self.selection_label.move(130, 105)
self.close_ckb_button = QPushButton("Close", self)
self.close_ckb_button.move(420, 150)
self.close_ckb_button.clicked.connect(lambda: self.close())
def keyPressEvent(self, event):
if midi_on_flag[0] is True:
return
else:
if len(self.custom_e_list) >= 18:
if self.is_e_list:
e_keys[:] = self.custom_e_list[:]
else:
c_keys[:] = self.custom_e_list[:]
self.close()
self.custom_e_list.append(event.text())
self.list_label.setText(str(self.custom_e_list))
class MainWindow(QWidget):
def __init__(self):
super().__init__()
def keyPressEvent(self, event):
if midi_on_flag[0] is True:
return
else:
if mono_flag is True:
sa.stop_all()
try:
sound = key_notes.get(event.text())
play_obj = sa.play_buffer(sound, 2, 2, sample_rate)
except TypeError:
return
def do_it_int16():
def wave_maker(a):
factor = 2**(a / 12.0)
waveform_mod = (sine_wave(octave_freq * (
factor)) * sm) + (triangle(freq3 * factor, detune_freq) * tm)
waveform = (sine_wave(
octave_freq * factor) * sm) + (triangle(freq3 * factor) * tm)
waveform_detune = (sine_wave(
octave_freq * factor, detune_freq) * sm) + (
triangle(freq3 * factor, detune_freq) * tm)
waveform = ((waveform + waveform_detune) *
(waveform_mod / 2 + 0.5)) * 0.12 * volume
waveform[:np.size(attak)] *= attak
waveform[-np.size(fade):] *= 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
applying_sliders_flag[0] = True
label_apply.hide()
fm = 0.25
freq3 = 440.0
detune_freq = detune_slider.value()
duration = duration_slider.value() / 10
octave_freq = (2**octave_slider.value()) * 220
ramp_amount = ramp_slider.value() / 10
roll_amount = delay_slider.value()
st = shape_slider.value() / 20
volume = volume_slider.value() / 100
tm = st * 1.2
sm = 1 - st
attack_size = attack_slider.value() / 100
fade_size = fade_slider.value() / 100
if midi_on_flag[0] is True:
num_range = midi_range
keys[:] = note_nums[:]
else:
if key_change_bool[0] is True:
num_range = c_range
else:
num_range = e_range
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
fade_size = int(np.size(x) * fade_size)
attak = np.linspace(0, 1, int(np.size(x) * attack_size))
fade = np.linspace(1, 0, fade_size if fade_size >= 1 else 1)
notes = []
with ThreadPoolExecutor() as executer:
notes = list(executer.map(wave_maker, num_range))
global key_notes
key_notes = dict(zip(keys, notes))
applying_sliders_flag[0] = False
return key_notes
def changed_duration():
val = duration_slider.value() * 0.1
duration_val_label.setText(str('{:2.1f}'.format(val)))
duration_val_label.adjustSize()
show_apply()
def changed_detune():
val = detune_slider.value()
detune_val_label.setText(str('{:2.1f}'.format(val)))
detune_val_label.adjustSize()
show_apply()
def changed_octave():
"""update octave label"""
val = (2**octave_slider.value()) * 220
octave_val_label.setText(str(val))
octave_val_label.adjustSize()
show_apply()
def changed_ramp():
val = ramp_slider.value() * 0.1
ramp_val_label.setText(str('{:2.1f}'.format(val)))
ramp_val_label.adjustSize()
show_apply()
def changed_roll():
val = delay_slider.value()
delay_val_label.setText(str('{:4.3f}\nSeconds'.format(val / 48000)))
delay_val_label.adjustSize()
show_apply()
def changed_attack():
val = attack_slider.value() * 0.01
attack_val_label.setText(str('{:3.2f}'.format(val)))
attack_val_label.adjustSize()
show_apply()
def changed_fade():
val = fade_slider.value() * 0.01
fade_val_label.setText(str('{:3.2f}'.format(val)))
fade_val_label.adjustSize()
show_apply()
def changed_vol():
val = volume_slider.value() * 0.01
vol_val_label.setText(str("{:3.2f}".format(val)))
vol_val_label.adjustSize()
show_apply()
def show_apply():
label_apply.show()
def flagger():
global mono_flag
mono_flag = not mono_flag
if mono_flag is True:
mono_button.setText('Mono')
else:
mono_button.setText('Poly')
def change_key():
if midi_on_flag[0] is True:
return
else:
key_change_bool[0] = not key_change_bool[0]
if key_change_bool[0] is True:
keys[:] = c_keys[:]
key_button.setText('C4')
else:
keys[:] = e_keys[:]
key_button.setText('E4')
do_it_int16()
def open_custom_kb_dialog():
if midi_on_flag[0] is True:
return
else:
custom_kb_dialog = CustomKeybinder()
custom_kb_dialog.show()
def open_midi_dialog():
if midi_on_flag[0] is True:
print("Close cutie-dead-synth to change midi")
else:
midi_dialog = ChoosePort()
midi_dialog.show()
def midi_stuff(port_arg):
def play_note(event):
global key_notes
if applying_sliders_flag[0] is True:
return
else:
if mono_flag is True:
sa.stop_all()
try:
sound = key_notes.get(event)
buffer_object = sa.play_buffer(sound, 2, 2, sample_rate)
except TypeError:
return
if len(port_arg) > 1:
portname = port_arg
else:
portname = None # Use default port
with mido.open_input(portname) as port:
print('Using {}'.format(port))
print('Waiting for messages...')
for message in port:
if message.type == 'note_on' and message.velocity > 0:
play_note(message.note)
def activate_midi(port_arg):
midi_thread = threading.Thread(target=midi_stuff, args=[port_arg], daemon=True)
midi_thread.start()
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}")
QSS = """
/* Styling */
QSlider::groove:horizontal {
border-radius: 1px;
height: 3px;
margin: 0px;
background-color: #222610;
}
QSlider::groove:horizontal:hover {
background-color: #21e4e4;
}
QSlider::handle:horizontal {
background-color: #728C00;
border: none;
height: 40px;
width: 20px;
margin: -10px 0;
border-radius: 10px;
padding: -20px 0px;
}
QSlider::groove:vertical {
border-radius: 1px;
width: 3px;
margin: 0px;
background-color: #222610;
}
QSlider::groove:vertical:hover {
background-color: #21e4e4;
}
QSlider::handle:vertical {
background-color: #728C00;
border: none;
width: 40px;
height: 20px;
margin: 0px -10px;
border-radius: 10px;
padding: 0px -20px;
}
QSlider:focus {
background-color: #0ba4a4;
}
MainWindow, QWidget#ck_win, QWidget#midi_win {
background-color: #313734;
}
QWidget#l_label {
background-color: #0ba4a4;
color: #313734;
}
QLabel {
color: #82edd8;
font-size: 14px;
}
QPushButton {
background-color: #728C00;
}
QPushButton:hover {
background-color: #e7ff00;
}
QPushButton:focus {
border: 2px solid #0ba4a4;
border-radius: 3px;
}
"""
c_keys = ['a', 'w', 's', 'e', 'd', 'f', 't',
'g', 'y', 'h', 'u', 'j', 'k', 'o', 'l', 'p', ';', '\'']
e_keys = ['a', 's', 'e', 'd', 'r', 'f', 't',
'g', 'h', 'u', 'j', 'i', 'k', 'l', 'p', ';', '[', '\'']
note_nums = [i for i in range(36, 97)]
midi_on_flag = [False]
keys = []
keys[:] = e_keys[:]
c_range = range(-9, 9)
e_range = range(-5, 13)
midi_range = range(-33, 28)
key_change_bool = [False]
applying_sliders_flag = [True]
mono_flag = False
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}")
app = QApplication(sys.argv[:1])
app.setStyleSheet(QSS)
win = MainWindow()
win.setGeometry(20, 100, 700, 500)
win.setWindowIcon(QtGui.QIcon('images/knotperfect-icon.png'))
win.setWindowTitle('Not Perfect')
shortcut_quit = QShortcut(QKeySequence('Ctrl+Q'), win)
shortcut_quit.activated.connect(lambda: app.quit())
shortcut_apply = QShortcut(QKeySequence('Ctrl+A'), win)
shortcut_apply.activated.connect(do_it_int16)
apply_button = QPushButton('Apply Settings', win)
apply_button.move(10, 10)
apply_button.clicked.connect(do_it_int16)
shortcut_mono = QShortcut(QKeySequence('Ctrl+M'), win)
shortcut_mono.activated.connect(flagger)
mono_button = QPushButton('Poly', win)
mono_button.move(200, 10)
mono_button.clicked.connect(flagger)
shortcut_key = QShortcut(QKeySequence('Ctrl+K'), win)
shortcut_key.activated.connect(change_key)
key_button = QPushButton('E4', win)
key_button.move(10, 290)
key_button.clicked.connect(change_key)
midi_button = QPushButton('Open Midi Input', win)
midi_button.move(10, 340)
midi_button.clicked.connect(open_midi_dialog)
# duration
duration_label = QLabel(win)
duration_label.setText('Duration')
duration_label.move(10, 50)
set0 = 14
duration_slider = QSlider(Qt.Horizontal, win)
duration_slider.setMinimumWidth(200)
duration_slider.setMinimum(0)
duration_slider.setMaximum(50)
duration_slider.setValue(set0)
duration_slider.setSingleStep(2)
duration_slider.move(80, 50)
duration_slider.valueChanged.connect(changed_duration)
duration_val_label = QLabel(win)
duration_val_label.setText(str('{:2.1f}'.format(set0 * 0.1)))
duration_val_label.move(290, 50)
# detune
detune_label = QLabel(win)
detune_label.setText('Detune')
detune_label.move(10, 90)
set1 = 9
detune_slider = QSlider(Qt.Horizontal, win)
detune_slider.setMinimumWidth(200)
detune_slider.setMinimum(0)
detune_slider.setMaximum(13)
detune_slider.setSingleStep(1)
detune_slider.setValue(set1)
detune_slider.move(80, 90)
detune_slider.valueChanged.connect(changed_detune)
detune_val_label = QLabel(win)
detune_val_label.setText(str(set1))
detune_val_label.move(290, 90)
# octave
octave_label = QLabel(win)
octave_label.setText('Octave')
octave_label.move(10, 130)
set2 = 1
octave_slider = QSlider(Qt.Horizontal, win)
octave_slider.setMinimumWidth(13)
octave_slider.setMinimum(0)
octave_slider.setMaximum(4)
octave_slider.setValue(set2)
octave_slider.move(80, 130)
octave_slider.valueChanged.connect(changed_octave)
octave_val_label = QLabel(win)
octave_val_label.setText(str((2**set2) * 220))
octave_val_label.move(190, 130)
# ramp_0
ramp_label = QLabel(win)
ramp_label.setText('Ramp')
ramp_label.move(10, 170)
set3 = 5
ramp_slider = QSlider(Qt.Horizontal, win)
ramp_slider.setMinimumWidth(200)
ramp_slider.setMinimum(0)
ramp_slider.setMaximum(20)
ramp_slider.setSingleStep(1)
ramp_slider.setValue(set3)
ramp_slider.move(80, 170)
ramp_slider.valueChanged.connect(changed_ramp)
ramp_val_label = QLabel(win)
ramp_val_label.setText(str(set3 * 0.1))
ramp_val_label.move(290, 170)
# roll
delay_label = QLabel(win)
delay_label.setText('Delay')
delay_label.move(10, 210)
set4 = 4000
delay_slider = QSlider(Qt.Horizontal, win)
delay_slider.setMinimumWidth(200)
delay_slider.setMinimum(0)
delay_slider.setMaximum(4000)
delay_slider.setSingleStep(100)
delay_slider.setValue(set4)
delay_slider.move(80, 210)
delay_slider.valueChanged.connect(changed_roll)
delay_val_label = QLabel(win)
delay_val_label.setText(str('{:4.3f}\nSeconds'.format(set4 / 48000)))
delay_val_label.move(290, 204)
# shape
sine_label = QLabel(win)
sine_label.setText('Sine')
sine_label.move(10, 250)
shape_slider = QSlider(Qt.Horizontal, win)
shape_slider.setMinimumWidth(200)
shape_slider.setMinimum(0)
shape_slider.setMaximum(20)
shape_slider.setSingleStep(1)
shape_slider.setValue(0)
shape_slider.move(80, 250)
shape_slider.valueChanged.connect(show_apply)
# attack
attack_label = QLabel(win)
attack_label.setText("Attack")
attack_label.move(368, 20)
attack_slider = QSlider(Qt.Vertical, win)
attack_slider.setMinimumHeight(100)
attack_slider.setMinimum(0)
attack_slider.setMaximum(100)
attack_slider.setSingleStep(1)
attack_slider.setValue(0)
attack_slider.move(380, 50)
attack_slider.valueChanged.connect(changed_attack)
attack_val_label = QLabel(win)
attack_val_label.setText(str('{:3.2f}'.format(0)))
attack_val_label.move(377, 160)
# fade
fade_label = QLabel(win)
fade_label.setText("Fade")
fade_label.move(442, 20)
set_7 = 20
fade_slider = QSlider(Qt.Vertical, win)
fade_slider.setMinimumHeight(100)
fade_slider.setMinimum(0)
fade_slider.setMaximum(100)
fade_slider.setSingleStep(1)
fade_slider.setValue(set_7)
fade_slider.move(450, 50)
fade_slider.valueChanged.connect(changed_fade)
fade_val_label = QLabel(win)
fade_val_label.setText(str('{:3.2f}'.format(set_7 * 0.01)))
fade_val_label.move(447, 160)
# volume
volume_label = QLabel(win)
volume_label.setText("volume")
volume_label.move(526, 20)
set_vol = 70
volume_slider = QSlider(Qt.Vertical, win)
volume_slider.setMinimumHeight(100)
volume_slider.setMinimum(0)
volume_slider.setMaximum(100)
volume_slider.setSingleStep(1)
volume_slider.setValue(set_vol)
volume_slider.move(540, 50)
volume_slider.valueChanged.connect(changed_vol)
vol_val_label = QLabel(win)
vol_val_label.setText(str("{:3.2f}".format(set_vol * 0.01)))
vol_val_label.move(538, 160)
triangle_label = QLabel(win)
triangle_label.setText('Triangle')
triangle_label.move(290, 250)
keybind_button = QPushButton("Custom Keyboard", win)
keybind_button.move(200, 290)
keybind_button.clicked.connect(open_custom_kb_dialog)
wav_button = QPushButton("Save Notes as .wav Files", win)
wav_button.move(200, 390)
wav_button.clicked.connect(save_wav)
close_button = QPushButton('Close', win)
close_button.move(10, 390)
close_button.clicked.connect(lambda: app.quit())
label_pic = QLabel(win)
label_apply = QLabel(win)
# dce9e2
pixmap = QPixmap('images/knotpforQTsmall.png')
pixmap_1 = QPixmap('images/say_apply.png')
label_pic.setPixmap(pixmap)
label_pic.move(435, 180)
label_apply.setPixmap(pixmap_1)
label_apply.move(410, 181)
label_apply.hide()
do_it_int16()
win.show()
sys.exit(app.exec_())