from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QCursor
import pyqtgraph as pg
import numpy as np
from .widgetutilities import downsample_plot
# Enable OpenGL and Weave to improve the performance of plotting functions.
pg.setConfigOptions(useOpenGL=True)
pg.setConfigOptions(useWeave=True)
# Colors of the waveform plot, vertical line (cursor) and waveform region
# selector
WAVEFORM_PEN = (20, 170, 100, 80) # (Red, Green, Blue, a)
WAVEFORM_BRUSH = pg.mkBrush((50, 255, 255, 45))
WAVEFORM_VLINE = pg.mkPen((255, 40, 35, 150), cosmetic=True, width=2)
class WaveformRegionItem(pg.LinearRegionItem):
clicked = pyqtSignal()
def __init__(self, values=[0, 1], orientation=None, brush=None,
movable=True, bounds=None):
pg.LinearRegionItem.__init__(self, values=values, brush=brush,
orientation=orientation, movable=movable,
bounds=bounds)
def mouseClickEvent(self, ev):
# Override the mouseClickEvent. Add self.clicked signal before the
# original method. Original method is called with super() method.
self.clicked.emit() # emit clicked signal
super(WaveformRegionItem, self).mouseClickEvent(ev) # call original
# method
def mouseDragEvent(self, ev):
# Override the mouseDragEvent. Add self.clicked signal before the
# original method. Original method is called with super() method.
self.clicked.emit() # emit clicked signal
super(WaveformRegionItem, self).mouseDragEvent(ev) # call original
# method
class SectionItem(pg.LinearRegionItem):
hovering = pyqtSignal(str)
item_initialized = pyqtSignal(object)
def __init__(self, values, label_section, color):
pg.LinearRegionItem.__init__(self, values=values, movable=False)
for line in self.lines:
line.setPen(pg.mkPen(None))
self.setBrush(pg.mkBrush(color))
self.label = label_section
# signals
self.item_initialized.emit(self)
def hoverEvent(self, ev):
if not ev.isExit():
self.hovering.emit(self.label)
else:
self.hovering.emit('')
[docs]class WaveformWidget(pg.GraphicsLayoutWidget):
limit = 900
samplerate = 44100.
def __init__(self):
pg.GraphicsLayoutWidget.__init__(self, parent=None)
# Set 0 margin 0 spacing to cover the whole area.
self.centralWidget.setContentsMargins(0, 0, 0, 0)
self.centralWidget.setSpacing(0)
self.section_items = []
[docs] def plot_waveform(self, raw_audio):
"""
Plots the given raw audio.
:param raw_audio: (list of numpy array) List of floats.
"""
# add a new plot
self.waveform = self.centralWidget.addPlot()
# hide x and y axis
self.waveform.hideAxis(axis='bottom')
self.waveform.hideAxis(axis='left')
# disable the mouse events and menu events
self.waveform.setMouseEnabled(x=False, y=False)
self.waveform.setMenuEnabled(False)
# downsampling the given plot array
self.visible = downsample_plot(raw_audio, self.limit)
# ratio is used in self.change_wf_region and self.get_waveform_region
# methods.
self.len = np.size(self.visible)
self.min = np.nanmin(self.visible)
self.max = np.nanmax(self.visible)
self.ratio = np.size(raw_audio) / self.len
self.waveform.plot(self.visible, connect='finite', pen=WAVEFORM_PEN)
# add waveform region item and playback cursor
self.__add_items_to_plot(self.len, self.min, self.max)
def __add_items_to_plot(self, len_plot, min_audio, max_audio):
"""
Adds a region selector item and vertical line for to the waveform plot.
:param len_plot: (int) Number of samples in plotted waveform array.
:param min_audio: (float) The minimum value of plotted waveform array.
:param max_audio: (float) The maximum value of plotted waveform array.
"""
# Create a waveform region item and add it to waveform plot
pos_wf_x_max = len_plot * 0.05 # Region item focuses on the 5% of
# waveform plot.
self.region_wf = WaveformRegionItem(values=[0, pos_wf_x_max],
brush=WAVEFORM_BRUSH,
bounds=[0., len_plot])
# Creating a cursor with pyqtgraph.ROI
self.vline_wf = pg.ROI(pos=[0, min_audio],
size=[0, max_audio - min_audio],
angle=0, pen=WAVEFORM_VLINE)
# add items to waveform plot
self.waveform.addItem(self.region_wf)
self.waveform.addItem(self.vline_wf)
# text item
self.section_label = pg.TextItem(text='')
self.waveform.addItem(self.section_label)
@property
def get_waveform_region(self):
"""
Returns the current position of the waveform region item in seconds if
the waveform region item exists.
:return: xmin, xmax (float): Minimum and maximum values of waveform region item
"""
if hasattr(self, 'region_wf'):
pos_wf_x_min, pos_wf_x_max = self.region_wf.getRegion()
x_min = (pos_wf_x_min * self.ratio) / self.samplerate
x_max = (pos_wf_x_max * self.ratio) / self.samplerate
return x_min, x_max
else:
return None, None
[docs] def change_wf_region(self, x_start, x_end):
"""
Sets the position of the waveform region item on the waveform widget.
:param x_start: Start point of the region item in seconds.
:param x_end: End point of the region item in seconds.
"""
x_start = (x_start * self.samplerate) / self.ratio
x_end = (x_end * self.samplerate) / self.ratio
self.region_wf.setRegion([x_start, x_end])
[docs] def update_wf_vline(self, playback_pos_sample):
"""
Updated the position of vertical line.
:param playback_pos_sample: (int) Position of playback in samples.
"""
pos = playback_pos_sample / self.ratio
if pos <= 0:
pos = 0
elif pos >= self.len:
pos = self.len
self.vline_wf.setPos([pos, self.min])
def wheelEvent(self, event):
delta = event.pixelDelta().y()
xmin, xmax = self.get_waveform_region
distance = (xmax - xmin) * 0.03
if delta > 0:
xmin += distance
xmax -= distance
elif delta < 0:
xmin -= distance
xmax += distance
self.change_wf_region(xmin, xmax)
[docs] def add_section(self, time, label, title, color):
"""
Plots the section on waveform widget.
:param time: (int) Position in seconds.
:param label: (str) Label of the given seciton.
:param title: (str) Title of the given section.
:param color: (tuple) RGBa values of a color. Ex: (20, 170, 100, 80)
"""
time *= (self.samplerate / self.ratio)
label += "\n" + title
section_item = SectionItem(values=time, label_section=label,
color=color)
self.section_items.append(section_item)
self.waveform.addItem(section_item)
section_item.hovering.connect(self.__hover_section)
def __hover_section(self, text):
self.section_label.setText(text)
org_pos = self.mapFromGlobal(QCursor.pos())
pos_x = self.len * (float(org_pos.x()) / self.waveform.width())
self.section_label.setPos(pos_x, self.max * 2. / 3)
[docs] def remove_sections(self):
"""
Removes the sections plotted on waveform widget.
"""
for item in self.section_items:
self.waveform.removeItem(item)
self.section_items = []