from PyQt6 import QtCore, QtGui from PyQt6.QtWidgets import ( QApplication, QWidget, QPushButton, QMainWindow, QVBoxLayout, QHBoxLayout, QGridLayout, QComboBox, QLabel, QFrame, QGridLayout, QTextEdit, QSpinBox, QSlider, QSizePolicy, QAbstractItemView, QScrollArea ) from PyQt6.QtCore import Qt from pyqtgraph import PlotWidget, plot import pyqtgraph as pg # import pyqtgraph.console from collections import Counter, namedtuple import sys import os import random # import pyaudio import ctcsound import numpy as np from scipy.fft import rfft, rfftfreq import time global_colors = {"background": "#e2e2e2", "blue": "#4fdfff", "pink": "#fc60ae", "yellow": "#eeea00", "purple": "#c600ee", "orange": "#ffa617", "green": "#8cff00"} class MainWindow(QWidget): def __init__(self, *args, **kwargs): super(QWidget, self).__init__(*args, **kwargs) self.setWindowTitle("Aya") self.setObjectName("main_window") self.resize(1000,600) self.modifier_list = [] self.shift_active = False # self.base_mode = Mode("Base") # self.control_mode = Mode("Control") # self.track_mode = Mode("Track") # self.base_mode = { # Qt.Key.Key_Q: { # "function": None, # "description": "Testing base mode." # } # } # print(self.base_mode[Qt.Key.Key_Q]["description"]) # Actions may need to be modified # Change the key they are triggered by # Whether they are repeatable when held down can be toggled by user # Suggests it should be class or dict, or just create a new namedtuple b/c you can't modify one # Initiate Csound self.csound = ctcsound.Csound() self.csound.compileCsd("aya.csd") self.csound.start() self.performance_thread = ctcsound.CsoundPerformanceThread(self.csound.csound()) self.performance_thread.play() self.y_minimum_ptr, _ = self.csound.channelPtr("meter_minimum", ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL) self.y_maximum_ptr, _ = self.csound.channelPtr("meter_maximum", ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL) self.playback_position, _ = self.csound.channelPtr("playback_position", ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL) self.playing_status, _ = self.csound.channelPtr("playing_status", ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL) main_grid_layout = QGridLayout() # self.graphWidget2 = pg.PlotWidget() # self.graphWidget2.setEnabled(False) # self.graphWidget2.setMinimumHeight(100) # self.graphWidget2.setMaximumHeight(200) # centralWidget = QWidget(objectName="central_widget") # centralWidget.setLayout(main_grid_layout) self.setLayout(main_grid_layout) # TRACKS PANEL self.tracks_panel = TracksPanel(self) self.previous_current_selection = None self.current_selection = QWidget() # CONTEXT PANEL self.context_panel = ContextPanel(self) # MODE PANEL self.mode_panel = ModePanel(self) main_grid_layout.addWidget(self.tracks_panel, 0, 0) main_grid_layout.addWidget(self.context_panel, 1, 0) main_grid_layout.addWidget(self.mode_panel, 2, 0) # CONTROL PANEL self.control_panel = ControlPanel(self) # OUTPUT PANEL self.output_panel = OutputPanel(self) main_grid_layout.addWidget(self.output_panel, 0, 1) main_grid_layout.addWidget(self.control_panel, 1, 1, 2, 1) main_grid_layout.setColumnStretch(0, 4) main_grid_layout.setColumnStretch(1, 1) main_grid_layout.setRowStretch(0, 5) main_grid_layout.setRowStretch(1, 1) main_grid_layout.setRowStretch(2, 2) # axis_length = 128 # self.x = [] # self.y_minimum = [] # self.y_maximum = [] # for i in range(axis_length): # self.x.append(i) # self.y_minimum.append(0) # self.y_maximum.append(0) # self.momentary_buffer_length = 512 # self.x_momentary_buffer = [] # for i in range(self.momentary_buffer_length): # self.x_momentary_buffer.append(i) # self.pen = pg.mkPen(color="#EC3490", width=4, capstyle="flatcap") # 2. Line plot # painter = QtGui.QPainter() # pen = QtGui.QPen() # pen.setWidth(40) # pen.setColor(QtGui.QColor('red')) # painter.setPen(pen) # painter.begin(self) # painter.drawRect(self.graphWidget2.getPlotItem().boundingRect()) # print(self.graphWidget2.getPlotItem().boundingRect()) # painter.end() # self.graphWidget2.setBackground("#3a2055") # self.graphWidget2.showGrid(x=False, y=False) # self.graphWidget2.setYRange(-1.5, 1.5, padding=0) # self.output_plot = self.graphWidget2.plot(self.x, self.y_minimum, pen=self.pen, fillLevel=0, brush="#EC3490") # self.output_plot2 = self.graphWidget2.plot(self.x, self.y_maximum, pen=self.pen, fillLevel=0, brush="#EC3490") # xaxis2 = self.graphWidget2.getAxis('bottom') # xaxis2.setStyle(showValues=False) # START/STOP button # self.button = QPushButton("START") # self.button.setCheckable(True) # self.button.clicked.connect(self.the_button_was_clicked) # leftColumn.addWidget(self.button) ## Timer to regularly update plot self.timer = QtCore.QTimer() self.timer.setInterval(17) self.timer.timeout.connect(self.update_widgets) # self.timer.start() Mode = namedtuple('Mode', 'name actions') Action = namedtuple( 'Action', 'name description function arguments shift_name shift_description shift_function shift_arguments', defaults=[None, None, None, None, None] ) self.base_mode = Mode( "Base", { Qt.Key.Key_Space: Action( # Space bar "Control mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Space ), Qt.Key.Key_Control: Action( # Command key "Command mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Control ), Qt.Key.Key_Meta: Action( # Control key "Context Selection mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Meta ), Qt.Key.Key_Alt: Action( # Option key "Alt mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Alt ), Qt.Key.Key_Shift: Action( # Shift key "Shift mode", "Add space bar to modifier list.", self.activate_shift, ), Qt.Key.Key_CapsLock: Action( "Toggle mode", "Toggles current mode on or off.", self.set_mode_hold, True ) } ) self.control_mode = Mode( "Control", { Qt.Key.Key_Space: Action( # Space bar "Control mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Space ), Qt.Key.Key_Control: Action( # Command key "Command mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Control ), Qt.Key.Key_Meta: Action( # Control key "Context Selection mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Meta ), Qt.Key.Key_Alt: Action( # Option key "Alt mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Alt ), Qt.Key.Key_Shift: Action( # Shift key "Shift mode", "Add space bar to modifier list.", self.activate_shift, ), Qt.Key.Key_Equal: Action( "Volume up", "Increase main volume.", self.control_panel.increment_volume, 2 ), Qt.Key.Key_Minus: Action( "Volume down", "Decrease main volume.", self.control_panel.increment_volume, -2 ), Qt.Key.Key_CapsLock: Action( "Toggle mode", "Toggles current mode on or off.", self.set_mode_hold, True ), Qt.Key.Key_M: Action( "Mute", "Mute main output", self.control_panel.mute_button.click ), Qt.Key.Key_R: Action( "Record", "Record main output", self.control_panel.record_button.click, None ) } ) self.track_mode = Mode( "Track", { Qt.Key.Key_Space: Action( # Space bar name = "Control mode", description = "Add space bar to modifier list.", function = self.add_modifier, arguments = Qt.Key.Key_Space ), Qt.Key.Key_Control: Action( # Command key "Command mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Control ), Qt.Key.Key_Meta: Action( # Control key "Context Selection mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Meta ), Qt.Key.Key_Alt: Action( # Option key "Alt mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Alt ), Qt.Key.Key_Shift: Action( # Shift key "Shift mode", "Add space bar to modifier list.", self.activate_shift, ), Qt.Key.Key_1: Action( "Select track 1", "Select track 1", self.setSelected, self.tracks_panel.track_list[0] ), Qt.Key.Key_Equal: Action( "Volume up", "Increase track volume.", self.tracks_panel.track_list[0].increment_volume, 2 ), Qt.Key.Key_Minus: Action( "Volume down", "Decrease track volume.", self.tracks_panel.track_list[0].increment_volume, -2 ), Qt.Key.Key_Return: Action( "Play track", "Play selection tracks.", self.tracks_panel.track_list[0].play_sound ), Qt.Key.Key_QuoteLeft: Action( "Volume 0", "Set track volume to 0.", self.update_volume, 0 ), Qt.Key.Key_CapsLock: Action( "Toggle mode", "Toggles current mode on or off.", self.set_mode_hold, True ), Qt.Key.Key_M: Action( "Mute", "Mute track.", self.tracks_panel.track_list[0].mute_button.click ), Qt.Key.Key_Q: Action( "Samples dropdown", "Open samples dropdown.", self.tracks_panel.track_list[0].samples_dropdown.showPopup ), Qt.Key.Key_A: Action( "Left boundary - backward large", "Move the left boundary backward.", self.tracks_panel.track_list[0].waveform_plot.move_boundary, ["left", "backward"], "Left boundary - backward small" ), Qt.Key.Key_S: Action( "Left boundary - forward large", "Move the left boundary forward.", self.tracks_panel.track_list[0].waveform_plot.move_boundary, ["left", "forward"], "Left boundary - forward small" ), Qt.Key.Key_D: Action( "Right boundary - backward large", "Move the right boundary backward.", self.tracks_panel.track_list[0].waveform_plot.move_boundary, ["right", "backward"], "Right boundary - backward small" ), Qt.Key.Key_F: Action( "Right boundary - forward large", "Move the right boundary forward.", self.tracks_panel.track_list[0].waveform_plot.move_boundary, ["right", "forward"], "Right boundary - forward small" ), Qt.Key.Key_Z: Action( "Both boundaries - backward large", "Move both boundaries backward.", self.tracks_panel.track_list[0].waveform_plot.move_boundary, ["both", "backward"], "Both boundaries - backward small" ), Qt.Key.Key_X: Action( "Both boundaries - forward large", "Move both boundaries forward.", self.tracks_panel.track_list[0].waveform_plot.move_boundary, ["both", "forward"], "Both boundaries - forward small" ), Qt.Key.Key_C: Action( "Zoom in", "Move into the boundary window.", self.tracks_panel.track_list[0].waveform_plot.zoom_in, ), Qt.Key.Key_V: Action( "Zoom out", "Move out of the boundary window.", self.tracks_panel.track_list[0].waveform_plot.zoom_out, ), Qt.Key.Key_N: Action( "New track", "Add new track.", self.tracks_panel.add_track2, ), Qt.Key.Key_BracketLeft: Action( "Previous track", "Select the previous track.", self.tracks_panel.select_previous_track ), Qt.Key.Key_BracketRight: Action( "Next track", "Select the next track.", self.tracks_panel.select_next_track ) } ) self.command_mode = Mode( "Command", { Qt.Key.Key_Space: Action( # Space bar "Control mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Space ), Qt.Key.Key_Control: Action( # Command key "Command mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Control ), Qt.Key.Key_Meta: Action( # Control key "Context Selection mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Meta ), Qt.Key.Key_Alt: Action( # Option key "Alt mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Alt ), Qt.Key.Key_Shift: Action( # Shift key "Shift mode", "Add space bar to modifier list.", self.activate_shift, ), Qt.Key.Key_CapsLock: Action( "Toggle mode", "Toggles current mode on or off.", self.set_mode_hold, True ) } ) self.alt_mode = Mode( "Alt", { Qt.Key.Key_Space: Action( # Space bar "Control mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Space ), Qt.Key.Key_Control: Action( # Command key "Command mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Control ), Qt.Key.Key_Meta: Action( # Control key "Context Selection mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Meta ), Qt.Key.Key_Alt: Action( # Option key "Alt mode", "Add space bar to modifier list.", self.add_modifier, Qt.Key.Key_Alt ), Qt.Key.Key_Shift: Action( # Shift key "Shift mode", "Add space bar to modifier list.", self.activate_shift, ), Qt.Key.Key_CapsLock: Action( "Toggle mode", "Toggles current mode on or off.", self.set_mode_hold, True ) } ) # self.shift_mode = Mode( # "Shift", { # Qt.Key.Key_Space: Action( # Space bar # "Control mode", # "Add space bar to modifier list.", # self.add_modifier, # Qt.Key.Key_Space # ), # Qt.Key.Key_Control: Action( # Command key # "Command mode", # "Add space bar to modifier list.", # self.add_modifier, # Qt.Key.Key_Control # ), # Qt.Key.Key_Meta: Action( # Control key # "Context Selection mode", # "Add space bar to modifier list.", # self.add_modifier, # Qt.Key.Key_Meta # ), # Qt.Key.Key_Alt: Action( # Option key # "Alt mode", # "Add space bar to modifier list.", # self.add_modifier, # Qt.Key.Key_Alt # ), # Qt.Key.Key_Shift: Action( # Shift key # "Shift mode", # "Add space bar to modifier list.", # self.add_modifier, # Qt.Key.Key_Shift # ), # Qt.Key.Key_CapsLock: Action( # "Toggle mode", # "Toggles current mode on or off.", # self.set_mode_hold, # True # ) # } # ) self.current_mode = self.base_mode self.mode_hold = False print((self.mode_hold)) def set_mode_hold(self, state): self.mode_hold = state print(self.mode_hold) def activate_shift(self): self.shift_active = True self.console_message(f'Shift active: {self.shift_active}') def deactivate_shift(self): self.shift_active = False self.console_message(f'Shift active: {self.shift_active}') def set_volume(self, volume): pass def setSelected(self, selection): print(self.previous_current_selection) print(self.current_selection) if self.current_selection != selection: self.previous_current_selection = self.current_selection print(self.previous_current_selection) self.current_selection = selection print(self.current_selection) self.current_selection.setProperty("class", "selected") self.previous_current_selection.setProperty("class", "") self.setStyleSheet(self.styleSheet()) print(self.previous_current_selection) print(self.current_selection) def keyPressEvent(self, event): print(f'Pressed: {event.key()}') try: action = self.current_mode.actions[event.key()] if self.shift_active == False: if action.arguments == None: action.function() else: action.function(action.arguments) else: if action.shift_function == None: if action.shift_arguments == None and action.arguments != None: action.function(action.arguments) if action.shift_arguments == None and action.arguments == None: action.function() if action.shift_arguments != None: action.function(action.shift_arguments) if action.shift_function != None: if action.shift_arguments == None and action.arguments != None: action.shift_function(action.arguments) if action.shift_arguments == None and action.arguments == None: action.shift_function() if action.shift_arguments != None: action.shift_function(action.shift_arguments) except Exception as e: self.console_message(str(e)) print(e) match event.key(): # case Qt.Key.Key_QuoteLeft: # self.control_volume_slider.setValue(0) # self.update_main_volume(0) case Qt.Key.Key_1: if [Qt.Key.Key_Meta] == self.modifier_list: if self.tracks_panel.track_list[0]: self.setSelected(self.tracks_panel.track_list[0]) else: self.console_message("Track 1 does not exist.") else: self.control_volume_slider.setValue(10) self.update_main_volume(10) case Qt.Key.Key_2: if [Qt.Key.Key_Meta] == self.modifier_list: try: self.tracks_panel.track_list[1] self.setSelected(self.tracks_panel.track_list[1]) except: self.console_message("Track 2 does not exist.") else: self.control_volume_slider.setValue(20) self.update_main_volume(20) case Qt.Key.Key_3: self.control_volume_slider.setValue(30) self.update_main_volume(30) case Qt.Key.Key_4: self.control_volume_slider.setValue(40) self.update_main_volume(40) case Qt.Key.Key_5: self.control_volume_slider.setValue(50) self.update_main_volume(50) case Qt.Key.Key_6: self.control_volume_slider.setValue(60) self.update_main_volume(60) case Qt.Key.Key_7: self.control_volume_slider.setValue(70) self.update_main_volume(70) case Qt.Key.Key_8: self.control_volume_slider.setValue(80) self.update_main_volume(80) case Qt.Key.Key_9: self.control_volume_slider.setValue(90) self.update_main_volume(90) case Qt.Key.Key_0: self.control_volume_slider.setValue(100) self.update_main_volume(100) def keyReleaseEvent(self, event): print(f'Released: {event.key()}') match event.key(): case Qt.Key.Key_Space: # self.setSelected(self.previous_current_selection) self.remove_modifier(event.key()) case Qt.Key.Key_Meta: # Control self.remove_modifier(event.key()) case Qt.Key.Key_Control: # Command self.remove_modifier(event.key()) case Qt.Key.Key_Alt: # Option self.remove_modifier(event.key()) case Qt.Key.Key_Shift: # Shift self.deactivate_shift() case Qt.Key.Key_CapsLock: self.set_mode_hold(False) self.set_mode(self.modifier_list) def add_modifier(self, key): if key not in self.modifier_list: self.modifier_list.append(key) self.modifier_list.sort() print(f'Modifier_list: {self.modifier_list}') self.set_mode(self.modifier_list) def remove_modifier(self, key): if key in self.modifier_list: self.modifier_list.remove(key) print(f'Modifier_list: {self.modifier_list}') if self.mode_hold == False: self.set_mode(self.modifier_list) def set_mode(self, modifier_list): # Compare modifier_list to different combinations # if self.mode_hold == False: match modifier_list: case []: self.current_mode = self.base_mode case [Qt.Key.Key_Space]: self.current_mode = self.control_mode case [Qt.Key.Key_Meta]: # Control self.current_mode = self.track_mode case [Qt.Key.Key_Control]: # Command self.current_mode = self.command_mode # case [Qt.Key.Key_Shift]: # self.current_mode = self.shift_mode case [Qt.Key.Key_Alt]: self.current_mode = self.alt_mode self.mode_panel.mode_label.setText(self.current_mode.name) # def eventFilter(self, object, event): # if object == target and event.type() == QEvent.KeyPress: # keyEvent = QKeyEvent(event) # if keyEvent.key() == Qt.Key.Key_Tab: # # Special tab handling # return True # else: # return False # return False def the_button_was_clicked(self, checked): # print("Clicked! Checked?", checked) if checked: self.button.setText("STOP") # self.performance_thread.play() # self.counter = 1 self.performance_thread.scoreEvent(False, "i", [self.cs_instrument.currentIndex() + 1, 0, -1, self.cs_sample.currentIndex() + 2]) self.start_time = time.time() self.timer.start() # self.csoundListener() # self.oscListener() else: self.button.setText("START") # self.performance_thread.pause() self.performance_thread.scoreEvent(False, "i", [-(self.cs_instrument.currentIndex() + 1), 0, 1, self.cs_sample.currentIndex() + 2]) self.timer.stop() # self.stopCsoundListener() # self.stopOSCListener() def update_volume(self, volume): if self.control_mute_button.isChecked() == False: self.csound.setControlChannel("main_volume", volume/100) self.console_message(f'Main volume: {self.control_volume_slider.value()/100}') self.main_volume_display.setText(f'{self.control_volume_slider.value()/100}') def console_message(self, message): self.output_panel.console.append(message) self.output_panel.console.verticalScrollBar().setValue(self.output_panel.console.verticalScrollBar().maximum()) def update_widgets(self): meter_maximum = self.y_maximum_ptr[0] self.control_panel.volume_meter.trigger_update(meter_maximum) self.tracks_panel.track_list[0].waveform_plot.update() class TracksPanel(QScrollArea): def __init__(self, main_widget, *args, **kwargs): super().__init__(*args, **kwargs) self.main_widget = main_widget # self.scroll_area = QScrollArea() self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setWidgetResizable(True) # self.setWidget(self) container = QWidget() self.setWidget(container) self.vbox = QVBoxLayout(container) self.vbox.layout().addWidget(QLabel("TRACKS"), 0, Qt.AlignmentFlag.AlignTop) # container.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) self.track_list = [] self.add_track2() container.layout().addStretch() # self.setLayout(QVBoxLayout()) # self.layout().addWidget(QLabel("TRACKS"), 0, Qt.AlignmentFlag.AlignTop) # self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) # self.track_list = [] # self.add_track() # self.layout().addStretch() def add_track(self): self.track_list.append(Track(self.main_widget)) self.layout().addWidget(self.track_list[-1]) def add_track2(self): self.track_list.append(Track(self.main_widget, len(self.track_list) + 1)) self.vbox.addWidget(self.track_list[-1]) def select_previous_track(self): pass def select_next_track(self): pass class Track(QFrame): def __init__(self, main_widget, track_number, *args, **kwargs): super().__init__(*args, **kwargs) self.main_widget = main_widget self.playing = False self.setMaximumHeight(200) self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) self.layout = QGridLayout() self.setLayout(self.layout) self.track_number = track_number self.track_number_label = QLabel(f'[{self.track_number}]') self.layout.addWidget(self.track_number_label, 0, 0) self.volume_label = QLabel("Vol") self.layout.addWidget(self.volume_label, 1, 0) self.pan_label = QLabel("Pan") self.layout.addWidget(self.pan_label, 2, 0) self.samples_dropdown = QComboBox() self.sample_list = self.load_samples() self.samples_dropdown.addItems(self.sample_list) self.samples_dropdown.currentIndexChanged.connect(self.update_track_sample) self.layout.addWidget(self.samples_dropdown, 0, 1) self.volume_slider = QSlider(Qt.Orientation.Horizontal) self.volume_slider.setRange(0, 100) self.layout.addWidget(self.volume_slider, 1, 1) self.pan_slider = QSlider(Qt.Orientation.Horizontal) self.pan_slider.setRange(0, 100) self.pan_slider.setValue(50) self.layout.addWidget(self.pan_slider, 2, 1) self.loop_button = QPushButton("Loop") self.loop_button.setCheckable(True) self.layout.addWidget(self.loop_button, 0, 2) self.mute_button = QPushButton("Mute") self.mute_button.setCheckable(True) self.layout.addWidget(self.mute_button, 1, 2) self.solo_button = QPushButton("Solo") self.solo_button.setCheckable(True) self.layout.addWidget(self.solo_button, 2, 2) self.waveform_plot = WaveformPlot(self.main_widget, 1) self.layout.addWidget(self.waveform_plot, 0, 3, 3, 1) self.layout.setColumnStretch(3, 2) def load_samples(self): table_code = "" count = 1 sample_list = sorted(os.listdir("samples")) for filename in sample_list: table_code = table_code + f'gisample{count} ftgen {count}, 0, 0, 1, "samples/{filename}", 0, 0, 1\n' count += 1 print(table_code) result = self.main_widget.csound.compileOrc(table_code) print(result) return sample_list def play_sound(self): if self.main_widget.tracks_panel.track_list[0].playing == False: #play sample self.main_widget.console_message("Track 1: playing") self.main_widget.csound.setControlChannel("function_table", self.samples_dropdown.currentIndex() + 1) self.main_widget.performance_thread.scoreEvent(False, "i", [1, 0, -1]) self.main_widget.start_time = time.time() self.main_widget.timer.start() # self.track.waveform_plot.playing = True print("Play sound") else: #stop sample # hit return -> waveform_plot.update() self.main_widget.console_message("Track 1: stopped") # self.waveform_plot.reset_scrubber() # self.main_widget.performance_thread.scoreEvent(False, "i", [-1, 0, 1]) self.main_widget.performance_thread.scoreEvent(False, "i", [2, 0, 0.1, 1]) self.waveform_plot.reset_scrubber() self.waveform_plot.reset_phase() # self.main_widget.timer.stop() # self.playing = False # self.waveform_plot.update() print("Stop sound") def update_track_sample(self, index): self.waveform_plot.change_sample(index + 1) self.main_widget.csound.setControlChannel("function_table", index + 1) self.waveform_plot.update() def increment_volume(self, increment): previous_value = self.main_widget.current_selection.volume_slider.value() self.main_widget.current_selection.volume_slider.setValue(self.main_widget.current_selection.volume_slider.value() + increment) if previous_value != self.main_widget.current_selection.volume_slider.value(): self.main_widget.console_message(f'Track {self.main_widget.current_selection.track_number} volume: {self.main_widget.current_selection.volume_slider.value()}') class WaveformPlot(QWidget): def __init__(self, main_widget, function_table, *args, **kwargs): super().__init__(*args, **kwargs) self.setSizePolicy( QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding ) self.main_widget = main_widget self.number_plot_bars = 90 self.padding = 10 self.padding_top = 15 self.padding_bottom = 30 self.boundary_width = 10 self.scrubber_width = 6 # sample_table_original = list of sample values from Csound function table # boundary_left_px = pixel offset from left waveform edge # boundary_right_px = pixel offset from right waveform edge, negative value # boundary_left/right_table = boundary offset pixel values converted to table index of Csound table # boundary_l/r_table_zoom = previous window edges when zoomed in table index form # scrubber_position = pixel offset from left window edge to display Csound playhead of sample self.sample_table_original = self.main_widget.csound.table(function_table) self.boundary_left = 0 self.boundary_left_table = 0 self.boundary_left_table_previous = 0 self.main_widget.csound.setControlChannel("boundary_left", 0) self.boundary_right = 0 self.boundary_right_table = len(self.sample_table_original) self.boundary_right_table_previous = len(self.sample_table_original) self.main_widget.csound.setControlChannel("boundary_right", len(self.sample_table_original)) self.scrubber_offset = 0 self.zoom = 1.0 self.zoom_list = [] self.initialized = False self.calculate_waveform_plot() def sizeHint(self): return QtCore.QSize(500,100) def resizeEvent(self, event): self.resize_boundaries(event) def paintEvent(self, e): self.previous_playing = self.main_widget.tracks_panel.track_list[0].playing self.main_widget.tracks_panel.track_list[0].playing = self.main_widget.playing_status[0] if self.previous_playing - self.main_widget.tracks_panel.track_list[0].playing == 1: self.reset_scrubber() self.reset_phase() if self.main_widget.tracks_panel.track_list[0].playing: self.scrubber_offset = self.main_widget.playback_position[0] else: self.scrubber_offset = self.boundary_left_table painter = QtGui.QPainter(self) brush = QtGui.QBrush() # Draw background if self.main_widget.playing_status == True: brush.setColor(QtGui.QColor(global_colors["background"])) else: brush.setColor(QtGui.QColor(global_colors["background"])) # brush.setColor(QtGui.QColor("#f0f0f0")) window_width = painter.device().width() window_height = painter.device().height() if self.initialized == False: self.boundary_right = window_width - self.padding * 2 self.initialized = True brush.setStyle(Qt.BrushStyle.SolidPattern) rect = QtCore.QRect(0, 0, window_width, window_height) painter.fillRect(rect, brush) if self.main_widget.playing_status == True: brush.setColor(QtGui.QColor("#F6F6F6")) rect = QtCore.QRectF( self.boundary_left + self.padding, self.padding_top, self.boundary_right - self.boundary_left, window_height - self.padding_bottom - self.padding_top ) painter.fillRect(rect, brush) # Draw plot bars bar_interval_size = (window_width - self.padding * 2) / self.number_plot_bars bar_width = bar_interval_size * 0.6 bar_spacer = bar_interval_size * 0.4 / 2 center_point_height = (window_height - self.padding_bottom - self.padding_top) / 2 center_point = center_point_height + self.padding_top brush = QtGui.QBrush() if self.main_widget.tracks_panel.track_list[0].playing == False: brush.setColor(QtGui.QColor(global_colors["pink"])) else: brush.setColor(QtGui.QColor(global_colors["pink"])) brush.setStyle(Qt.BrushStyle.SolidPattern) for n in range(self.number_plot_bars): height_top = self.data_top_bars[n] * (center_point_height * 0.85) if height_top < 1: height_top = 1 height_bottom = -self.data_top_bars[n] * (center_point_height * 0.85) if height_bottom > -1: height_bottom = -1 rect_bar_top = QtCore.QRectF( n * bar_interval_size + bar_spacer + self.padding, center_point, bar_width, height_top ) painter.fillRect(rect_bar_top, brush) rect_bar_bottom = QtCore.QRectF( n * bar_interval_size + bar_spacer + self.padding, center_point, bar_width, height_bottom ) painter.fillRect(rect_bar_bottom, brush) # Draw window boundaries and scrubber brush.setColor(QtGui.QColor(global_colors["blue"])) painter.setOpacity(0.5) rect_left_boundary = QtCore.QRectF( self.boundary_left + self.padding, self.padding_top, -self.boundary_width, window_height - self.padding_bottom - self.padding_top ) painter.fillRect(rect_left_boundary, brush) rect_right_boundary = QtCore.QRectF( self.boundary_right + self.padding, self.padding_top, self.boundary_width, window_height - self.padding_bottom - self.padding_top ) painter.fillRect(rect_right_boundary, brush) painter.setOpacity(0.8) brush.setColor(QtGui.QColor(global_colors["orange"])) rect_scrubber = QtCore.QRectF( ((self.scrubber_offset - self.boundary_left_table_previous)/ self.sample_table_window_length) * (window_width - self.padding * 2) + self.padding - self.scrubber_width, self.padding_top + (window_height - self.padding_top - self.padding_bottom) / 15, self.scrubber_width, window_height - self.padding_bottom - self.padding_top - (window_height - self.padding_top - self.padding_bottom) * 2 / 15 ) painter.fillRect(rect_scrubber, brush) # Draw positional data pen = QtGui.QPen() pen.setWidth(1) pen.setColor(QtGui.QColor(global_colors["purple"])) painter.setPen(pen) font = QtGui.QFont() font.setFamily('DinaRemasterII') font.setBold(True) font.setPointSize(13) painter.setFont(font) window_span = (self.boundary_right_table - self.boundary_left_table) / 44100 painter.drawText( self.padding + int((self.boundary_right - self.boundary_left) / 2) - 50 + int(self.boundary_left), 3, 100, 12, Qt.AlignmentFlag.AlignCenter, f'{round(window_span, 2)}s') painter.drawText(self.padding + 10, window_height - 13, f'left: {self.boundary_left}') painter.drawText(self.padding + 10, window_height - 3, f'right: {self.boundary_right}') painter.drawText(self.padding + 100, window_height - 13, f'left_table: {self.boundary_left_table}') painter.drawText(self.padding + 100, window_height - 3, f'right_table: {self.boundary_right_table}') # painter.drawText(self.padding + 240, window_height - 13, f'left_table_previous: {self.boundary_left_table_previous}') # painter.drawText(self.padding + 240, window_height - 3, f'right_table_previous: {self.boundary_right_table_previous}') # painter.drawText(self.padding + 240, window_height - 13, f'playing: {self.main_widget.tracks_panel.track_list[0].playing}') painter.drawText(self.padding + 240, window_height - 13, f'zoom: x {self.zoom}') painter.drawText(self.padding + 240, window_height - 3, f'scrubber: {int(self.scrubber_offset)}') def calculate_waveform_plot(self): sample_table_window_check = self.sample_table_original[self.boundary_left_table:self.boundary_right_table] sample_table_window_length_check = len(sample_table_window_check) samples_per_bar_check = sample_table_window_length_check / self.number_plot_bars if samples_per_bar_check > self.number_plot_bars * 2: self.sample_table_window = sample_table_window_check self.sample_table_window_length = sample_table_window_length_check self.samples_per_bar = samples_per_bar_check self.data_top_bars = [] self.data_bottom_bars = [] bar_max = 0 bar_min = 0 sample_count = 0 for sample in self.sample_table_window: if sample_count < self.samples_per_bar: if sample > bar_max: bar_max = sample if sample < bar_min: bar_min = sample sample_count += 1 else: self.data_top_bars.append(bar_max) self.data_bottom_bars.append(bar_min) sample_count = 0 bar_max = 0 bar_min = 0 if sample_count > 0: self.data_top_bars.append(bar_max) self.data_bottom_bars.append(bar_min) print(f'Sample table window length: {self.sample_table_window_length}') print(f'Samples per bar: {self.samples_per_bar}') return True else: return False def move_boundary(self, args): window, direction = args[0], args[1] if self.main_widget.shift_active == False: increment = 10 else: increment = 1 print(window, direction, increment) # Change window position window_width = self.frameGeometry().width() - self.padding * 2 direction = 1 if direction == "forward" else -1 increment = increment * direction if window == "left": self.boundary_left += increment print(self.boundary_left) if self.boundary_left < 0: self.boundary_left = 0 if self.boundary_left >= self.boundary_right: self.boundary_left = self.boundary_right - 1 if self.main_widget.tracks_panel.track_list[0].playing == False: self.scrubber_offset = int(self.boundary_left/window_width * self.sample_table_window_length + self.boundary_left_table_previous) # self.reset_scrubber() self.reset_phase() elif window == "right": self.boundary_right += increment if self.boundary_right > window_width - self.padding: self.boundary_right = window_width if self.boundary_right <= self.boundary_left: self.boundary_right = self.boundary_left + 1 elif window == "both": self.boundary_left += increment self.boundary_right += increment if direction == 1: if self.boundary_right > window_width: self.boundary_left = self.boundary_left + window_width - self.boundary_right self.boundary_right = window_width else: if self.boundary_left < 0: self.boundary_right -= self.boundary_left self.boundary_left = 0 self.convert_px_to_table_index(window_width) print(f'window_width = {window_width}') print(f'boundary_left = {self.boundary_left}') print(f'boundary_right = {self.boundary_right}') print(f'boundary_left_table = {self.boundary_left_table}') print(f'boundary_right_table = {self.boundary_right_table}') print(f'boundary_left_table_previous = {self.boundary_left_table_previous}') print(f'boundary_right_table_previous = {self.boundary_right_table_previous}') print(f'zoom_list = {self.zoom_list}') print(f'scrubber_offset = {self.scrubber_offset}') self.update() # print(f'Left offset = {self.boundary_left + self.padding}') # print(f'Right offset = {self.boundary_right + window_width - self.padding}') def convert_px_to_table_index(self, window_width): self.boundary_left_table = int(self.boundary_left/window_width * self.sample_table_window_length + self.boundary_left_table_previous) self.main_widget.csound.setControlChannel("boundary_left", self.boundary_left_table) self.boundary_right_table = int(self.boundary_right/window_width * self.sample_table_window_length + self.boundary_left_table_previous) self.main_widget.csound.setControlChannel("boundary_right", self.boundary_right_table) def reset_scrubber(self): self.scrubber_offset = self.boundary_left_table print(f'Scrubber reset: {self.scrubber_offset}') def resize_boundaries(self, event): self.boundary_left = (self.boundary_left/(event.oldSize().width() - self.padding * 2) * (event.size().width() - self.padding * 2)) self.boundary_right = (self.boundary_right/(event.oldSize().width() - self.padding * 2) * (event.size().width() - self.padding * 2)) print(f'old size: {event.oldSize().width()}') print(f'new size: {event.size().width()}') print(f'boundary left: {self.boundary_left}') print(f'boundary right: {self.boundary_right}') def zoom_in(self): if self.boundary_left == 0 and self.boundary_right == self.frameGeometry().width() - self.padding * 2: self.main_widget.console_message("Window hasn't changed") else: result = self.calculate_waveform_plot() if result == True: # Store current left and right table boundaries self.zoom_list.append((self.boundary_left_table_previous, self.boundary_right_table_previous)) self.boundary_left_table_previous = self.boundary_left_table self.boundary_right_table_previous = self.boundary_right_table # Calculate new plot bars # Determine if there are at least number_plot_bars table values between boundaries self.boundary_left = 0 self.boundary_right = self.frameGeometry().width() - self.padding * 2 self.convert_px_to_table_index(self.frameGeometry().width() - self.padding * 2) self.reset_scrubber() self.main_widget.console_message("Track 1: zoomed in") self.calculate_zoom_amount() self.update() else: self.main_widget.console_message("Maximum zoom!") def zoom_out(self): if self.zoom_list: previous_zoom_window = self.zoom_list.pop() self.boundary_left_table = previous_zoom_window[0] self.boundary_right_table = previous_zoom_window[1] self.boundary_left_table_previous = self.zoom_list[-1][0] if self.zoom_list else 0 self.boundary_right_table_previous = self.zoom_list[-1][1] if self.zoom_list else len(self.sample_table_original) self.calculate_waveform_plot() self.boundary_left = 0 self.boundary_right = self.frameGeometry().width() - self.padding * 2 self.convert_px_to_table_index(self.frameGeometry().width() - self.padding * 2) self.reset_scrubber() self.calculate_zoom_amount() self.update() self.main_widget.console_message("Track 1: zoomed out") else: self.main_widget.console_message("Already zoomed out") def calculate_zoom_amount(self): self.zoom = round(len(self.sample_table_original)/self.sample_table_window_length, 2) def change_sample(self, function_table): # Calculate # If previous sample was zoomed in, zoom all the way out in new sample # scrubber_offset / total length of previous sample * length of new sample # if track is playing, reset phase to proportional to new sample # if track is not playing, reset phase to boundary_left sample_table_previous = self.sample_table_original self.sample_table_original = self.main_widget.csound.table(function_table) if self.main_widget.tracks_panel.track_list[0].playing == True: self.scrubber_offset = int(self.scrubber_offset / len(sample_table_previous) * len(self.sample_table_original)) else: self.scrubber_offset = 0 self.reset_phase() # send new scrubber_offset to Csound # Convert boundary_left_table of previous sample to new sample # Reset zoom_list # self.boundary_left_table = int(self.boundary_left_table / len(sample_table_previous) * len(self.sample_table_original)) # self.boundary_right_table = int(self.boundary_right_table / len(sample_table_previous) * len(self.sample_table_original)) # self.boundary_left_table_previous = int(self.boundary_left_table_previous / len(sample_table_previous) * len(self.sample_table_original)) # self.boundary_right_table_previous = int(self.boundary_right_table_previous / len(sample_table_previous) * len(self.sample_table_original)) self.boundary_left = 0 self.boundary_right = self.frameGeometry().width() - self.padding * 2 self.boundary_left_table = 0 self.boundary_right_table = len(self.sample_table_original) self.main_widget.csound.setControlChannel("boundary_left", self.boundary_left_table) self.main_widget.csound.setControlChannel("boundary_right", self.boundary_right_table) self.zoom_list = [] # self.boundary_left = 0 # self.boundary_left_table = 0 # self.boundary_left_table_previous = 0 # self.main_widget.csound.setControlChannel("boundary_left", 0) # self.boundary_right = 0 # self.boundary_right_table = len(self.sample_table_original) # self.boundary_right_table_previous = len(self.sample_table_original) # self.main_widget.csound.setControlChannel("boundary_right", len(self.sample_table_original)) # self.scrubber_offset = 0 # self.zoom_list = [] self.calculate_waveform_plot() def reset_phase(self): self.main_widget.csound.setControlChannel("phase_reset", self.scrubber_offset) class ContextPanel(QFrame): def __init__(self, main_widget, *args, **kwargs): super().__init__(*args, **kwargs) self.main_widget = main_widget layout = QVBoxLayout() layout.addWidget(QLabel("CONTEXT"), 0, Qt.AlignmentFlag.AlignTop) layout.addStretch() self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) # self.setProperty("class", "blue_border") self.setLayout(layout) class ModePanel(QFrame): def __init__(self, main_widget, *args, **kwargs): super().__init__(*args, **kwargs) self.main_widget = main_widget layout = QVBoxLayout() layout.addWidget(QLabel("MODE"), 0, Qt.AlignmentFlag.AlignTop) self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) # self.setProperty("class", "blue_border") self.setLayout(layout) self.mode_label = QLabel("Base Mode") layout.addWidget(self.mode_label, 0, Qt.AlignmentFlag.AlignTop) class ControlPanel(QFrame): def __init__(self, main_widget, *args, **kwargs): super().__init__(*args, **kwargs) self.main_widget = main_widget layout = QVBoxLayout() top_layout = QHBoxLayout() volume_layout = QVBoxLayout() buttons_layout = QVBoxLayout() layout.addWidget(QLabel("CONTROL"), 0, Qt.AlignmentFlag.AlignTop) layout.addLayout(top_layout) self.volume_meter = VolumeMeter(30) top_layout.addWidget(self.volume_meter) top_layout.addLayout(volume_layout) top_layout.addLayout(buttons_layout) self.setLayout(layout) # Main volume slide self.volume_slider = QSlider() self.volume_slider.setRange(0, 100) self.volume_slider.setValue(0) volume_layout.addWidget(self.volume_slider) self.main_volume_display = QLabel("0.0") volume_layout.addWidget(self.main_volume_display) # Buttons self.mute_button = QPushButton("Mute") self.mute_button.setCheckable(True) self.mute_button.clicked.connect(self.mute_button_pressed) self.record_button = QPushButton("Rec") self.record_button.setCheckable(True) self.record_button.clicked.connect(self.record_button_pressed) buttons_layout.addWidget(self.mute_button) buttons_layout.addWidget(self.record_button) def increment_volume(self, increment): previous_value = self.volume_slider.value() self.volume_slider.setValue(self.volume_slider.value() + increment) self.main_widget.csound.setControlChannel("main_volume", self.volume_slider.value()/100) if self.volume_slider.value() != previous_value: self.main_volume_display.setText(str(self.volume_slider.value())) self.main_widget.console_message(f'Main volume: {self.volume_slider.value()}') def mute_button_pressed(self): if self.mute_button.isChecked() == True: self.main_widget.csound.setControlChannel("main_volume", 0) self.main_widget.console_message("Main: muted") else: self.main_widget.csound.setControlChannel("main_volume", self.volume_slider.value()/100) self.main_widget.console_message("Main: unmuted") def record_button_pressed(self): if self.record_button.isChecked() == True: # Tell Csound to start recording self.main_widget.console_message("Record: started") else: # Tell Csound to stop recording self.main_widget.console_message("Record: stopped") class VolumeMeter(QWidget): def __init__(self, n_bars, *args, **kwargs): super().__init__(*args, **kwargs) self.setSizePolicy( QSizePolicy.Policy.Minimum, QSizePolicy.Policy.MinimumExpanding ) self.minimum = 0 self.maximum = 150 self.n_bars = n_bars self.meter_maximum = 0 self.border = 4 self.padding = 10 def sizeHint(self): return QtCore.QSize(90,100) def paintEvent(self, e): painter = QtGui.QPainter(self) brush = QtGui.QBrush() brush.setColor(QtGui.QColor(global_colors["blue"])) brush.setStyle(Qt.BrushStyle.SolidPattern) height = painter.device().height() width = painter.device().width() # Get meter data from Csound # meter_maximum = self.y_maximum_ptr[0] # # meter_minimum = self.csound.controlChannel("meter_minimum")[0] # meter_minimum = self.y_minimum_ptr[0] # self.control_meter_left = (int(meter_maximum*100)) # self.control_meter_right = (int(meter_maximum*100)) # Draw background rect = QtCore.QRect(0, 0, painter.device().width(), painter.device().height()) # painter.fillRect(rect, brush) # Draw rectangle outline # pen = QtGui.QPen() # pen.setWidth(self.border) # pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) # pen.setColor(QtGui.QColor(global_colors["blue"])) # painter.setPen(pen) meter_outline = QtCore.QRectF( 0, 0, width / 2 - self.padding / 2, height) painter.fillRect(meter_outline, brush) meter_outline = QtCore.QRectF( width / 2 + self.padding / 2, 0, width - self.padding / 2, height) painter.fillRect(meter_outline, brush) brush.setColor(QtGui.QColor(global_colors["background"])) meter_outline = QtCore.QRectF( self.border, self.border, width / 2 - self.padding / 2 - self.border * 2, height - self.border * 2) painter.fillRect(meter_outline, brush) meter_outline = QtCore.QRectF( width / 2 + self.padding / 2 + self.border, self.border, width / 2 - self.padding / 2 - self.border * 2, height - self.border * 2) painter.fillRect(meter_outline, brush) brush.setColor(QtGui.QColor(global_colors["blue"])) unity_gain_height = (1 - 100 / (self.maximum - self.minimum)) * (height - self.border * 2) unity_gain_line = QtCore.QRectF( self.border, unity_gain_height, width / 2 - self.padding / 2 - self.border * 2, 4 ) painter.fillRect(unity_gain_line, brush) unity_gain_line = QtCore.QRectF( width / 2 + self.padding / 2 + self.border, unity_gain_height, width / 2 - self.padding / 2 - self.border * 2, 4 ) painter.fillRect(unity_gain_line, brush) # Draw volume bars step_size = height / self.n_bars bar_height = step_size * 0.6 n_steps_to_draw = int(self.meter_maximum / (self.maximum - self.minimum) * self.n_bars) warning_level = 90 / (self.maximum - self.minimum) * self.n_bars for n in range(n_steps_to_draw): if n >= warning_level: brush.setColor(QtGui.QColor(global_colors["orange"])) else: brush.setColor(QtGui.QColor(global_colors["pink"])) rect = QtCore.QRectF( self.border * 2, height - self.border * 2 - n * step_size, width / 2 - self.border * 4 - self.padding / 2, -bar_height ) painter.fillRect(rect, brush) rect = QtCore.QRectF( width - self.border * 2, height - self.border * 2 - n * step_size, -(width / 2 - self.border * 4 - self.padding / 2), -bar_height ) painter.fillRect(rect, brush) painter.end() def trigger_update(self, meter_maximum): self.meter_maximum = int(meter_maximum*100) self.update() class OutputPanel(QFrame): def __init__(self, main_widget, *args, **kwargs): super().__init__(*args, **kwargs) self.main_widget = main_widget layout = QVBoxLayout() self.console = QTextEdit() self.console.setReadOnly(True) self.console.setEnabled(False) self.console.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) layout.addWidget(self.console) self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) self.setLayout(layout) def main(): app = QApplication(sys.argv) window = MainWindow() with open("styles.qss","r") as file: window.setStyleSheet(file.read()) window.show() sys.exit(app.exec()) if __name__ == '__main__': main()