Add a VUMeter node.
parent
d34f92e08e
commit
cb040f3686
|
@ -43,6 +43,7 @@ from .metronome.node_description import MetronomeDescription
|
|||
from .midi_velocity_mapper.node_description import MidiVelocityMapperDescription
|
||||
from .cv_mapper.node_description import CVMapperDescription
|
||||
from .oscilloscope.node_description import OscilloscopeDescription
|
||||
from .vumeter.node_description import VUMeterDescription
|
||||
|
||||
|
||||
def node_descriptions() -> Iterator[node_db.NodeDescription]:
|
||||
|
@ -66,3 +67,4 @@ def node_descriptions() -> Iterator[node_db.NodeDescription]:
|
|||
yield MidiVelocityMapperDescription
|
||||
yield CVMapperDescription
|
||||
yield OscilloscopeDescription
|
||||
yield VUMeterDescription
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "noisicaa/builtin_nodes/midi_velocity_mapper/processor.h"
|
||||
#include "noisicaa/builtin_nodes/cv_mapper/processor.h"
|
||||
#include "noisicaa/builtin_nodes/oscilloscope/processor.h"
|
||||
#include "noisicaa/builtin_nodes/vumeter/processor.h"
|
||||
|
||||
namespace noisicaa {
|
||||
|
||||
|
@ -101,6 +102,9 @@ StatusOr<Processor*> create_processor(
|
|||
} else if (desc.processor().type() == "builtin://oscilloscope") {
|
||||
assert(desc.type() == pb::NodeDescription::PROCESSOR);
|
||||
return new ProcessorOscilloscope(realm_name, node_id, host_system, desc);
|
||||
} else if (desc.processor().type() == "builtin://vumeter") {
|
||||
assert(desc.type() == pb::NodeDescription::PROCESSOR);
|
||||
return new ProcessorVUMeter(realm_name, node_id, host_system, desc);
|
||||
}
|
||||
|
||||
return ERROR_STATUS("Invalid processor type %s", desc.processor().type().c_str());
|
||||
|
|
|
@ -45,6 +45,7 @@ from .metronome.node_ui import MetronomeNode
|
|||
from .midi_velocity_mapper.node_ui import MidiVelocityMapperNode
|
||||
from .cv_mapper.node_ui import CVMapperNode
|
||||
from .oscilloscope.node_ui import OscilloscopeNode
|
||||
from .vumeter.node_ui import VUMeterNode
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from noisicaa.ui.graph import base_node
|
||||
|
@ -69,6 +70,7 @@ node_ui_cls_map = {
|
|||
'builtin://midi-velocity-mapper': MidiVelocityMapperNode,
|
||||
'builtin://cv-mapper': CVMapperNode,
|
||||
'builtin://oscilloscope': OscilloscopeNode,
|
||||
'builtin://vumeter': VUMeterNode,
|
||||
} # type: Dict[str, Type[base_node.Node]]
|
||||
|
||||
track_editor_cls_map = {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
||||
|
||||
from noisicaa import node_db
|
||||
|
||||
|
||||
VUMeterDescription = node_db.NodeDescription(
|
||||
uri='builtin://vumeter',
|
||||
display_name='VU Meter',
|
||||
type=node_db.NodeDescription.PROCESSOR,
|
||||
node_ui=node_db.NodeUIDescription(
|
||||
type='builtin://vumeter',
|
||||
),
|
||||
builtin_icon='node-type-builtin',
|
||||
processor=node_db.ProcessorDescription(
|
||||
type='builtin://vumeter',
|
||||
),
|
||||
ports=[
|
||||
node_db.PortDescription(
|
||||
name='in:left',
|
||||
direction=node_db.PortDescription.INPUT,
|
||||
types=[node_db.PortDescription.AUDIO],
|
||||
),
|
||||
node_db.PortDescription(
|
||||
name='in:right',
|
||||
direction=node_db.PortDescription.INPUT,
|
||||
types=[node_db.PortDescription.AUDIO],
|
||||
),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from noisicaa import core
|
||||
from noisicaa import music
|
||||
from noisicaa.ui import ui_base
|
||||
from noisicaa.ui import vumeter
|
||||
from noisicaa.ui.graph import base_node
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VUMeterNodeWidget(ui_base.ProjectMixin, core.AutoCleanupMixin, QtWidgets.QWidget):
|
||||
def __init__(self, node: music.BaseNode, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__node = node
|
||||
|
||||
self.__vu_meter = vumeter.VUMeter(self)
|
||||
|
||||
self.__meter_urid = self.app.urid_mapper.map(
|
||||
'http://noisicaa.odahoda.de/lv2/processor_vumeter#meter')
|
||||
|
||||
listener = self.audioproc_client.node_messages.add(
|
||||
'%016x' % self.__node.id, self.__nodeMessage)
|
||||
self.add_cleanup_function(listener.remove)
|
||||
|
||||
self.setMinimumSize(QtCore.QSize(10, 10))
|
||||
|
||||
self.__current_orientation = None # type: Qt.Orientation
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(self.__vu_meter)
|
||||
self.setLayout(layout)
|
||||
|
||||
def __nodeMessage(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.__vu_meter.setLeftValue(current_left)
|
||||
self.__vu_meter.setLeftPeak(peak_left)
|
||||
self.__vu_meter.setRightValue(current_right)
|
||||
self.__vu_meter.setRightPeak(peak_right)
|
||||
|
||||
def resizeEvent(self, evt: QtGui.QResizeEvent) -> None:
|
||||
super().resizeEvent(evt)
|
||||
|
||||
w, h = self.width(), self.height()
|
||||
if w > h:
|
||||
orientation = Qt.Horizontal
|
||||
else:
|
||||
orientation = Qt.Vertical
|
||||
|
||||
if orientation == self.__current_orientation:
|
||||
return
|
||||
|
||||
self.__vu_meter.setOrientation(orientation)
|
||||
|
||||
self.__current_orientation = orientation
|
||||
|
||||
|
||||
class VUMeterNode(base_node.Node):
|
||||
def createBodyWidget(self) -> QtWidgets.QWidget:
|
||||
widget = VUMeterNodeWidget(node=self.node(), context=self.context)
|
||||
self.add_cleanup_function(widget.cleanup)
|
||||
return widget
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* @begin:license
|
||||
*
|
||||
* Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* @end:license
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
|
||||
|
||||
#include "noisicaa/host_system/host_system.h"
|
||||
#include "noisicaa/audioproc/public/processor_message.pb.h"
|
||||
#include "noisicaa/audioproc/engine/misc.h"
|
||||
#include "noisicaa/audioproc/engine/message_queue.h"
|
||||
#include "noisicaa/builtin_nodes/vumeter/processor.h"
|
||||
|
||||
namespace noisicaa {
|
||||
|
||||
static const float min_db = -70.0f;
|
||||
static const float max_db = 20.0f;
|
||||
|
||||
ProcessorVUMeter::ProcessorVUMeter(
|
||||
const string& realm_name, const string& node_id, HostSystem *host_system,
|
||||
const pb::NodeDescription& desc)
|
||||
: Processor(
|
||||
realm_name, node_id, "noisicaa.audioproc.engine.processor.vumeter", host_system, desc) {}
|
||||
|
||||
Status ProcessorVUMeter::setup_internal() {
|
||||
RETURN_IF_ERROR(Processor::setup_internal());
|
||||
|
||||
_meter_urid = _host_system->lv2->map(
|
||||
"http://noisicaa.odahoda.de/lv2/processor_vumeter#meter");
|
||||
|
||||
_window_size = min(
|
||||
(uint32_t)(0.05 * _host_system->sample_rate()), // 50ms
|
||||
_host_system->sample_rate());
|
||||
_history_pos = 0;
|
||||
_peak_decay = 20 / (0.4 * _host_system->sample_rate());
|
||||
for (int ch = 0 ; ch < 2 ; ++ch) {
|
||||
_history[ch].reset(new float[_window_size]);
|
||||
for (uint32_t i = 0 ; i < _window_size ; ++i) {
|
||||
_history[ch].get()[i] = min_db;
|
||||
}
|
||||
|
||||
_peak_hold[ch] = 0;
|
||||
_peak[ch] = min_db;
|
||||
}
|
||||
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
void ProcessorVUMeter::cleanup_internal() {
|
||||
for (int ch = 0 ; ch < 2 ; ++ch) {
|
||||
_history[ch].reset();
|
||||
}
|
||||
|
||||
Processor::cleanup_internal();
|
||||
}
|
||||
|
||||
Status ProcessorVUMeter::process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper) {
|
||||
static const int LEFT = 0;
|
||||
static const int RIGHT = 1;
|
||||
|
||||
float* buf[2] = {
|
||||
(float*)_buffers[LEFT]->data(),
|
||||
(float*)_buffers[RIGHT]->data()
|
||||
};
|
||||
for (uint32_t i = 0 ; i < _host_system->block_size() ; ++i) {
|
||||
for (int ch = 0 ; ch < 2 ; ++ch) {
|
||||
float value = logf(fabsf(*(buf[ch]))) / 0.11512925f;
|
||||
value = max(min_db, min(value, max_db));
|
||||
|
||||
_history[ch].get()[_history_pos] = value;
|
||||
|
||||
if (value > _peak[ch]) {
|
||||
_peak_hold[ch] = int(0.5 * _host_system->sample_rate());
|
||||
_peak[ch] = value;
|
||||
} else if (_peak_hold[ch] == 0) {
|
||||
_peak[ch] = max(min_db, _peak[ch] - _peak_decay);
|
||||
} else {
|
||||
--_peak_hold[ch];
|
||||
}
|
||||
|
||||
++buf[ch];
|
||||
}
|
||||
|
||||
_history_pos = (_history_pos + 1) % _window_size;
|
||||
}
|
||||
|
||||
float current[2] = { min_db, min_db };
|
||||
for (uint32_t i = 0 ; i < _window_size ; ++i) {
|
||||
for (int ch = 0 ; ch < 2 ; ++ch) {
|
||||
current[ch] = max(current[ch], _history[ch].get()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t atom[200];
|
||||
LV2_Atom_Forge forge;
|
||||
lv2_atom_forge_init(&forge, &_host_system->lv2->urid_map);
|
||||
lv2_atom_forge_set_buffer(&forge, atom, sizeof(atom));
|
||||
|
||||
LV2_Atom_Forge_Frame oframe;
|
||||
lv2_atom_forge_object(&forge, &oframe, _host_system->lv2->urid.core_nodemsg, 0);
|
||||
|
||||
lv2_atom_forge_key(&forge, _meter_urid);
|
||||
LV2_Atom_Forge_Frame tframe;
|
||||
lv2_atom_forge_tuple(&forge, &tframe);
|
||||
for (int ch = 0 ; ch < 2 ; ++ch) {
|
||||
lv2_atom_forge_float(&forge, current[ch]);
|
||||
lv2_atom_forge_float(&forge, _peak[ch]);
|
||||
}
|
||||
lv2_atom_forge_pop(&forge, &tframe);
|
||||
|
||||
lv2_atom_forge_pop(&forge, &oframe);
|
||||
|
||||
NodeMessage::push(ctxt->out_messages, _node_id, (LV2_Atom*)atom);
|
||||
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// -*- mode: c++ -*-
|
||||
|
||||
/*
|
||||
* @begin:license
|
||||
*
|
||||
* Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* @end:license
|
||||
*/
|
||||
|
||||
#ifndef _NOISICAA_BUILTIN_NODES_VUMETER_PROCESSOR_H
|
||||
#define _NOISICAA_BUILTIN_NODES_VUMETER_PROCESSOR_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
|
||||
|
||||
#include "noisicaa/core/status.h"
|
||||
#include "noisicaa/audioproc/engine/processor.h"
|
||||
|
||||
namespace noisicaa {
|
||||
|
||||
using namespace std;
|
||||
|
||||
class HostSystem;
|
||||
|
||||
class ProcessorVUMeter : public Processor {
|
||||
public:
|
||||
ProcessorVUMeter(
|
||||
const string& realm_name, const string& node_id, HostSystem* host_system,
|
||||
const pb::NodeDescription& desc);
|
||||
|
||||
protected:
|
||||
Status setup_internal() override;
|
||||
void cleanup_internal() override;
|
||||
Status process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper) override;
|
||||
|
||||
private:
|
||||
LV2_URID _meter_urid;
|
||||
|
||||
size_t _window_size;
|
||||
uint32_t _history_pos;
|
||||
unique_ptr<float> _history[2];
|
||||
unique_ptr<float> _history_right;
|
||||
float _peak_decay;
|
||||
uint32_t _peak_hold[2];
|
||||
float _peak[2];
|
||||
};
|
||||
|
||||
} // namespace noisicaa
|
||||
|
||||
#endif
|
|
@ -0,0 +1,33 @@
|
|||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
||||
|
||||
from noisidev import unittest
|
||||
from noisidev import unittest_processor_mixins
|
||||
|
||||
|
||||
class ProcessorVUMeterTest(
|
||||
unittest_processor_mixins.ProcessorTestMixin,
|
||||
unittest.TestCase):
|
||||
def test_process_block(self):
|
||||
self.node_description = self.node_db['builtin://vumeter']
|
||||
self.create_processor()
|
||||
self.fill_buffer('in:left', 1.0)
|
||||
self.fill_buffer('in:right', -1.0)
|
||||
self.process_block()
|
|
@ -0,0 +1,39 @@
|
|||
# -*- mode: python -*-
|
||||
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
||||
|
||||
def build(ctx):
|
||||
ctx.py_module('__init__.py')
|
||||
ctx.py_module('node_description.py')
|
||||
ctx.py_module('node_ui.py')
|
||||
ctx.py_test('processor_test.py')
|
||||
|
||||
ctx.shlib(
|
||||
target='noisicaa-builtin_nodes-vumeter-processor',
|
||||
features=['cxxshlib'],
|
||||
source=[
|
||||
ctx.cpp_module('processor.cpp'),
|
||||
],
|
||||
use=[
|
||||
'noisicaa-audioproc-public',
|
||||
'noisicaa-host_system',
|
||||
]
|
||||
)
|
|
@ -69,6 +69,7 @@ def build(ctx):
|
|||
'noisicaa-builtin_nodes-midi_velocity_mapper-processor',
|
||||
'noisicaa-builtin_nodes-cv_mapper-processor',
|
||||
'noisicaa-builtin_nodes-oscilloscope-processor',
|
||||
'noisicaa-builtin_nodes-vumeter-processor',
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -93,3 +94,4 @@ def build(ctx):
|
|||
ctx.recurse('midi_velocity_mapper')
|
||||
ctx.recurse('cv_mapper')
|
||||
ctx.recurse('oscilloscope')
|
||||
ctx.recurse('vumeter')
|
||||
|
|
Loading…
Reference in New Issue