Redesigned toolbar.

- Use a custom layout (instead of QToolBar).
- Add a VUMeter for master output.
- Move widget related stuff from player_state.py to project_view.py
time
Ben Niemann 2020-02-22 08:09:51 +01:00
parent cb040f3686
commit f8162bbb7e
2 changed files with 188 additions and 83 deletions

View File

@ -51,67 +51,6 @@ class MoveTo(enum.Enum):
NextBeat = 3
class TimeDisplayMode(enum.Enum):
MusicalTime = 0
RealTime = 1
class TimeDisplay(QtWidgets.QLCDNumber):
def __init__(self, parent: QtWidgets.QWidget, time_mapper: audioproc.TimeMapper) -> None:
super().__init__(parent)
self.setDigitCount(9)
self.setSegmentStyle(QtWidgets.QLCDNumber.Flat)
self.setFrameStyle(QtWidgets.QFrame.Panel)
self.setFrameShadow(QtWidgets.QFrame.Sunken)
self.__time_mapper = time_mapper
self.__time_mode = TimeDisplayMode.MusicalTime
self.__current_time = audioproc.MusicalTime()
def __update(self) -> None:
if self.__time_mode == TimeDisplayMode.MusicalTime:
beat = self.__current_time / audioproc.MusicalDuration(1, 4)
self.display('%.3f' % beat)
else:
assert self.__time_mode == TimeDisplayMode.RealTime
t = self.__time_mapper.musical_to_sample_time(self.__current_time) / self.__time_mapper.sample_rate
millis = int(1000 * t) % 1000
seconds = int(t) % 60
minutes = int(t) // 60
self.display('%d:%02d.%03d' % (minutes, seconds, millis))
def setCurrentTime(self, current_time: audioproc.MusicalTime) -> None:
self.__current_time = current_time
self.__update()
def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
if evt.button() == Qt.LeftButton:
if self.__time_mode == TimeDisplayMode.MusicalTime:
self.__time_mode = TimeDisplayMode.RealTime
else:
self.__time_mode = TimeDisplayMode.MusicalTime
self.__update()
evt.accept()
return
super().mousePressEvent(evt)
class TimeDisplayAction(QtWidgets.QWidgetAction):
def __init__(self, player_state: 'PlayerState', time_mapper: audioproc.TimeMapper) -> None:
super().__init__(player_state)
self.__player_state = player_state
self.__time_mapper = time_mapper
def createWidget(self, parent: QtWidgets.QWidget) -> QtWidgets.QWidget:
display = TimeDisplay(parent, self.__time_mapper)
self.__player_state.currentTimeChanged.connect(display.setCurrentTime)
display.setCurrentTime(self.__player_state.currentTime())
return display
class PlayerState(ui_base.ProjectMixin, QtCore.QObject):
playingChanged = QtCore.pyqtSignal(bool)
currentTimeChanged = QtCore.pyqtSignal(object)
@ -135,8 +74,6 @@ class PlayerState(ui_base.ProjectMixin, QtCore.QObject):
self.__player_id = None # type: str
self.__time_display = TimeDisplayAction(self, self.time_mapper)
self.__move_to_start_action = QtWidgets.QAction("Move to start", self)
self.__move_to_start_action.setIcon(QtGui.QIcon(
os.path.join(constants.DATA_DIR, 'icons', 'media-skip-backward.svg')))
@ -184,6 +121,24 @@ class PlayerState(ui_base.ProjectMixin, QtCore.QObject):
def __set_session_value(self, key: str, value: Any) -> None:
self.set_session_value(self.__session_prefix + key, value)
def togglePlaybackAction(self) -> QtWidgets.QAction:
return self.__toggle_action
def toggleLoopAction(self) -> QtWidgets.QAction:
return self.__loop_action
def moveToStartAction(self) -> QtWidgets.QAction:
return self.__move_to_start_action
def moveToEndAction(self) -> QtWidgets.QAction:
return self.__move_to_end_action
def moveToPrevAction(self) -> QtWidgets.QAction:
return self.__move_to_prev_action
def moveToNextAction(self) -> QtWidgets.QAction:
return self.__move_to_next_action
def playerID(self) -> str:
return self.__player_id
@ -206,17 +161,6 @@ class PlayerState(ui_base.ProjectMixin, QtCore.QObject):
if player_state.HasField('loop_end_time'):
self.setLoopEndTime(audioproc.MusicalTime.from_proto(player_state.loop_end_time))
def populateToolBar(self, toolbar: QtWidgets.QToolBar) -> None:
toolbar.addAction(self.__time_display)
toolbar.addSeparator()
toolbar.addAction(self.__toggle_action)
toolbar.addAction(self.__loop_action)
toolbar.addSeparator()
toolbar.addAction(self.__move_to_start_action)
toolbar.addAction(self.__move_to_prev_action)
toolbar.addAction(self.__move_to_next_action)
toolbar.addAction(self.__move_to_end_action)
def setTimeMode(self, mode: TimeMode) -> None:
self.__time_mode = mode
@ -237,7 +181,8 @@ class PlayerState(ui_base.ProjectMixin, QtCore.QObject):
def playing(self) -> bool:
return self.__playing
def setCurrentTime(self, current_time: audioproc.MusicalTime, from_engine=False) -> None:
def setCurrentTime(
self, current_time: audioproc.MusicalTime, from_engine: bool = False) -> None:
if current_time == self.__current_time:
return
@ -321,14 +266,18 @@ class PlayerState(ui_base.ProjectMixin, QtCore.QObject):
self.setCurrentTime(self.time_mapper.end_time)
elif where == MoveTo.PrevBeat:
beat = int((self.__current_time + audioproc.MusicalDuration(3, 16)) / audioproc.MusicalTime(1, 4))
beat = int(
(self.__current_time + audioproc.MusicalDuration(3, 16))
/ audioproc.MusicalTime(1, 4))
new_time = audioproc.MusicalTime(beat - 1, 4)
if new_time < audioproc.MusicalTime(0, 1):
new_time = audioproc.MusicalTime(0, 1)
self.setCurrentTime(new_time)
elif where == MoveTo.NextBeat:
beat = int((self.__current_time + audioproc.MusicalDuration(3, 16)) / audioproc.MusicalTime(1, 4))
beat = int(
(self.__current_time + audioproc.MusicalDuration(3, 16))
/ audioproc.MusicalTime(1, 4))
new_time = audioproc.MusicalTime(beat + 1, 4)
if new_time > self.time_mapper.end_time:
new_time = self.time_mapper.end_time

View File

@ -20,11 +20,12 @@
#
# @end:license
import enum
import functools
import logging
import uuid
import typing
from typing import Any, Tuple
from typing import Any, Dict, Tuple
from PyQt5.QtCore import Qt
from PyQt5 import QtCore
@ -39,6 +40,7 @@ from . import render_dialog
from . import project_registry
from .track_list import view as track_list_view
from . import player_state as player_state_lib
from . import vumeter
if typing.TYPE_CHECKING:
from noisicaa import core
@ -46,6 +48,55 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
class TimeDisplayMode(enum.Enum):
MusicalTime = 0
RealTime = 1
class TimeDisplay(QtWidgets.QLCDNumber):
def __init__(self, parent: QtWidgets.QWidget, time_mapper: audioproc.TimeMapper) -> None:
super().__init__(parent)
self.setDigitCount(9)
self.setSegmentStyle(QtWidgets.QLCDNumber.Flat)
self.setFrameStyle(QtWidgets.QFrame.Panel)
self.setFrameShadow(QtWidgets.QFrame.Sunken)
self.__time_mapper = time_mapper
self.__time_mode = TimeDisplayMode.MusicalTime
self.__current_time = audioproc.MusicalTime()
def __update(self) -> None:
if self.__time_mode == TimeDisplayMode.MusicalTime:
beat = self.__current_time / audioproc.MusicalDuration(1, 4)
self.display('%.3f' % beat)
else:
assert self.__time_mode == TimeDisplayMode.RealTime
t = (self.__time_mapper.musical_to_sample_time(self.__current_time)
/ self.__time_mapper.sample_rate)
millis = int(1000 * t) % 1000
seconds = int(t) % 60
minutes = int(t) // 60
self.display('%d:%02d.%03d' % (minutes, seconds, millis))
def setCurrentTime(self, current_time: audioproc.MusicalTime) -> None:
self.__current_time = current_time
self.__update()
def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
if evt.button() == Qt.LeftButton:
if self.__time_mode == TimeDisplayMode.MusicalTime:
self.__time_mode = TimeDisplayMode.RealTime
else:
self.__time_mode = TimeDisplayMode.MusicalTime
self.__update()
evt.accept()
return
super().mousePressEvent(evt)
class ProjectView(ui_base.AbstractProjectView):
playingChanged = QtCore.pyqtSignal(bool)
loopEnabledChanged = QtCore.pyqtSignal(bool)
@ -67,6 +118,8 @@ class ProjectView(ui_base.AbstractProjectView):
self.__player_realm = None # type: str
self.__player_node_id = None # type: str
self.__player_status_listener = None # type: core.Listener
self.__vumeter_node_id = None # type: str
self.__vumeter_listener = None # type: core.Listener
self.__player_state = player_state_lib.PlayerState(context=self.context)
@ -86,14 +139,76 @@ class ProjectView(ui_base.AbstractProjectView):
self.__splitter.setCollapsible(0, False)
self.__splitter.addWidget(self.__graph)
self.__toolbar = QtWidgets.QToolBar(self)
self.__toolbar.setObjectName('toolbar:%016x' % self.project.id)
self.__player_state.populateToolBar(self.__toolbar)
self.__time_display = TimeDisplay(self, self.time_mapper)
self.__time_display.setMinimumWidth(9*20)
self.__player_state.currentTimeChanged.connect(self.__time_display.setCurrentTime)
self.__time_display.setCurrentTime(self.__player_state.currentTime())
self.__vumeter = vumeter.VUMeter(self)
self.__vumeter.setMinimumWidth(250)
self.__toggle_playback_button = QtWidgets.QToolButton(self)
self.__toggle_playback_button.setDefaultAction(self.__player_state.togglePlaybackAction())
self.__toggle_playback_button.setIconSize(QtCore.QSize(54, 54))
self.__toggle_playback_button.setAutoRaise(True)
self.__toggle_loop_button = QtWidgets.QToolButton(self)
self.__toggle_loop_button.setDefaultAction(self.__player_state.toggleLoopAction())
self.__toggle_loop_button.setIconSize(QtCore.QSize(24, 24))
self.__toggle_loop_button.setAutoRaise(True)
self.__move_to_start_button = QtWidgets.QToolButton(self)
self.__move_to_start_button.setDefaultAction(self.__player_state.moveToStartAction())
self.__move_to_start_button.setIconSize(QtCore.QSize(24, 24))
self.__move_to_start_button.setAutoRaise(True)
self.__move_to_end_button = QtWidgets.QToolButton(self)
self.__move_to_end_button.setDefaultAction(self.__player_state.moveToEndAction())
self.__move_to_end_button.setIconSize(QtCore.QSize(24, 24))
self.__move_to_end_button.setAutoRaise(True)
self.__move_to_prev_button = QtWidgets.QToolButton(self)
self.__move_to_prev_button.setDefaultAction(self.__player_state.moveToPrevAction())
self.__move_to_prev_button.setIconSize(QtCore.QSize(24, 24))
self.__move_to_prev_button.setAutoRaise(True)
self.__move_to_next_button = QtWidgets.QToolButton(self)
self.__move_to_next_button.setDefaultAction(self.__player_state.moveToNextAction())
self.__move_to_next_button.setIconSize(QtCore.QSize(24, 24))
self.__move_to_next_button.setAutoRaise(True)
tb_layout = QtWidgets.QGridLayout()
tb_layout.setContentsMargins(0, 2, 0, 2)
tb_layout.setSpacing(0)
c = 0
tb_layout.addWidget(self.__toggle_playback_button, 0, c, 2, 1)
c += 1
tb_layout.addItem(QtWidgets.QSpacerItem(4, 4), 0, c, 2, 1)
c += 1
tb_layout.addWidget(self.__toggle_loop_button, 0, c, 1, 1)
c += 1
tb_layout.addItem(QtWidgets.QSpacerItem(4, 4), 0, c, 2, 1)
c += 1
tb_layout.addWidget(self.__move_to_start_button, 0, c, 1, 1)
tb_layout.addWidget(self.__move_to_prev_button, 1, c, 1, 1)
c += 1
tb_layout.addWidget(self.__move_to_end_button, 0, c, 1, 1)
tb_layout.addWidget(self.__move_to_next_button, 1, c, 1, 1)
c += 1
tb_layout.addItem(QtWidgets.QSpacerItem(4, 4), 0, c, 2, 1)
c += 1
tb_layout.addWidget(self.__time_display, 0, c, 2, 1)
c += 1
tb_layout.addItem(QtWidgets.QSpacerItem(4, 4), 0, c, 2, 1)
c += 1
tb_layout.addWidget(self.__vumeter, 0, c, 2, 1)
c += 1
tb_layout.setColumnStretch(c, 1)
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(self.__toolbar)
layout.addLayout(tb_layout)
layout.addWidget(self.__splitter)
self.setLayout(layout)
@ -115,7 +230,6 @@ class ProjectView(ui_base.AbstractProjectView):
loop_end_time=self.__player_state.loopEndTimeProto()))
self.__player_node_id = uuid.uuid4().hex
await self.audioproc_client.add_node(
'root',
id=self.__player_node_id,
@ -132,7 +246,40 @@ class ProjectView(ui_base.AbstractProjectView):
'sink', 'in:right',
node_db.PortDescription.AUDIO)
self.__vumeter_node_id = uuid.uuid4().hex
await self.audioproc_client.add_node(
'root',
id=self.__vumeter_node_id,
description=self.project.get_node_description('builtin://vumeter'))
await self.audioproc_client.connect_ports(
'root',
self.__player_node_id, 'out:left',
self.__vumeter_node_id, 'in:left',
node_db.PortDescription.AUDIO)
await self.audioproc_client.connect_ports(
'root',
self.__player_node_id, 'out:right',
self.__vumeter_node_id, 'in:right',
node_db.PortDescription.AUDIO)
self.__vumeter_listener = self.audioproc_client.node_messages.add(
self.__vumeter_node_id, self.__vumeterMessage)
async def cleanup(self) -> None:
if self.__vumeter_listener is not None:
self.__vumeter_listener.remove()
self.__vumeter_listener = None
if self.__vumeter_node_id is not None:
assert self.__player_node_id is not None
await self.audioproc_client.disconnect_ports(
'root', self.__player_node_id, 'out:left', self.__vumeter_node_id, 'in:left')
await self.audioproc_client.disconnect_ports(
'root', self.__player_node_id, 'out:right', self.__vumeter_node_id, 'in:right')
await self.audioproc_client.remove_node(
'root', self.__vumeter_node_id)
self.__vumeter_node_id = None
if self.__player_node_id is not None:
await self.audioproc_client.disconnect_ports(
'root', self.__player_node_id, 'out:left', 'sink', 'in:left')
@ -167,6 +314,15 @@ class ProjectView(ui_base.AbstractProjectView):
await self.audioproc_client.send_node_messages(
self.__player_realm, audioproc.ProcessorMessageList(messages=[msg]))
def __vumeterMessage(self, msg: Dict[str, Any]) -> None:
meter = 'http://noisicaa.odahoda.de/lv2/processor_vumeter#meter'
if meter in msg:
current_left, peak_left, current_right, peak_right = msg[meter]
self.__vumeter.setLeftValue(current_left)
self.__vumeter.setLeftPeak(peak_left)
self.__vumeter.setRightValue(current_right)
self.__vumeter.setRightPeak(peak_right)
def onRender(self) -> None:
dialog = render_dialog.RenderDialog(parent=self, context=self.context)
dialog.setModal(True)