diff --git a/audio_stuff/midi/midi_piano.py b/audio_stuff/midi/midi_piano.py index 8d214b4..7fe1e54 100644 --- a/audio_stuff/midi/midi_piano.py +++ b/audio_stuff/midi/midi_piano.py @@ -119,10 +119,12 @@ def send_midi_note(note, velocity=127, type="note_on"): ) button.grid(row=0, column=white_keys[i] + a * 12) - def press(event, i=i, a=a): + def press(event, button=button, i=i, a=a): + button.config(relief="sunken") return send_midi_note(white_keys[i] + a * 12, type="note_on") - def release(event, i=i, a=a): + def release(event, button=button, i=i, a=a): + button.config(relief="raised") return send_midi_note(white_keys[i] + a * 12, type="note_off") button.bind("", press) diff --git a/pythonaisynth/__main__.py b/pythonaisynth/__main__.py new file mode 100644 index 0000000..40e2b01 --- /dev/null +++ b/pythonaisynth/__main__.py @@ -0,0 +1,4 @@ +from .main import main + +if __name__ == "__main__": + main() diff --git a/pythonaisynth/_version.py b/pythonaisynth/_version.py index 8ea6122..df1fd0c 100644 --- a/pythonaisynth/_version.py +++ b/pythonaisynth/_version.py @@ -1 +1 @@ -version = "3.1.0" +version = "3.2.0" diff --git a/pythonaisynth/main.py b/pythonaisynth/main.py index 5ec6bf0..37b43ca 100644 --- a/pythonaisynth/main.py +++ b/pythonaisynth/main.py @@ -138,6 +138,8 @@ def __init__(self, *args, manager: SyncManager = None, **kwargs): self.graph.grid(row=1, column=0, columnspan=3, sticky="NSEW") self.create_controll_column() self.create_status_bar() + self.is_recording = False + # self.is_paused = False # sys.stdout = utils.QueueSTD_OUT(self.std_queue) # def destroy(self): @@ -327,6 +329,8 @@ def create_menu(self): ("Play Music from MIDI Port", self.play_music), ("Play Music from MIDI File", self.play_music_file), ("Play Example", self.play_example), + ("Record to File", self.start_recording), + ("Stop Recording", self.stop_recording), ], ) @@ -535,6 +539,23 @@ def play_example(self): # path = os.path.join(os.path.dirname(__file__), "fuer_elise.py") # subprocess.Popen(["python", path]) + def start_recording(self): + if self.synth and not self.is_recording: + file_path = filedialog.asksaveasfilename( + title="Save Recording", + defaultextension=".wav", + filetypes=(("WAV files", "*.wav"), ("All files", "*.*")), + ) + if file_path: + print(f"Recording will be saved to: {file_path}") + self.synth.start_recording(file_path) + self.is_recording = True + + def stop_recording(self): + if self.synth and self.is_recording: + self.synth.stop_recording() + self.is_recording = False + # for "invalid command" in the tk.TK().after() function when programm gets closed # def start_server(): diff --git a/pythonaisynth/music.py b/pythonaisynth/music.py index 1fd02c6..7ea8484 100644 --- a/pythonaisynth/music.py +++ b/pythonaisynth/music.py @@ -1,16 +1,23 @@ +import ctypes +import multiprocessing +import wave import mido import scipy import torch from .fourier_neural_network import FourierNN from pythonaisynth import utils import atexit -from multiprocessing import Process, Queue, current_process +from multiprocessing import Process, Queue, Value, current_process import sys from tkinter import filedialog import numpy as np import sounddevice as sd import pyaudio +START = 0 +STOP = 1 +PAUSE = 2 + def musik_from_file(fourier_nn: FourierNN): import sounddevice as sd @@ -275,7 +282,12 @@ def apply(self, sound): class Synth2: - def __init__(self, fourier_nn, stdout: Queue = None, port_name=None): + def __init__( + self, + fourier_nn, + stdout: Queue = None, + port_name=None, + ): self.stdout = stdout self.live_synth: Process = None self.notes_ready = False @@ -292,6 +304,16 @@ def __init__(self, fourier_nn, stdout: Queue = None, port_name=None): self.t_buffer = torch.tensor(t, dtype=torch.float32) print(self.t_buffer.shape) self.port_name = port_name + self.command_queue = multiprocessing.Queue() + + def start_recording(self, file_name): + self.command_queue.put((START, file_name)) + + def stop_recording(self): + self.command_queue.put((STOP,)) + + def pause_recording(self): + self.command_queue.put((PAUSE,)) def set_port_name(self, port_name): self.port_name = port_name @@ -350,9 +372,27 @@ def live_synth_loop(self): notes = {} model = self.fourier_nn.current_model.to(self.fourier_nn.device) self.play_init_sound() + output_file = None + is_recoding = False with mido.open_input(self.port_name) as midi_input: while True: + if not self.command_queue.empty(): + c = self.command_queue.get_nowait() + if is_recoding and not (output_file is None): + if c[0] == STOP: + output_file.close() + output_file = None + is_recoding = False + elif c[0] == PAUSE: + is_recoding = False + else: + if c[0] == START: + is_recoding = True + output_file = wave.open(c[1], "wb") + output_file.setnchannels(1) # Mono + output_file.setsampwidth(4) + output_file.setframerate(self.sample_rate) # for _ in utils.timed_loop(True): available_buffer = stream.get_write_available() if available_buffer == 0: @@ -423,8 +463,13 @@ def live_synth_loop(self): audio_data = normalize(audio_data) audio_data *= 1 # oscilating_amplitude audio_data = np.clip(audio_data, -1, 1) + audio_data = audio_data.astype(np.float32) + + if is_recoding: + output_file.writeframes(audio_data) + stream.write( - audio_data.astype(np.float32), + audio_data, available_buffer, exception_on_underflow=True, ) @@ -447,10 +492,9 @@ def run_live_synth(self): atexit.register(utils.DIE, self.live_synth, 0, 0) def __getstate__(self) -> object: - live_synth = self.live_synth - del self.live_synth Synth_dict = self.__dict__.copy() - self.live_synth = live_synth + del Synth_dict["live_synth"] + # del Synth_dict["command_queue"] return Synth_dict def __setstate__(self, state):