class audioproc():

    def __init__(self):
        self.logger = logging.getLogger(__name__)

        self.freq = linspace(0, SAMPLING_RATE / 2, 10)
        self.A = 0. * self.freq
        self.B = 0. * self.freq
        self.C = 0. * self.freq
        self.maxfreq = 1.
        self.window = arange(0, 1)
        self.size_sq = 1.

        self.fft_size = 10

    def analyzelive(self, samples):
        # FFT for a linear transformation in frequency scale
        fft = rfft(samples * self.window)
        spectrum = self.norm_square(fft)

        return spectrum

    def norm_square(self, fft):
        return (fft*fft.conjugate()).real / self.size_sq

    def set_fftsize(self, fft_size):
        if fft_size != self.fft_size:
            self.fft_size = fft_size
            self.update_freq_cache()
            self.update_window()
            self.update_size()

    def set_maxfreq(self, maxfreq):
        if maxfreq != self.maxfreq:
            self.maxfreq = maxfreq

            self.update_freq_cache()
            self.update_window()
            self.update_size()

    def get_freq_scale(self):
        return self.freq

    def get_freq_weighting(self):
        return self.A, self.B, self.C

    def update_size(self):
        self.size_sq = float(self.fft_size) ** 2

    def update_window(self):
        N = self.fft_size
        n = arange(0, N)
        # Hann window : better frequency resolution than the rectangular window
        self.window = 0.5 * (1. - cos(2 * pi * n / (N - 1)))
        self.logger.info("audioproc: updating window")

    def update_freq_cache(self):
        if len(self.freq) != self.fft_size / 2 + 1:
            self.logger.info("audioproc: updating self.freq cache")
            self.freq = linspace(0, SAMPLING_RATE // 2, self.fft_size // 2 + 1)

            # compute psychoacoustic weighting. See http://en.wikipedia.org/wiki/A-weighting
            f = self.freq
            Rc = 12200. ** 2 * f ** 2 / ((f ** 2 + 20.6 ** 2) * (f ** 2 + 12200. ** 2))
            Rb = 12200. ** 2 * f ** 3 / ((f ** 2 + 20.6 ** 2) * (f ** 2 + 12200. ** 2) * ((f ** 2 + 158.5 ** 2) ** 0.5))
            Ra = 12200. ** 2 * f ** 4 / ((f ** 2 + 20.6 ** 2) * (f ** 2 + 12200. ** 2) * ((f ** 2 + 107.7 ** 2) ** 0.5) * ((f ** 2 + 737.9 ** 2) ** 0.5))
            eps = 1e-50
            self.C = 0.06 + 20. * log10(Rc + eps)
            self.B = 0.17 + 20. * log10(Rb + eps)
            self.A = 2.0 + 20. * log10(Ra + eps)

    # above is done a FFT of the signal. This is ok for linear frequency scale, but
    # not satisfying for logarithmic scale, which is much more adapted to voice or music
    # analysis
    # Instead a constant Q transform should be used

    # Alternatively, we could use a ear/cochlear model : logarithmic
    # frequency scale, 4000 logarithmic-spaced bins, quality factors
    # determined from mechanical model, and 50 ms smoothing afterwards
    # for the sensor cell response time. The problem here comes from the
    # implementation: how to do it cleverly ?
    # on top of that, we could add the reponse of the middle ear, which is
    # a roughly band-pass filter centered around 1 kHz (see psychoacoustic
    # models)

    # def analyzelive_cochlear(self, samples, num_channels, lowfreq, maxfreq):
    #       samples -= samples.mean()
    #
    #       fs = 16000.

    #       [ERBforward, ERBfeedback] = MakeERBFilters(SAMPLING_RATE, num_channels, lowfreq)
    #       filtered_samples = ERBFilterBank(ERBforward, ERBfeedback, samples)

    #       spectrum = (abs(filtered_samples)**2).mean(axis=1)
    #       self.freq = frequencies(SAMPLING_RATE, num_channels, lowfreq)
    #
    #       return spectrum[::-1], self.freq[::-1] class Spectrum_Settings_Dialog(QtWidgets.QDialog):

    def __init__(self, parent):
        super().__init__(parent)

        self.logger = logging.getLogger(__name__)

        self.setWindowTitle("Spectrum settings")

        self.formLayout = QtWidgets.QFormLayout(self)

        self.comboBox_dual_channel = QtWidgets.QComboBox(self)
        self.comboBox_dual_channel.setObjectName("dual")
        self.comboBox_dual_channel.addItem("Single-channel")
        self.comboBox_dual_channel.addItem("Dual-channel")
        self.comboBox_dual_channel.setCurrentIndex(0)

        self.comboBox_fftsize = QtWidgets.QComboBox(self)
        self.comboBox_fftsize.setObjectName("comboBox_fftsize")
        self.comboBox_fftsize.addItem("32 points")
        self.comboBox_fftsize.addItem("64 points")
        self.comboBox_fftsize.addItem("128 points")
        self.comboBox_fftsize.addItem("256 points")
        self.comboBox_fftsize.addItem("512 points")
        self.comboBox_fftsize.addItem("1024 points")
        self.comboBox_fftsize.addItem("2048 points")
        self.comboBox_fftsize.addItem("4096 points")
        self.comboBox_fftsize.addItem("8192 points")
        self.comboBox_fftsize.addItem("16384 points")
        self.comboBox_fftsize.setCurrentIndex(DEFAULT_FFT_SIZE)

        self.comboBox_freqscale = QtWidgets.QComboBox(self)
        self.comboBox_freqscale.setObjectName("comboBox_freqscale")
        for scale in fscales.ALL:
            self.comboBox_freqscale.addItem(scale.NAME)
        self.comboBox_freqscale.setCurrentIndex(DEFAULT_FREQ_SCALE)

        self.spinBox_minfreq = QtWidgets.QSpinBox(self)
        self.spinBox_minfreq.setMinimum(20)
        self.spinBox_minfreq.setMaximum(SAMPLING_RATE / 2)
        self.spinBox_minfreq.setSingleStep(10)
        self.spinBox_minfreq.setValue(DEFAULT_MINFREQ)
        self.spinBox_minfreq.setObjectName("spinBox_minfreq")
        self.spinBox_minfreq.setSuffix(" Hz")

        self.spinBox_maxfreq = QtWidgets.QSpinBox(self)
        self.spinBox_maxfreq.setMinimum(20)
        self.spinBox_maxfreq.setMaximum(SAMPLING_RATE / 2)
        self.spinBox_maxfreq.setSingleStep(1000)
        self.spinBox_maxfreq.setProperty("value", DEFAULT_MAXFREQ)
        self.spinBox_maxfreq.setObjectName("spinBox_maxfreq")
        self.spinBox_maxfreq.setSuffix(" Hz")

        self.spinBox_specmin = QtWidgets.QSpinBox(self)
        self.spinBox_specmin.setKeyboardTracking(False)
        self.spinBox_specmin.setMinimum(-200)
        self.spinBox_specmin.setMaximum(200)
        self.spinBox_specmin.setProperty("value", DEFAULT_SPEC_MIN)
        self.spinBox_specmin.setObjectName("spinBox_specmin")
        self.spinBox_specmin.setSuffix(" dB")

        self.spinBox_specmax = QtWidgets.QSpinBox(self)
        self.spinBox_specmax.setKeyboardTracking(False)
        self.spinBox_specmax.setMinimum(-200)
        self.spinBox_specmax.setMaximum(200)
        self.spinBox_specmax.setProperty("value", DEFAULT_SPEC_MAX)
        self.spinBox_specmax.setObjectName("spinBox_specmax")
        self.spinBox_specmax.setSuffix(" dB")

        self.comboBox_weighting = QtWidgets.QComboBox(self)
        self.comboBox_weighting.setObjectName("weighting")
        self.comboBox_weighting.addItem("None")
        self.comboBox_weighting.addItem("A")
        self.comboBox_weighting.addItem("B")
        self.comboBox_weighting.addItem("C")
        self.comboBox_weighting.setCurrentIndex(DEFAULT_WEIGHTING)

        self.comboBox_response_time = QtWidgets.QComboBox(self)
        self.comboBox_response_time.setObjectName("response_time")
        self.comboBox_response_time.addItem("25 ms (Impulse)")
        self.comboBox_response_time.addItem("125 ms (Fast)")
        self.comboBox_response_time.addItem("300 ms")
        self.comboBox_response_time.addItem("1s (Slow)")
        self.comboBox_response_time.setCurrentIndex(DEFAULT_RESPONSE_TIME_INDEX)

        self.checkBox_showFreqLabels = QtWidgets.QCheckBox(self)
        self.checkBox_showFreqLabels.setObjectName("showFreqLabels")
        self.checkBox_showFreqLabels.setChecked(DEFAULT_SHOW_FREQ_LABELS)

        self.formLayout.addRow("Measurement type:", self.comboBox_dual_channel)
        self.formLayout.addRow("FFT Size:", self.comboBox_fftsize)
        self.formLayout.addRow("Frequency scale:", self.comboBox_freqscale)
        self.formLayout.addRow("Min frequency:", self.spinBox_minfreq)
        self.formLayout.addRow("Max frequency:", self.spinBox_maxfreq)
        self.formLayout.addRow("Min:", self.spinBox_specmin)
        self.formLayout.addRow("Max:", self.spinBox_specmax)
        self.formLayout.addRow("Middle-ear weighting:", self.comboBox_weighting)
        self.formLayout.addRow("Response time:", self.comboBox_response_time)
        self.formLayout.addRow("Display max-frequency label:", self.checkBox_showFreqLabels)

        self.setLayout(self.formLayout)

        self.comboBox_dual_channel.currentIndexChanged.connect(self.dualchannelchanged)
        self.comboBox_fftsize.currentIndexChanged.connect(self.fftsizechanged)
        self.comboBox_freqscale.currentIndexChanged.connect(self.freqscalechanged)
        self.spinBox_minfreq.valueChanged.connect(self.parent().setminfreq)
        self.spinBox_maxfreq.valueChanged.connect(self.parent().setmaxfreq)
        self.spinBox_specmin.valueChanged.connect(self.parent().setmin)
        self.spinBox_specmax.valueChanged.connect(self.parent().setmax)
        self.comboBox_weighting.currentIndexChanged.connect(self.parent().setweighting)
        self.comboBox_response_time.currentIndexChanged.connect(self.responsetimechanged)
        self.checkBox_showFreqLabels.toggled.connect(self.parent().setShowFreqLabel)

    # slot
    def dualchannelchanged(self, index):
        if index == 0:
            self.parent().setdualchannels(False)
        else:
            self.parent().setdualchannels(True)

    # slot
    def fftsizechanged(self, index):
        self.logger.info("fft_size_changed slot %d %d %f", index, 2 ** index * 32, 150000 / 2 ** index * 32)
        # FIXME the size should not be found from the index, but attached as item data
        fft_size = 2 ** index * 32
        self.parent().setfftsize(fft_size)

    # slot
    def freqscalechanged(self, index):
        self.logger.info("freq_scale slot %d %s", index, fscales.ALL[index].NAME)
        self.parent().PlotZoneSpect.setfreqscale(fscales.ALL[index])

    # slot
    def responsetimechanged(self, index):
        if index == 0:
            response_time = 0.025
        elif index == 1:
            response_time = 0.125
        elif index == 2:
            response_time = 0.3
        elif index == 3:
            response_time = 1.
        self.logger.info("responsetimechanged slot %d %d", index, response_time)
        self.parent().setresponsetime(response_time)

    # method
    def saveState(self, settings):
        settings.setValue("fftSize", self.comboBox_fftsize.currentIndex())
        settings.setValue("freqScale", self.comboBox_freqscale.currentIndex())
        settings.setValue("freqMin", self.spinBox_minfreq.value())
        settings.setValue("freqMax", self.spinBox_maxfreq.value())
        settings.setValue("Min", self.spinBox_specmin.value())
        settings.setValue("Max", self.spinBox_specmax.value())
        settings.setValue("weighting", self.comboBox_weighting.currentIndex())
        settings.setValue("responseTime", self.comboBox_response_time.currentIndex())
        settings.setValue("showFreqLabels", self.checkBox_showFreqLabels.isChecked())

    # method
    def restoreState(self, settings):
        fft_size = settings.value("fftSize", DEFAULT_FFT_SIZE, type=int)  # 7th index is 1024 points
        self.comboBox_fftsize.setCurrentIndex(fft_size)
        freqscale = settings.value("freqScale", DEFAULT_FREQ_SCALE, type=int)
        self.comboBox_freqscale.setCurrentIndex(freqscale)
        freqMin = settings.value("freqMin", DEFAULT_MINFREQ, type=int)
        self.spinBox_minfreq.setValue(freqMin)
        freqMax = settings.value("freqMax", DEFAULT_MAXFREQ, type=int)
        self.spinBox_maxfreq.setValue(freqMax)
        colorMin = settings.value("Min", DEFAULT_SPEC_MIN, type=int)
        self.spinBox_specmin.setValue(colorMin)
        colorMax = settings.value("Max", DEFAULT_SPEC_MAX, type=int)
        self.spinBox_specmax.setValue(colorMax)
        weighting = settings.value("weighting", DEFAULT_WEIGHTING, type=int)
        self.comboBox_weighting.setCurrentIndex(weighting)
        responseTime = settings.value("responseTime", DEFAULT_RESPONSE_TIME_INDEX, type=int)
        self.comboBox_response_time.setCurrentIndex(responseTime)
        showFreqLabels = settings.value("showFreqLabels", DEFAULT_SHOW_FREQ_LABELS, type=bool)
        self.checkBox_showFreqLabels.setChecked(showFreqLabels) DEFAULT_FFT_SIZE = 8  # 8192 points DEFAULT_FREQ_SCALE = 2  # Mel DEFAULT_MAXFREQ = 20000 DEFAULT_MINFREQ = 20 DEFAULT_SPEC_MIN = -100 DEFAULT_SPEC_MAX = -20 DEFAULT_WEIGHTING = 1  # A DEFAULT_RESPONSE_TIME = 0.025 DEFAULT_SHOW_FREQ_LABELS = True SAMPLING_RATE = 48000 class SpectrumPlotWidget(QtWidgets.QWidget):

    def __init__(self, parent, engine):
        super(SpectrumPlotWidget, self).__init__(parent)

        self.logger = logging.getLogger(__name__)

        store = GetStore()
        self._spectrum_data = Spectrum_Data(store)
        store._dock_states.append(self._spectrum_data)
        state_id = len(store._dock_states) - 1

        self._curve_signal = FilledCurve(CurveType.SIGNAL)
        self._spectrum_data.add_plot_item(self._curve_signal)

        self._curve_peak = FilledCurve(CurveType.PEEK)

        self._spectrum_data.show_legend = False
        self._spectrum_data.vertical_axis.name = "PSD (dB)"
        self._spectrum_data.vertical_axis.setTrackerFormatter(lambda x: "%.1f dB" % (x))
        self._spectrum_data.horizontal_axis.name = "Frequency (Hz)"
        self._spectrum_data.horizontal_axis.setTrackerFormatter(lambda x: "%.0f Hz" % (x))

        self._spectrum_data.vertical_axis.setRange(0, 1)
        self._spectrum_data.horizontal_axis.setRange(0, 22000)

        self._baseline = Baseline.PLOT_BOTTOM

        self.paused = False

        self.peaks_enabled = True
        self.peak = zeros((3,))
        self.peak_int = zeros((3,))
        self.peak_decay = ones((3,)) * PEAK_DECAY_RATE

        self.normVerticalScaleTransform = CoordinateTransform(0, 1, 1, 0, 0)
        self.normHorizontalScaleTransform = CoordinateTransform(0, 22000, 1, 0, 0)

        plotLayout = QtWidgets.QGridLayout(self)
        plotLayout.setSpacing(0)
        plotLayout.setContentsMargins(0, 0, 0, 0)

        self.quickWidget = QQuickWidget(engine, self)
        self.quickWidget.statusChanged.connect(self.on_status_changed)
        self.quickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
        self.quickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self.quickWidget.setSource(qml_url("Spectrum.qml"))
        
        raise_if_error(self.quickWidget)

        self.quickWidget.rootObject().setProperty("stateId", state_id)

        plotLayout.addWidget(self.quickWidget)

        self.setLayout(plotLayout)

    def on_status_changed(self, status):
        if status == QQuickWidget.Error:
            for error in self.quickWidget.errors():
                self.logger.error("QML error: " + error.toString())

    def setfreqscale(self, scale):
        self.normHorizontalScaleTransform.setScale(scale)
        self._spectrum_data.horizontal_axis.scale_division.setScale(scale)
        self._spectrum_data.horizontal_axis.coordinate_transform.setScale(scale)

    def setfreqrange(self, minfreq, maxfreq):
        self.xmin = minfreq
        self.xmax = maxfreq

        self._spectrum_data.horizontal_axis.setRange(minfreq, maxfreq)
        self.normHorizontalScaleTransform.setRange(minfreq, maxfreq)

    def setspecrange(self, spec_min, spec_max):
        if spec_min > spec_max:
            spec_min, spec_max = spec_max, spec_min

        self._spectrum_data.vertical_axis.setRange(spec_min, spec_max)
        self.normVerticalScaleTransform.setRange(spec_min, spec_max)

    def setweighting(self, weighting):
        if weighting == 0:
            title = "PSD (dB)"
        elif weighting == 1:
            title = "PSD (dB A)"
        elif weighting == 2:
            title = "PSD (dB B)"
        else:
            title = "PSD (dB C)"

        self._spectrum_data.vertical_axis.name = title

    def setShowFreqLabel(self, showFreqLabel):
        self._spectrum_data.showFrequencyTracker = showFreqLabel

    def set_peaks_enabled(self, enabled):
        self.peaks_enabled = enabled

        if enabled:
            self._spectrum_data.insert_plot_item(0, self._curve_peak)
        else:
            self._spectrum_data.remove_plot_item(self._curve_peak)

    def set_baseline_displayUnits(self, baseline):
        self._baseline = Baseline.PLOT_BOTTOM

    def set_baseline_dataUnits(self, baseline):
        self._baseline = Baseline.DATA_ZERO

    def setdata(self, x, y, fmax):
        if not self.paused:
            if fmax < 2e2:
                text = "%.1f Hz" % (fmax)
            else:
                text = "%d Hz" % (np.rint(fmax))
            self._spectrum_data.setFmax(text, self.normHorizontalScaleTransform.toScreen(fmax))

            M = np.max(y)
            m = self.normVerticalScaleTransform.coord_min
            y_int = (y-m)/(np.abs(M-m)+1e-3)

            x_left = zeros(x.shape)
            x_right = zeros(x.shape)
            x_left[0] = 1e-10
            x_left[1:] = (x[1:] + x[:-1]) / 2.
            x_right[:-1] = x_left[1:]
            x_right[-1] = float(SAMPLING_RATE / 2)
            scaled_x_left = self.normHorizontalScaleTransform.toScreen(x_left)
            scaled_x_right = self.normHorizontalScaleTransform.toScreen(x_right)

            baseline = 1. if self._baseline == Baseline.PLOT_BOTTOM else (1. - self.normVerticalScaleTransform.toScreen(0.))

            scaled_y = 1. - self.normVerticalScaleTransform.toScreen(y)
            z = y_int
            self._curve_signal.setData(scaled_x_left, scaled_x_right, scaled_y, z, baseline)

            if self.peaks_enabled:
                self.compute_peaks(y)
                scaled_peak = 1. - self.normVerticalScaleTransform.toScreen(self.peak)
                z_peak = self.peak_int
                self._curve_peak.setData(scaled_x_left, scaled_x_right, scaled_peak, z_peak, baseline)

    def draw(self):
        return

    def pause(self):
        self.paused = True

    def restart(self):
        self.paused = False

    def canvasUpdate(self):
        return

    def compute_peaks(self, y):
        if len(self.peak) != len(y):
            y_ones = ones(y.shape)
            self.peak = y_ones * (-500.)
            self.peak_int = zeros(y.shape)
            self.peak_decay = y_ones * 20. * log10(PEAK_DECAY_RATE) * 5000

        mask1 = (self.peak < y)
        mask2 = (~mask1)
        mask2_a = mask2 * (self.peak_int < 0.2)
        mask2_b = mask2 * (self.peak_int >= 0.2)

        self.peak[mask1] = y[mask1]
        self.peak[mask2_a] = self.peak[mask2_a] + self.peak_decay[mask2_a]

        self.peak_decay[mask1] = 20. * log10(PEAK_DECAY_RATE) * 5000
        self.peak_decay[mask2_a] += 20. * log10(PEAK_DECAY_RATE) * 5000

        self.peak_int[mask1] = 1.
        self.peak_int[mask2_b] *= 0.975
from PyQt5 import QtWidgets
from numpy import log10, argmax, zeros, arange, floor, float64
from friture.audioproc import audioproc  # audio processing class
from friture.spectrum_settings import (Spectrum_Settings_Dialog,  # settings dialog
                                       DEFAULT_FFT_SIZE,
                                       DEFAULT_FREQ_SCALE,
                                       DEFAULT_MAXFREQ,
                                       DEFAULT_MINFREQ,
                                       DEFAULT_SPEC_MIN,
                                       DEFAULT_SPEC_MAX,
                                       DEFAULT_WEIGHTING,
                                       DEFAULT_RESPONSE_TIME,
                                       DEFAULT_SHOW_FREQ_LABELS)
from friture.audiobackend import SAMPLING_RATE
from friture.spectrumPlotWidget import SpectrumPlotWidget
from friture_extensions.exp_smoothing_conv import pyx_exp_smoothed_value_numpy
import friture.plotting.frequency_scales as fscales
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2009 Timoth?Lecomte

# This file is part of Friture.
#
# Friture is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as published by
# the Free Software Foundation.
#
# Friture is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Friture.  If not, see <http://www.gnu.org/licenses/>.




class Spectrum_Widget(QtWidgets.QWidget):

    def __init__(self, parent, engine):
        super().__init__(parent)

        self.audiobuffer = None

        self.setObjectName("Spectrum_Widget")
        self.gridLayout = QtWidgets.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.gridLayout.setContentsMargins(2, 2, 2, 2)
        self.PlotZoneSpect = SpectrumPlotWidget(self, engine)
        self.PlotZoneSpect.setObjectName("PlotZoneSpect")
        self.gridLayout.addWidget(self.PlotZoneSpect, 0, 0, 1, 1)

        # initialize the class instance that will do the fft
        self.proc = audioproc()

        self.maxfreq = DEFAULT_MAXFREQ
        self.proc.set_maxfreq(self.maxfreq)

        self.minfreq = DEFAULT_MINFREQ