Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions friture/FritureHost.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import Friture 1.0

Rectangle {
id: mainWindow
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
color: systemPalette.window
anchors.fill: parent
}
9 changes: 2 additions & 7 deletions friture/Levels.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@ Rectangle {
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
color: systemPalette.window

property var stateId
property LevelViewModel level_view_model: Store.dock_states[stateId]

property string fixedFont

// parent here will be unset on exit
height: parent ? parent.height : 0
required property LevelViewModel level_view_model
required property string fixedFont

// make width dependent on the text labels
// but do not bind directly to their widths
Expand Down
56 changes: 56 additions & 0 deletions friture/MainWindow.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import Friture 1.0
import "./playback"

Rectangle {
id: main_window
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
color: systemPalette.window
anchors.fill: parent

required property MainWindowViewModel main_window_view_model
required property string fixedFont

GridLayout {
objectName: "main_row_layout"
anchors.fill: parent
rows: main_window.main_window_view_model.playback_control_enabled ? 2 : 1
columns: 2
rowSpacing: 3
columnSpacing: 3

Levels {
level_view_model: main_window.main_window_view_model.level_view_model
Layout.row: 0
Layout.rowSpan: main_window.main_window_view_model.playback_control_enabled ? 2 : 1
Layout.column: 0
Layout.fillHeight: true
Layout.margins: 5
fixedFont: main_window.fixedFont
}

TileLayout {
id: tileLayout
objectName: "main_tile_layout"
Layout.row: 0
Layout.column: 1
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 5
}

PlaybackControl {
id: playbackControl
Layout.row: 1
Layout.column: 1
Layout.fillWidth: true
Layout.margins: 5

viewModel: main_window.main_window_view_model.playback_control_view_model

visible: main_window.main_window_view_model.playback_control_enabled
}
}
}
83 changes: 56 additions & 27 deletions friture/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
from PyQt5 import QtCore, QtWidgets
# specifically import from PyQt5.QtGui and QWidgets for startup time improvement :
from PyQt5.QtWidgets import QMainWindow, QHBoxLayout, QVBoxLayout, QApplication, QSplashScreen
from PyQt5.QtGui import QPixmap
from PyQt5.QtQml import QQmlEngine, qmlRegisterSingletonType, qmlRegisterType
from PyQt5.QtGui import QPixmap, QFontDatabase
from PyQt5.QtQml import QQmlEngine, qmlRegisterSingletonType, qmlRegisterType, QQmlComponent
from PyQt5.QtQuickWidgets import QQuickWidget
from PyQt5.QtCore import QObject

Expand All @@ -39,16 +39,18 @@
# importing friture.exceptionhandler also installs a temporary exception hook
from friture.exceptionhandler import errorBox, fileexcepthook
import friture
from friture.playback.playback_control_view_model import PlaybackControlViewModel
from friture.ui_friture import Ui_MainWindow
from friture.about import About_Dialog # About dialog
from friture.settings import Settings_Dialog # Setting dialog
from friture.audiobuffer import AudioBuffer # audio ring buffer class
from friture.audiobackend import AudioBackend # audio backend class
from friture.dockmanager import DockManager
from friture.tileLayout import TileLayout
from friture.tilelayout import TileLayout
from friture.level_view_model import LevelViewModel
from friture.level_data import LevelData
from friture.levels import Levels_Widget
from friture.main_window_view_model import MainWindowViewModel
from friture.store import GetStore, Store
from friture.scope_data import Scope_Data
from friture.axis import Axis
Expand All @@ -64,7 +66,7 @@
from friture.spectrum_data import Spectrum_Data
from friture.plotFilledCurve import PlotFilledCurve
from friture.filled_curve import FilledCurve
from friture.qml_tools import qml_url, raise_if_error
from friture.qml_tools import qml_url, raise_if_error, component_raise_if_error
from friture.generators.sine import Sine_Generator_Settings_View_Model
from friture.generators.white import White_Generator_Settings_View_Model
from friture.generators.pink import Pink_Generator_Settings_View_Model
Expand Down Expand Up @@ -109,6 +111,8 @@ def __init__(self):
qmlRegisterType(Spectrum_Data, 'Friture', 1, 0, 'SpectrumData')
qmlRegisterType(LevelData, 'Friture', 1, 0, 'LevelData')
qmlRegisterType(LevelViewModel, 'Friture', 1, 0, 'LevelViewModel')
qmlRegisterType(PlaybackControlViewModel, 'Friture', 1, 0, 'PlaybackControlViewModel')
qmlRegisterType(MainWindowViewModel, 'Friture', 1, 0, 'MainWindowViewModel')
qmlRegisterType(Axis, 'Friture', 1, 0, 'Axis')
qmlRegisterType(Curve, 'Friture', 1, 0, 'Curve')
qmlRegisterType(FilledCurve, 'Friture', 1, 0, 'FilledCurve')
Expand Down Expand Up @@ -152,36 +156,44 @@ def __init__(self):
self.about_dialog = About_Dialog(self, self.slow_timer)
self.settings_dialog = Settings_Dialog(self)

self.level_widget = Levels_Widget(self, self.qml_engine)
self.level_widget.set_buffer(self.audiobuffer)
self.audiobuffer.new_data_available.connect(self.level_widget.handle_new_data)

self.hboxLayout = QHBoxLayout(self.ui.centralwidget)
self.hboxLayout.setContentsMargins(0, 0, 0, 0)
self.hboxLayout.addWidget(self.level_widget)

self.vboxLayout = QVBoxLayout()
self.hboxLayout.addLayout(self.vboxLayout)

self.centralQuickWidget = QQuickWidget(self.qml_engine, self)
self.centralQuickWidget.setObjectName("centralQuickWidget")
self.centralQuickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.centralQuickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
self.centralQuickWidget.setSource(qml_url("CentralWidget.qml"))
self.vboxLayout.addWidget(self.centralQuickWidget)
self.centralQuickWidget.setSource(qml_url("FritureHost.qml"))

raise_if_error(self.centralQuickWidget)

central_widget_root = self.centralQuickWidget.rootObject()
self.main_grid_layout = central_widget_root.findChild(QObject, "main_tile_layout")
assert self.main_grid_layout is not None, "Main grid layout not found in CentralWidget.qml"
self.hboxLayout = QHBoxLayout(self.ui.centralwidget)
self.hboxLayout.setContentsMargins(0, 0, 0, 0)
self.hboxLayout.addWidget(self.centralQuickWidget)

qml_component = QQmlComponent(self.qml_engine)
qml_component.loadUrl(qml_url("MainWindow.qml"))
component_raise_if_error(qml_component)

self._main_window_view_model = MainWindowViewModel(self.qml_engine)

context = self.qml_engine.rootContext()
central_widget_root = qml_component.createWithInitialProperties(
{
"main_window_view_model": self._main_window_view_model,
"fixedFont": QFontDatabase.systemFont(QFontDatabase.FixedFont).family()
},
context) # type: ignore
central_widget_root.setParent(self.qml_engine)
central_widget_root.setParentItem(self.centralQuickWidget.rootObject()) # type: ignore

self.main_tile_layout = central_widget_root.findChild(QObject, "main_tile_layout")
assert self.main_tile_layout is not None, "Main tile layout not found in CentralWidget.qml"

self.level_widget = Levels_Widget(self, self._main_window_view_model.level_view_model)
self.level_widget.set_buffer(self.audiobuffer)
self.audiobuffer.new_data_available.connect(self.level_widget.handle_new_data)

self.playback_widget = PlaybackControlWidget(
self, self.qml_engine, self.player)
self.playback_widget.setVisible(self.settings_dialog.show_playback)
self.vboxLayout.addWidget(self.playback_widget)
self.playback_widget = PlaybackControlWidget(self, self.player, self._main_window_view_model.playback_control_view_model)

self.dockmanager = DockManager(self, self.main_grid_layout)
self.dockmanager = DockManager(self, self.main_tile_layout)

# timer ticks
self.display_timer.timeout.connect(self.dockmanager.canvasUpdate)
Expand All @@ -193,7 +205,7 @@ def __init__(self):
self.ui.actionSettings.triggered.connect(self.settings_called)
self.ui.actionAbout.triggered.connect(self.about_called)
self.ui.actionNew_dock.triggered.connect(self.dockmanager.new_dock)
self.playback_widget.recording_toggled.connect(self.timer_toggle)
self.playback_widget.recording_toggled.connect(self.timer_changed)

# settings changes
self.settings_dialog.show_playback_changed.connect(self.show_playback_changed)
Expand Down Expand Up @@ -237,7 +249,7 @@ def settings_called(self):
self.settings_dialog.show()

def show_playback_changed(self, show: bool) -> None:
self.playback_widget.setVisible(show)
self._main_window_view_model.playback_control_enabled = show

# slot
def about_called(self):
Expand Down Expand Up @@ -341,6 +353,23 @@ def timer_toggle(self):
AudioBackend().restart()
self.dockmanager.restart()

# slot
def timer_changed(self, recording: bool):
if not recording and self.display_timer.isActive():
self.logger.info("Timer stop")
self.display_timer.stop()
self.ui.actionStart.setText("Start")
self.playback_widget.stop_recording()
AudioBackend().pause()
self.dockmanager.pause()

if recording and not self.display_timer.isActive():
self.logger.info("Timer start")
self.display_timer.start()
self.ui.actionStart.setText("Stop")
self.playback_widget.start_recording()
AudioBackend().restart()
self.dockmanager.restart()

def qt_message_handler(mode, context, message):
logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion friture/dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(
context = self.qml_engine.rootContext()
self.dock_qml = dock_component.createWithInitialProperties({}, context)
self.dock_qml.setParent(self.qml_engine)
self.dock_qml.setParentItem(self.parent().main_grid_layout) # type: ignore
self.dock_qml.setParentItem(self.parent().main_tile_layout) # type: ignore

initialProperties = {"viewModel": self.controlbar_viewmodel}
component = QQmlComponent(self.qml_engine)
Expand Down
2 changes: 1 addition & 1 deletion friture/dockmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from PyQt5.QtWidgets import QMainWindow
from friture.defaults import DEFAULT_DOCKS
from friture.dock import Dock
from friture.tileLayout import TileLayout
from friture.tilelayout import TileLayout

from typing import Dict, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
Expand Down
47 changes: 7 additions & 40 deletions friture/levels.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,62 +19,32 @@

"""Level widget that displays peak and RMS levels for 1 or 2 ports."""

from PyQt5 import QtWidgets
from PyQt5.QtQml import QQmlComponent
from PyQt5.QtQuick import QQuickWindow # type: ignore
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtCore import QObject
import numpy as np

from friture.store import GetStore
from friture.levels_settings import Levels_Settings_Dialog # settings dialog
from friture.audioproc import audioproc
from friture.level_view_model import LevelViewModel
from friture.iec import dB_to_IEC
from friture_extensions.exp_smoothing_conv import pyx_exp_smoothed_value
from friture.audiobackend import SAMPLING_RATE
from friture.qml_tools import qml_url, raise_if_error

SMOOTH_DISPLAY_TIMER_PERIOD_MS = 25
LEVEL_TEXT_LABEL_PERIOD_MS = 250

LEVEL_TEXT_LABEL_STEPS = LEVEL_TEXT_LABEL_PERIOD_MS / SMOOTH_DISPLAY_TIMER_PERIOD_MS

class Levels_Widget(QtWidgets.QWidget):
class Levels_Widget(QObject):

def __init__(self, parent, engine):
def __init__(self, parent, view_model):
super().__init__(parent)
self.setObjectName("Levels_Widget")

self.gridLayout = QtWidgets.QVBoxLayout(self)
self.gridLayout.setObjectName("gridLayout")

store = GetStore()
self.level_view_model = LevelViewModel(store)
store._dock_states.append(self.level_view_model)
state_id = len(store._dock_states) - 1

self.quickWindow = QQuickWindow()
component = QQmlComponent(engine, qml_url("Levels.qml"), self)
raise_if_error(component)

fixedFont = QFontDatabase.systemFont(QFontDatabase.FixedFont)

engineContext = engine.rootContext()
initialProperties = {"parent": self.quickWindow.contentItem(), "stateId": state_id, "fixedFont": fixedFont }
self.qmlObject = component.createWithInitialProperties(initialProperties, engineContext)
self.qmlObject.setParent(self.quickWindow)

self.quickWidget = QtWidgets.QWidget.createWindowContainer(self.quickWindow, self)
self.quickWidget.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addWidget(self.quickWidget)

self.qmlObject.widthChanged.connect(self.onWidthChanged)
self.onWidthChanged()
self._parent = parent
self.level_view_model = view_model

self.audiobuffer = None

# initialize the settings dialog
self.settings_dialog = Levels_Settings_Dialog(self)
self.settings_dialog = Levels_Settings_Dialog(parent)

# initialize the class instance that will do the fft
self.proc = audioproc()
Expand Down Expand Up @@ -107,9 +77,6 @@ def __init__(self, parent, engine):

self.i = 0

def onWidthChanged(self):
self.quickWidget.setFixedWidth(int(self.qmlObject.width()))

# method
def set_buffer(self, buffer):
self.audiobuffer = buffer
Expand Down Expand Up @@ -165,7 +132,7 @@ def handle_new_data(self, floatdata):

# method
def canvasUpdate(self):
if not self.isVisible():
if not self._parent.isVisible():
return

self.i += 1
Expand Down
Loading