ADSR: visualize envelope and track current position.

main
Ben Niemann 1 year ago
parent d9f344e4e0
commit 575979796a

@ -0,0 +1,19 @@
# @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,59 @@
/*
* @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 <flatbuffers/flatbuffers.h>
#include "noisicaa/core/flatbuffers.h"
#include "noisicaa/engine/block_context.h"
#include "noisicaa/engine/buffers.h"
#include "noisicaa/node_lib/adsr/audio.h"
#include "noisicaa/node_lib/adsr/node_message_generated.h"
namespace noisicaa::engine::adsr {
Node::Node(EngineImpl* engine, uint64_t id, const char* logger_name)
: FaustNode(engine, id, logger_name) {}
Status Node::init(const NodeDescription* description) {
RETURN_IF_ERROR(FaustNode::init(description));
return Status::Ok();
}
Status Node::run(BlockContext* ctxt, const std::vector<Buffer*>* buffers) {
RETURN_IF_ERROR(FaustNode::run(ctxt, buffers));
float* time = (float*)buffers->at(2)->data();
FlatBufferAllocator allocator;
flatbuffers::FlatBufferBuilder fbs(allocator.buf_size, &allocator);
NodeMessageBuilder msg_builder(fbs);
msg_builder.add_pos(time[0] / ctxt->sample_rate);
fbs.Finish(msg_builder.Finish());
push_node_message(ctxt, fbs.GetBufferPointer(), fbs.GetSize());
return Status::Ok();
}
}

@ -20,14 +20,52 @@
* @end:license
*/
declare input0_name "gate";
ma = library("maths.lib");
en = library("envelopes.lib");
os = library("oscillators.lib");
declare input0_name "gate";
declare output0_name "out";
declare output1_name "time";
at = hslider("attack", 0.05, 0.0, 10.0, 0.001);
dt = hslider("decay", 0.05, 0.0, 10.0, 0.001);
sl = hslider("sustain", 0.5, 0.0, 1.0, 0.001);
rt = hslider("release", 0.5, 0.0, 10.0, 0.001);
process = en.adsr(at, dt, sl, rt);
// copy'n'pasted from faust's envelope lib and extended to include the current envelope position in
// a second output stream.
adsr(at,dt,sl,rt,gate) = (ADS : *(1-R) : max(0)), T
with {
// Durations in samples
an = max(1, at*ma.SR);
dn = max(1, dt*ma.SR);
rn = max(1, rt*ma.SR);
// Deltas per samples
adelta = 1/an;
ddelta = (1-sl)/dn;
// Attack time (starts when gate changes and raises until gate == 0)
atime = +(gate) ~ *(gate' >= gate);
// Attack curve
A = atime * adelta;
// Decay curve
D0 = 1 + an * ddelta;
D = D0 - atime * ddelta;
// ADS part
ADS = min(A, max(D, sl));
// Release time starts when gate is 0
rtime = (+(1) : *(gate == 0)) ~ _;
// Release curve starts when gate is 0 with the current value of the envelope
R = rtime/rn;
// The current time position of the envelope.
T = min(atime, an + dn) * gate + (an + dn) * (gate == 0) + rtime;
};
process = adsr(at, dt, sl, rt);

@ -0,0 +1,47 @@
// -*- 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_NODE_LIB_ASDR_AUDIO_H
#define _NOISICAA_NODE_LIB_ASDR_AUDIO_H
#include "noisicaa/engine/faust_node.h"
namespace noisicaa::engine {
class EngineImpl;
namespace adsr {
class Node : public noisicaa::engine::FaustNode {
public:
Node(EngineImpl* engine, uint64_t id, const char* logger_name);
Status init(const NodeDescription* description) override;
Status run(BlockContext* ctxt, const std::vector<Buffer*>* buffers) override;
protected:
};
} // namespace adsr
} // namespace noisicaa::engine
#endif

@ -2,4 +2,8 @@ uri: builtin://adsr
audio_class_uri: node://adsr
display_name: Envelope (ADSR)
ui:
uri: builtin://adsr
body_qml: noisicaa/node_lib/adsr/ui.qml
ports:
- name: time
internal: true

@ -0,0 +1,27 @@
// @begin:license
//
// Copyright (c) 2015-2021, Ben 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
namespace noisicaa.engine.adsr;
table NodeMessage {
pos : float;
}
root_type NodeMessage;

@ -0,0 +1,46 @@
# @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
from PySide2 import QtCore
from noisicaa.ui.GraphNode import GraphNode
from . import node_message_generated as node_message_fb
logger = logging.getLogger(__name__)
class Node(GraphNode):
posChanged = QtCore.Signal(float)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.setNodeMessageHandler(self.__handleNodeMessage)
def __handleNodeMessage(self, msg_serialized: bytes) -> None:
msg = node_message_fb.NodeMessage(msg_serialized)
self.posChanged.emit(msg.pos)
URI = 'builtin://adsr'
CLASS = Node

@ -0,0 +1,190 @@
import QtQuick 2.12
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import noisicaa.ui.components 1.0
ColumnLayout {
id: root
Logger {
id: logger
name: "noisicaa.node_lib.adsr.ui"
}
property QtObject d
property color bgColor: ApplicationWindow.window ? ApplicationWindow.window.color : "#ffffff"
anchors.centerIn: parent
ColumnLayout {
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 80
color: Qt.lighter(root.bgColor, 1.2)
border.width: 1
border.color: Qt.tint(
Qt.hsva(color.hsvHue, color.hsvSaturation, 1.0 - color.hsvValue, 1.0),
"#80808080")
Plot {
anchors.fill: parent
anchors.margins: 3
backgroundColor: parent.color
xMin: -0.05
xMax: 1.05
yMin: -0.05
yMax: 1.05
showCursor: true
cursorColor: Qt.hsva(backgroundColor.hsvHue, backgroundColor.hsvSaturation, 1.0 - backgroundColor.hsvValue, 0.8)
PointPlot {
property var baseColor: Qt.lighter(root.bgColor, 1.2)
id: plotPoints
pointColor: Qt.hsva(baseColor.hsvHue, baseColor.hsvSaturation, 1.0 - baseColor.hsvValue, 0.5)
pointSize: 3.0
}
LinePlot {
property var baseColor: Qt.lighter(root.bgColor, 1.2)
id: plotLines
lineColor: Qt.hsva(baseColor.hsvHue, baseColor.hsvSaturation, 1.0 - baseColor.hsvValue, 1.0)
lineWidth: 1.0
}
function updatePlot() {
var at = d.port("attack").port.controlValue;
var dc = d.port("decay").port.controlValue;
var su = d.port("sustain").port.controlValue;
var rl = d.port("release").port.controlValue;
xMax = at + dc + rl + 0.05;
plotLines.clearPoints();
plotLines.addPoint(0.0, 0.0);
plotLines.addPoint(at, 1.0);
plotLines.addPoint(at + dc, su);
plotLines.addPoint(at + dc + rl, 0);
plotPoints.clearPoints();
plotPoints.addPoint(at, 1.0);
plotPoints.addPoint(at + dc, su);
}
Component.onCompleted: {
d.posChanged.connect(function (pos) {
cursorPos = pos;
});
updatePlot();
d.port("attack").port.controlValueChanged.connect(updatePlot);
d.port("decay").port.controlValueChanged.connect(updatePlot);
d.port("sustain").port.controlValueChanged.connect(updatePlot);
d.port("release").port.controlValueChanged.connect(updatePlot);
}
}
}
ColumnLayout {
id: attackControl
property QtObject port: d.port("attack")
Label {
text: "attack"
}
RowLayout {
Text {
text: attackControl.port.port.controlValue.toFixed(2)
}
Slider {
orientation: Qt.Horizontal
from: 0
to: 10
stepSize: 0.001
snapMode: Slider.SnapAlways
enabled: attackControl.port.numConnections == 0
value: attackControl.port.port.controlValue
onValueChanged: attackControl.port.setControlValue(value);
}
}
}
ColumnLayout {
id: decayControl
property QtObject port: d.port("decay")
Label {
text: "decay"
}
RowLayout {
Text {
text: decayControl.port.port.controlValue.toFixed(2)
}
Slider {
orientation: Qt.Horizontal
from: 0
to: 10
stepSize: 0.001
snapMode: Slider.SnapAlways
enabled: decayControl.port.numConnections == 0
value: decayControl.port.port.controlValue
onValueChanged: decayControl.port.setControlValue(value);
}
}
}
ColumnLayout {
id: releaseControl
property QtObject port: d.port("release")
Label {
text: "release"
}
RowLayout {
Text {
text: releaseControl.port.port.controlValue.toFixed(2)
}
Slider {
orientation: Qt.Horizontal
from: 0
to: 10
stepSize: 0.001
snapMode: Slider.SnapAlways
enabled: releaseControl.port.numConnections == 0
value: releaseControl.port.port.controlValue
onValueChanged: releaseControl.port.setControlValue(value);
}
}
}
ColumnLayout {
id: sustainControl
property QtObject port: d.port("sustain")
Label {
text: "sustain"
}
RowLayout {
Text {
text: sustainControl.port.port.controlValue.toFixed(2)
}
Slider {
orientation: Qt.Horizontal
from: 0
to: 1
stepSize: 0.001
snapMode: Slider.SnapAlways
enabled: sustainControl.port.numConnections == 0
value: sustainControl.port.port.controlValue
onValueChanged: sustainControl.port.setControlValue(value);
}
}
}
}
}

@ -19,4 +19,9 @@
# @end:license
def build(ctx):
ctx.noise_faust_node()
ctx.noise_faust_node(
has_audio=True,
has_ui=True,
has_qml=True,
has_node_message=True,
)

Loading…
Cancel
Save