Skip to content

SPL spectrum and microphone calibration files #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
22 changes: 22 additions & 0 deletions friture/Levels.qml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ Rectangle {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
color: systemPalette.windowText
}

Text {
id: splValues
textFormat: Text.MarkdownText
text: level_view_model.two_channels ? "1: " + level_to_text(level_view_model.level_data_slow.level_spl) + "<br />2: " + level_to_text(level_view_model.level_data_slow_2.level_spl) : level_to_text(level_view_model.level_data_slow.level_spl)
font.pointSize: 14
font.bold: true
verticalAlignment: Text.AlignBottom
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
color: systemPalette.windowText
}

Text {
id: splLegend
textFormat: Text.PlainText
text: "dB SPL"
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
color: systemPalette.windowText
}

LevelsMeter {
Layout.fillHeight: true
Expand Down
57 changes: 57 additions & 0 deletions friture/SPL.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import QtQuick 2.15
import QtQuick.Window 2.2
import QtQuick.Layouts 1.15
import QtQuick.Shapes 1.15
import Friture 1.0

Item {
id: container
property var stateId

// delay the load of the Plot until stateId has been set
Loader {
id: loader
anchors.fill: parent
}

onStateIdChanged: {
console.log("stateId changed: " + stateId)
loader.sourceComponent = plotComponent
}

Component {
id: plotComponent
Plot {
scopedata: Store.dock_states[container.stateId]

Repeater {
model: scopedata.plot_items

PlotFilledCurve {
anchors.fill: parent
curve: modelData
}
}

Column {
anchors.fill: parent
spacing: 0
FrequencyTracker {
visible: scopedata.showFrequencyTracker
anchors.left: parent.left
anchors.right: parent.right
fmaxValue: scopedata.fmaxValue
fmaxLogicalValue: scopedata.fmaxLogicalValue
}

FrequencyTracker {
visible: scopedata.showPitchTracker
anchors.left: parent.left
anchors.right: parent.right
fmaxValue: scopedata.fpitchDisplayText
fmaxLogicalValue: scopedata.fpitchValue
}
}
}
}
}
14 changes: 14 additions & 0 deletions friture/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from friture.level_view_model import LevelViewModel
from friture.level_data import LevelData
from friture.levels import Levels_Widget
from friture.octavespectrum import OctaveSpectrum_Widget
from friture.store import GetStore, Store
from friture.scope_data import Scope_Data
from friture.axis import Axis
Expand All @@ -58,6 +59,7 @@
from friture.spectrogram_item import SpectrogramItem
from friture.spectrogram_item_data import SpectrogramImageData
from friture.spectrum_data import Spectrum_Data
from friture.spl_data import SPLData
from friture.plotFilledCurve import PlotFilledCurve
from friture.filled_curve import FilledCurve
from friture.qml_tools import qml_url
Expand Down Expand Up @@ -98,6 +100,7 @@ def __init__(self):
qmlRegisterType(CoordinateTransform, 'Friture', 1, 0, 'CoordinateTransform')
qmlRegisterType(Scope_Data, 'Friture', 1, 0, 'ScopeData')
qmlRegisterType(Spectrum_Data, 'Friture', 1, 0, 'SpectrumData')
qmlRegisterType(SPLData, 'Friture', 1, 0, 'SPLData')
qmlRegisterType(LevelData, 'Friture', 1, 0, 'LevelData')
qmlRegisterType(LevelViewModel, 'Friture', 1, 0, 'LevelViewModel')
qmlRegisterType(Axis, 'Friture', 1, 0, 'Axis')
Expand All @@ -110,6 +113,9 @@ def __init__(self):
qmlRegisterType(ColorBar, 'Friture', 1, 0, 'ColorBar')
qmlRegisterType(Tick, 'Friture', 1, 0, 'Tick')
qmlRegisterSingletonType(Store, 'Friture', 1, 0, 'Store', lambda engine, script_engine: GetStore())

self.original_calibration = [0, 0]
self.original_calibration_freqs = [0, 20000]

# Setup the user interface
self.ui = Ui_MainWindow()
Expand Down Expand Up @@ -310,6 +316,14 @@ def timer_toggle(self):
self.playback_widget.start_recording()
AudioBackend().restart()
self.dockmanager.restart()

def recalculate_calibration(self):
for dock in self.dockmanager.docks:
if not dock.audiowidget is None:
if hasattr(dock.audiowidget, "proc"):
dock.audiowidget.proc.recalculate_calibration()
elif type(dock.audiowidget) == OctaveSpectrum_Widget:
dock.audiowidget.filters.recalculate_calibration()


def qt_message_handler(mode, context, message):
Expand Down
20 changes: 18 additions & 2 deletions friture/audioproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@

import logging

from numpy import linspace, log10, cos, arange, pi
from numpy import linspace, log10, cos, arange, pi, interp
from numpy.fft import rfft
from friture.audiobackend import SAMPLING_RATE


class audioproc():

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

self.freq = linspace(0, SAMPLING_RATE / 2, 10)
Expand All @@ -39,6 +40,10 @@ def __init__(self):

self.fft_size = 10

self.calibration = [0 for i in self.freq]

self.recalculate_calibration()

def analyzelive(self, samples):
# FFT for a linear transformation in frequency scale
fft = rfft(samples * self.window)
Expand Down Expand Up @@ -95,6 +100,17 @@ def update_freq_cache(self):
self.B = 0.17 + 20. * log10(Rb + eps)
self.A = 2.0 + 20. * log10(Ra + eps)

self.recalculate_calibration()

def recalculate_calibration(self):
try:
if hasattr(self.parent_widget.parent(), "dockmanager"):
self.calibration = interp(self.freq, self.parent_widget.parent().dockmanager.parent().original_calibration_freqs, self.parent_widget.parent().dockmanager.parent().original_calibration)
else:
self.calibration = interp(self.freq, self.parent_widget.parent().original_calibration_freqs, self.parent_widget.parent().original_calibration)
except:
self.calibration = [0 for i in self.freq]

# 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
Expand Down
16 changes: 16 additions & 0 deletions friture/level_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@

class LevelData(QtCore.QObject):
level_rms_changed = QtCore.pyqtSignal(float)
level_spl_changed = QtCore.pyqtSignal(float)
level_max_changed = QtCore.pyqtSignal(float)

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

self._level_rms = -30.
self._level_spl = -30.
self._level_max = -30.

@pyqtProperty(float, notify=level_rms_changed) # type: ignore
Expand All @@ -46,6 +48,20 @@ def level_rms(self, level_rms):
self._level_rms = level_rms
self.level_rms_changed.emit(level_rms)

@pyqtProperty(float, notify=level_spl_changed) # type: ignore
def level_spl(self):
return self._level_spl

@pyqtProperty(float, notify=level_spl_changed) # type: ignore
def level_spl_iec(self):
return dB_to_IEC(self._level_spl)

@level_spl.setter # type: ignore
def level_spl(self, level_spl):
if self._level_spl != level_spl:
self._level_spl = level_spl
self.level_spl_changed.emit(level_spl)

@pyqtProperty(float, notify=level_max_changed) # type: ignore
def level_max(self):
return self._level_max
Expand Down
10 changes: 9 additions & 1 deletion friture/levels.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from friture.audiobackend import SAMPLING_RATE
from friture.qml_tools import qml_url, raise_if_error

from friture.spl_settings import SPL_Settings_Dialog

SMOOTH_DISPLAY_TIMER_PERIOD_MS = 25
LEVEL_TEXT_LABEL_PERIOD_MS = 250

Expand Down Expand Up @@ -73,8 +75,10 @@ def __init__(self, parent, engine):
# initialize the settings dialog
self.settings_dialog = Levels_Settings_Dialog(self)

self.calibration = 0

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

# time = SMOOTH_DISPLAY_TIMER_PERIOD_MS/1000. #DISPLAY
# time = 0.025 #IMPULSE setting for a sound level meter
Expand Down Expand Up @@ -136,6 +140,7 @@ def handle_new_data(self, floatdata):
self.old_rms = value_rms

self.level_view_model.level_data.level_rms = 10. * np.log10(value_rms + 0. * 1e-80)
self.level_view_model.level_data.level_spl = self.level_view_model.level_data.level_rms + 94.0 + self.calibration
self.level_view_model.level_data.level_max = 20. * np.log10(self.old_max + 0. * 1e-80)
self.level_view_model.level_data_ballistic.peak_iec = dB_to_IEC(max(self.level_view_model.level_data.level_max, self.level_view_model.level_data.level_rms))

Expand All @@ -157,6 +162,7 @@ def handle_new_data(self, floatdata):
self.old_rms_2 = value_rms

self.level_view_model.level_data_2.level_rms = 10. * np.log10(value_rms + 0. * 1e-80)
self.level_view_model.level_data_2.level_spl = self.level_view_model.level_data_2.level_rms + 94.0 + self.calibration
self.level_view_model.level_data_2.level_max = 20. * np.log10(self.old_max_2 + 0. * 1e-80)
self.level_view_model.level_data_ballistic_2.peak_iec = dB_to_IEC(max(self.level_view_model.level_data_2.level_max, self.level_view_model.level_data_2.level_rms))

Expand All @@ -169,10 +175,12 @@ def canvasUpdate(self):

if self.i == LEVEL_TEXT_LABEL_STEPS:
self.level_view_model.level_data_slow.level_rms = self.level_view_model.level_data.level_rms
self.level_view_model.level_data_slow.level_spl = self.level_view_model.level_data.level_spl
self.level_view_model.level_data_slow.level_max = self.level_view_model.level_data.level_max

if self.two_channels:
self.level_view_model.level_data_slow_2.level_rms = self.level_view_model.level_data_2.level_rms
self.level_view_model.level_data_slow_2.level_spl = self.level_view_model.level_data_2.level_spl
self.level_view_model.level_data_slow_2.level_max = self.level_view_model.level_data_2.level_max

self.i = self.i % LEVEL_TEXT_LABEL_STEPS
Expand Down
2 changes: 1 addition & 1 deletion friture/longlevels.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def __init__(self, parent, engine):
self.settings_dialog = LongLevels_Settings_Dialog(self)

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

self.level = None # 1e-30
self.level_rms = -200.
Expand Down
17 changes: 15 additions & 2 deletions friture/octavefilters.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with Friture. If not, see <http://www.gnu.org/licenses/>.

from numpy import log10, array, where
from numpy import log10, array, where, interp

from friture.filter import (octave_filter_bank_decimation, octave_frequencies,
octave_filter_bank_decimation_filtic, NOCTAVE)
Expand All @@ -26,12 +26,16 @@

class Octave_Filters():

def __init__(self, bandsperoctave):
def __init__(self, parent, bandsperoctave):
[self.bdec, self.adec] = generated_filters.PARAMS['dec']

self.bdec = array(self.bdec)
self.adec = array(self.adec)

self.calibration = []

self.parent_widget = parent

self.setbandsperoctave(bandsperoctave)

def filter(self, floatdata):
Expand All @@ -48,12 +52,21 @@ def get_decs(self):

return decs

def recalculate_calibration(self):
try:

self.calibration = interp(self.fi, self.parent_widget.parent().dockmanager.parent().original_calibration_freqs, self.parent_widget.parent().dockmanager.parent().original_calibration)
except:
self.calibration = [0 for i in self.fi]

def setbandsperoctave(self, bandsperoctave):
self.bandsperoctave = bandsperoctave
self.nbands = NOCTAVE * self.bandsperoctave
self.fi, self.flow, self.fhigh = octave_frequencies(self.nbands, self.bandsperoctave)
[self.boct, self.aoct, fi, flow, fhigh] = generated_filters.PARAMS['%d' % bandsperoctave]

self.recalculate_calibration()

self.boct = [array(f) for f in self.boct]
self.aoct = [array(f) for f in self.aoct]

Expand Down
4 changes: 2 additions & 2 deletions friture/octavespectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def __init__(self, parent, engine):
self.PlotZoneSpect.setspecrange(self.spec_min, self.spec_max)
self.PlotZoneSpect.setweighting(self.weighting)

self.filters = Octave_Filters(DEFAULT_BANDSPEROCTAVE)
self.filters = Octave_Filters(self, DEFAULT_BANDSPEROCTAVE)
self.dispbuffers = [0] * DEFAULT_BANDSPEROCTAVE * NOCTAVE

# set kernel and parameters for the smoothing filter
Expand Down Expand Up @@ -118,7 +118,7 @@ def handle_new_data(self, floatdata):
w = self.filters.C

epsilon = 1e-30
db_spectrogram = 10 * log10(sp + epsilon) + w
db_spectrogram = 10 * log10(sp + epsilon) + w + self.filters.calibration
self.PlotZoneSpect.setdata(self.filters.flow, self.filters.fhigh, self.filters.f_nominal, db_spectrogram)

# method
Expand Down
2 changes: 1 addition & 1 deletion friture/pitch_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def __init__(
self.out_buf = RingBuffer()
self.out_offset = self.out_buf.offset

self.proc = audioproc()
self.proc = audioproc(self)
self.proc.set_fftsize(self.fft_size)

def set_input_buffer(self, new_buf: RingBuffer) -> None:
Expand Down
Loading