Browse Source

Generate a JSON dump of the faust meta data. Autogenerate the NodeDescription from it.

builtin-nodes
Ben Niemann 3 years ago
parent
commit
016755c7f3
  1. 2
      CMakeLists.txt
  2. 12
      bin/build-faust-processor
  3. 1
      listdeps
  4. 171
      noisicaa/audioproc/engine/processor_faust.cpp
  5. 2
      noisicaa/audioproc/engine/processor_faust.h
  6. 53
      noisicaa/builtin_nodes/oscillator/node_description.py
  7. 17
      noisicaa/builtin_nodes/oscillator/processor.dsp
  8. 16
      noisicaa/builtin_nodes/oscillator/processor_test.py
  9. 2
      noisicaa/node_db/CMakeLists.txt
  10. 5
      noisicaa/node_db/__init__.py
  11. 154
      noisicaa/node_db/faust_parser.py
  12. 126
      noisicaa/node_db/faust_parser_test.py

2
CMakeLists.txt

@ -141,7 +141,7 @@ endmacro(render_csound)
macro(faust_dsp clsName src)
string(REGEX REPLACE "\\.dsp$" "" base ${src})
add_custom_command(
OUTPUT ${base}.cpp ${base}.h
OUTPUT ${base}.cpp ${base}.h ${base}.json
COMMAND bin/build-faust-processor ${clsName} ${CMAKE_CURRENT_LIST_DIR}/${src} ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${src}

12
bin/build-faust-processor

@ -1,5 +1,7 @@
#!/bin/bash
set -e
CLASSNAME=$1
SRC=$2
DESTDIR=$3
@ -29,3 +31,13 @@ sed <noisicaa/audioproc/engine/processor_faust.h.tmpl >${DESTDIR}/${BASE}.h \
-e "s#<<base>>#${BASE}#g" \
-e "s#<<className>>#${CLASSNAME}#g" \
-e "s#<<classNameUpper>>#${CLASSNAME_UPPER}#g"
LD_LIBRARY_PATH=${VIRTUAL_ENV}/lib ${VIRTUAL_ENV}/bin/faust \
--import-dir ${VIRTUAL_ENV}/share/faustlibraries/ \
--language cpp \
-a gen-json.cpp \
-o ${DESTDIR}/${BASE}.json_dumper.cpp \
${SRC}
g++ -I${VIRTUAL_ENV}/include -o${DESTDIR}/${BASE}.json_dumper ${DESTDIR}/${BASE}.json_dumper.cpp
${DESTDIR}/${BASE}.json_dumper >${DESTDIR}/${BASE}.json

1
listdeps

@ -60,6 +60,7 @@ PIP_DEPS = {
PKG('protobuf==3.7.1'),
PKG('psutil'),
PKG('pyaudio'),
PKG('pyparsing'),
PKG('quamash'),
PKG('toposort'),
PKG('urwid'),

171
noisicaa/audioproc/engine/processor_faust.cpp

@ -20,6 +20,8 @@
* @end:license
*/
#include <map>
#include "faust/dsp/dsp.h"
#include "faust/gui/meta.h"
#include "faust/gui/UI.h"
@ -30,6 +32,51 @@
namespace noisicaa {
class FaustControls : public UI {
public:
int num_controls() const {
return _control_map.size();
}
float* get_control_ptr(const string& name) {
return _control_map[name];
}
void openTabBox(const char* label) override {}
void openHorizontalBox(const char* label) override {}
void openVerticalBox(const char* label) override {}
void closeBox() override {}
void addButton(const char* label, float* zone) override {
_control_map[label] = zone;
}
void addCheckButton(const char* label, float* zone) override {
_control_map[label] = zone;
}
void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step) override {
_control_map[label] = zone;
}
void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step) override {
_control_map[label] = zone;
}
void addNumEntry(const char* label, float* zone, float init, float min, float max, float step) override {
_control_map[label] = zone;
}
void addHorizontalBargraph(const char* label, float* zone, float min, float max) override {
_control_map[label] = zone;
}
void addVerticalBargraph(const char* label, float* zone, float min, float max) override {
_control_map[label] = zone;
}
void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) override {}
void declare(float* zone, const char* key, const char* val) override {}
private:
map<string, float*> _control_map;
};
ProcessorFaust::ProcessorFaust(
const string& realm_name, const string& node_id, HostSystem *host_system,
const pb::NodeDescription& desc)
@ -42,50 +89,78 @@ Status ProcessorFaust::setup_internal() {
_dsp.reset(create_dsp());
_dsp->init(_host_system->sample_rate());
int num_inputs = 0;
int num_outputs = 0;
for (const auto& port_desc : _desc.ports()) {
if (port_desc.direction() == pb::PortDescription::INPUT) {
FaustControls controls;
_dsp->buildUserInterface(&controls);
int dsp_ports = _dsp->getNumInputs() + _dsp->getNumOutputs() + controls.num_controls();
if (dsp_ports != _desc.ports_size()) {
return ERROR_STATUS("Port mismatch (desc=%d, dsp=%d)", _desc.ports_size(), dsp_ports);
}
_buffers.reset(new BufferPtr[_desc.ports_size()]);
_inputs.reset(new float*[_dsp->getNumInputs()]);
_outputs.reset(new float*[_dsp->getNumOutputs()]);
_controls.reset(new float*[controls.num_controls()]);
int control_idx = 0;
for (int port_idx = 0 ; port_idx < _desc.ports_size() ; ++port_idx) {
const auto& port_desc = _desc.ports(port_idx);
if (port_idx < _dsp->getNumInputs()) {
if (port_desc.direction() != pb::PortDescription::INPUT) {
return ERROR_STATUS(
"Port %d: Expected INPUT port, got %s",
port_idx, pb::PortDescription::Direction_Name(port_desc.direction()).c_str());
}
if (port_desc.type() != pb::PortDescription::AUDIO
&& port_desc.type() != pb::PortDescription::ARATE_CONTROL) {
return ERROR_STATUS(
"Invalid input port type %s",
pb::PortDescription::Type_Name(port_desc.type()).c_str());
"Port %d: Expected AUDIO/ARATE_CONTROL port, got %s",
port_idx, pb::PortDescription::Type_Name(port_desc.type()).c_str());
}
} else if (port_idx < _dsp->getNumInputs() + _dsp->getNumOutputs()) {
if (port_desc.direction() != pb::PortDescription::OUTPUT) {
return ERROR_STATUS(
"Port %d: Expected OUTPUT port, got %s",
port_idx, pb::PortDescription::Direction_Name(port_desc.direction()).c_str());
}
++num_inputs;
} else {
if (port_desc.type() != pb::PortDescription::AUDIO
&& port_desc.type() != pb::PortDescription::ARATE_CONTROL) {
return ERROR_STATUS(
"Invalid output port type %s",
pb::PortDescription::Type_Name(port_desc.type()).c_str());
"Port %d: Expected AUDIO/ARATE_CONTROL port, got %s",
port_idx, pb::PortDescription::Type_Name(port_desc.type()).c_str());
}
} else {
if (port_desc.direction() != pb::PortDescription::INPUT) {
return ERROR_STATUS(
"Port %d: Expected INPUT port, got %s",
port_idx, pb::PortDescription::Direction_Name(port_desc.direction()).c_str());
}
if (port_desc.type() != pb::PortDescription::KRATE_CONTROL) {
return ERROR_STATUS(
"Port %d: Expected KRATE_CONTROL port, got %s",
port_idx, pb::PortDescription::Type_Name(port_desc.type()).c_str());
}
++num_outputs;
}
}
if (num_inputs != _dsp->getNumInputs()) {
return ERROR_STATUS(
"Number of input ports does not match (desc=%d vs. dsp=%d)",
num_inputs, _dsp->getNumInputs());
}
if (num_outputs != _dsp->getNumOutputs()) {
return ERROR_STATUS(
"Number of output ports does not match (desc=%d vs. dsp=%d)",
num_outputs, _dsp->getNumOutputs());
float* control_ptr = controls.get_control_ptr(port_desc.name());
if (control_ptr == nullptr) {
return ERROR_STATUS(
"Port %d: Control '%s' not declared by DSP",
port_idx, port_desc.name().c_str());
}
_controls.get()[control_idx] = control_ptr;
++control_idx;
}
}
_inputs.reset(new float*[_dsp->getNumInputs()]);
_outputs.reset(new float*[_dsp->getNumOutputs()]);
return Status::Ok();
}
void ProcessorFaust::cleanup_internal() {
_dsp.reset();
_buffers.reset();
_inputs.reset();
_outputs.reset();
_controls.reset();
Processor::cleanup_internal();
}
@ -96,26 +171,7 @@ Status ProcessorFaust::connect_port_internal(
return ERROR_STATUS("Invalid port index %d", port_idx);
}
uint32_t in_idx = 0;
uint32_t out_idx = 0;
for (const auto& port_desc : _desc.ports()) {
if (in_idx + out_idx == port_idx) {
if (port_desc.direction() == pb::PortDescription::INPUT) {
_inputs.get()[in_idx] = (float*)buf;
} else {
_outputs.get()[out_idx] = (float*)buf;
}
break;
}
if (port_desc.direction() == pb::PortDescription::INPUT) {
++in_idx;
} else {
++out_idx;
}
}
_buffers.get()[port_idx] = buf;
return Status::Ok();
}
@ -125,15 +181,24 @@ Status ProcessorFaust::process_block_internal(BlockContext* ctxt, TimeMapper* ti
float** inputs = _inputs.get();
float** outputs = _outputs.get();
for (int port_idx = 0 ; port_idx < _dsp->getNumInputs() ; ++port_idx) {
if (inputs[port_idx] == nullptr) {
return ERROR_STATUS("Input port %d not connected.", port_idx);
int input_idx = 0;
int output_idx = 0;
int control_idx = 0;
for (int port_idx = 0 ; port_idx < _desc.ports_size() ; ++port_idx) {
BufferPtr buf = _buffers.get()[port_idx];
if (buf == nullptr) {
return ERROR_STATUS("Port %d not connected.", port_idx);
}
}
for (int port_idx = 0 ; port_idx < _dsp->getNumOutputs() ; ++port_idx) {
if (outputs[port_idx] == nullptr) {
return ERROR_STATUS("Output port %d not connected.", port_idx);
if (port_idx < _dsp->getNumInputs()) {
inputs[input_idx] = (float*)buf;
++input_idx;
} else if (port_idx < _dsp->getNumInputs() + _dsp->getNumOutputs()) {
outputs[output_idx] = (float*)buf;
++output_idx;
} else {
*(_controls.get()[control_idx]) = *((float*)buf);
++control_idx;
}
}

2
noisicaa/audioproc/engine/processor_faust.h

@ -56,6 +56,8 @@ protected:
private:
unique_ptr<FaustDSP> _dsp;
unique_ptr<BufferPtr> _buffers;
unique_ptr<float*> _controls;
unique_ptr<float*> _inputs;
unique_ptr<float*> _outputs;
};

53
noisicaa/builtin_nodes/oscillator/node_description.py

@ -20,55 +20,10 @@
#
# @end:license
import os.path
from noisicaa import node_db
PROCESSOR_JSON_PATH = os.path.join(os.path.dirname(__file__), 'processor.json')
OscillatorDescription = node_db.NodeDescription(
uri='builtin://oscillator',
display_name='Oscillator',
type=node_db.NodeDescription.PROCESSOR,
node_ui=node_db.NodeUIDescription(
type='builtin://generic',
),
builtin_icon='node-type-builtin',
processor=node_db.ProcessorDescription(
type='builtin://oscillator',
),
ports=[
node_db.PortDescription(
name='waveform',
display_name="Waveform",
direction=node_db.PortDescription.INPUT,
type=node_db.PortDescription.ARATE_CONTROL,
enum_value=node_db.EnumValueDescription(
default=0.0,
items=[
node_db.EnumValueItem(
name="Sine",
value=0.0),
node_db.EnumValueItem(
name="Sawtooth",
value=1.0),
node_db.EnumValueItem(
name="Square",
value=2.0),
]),
),
node_db.PortDescription(
name='freq',
display_name="Frequency (Hz)",
direction=node_db.PortDescription.INPUT,
type=node_db.PortDescription.ARATE_CONTROL,
float_value=node_db.FloatValueDescription(
default=440.0,
min=1.0,
max=20000.0,
scale=node_db.FloatValueDescription.LOG),
),
node_db.PortDescription(
name='out',
direction=node_db.PortDescription.OUTPUT,
type=node_db.PortDescription.AUDIO,
),
]
)
OscillatorDescription = node_db.faust_json_to_node_description(PROCESSOR_JSON_PATH)

17
noisicaa/builtin_nodes/oscillator/processor.dsp

@ -20,10 +20,25 @@
* @end:license
*/
declare name "Oscillator";
declare uri "builtin://oscillator";
declare input0_name "freq";
declare input0_display_name "Frequency (Hz)";
declare input0_float_value "1 20000 440";
declare input0_scale "log";
declare input0_type "ARATE_CONTROL";
declare output0_name "out";
declare output0_display_name "Output";
declare output0_type "AUDIO";
import("stdfaust.lib");
sine = os.osc;
sawtooth = os.sawtooth;
square = os.square;
process = _, (_ <: sine, sawtooth, square) : select3;
shape = nentry("waveform[display_name:Waveform][style:menu{'Sine':0.0; 'Sawtooth':1.0; 'Square':2.0}]", 0.0, 0.0, 2.0, 1.0);
oscillator(freq) = sine(freq), sawtooth(freq), square(freq) : select3(shape);
process = oscillator;

16
noisicaa/builtin_nodes/oscillator/processor_test.py

@ -18,10 +18,13 @@
#
# @end:license
import os.path
from noisidev import unittest
from noisidev import unittest_mixins
from noisidev import unittest_engine_mixins
from noisidev import unittest_engine_utils
from noisicaa import node_db
from noisicaa.audioproc.engine import block_context
from noisicaa.audioproc.engine import buffers
from noisicaa.audioproc.engine import processor
@ -31,6 +34,9 @@ class ProcessorOscillatorTestMixin(
unittest_engine_mixins.HostSystemMixin,
unittest_mixins.NodeDBMixin,
unittest.TestCase):
def test_json(self):
node_db.faust_json_to_node_description(os.path.join(os.path.dirname(__file__), 'processor.json'))
def test_oscillator(self):
plugin_uri = 'builtin://oscillator'
node_description = self.node_db[plugin_uri]
@ -40,21 +46,21 @@ class ProcessorOscillatorTestMixin(
buffer_mgr = unittest_engine_utils.BufferManager(self.host_system)
waveform = buffer_mgr.allocate('waveform', buffers.PyFloatAudioBlockBuffer())
freq = buffer_mgr.allocate('freq', buffers.PyFloatAudioBlockBuffer())
out = buffer_mgr.allocate('out', buffers.PyFloatAudioBlockBuffer())
waveform = buffer_mgr.allocate('waveform', buffers.PyFloatControlValueBuffer())
ctxt = block_context.PyBlockContext()
ctxt.sample_pos = 1024
proc.connect_port(ctxt, 0, buffer_mgr.data('waveform'))
proc.connect_port(ctxt, 1, buffer_mgr.data('freq'))
proc.connect_port(ctxt, 2, buffer_mgr.data('out'))
proc.connect_port(ctxt, 0, buffer_mgr.data('freq'))
proc.connect_port(ctxt, 1, buffer_mgr.data('out'))
proc.connect_port(ctxt, 2, buffer_mgr.data('waveform'))
for i in range(self.host_system.block_size):
waveform[i] = 0.0
freq[i] = 440
out[i] = 0.0
waveform[0] = 0.0
proc.process_block(ctxt, None) # TODO: pass time_mapper
self.assertTrue(any(v != 0.0 for v in out))

2
noisicaa/node_db/CMakeLists.txt

@ -21,6 +21,8 @@
add_python_package(
client.py
client_test.py
faust_parser.py
faust_parser_test.py
presets.py
presets_test.py
process.py

5
noisicaa/node_db/__init__.py

@ -35,7 +35,6 @@ from .node_description_pb2 import (
NodeUIDescription,
NodeDescription,
)
from .private.builtin_scanner import Builtins
# from .presets import (
# Preset,
# PresetError,
@ -47,3 +46,7 @@ from .node_db_pb2 import (
from .utils import (
get_port,
)
from .faust_parser import (
faust_json_to_node_description,
)
from .private.builtin_scanner import Builtins

154
noisicaa/node_db/faust_parser.py

@ -0,0 +1,154 @@
#!/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 functools
import itertools
import json
import re
import pyparsing
from . import node_description_pb2
COLON = pyparsing.Suppress(':')
SEMICOLON = pyparsing.Suppress(';')
LBRACE = pyparsing.Suppress('{')
RBRACE = pyparsing.Suppress('}')
MENU_ITEM_LABEL = pyparsing.QuotedString(quoteChar='\'', unquoteResults=True)
MENU_ITEM_VALUE = pyparsing.Word('0123456789.')
MENU_ITEM = pyparsing.Group(MENU_ITEM_LABEL + COLON + MENU_ITEM_VALUE)
MENU_SPEC = LBRACE + MENU_ITEM + pyparsing.OneOrMore(SEMICOLON + MENU_ITEM) + RBRACE
def _set_port_attr(port_desc, key, value):
if key == 'name':
port_desc.name = value
elif key == 'display_name':
port_desc.display_name = value
elif key == 'type':
port_desc.type = node_description_pb2.PortDescription.Type.Value(value)
elif key == 'scale':
if value == 'log':
port_desc.float_value.scale = node_description_pb2.FloatValueDescription.LOG
else:
raise ValueError("Unknown scale type '%s'" % value)
elif key == 'float_value':
p = value.split()
port_desc.float_value.min = float(p[0])
port_desc.float_value.max = float(p[1])
port_desc.float_value.default = float(p[2])
elif key == 'style':
m = re.match(r'[a-zA-Z_]+\b', value)
if not m:
raise ValueError("Malformed style spec '%s'" % value)
style = m.group(0)
if style == 'menu':
value = value[len(style):].strip()
try:
menu_spec = MENU_SPEC.parseString(value)
except pyparsing.ParseException as exc:
raise ValueError("Malformed menu spec '%s': %s" % (value, exc))
for name, value in menu_spec:
port_desc.enum_value.items.add(name=name, value=float(value))
else:
raise ValueError("Unknown style '%s'" % style)
else:
raise ValueError("Unknown meta key '%s'" % key)
def _list_controls(items):
for item in items:
if item['type'] in {'vgroup', 'hgroup', 'tgroup'}:
yield from _list_controls(item['items'])
elif item['type'] in {'nentry'}:
yield item
else:
raise ValueError("Unsupported UI item type '%s'" % item['type'])
REQUIRED_PORT_FIELDS = ['name', 'type']
def faust_json_to_node_description(path: str) -> node_description_pb2.NodeDescription:
node_desc = node_description_pb2.NodeDescription(
type=node_description_pb2.NodeDescription.PROCESSOR,
node_ui=node_description_pb2.NodeUIDescription(
type='builtin://generic',
),
builtin_icon='node-type-builtin',
)
with open(path, 'rb') as fp:
faust_desc = json.load(fp)
meta = dict(itertools.chain.from_iterable(m.items() for m in faust_desc['meta']))
node_desc.display_name = faust_desc['name']
node_desc.uri = meta['uri']
node_desc.processor.type = meta['uri']
for port_num in range(int(faust_desc['inputs'])):
port_desc = node_desc.ports.add()
port_desc.direction = node_description_pb2.PortDescription.INPUT
for key, value in meta.items():
if not key.startswith('input%d_' % port_num):
continue
key = key.split('_', 1)[1]
_set_port_attr(port_desc, key, value)
for field in REQUIRED_PORT_FIELDS:
if not port_desc.HasField(field):
raise ValueError("Missing field '%s' for input port %d" % (field, port_num))
for port_num in range(int(faust_desc['outputs'])):
port_desc = node_desc.ports.add()
port_desc.direction = node_description_pb2.PortDescription.OUTPUT
for key, value in meta.items():
if not key.startswith('output%d_' % port_num):
continue
key = key.split('_', 1)[1]
_set_port_attr(port_desc, key, value)
for field in REQUIRED_PORT_FIELDS:
if not port_desc.HasField(field):
raise ValueError("Missing field '%s' for output port %d" % (field, port_num))
for control in _list_controls(faust_desc['ui']):
control_meta = dict(itertools.chain.from_iterable(m.items() for m in control['meta']))
port_desc = node_desc.ports.add()
port_desc.name = control['label']
port_desc.direction = node_description_pb2.PortDescription.INPUT
port_desc.type = node_description_pb2.PortDescription.KRATE_CONTROL
for key, value in control_meta.items():
_set_port_attr(port_desc, key, value)
if port_desc.HasField('enum_value'):
port_desc.enum_value.default = float(control['init'])
else:
port_desc.float_value.min = float(control['min'])
port_desc.float_value.max = float(control['max'])
port_desc.float_value.default = float(control['init'])
return node_desc

126
noisicaa/node_db/faust_parser_test.py

@ -0,0 +1,126 @@
#!/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 builtins
import textwrap
from mox3 import stubout
from pyfakefs import fake_filesystem
from noisidev import unittest
from . import node_description_pb2
from . import faust_parser
class FauseJsonToNodeDescriptionTest(unittest.TestCase):
def setup_testcase(self):
self.stubs = stubout.StubOutForTesting()
self.addCleanup(self.stubs.SmartUnsetAll)
# Setup fake filesystem.
self.fs = fake_filesystem.FakeFilesystem()
self.fake_os = fake_filesystem.FakeOsModule(self.fs)
self.fake_open = fake_filesystem.FakeFileOpen(self.fs)
self.stubs.SmartSet(builtins, 'open', self.fake_open)
def test_foo(self):
processor_json = textwrap.dedent('''\
{
"name": "Oscillator",
"filename": "processor",
"inputs": "1",
"outputs": "1",
"meta": [
{ "basics.lib/name": "Faust Basic Element Library" },
{ "basics.lib/version": "0.0" },
{ "filename": "processor" },
{ "input0_display_name": "Frequency (Hz)" },
{ "input0_name": "freq" },
{ "input0_float_value": "1 20000 440" },
{ "input0_scale": "log" },
{ "input0_type": "ARATE_CONTROL" },
{ "maths.lib/author": "GRAME" },
{ "maths.lib/copyright": "GRAME" },
{ "maths.lib/license": "LGPL with exception" },
{ "maths.lib/name": "Faust Math Library" },
{ "maths.lib/version": "2.1" },
{ "name": "Oscillator" },
{ "oscillators.lib/name": "Faust Oscillator Library" },
{ "oscillators.lib/version": "0.0" },
{ "output0_display_name": "Output" },
{ "output0_name": "out" },
{ "output0_type": "AUDIO" },
{ "uri": "builtin://oscillator" }
],
"ui": [
{
"type": "vgroup",
"label": "Oscillator",
"items": [
{
"type": "nentry",
"label": "waveform",
"address": "/Oscillator/waveform",
"meta": [
{ "display_name": "Waveform" },
{ "style": "menu{'Sine':0.0; 'Sawtooth':1.0; 'Square':2.0}" }
],
"init": "0",
"min": "0",
"max": "2",
"step": "1"
}
]
}
]
}
''')
self.fs.create_file('/processor.json', contents=processor_json)
node_desc = faust_parser.faust_json_to_node_description('/processor.json')
self.assertEqual(node_desc.display_name, 'Oscillator')
self.assertEqual(node_desc.uri, 'builtin://oscillator')
ports = {
port_desc.name: port_desc
for port_desc in node_desc.ports
}
self.assertEqual(set(ports), {'waveform', 'out', 'freq'})
self.assertEqual(ports['out'].display_name, "Output")
self.assertEqual(ports['out'].type, node_description_pb2.PortDescription.AUDIO)
self.assertEqual(ports['out'].direction, node_description_pb2.PortDescription.OUTPUT)
self.assertEqual(ports['freq'].display_name, "Frequency (Hz)")
self.assertEqual(ports['freq'].type, node_description_pb2.PortDescription.ARATE_CONTROL)
self.assertEqual(ports['freq'].direction, node_description_pb2.PortDescription.INPUT)
self.assertEqual(ports['freq'].float_value.min, 1.0)
self.assertEqual(ports['freq'].float_value.max, 20000.0)
self.assertEqual(ports['freq'].float_value.default, 440.0)
self.assertEqual(ports['freq'].float_value.scale, node_description_pb2.FloatValueDescription.LOG)
self.assertEqual(ports['waveform'].display_name, "Waveform")
self.assertEqual(ports['waveform'].type, node_description_pb2.PortDescription.KRATE_CONTROL)
self.assertEqual(ports['waveform'].direction, node_description_pb2.PortDescription.INPUT)
self.assertEqual(ports['waveform'].enum_value.default, 0.0)
self.assertEqual(ports['waveform'].enum_value.items[0].name, 'Sine')
self.assertEqual(ports['waveform'].enum_value.items[0].value, 0.0)
self.assertEqual(ports['waveform'].enum_value.items[1].name, 'Sawtooth')
self.assertEqual(ports['waveform'].enum_value.items[1].value, 1.0)
self.assertEqual(ports['waveform'].enum_value.items[2].name, 'Square')
self.assertEqual(ports['waveform'].enum_value.items[2].value, 2.0)
Loading…
Cancel
Save