Alpha
This commit is contained in:
Vendored
-45
@@ -172,51 +172,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"name": "(gdb) pipedal Launch",
|
|
||||||
"type": "cppdbg",
|
|
||||||
"request": "launch",
|
|
||||||
// Resolved by CMake Tools:
|
|
||||||
"program": "${command:cmake.launchTargetPath}",
|
|
||||||
///"args": [ "[lv2host_leak]" ],
|
|
||||||
|
|
||||||
"args": [
|
|
||||||
"/etc/pipedal/config",
|
|
||||||
"/etc/pipedal/react",
|
|
||||||
"-port",
|
|
||||||
"0.0.0.0:8080",
|
|
||||||
"-shutdownPort",
|
|
||||||
"3147"
|
|
||||||
],
|
|
||||||
"stopAtEntry": false,
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"environment": [
|
|
||||||
{
|
|
||||||
// add the directory where our target was built to the PATHs
|
|
||||||
// it gets resolved by CMake Tools:
|
|
||||||
"name": "PATH",
|
|
||||||
"value": "$PATH:${command:cmake.launchTargetDirectory}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ASAN_OPTIONS",
|
|
||||||
"value": "detect_leaks=0"
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "OTHER_VALUE",
|
|
||||||
"value": "Something something"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalConsole": false,
|
|
||||||
"MIMode": "gdb",
|
|
||||||
"setupCommands": [
|
|
||||||
{
|
|
||||||
"description": "Enable pretty-printing for gdb",
|
|
||||||
"text": "-enable-pretty-printing",
|
|
||||||
"ignoreFailures": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "(gdb) processCopyrights Launch",
|
"name": "(gdb) processCopyrights Launch",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-cab-sim>
|
<http://two-play.com/plugins/toob-cab-sim>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment "TooB amplifier cabinet simulator." ;
|
rdfs:comment "TooB amplifier cabinet simulator." ;
|
||||||
|
|
||||||
mod:brand "TooB";
|
mod:brand "TooB";
|
||||||
|
|||||||
@@ -0,0 +1,364 @@
|
|||||||
|
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
||||||
|
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||||
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||||
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||||
|
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
|
||||||
|
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||||
|
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||||
|
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
||||||
|
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
|
||||||
|
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
|
||||||
|
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
|
||||||
|
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||||
|
@prefix mod: <http://moddevices.com/ns/mod#> .
|
||||||
|
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
|
||||||
|
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
|
||||||
|
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
|
||||||
|
@prefix pipedal_ui: <http://github.com/rerdavies/pipedal/ui#> .
|
||||||
|
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
|
||||||
|
|
||||||
|
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||||
|
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
|
||||||
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||||
|
@prefix state: <http://lv2plug.in/ns/ext/state#> .
|
||||||
|
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||||
|
|
||||||
|
|
||||||
|
@prefix toobConvolutionReverb: <http://two-play.com/plugins/toob-convolution-reverb#> .
|
||||||
|
@prefix toobImpulse: <http://two-play.com/impulseFile#> .
|
||||||
|
|
||||||
|
toobImpulse:#impulseFile
|
||||||
|
a rdf:property;
|
||||||
|
rdfs:label "File";
|
||||||
|
rdfs:range atom:Path.
|
||||||
|
|
||||||
|
|
||||||
|
<http://two-play.com/rerdavies#me>
|
||||||
|
a foaf:Person ;
|
||||||
|
foaf:name "Robin Davies" ;
|
||||||
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
|
<http://two-play.com/plugins/toob-convolution-reverb>
|
||||||
|
a lv2:Plugin ,
|
||||||
|
lv2:ReverbPlugin ;
|
||||||
|
doap:name "TooB Convolution Reverb" ,
|
||||||
|
"TooB Convolution Reverb"@en-gb-gb
|
||||||
|
;
|
||||||
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
|
lv2:minorVersion 0 ;
|
||||||
|
lv2:microVersion 14 ;
|
||||||
|
rdfs:comment """
|
||||||
|
|
||||||
|
Convolution reverb is a notoriously compute-intensive effect. For best results, use the Max T control to constrain the length of the impulse file to
|
||||||
|
under two seconds.
|
||||||
|
|
||||||
|
The Max T control affects how much of the impulse file is used. If the length of the impulse file is longer than the specified time, samples occuring after
|
||||||
|
the specified time are discarded, and the tail of the impulse convolution is fed back into the imput of the convolution. This ensures a smooth reverb tail,
|
||||||
|
and has little effect on the quality of the resulting reverb. If the impulse file is shorter than the specified time, then all samples in the impulse
|
||||||
|
file are used, and no feedback occurs.
|
||||||
|
|
||||||
|
Because reverb impulse files typically include the original sound, you will often get strange comb filter effects if you use the Direct mix control;
|
||||||
|
but this varies depending on the impulse file in question. Usually, the Direct mix control should be set to -INF.
|
||||||
|
|
||||||
|
The Predelay control determines whether Toob Convolution Reverb will strip leading silence from the impulse data. Turning it on preserves silence in the
|
||||||
|
impulse signal; turning it off removes leading silence. If the the Direct mix control is set to -INF dB, there's no real point in
|
||||||
|
delaying the convolution reverb output; you want responses as quickly as possible; the Predelay control should be set to off.
|
||||||
|
The leading silence in impulse files can sometimes be quite significant.
|
||||||
|
|
||||||
|
If the input file has 4 channels, Toob Convolution Reverb assumes the signal is in Ambisonic b-format, and downmixes the channels accordingly. Unfortunately,
|
||||||
|
it currently dowmixes the Ambisonic audio to mono. Exepct a stereo version of Toob Convolution Reverb in the near future, which will make better use of
|
||||||
|
Ambisonic source material.
|
||||||
|
|
||||||
|
Toob Convolution Reverb comes with a nominal set of reverb impulse files to get you started. You are strongly encouraged to seek out reverb impulse files
|
||||||
|
on the internet.
|
||||||
|
|
||||||
|
Special thanks are extended to the the OpenAir project, wich graciously provided the base set of impulse files distributed with Toob Convolution Reverb under
|
||||||
|
licensing terms that are compatible with the Toob Amp project's licensing terms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Toob Convolution Reverb's impulse files were provided by the OpenAir project.
|
||||||
|
https://www.openair.hosted.york.ac.uk, under CC-By-4.0 licenses.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Arthur Sykes Rymer Auditorium.wav
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
By: www.openairlib.net, University of York
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=425
|
||||||
|
|
||||||
|
|
||||||
|
Jack Lyons Hall, University of York.wav
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
By: www.openairlib.net, Audiolab University of York, Alex Duffell, Aishwarya Sridhar, Zhong Li
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=571
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Koli National Park - Winter.wav
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
By: www.openairlib.net, Audio Lab University of York, Andrew Chadwick, Simon Shelley
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=584
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
St. Margaret’s Church.wav
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
By: www.openairlib.net, AudioLab University of York, www.ncem.co.uk
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=702
|
||||||
|
|
||||||
|
|
||||||
|
Genesis 6 Studio Live Room.wav
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
By: www.openairlib.net
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=483
|
||||||
|
|
||||||
|
""" ;
|
||||||
|
|
||||||
|
mod:brand "TooB";
|
||||||
|
mod:label "Convolution Reverb";
|
||||||
|
lv2:requiredFeature urid:map ;
|
||||||
|
lv2:extensionData state:interface;
|
||||||
|
|
||||||
|
|
||||||
|
lv2:optionalFeature lv2:hardRTCapable;
|
||||||
|
patch:readable
|
||||||
|
toobImpulse:#impulseFile
|
||||||
|
;
|
||||||
|
patch:writable
|
||||||
|
toobImpulse:#impulseFile
|
||||||
|
;
|
||||||
|
lv2:extensionData state:interface,
|
||||||
|
work:interface;
|
||||||
|
|
||||||
|
lv2:port
|
||||||
|
[
|
||||||
|
a lv2:InputPort ,
|
||||||
|
lv2:ControlPort ;
|
||||||
|
|
||||||
|
lv2:index 0;
|
||||||
|
lv2:symbol "time" ;
|
||||||
|
lv2:name "Max T";
|
||||||
|
lv2:default 2.0 ;
|
||||||
|
lv2:minimum 0.1 ;
|
||||||
|
lv2:maximum 6.0 ;
|
||||||
|
|
||||||
|
lv2:portProperty epp:notAutomatic ;
|
||||||
|
lv2:portProperty epp:expensive ;
|
||||||
|
|
||||||
|
units:unit units:s ;
|
||||||
|
|
||||||
|
rdfs:comment "Maximum length of impulse data to use." ;
|
||||||
|
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:InputPort ,
|
||||||
|
lv2:ControlPort ;
|
||||||
|
|
||||||
|
lv2:index 1;
|
||||||
|
lv2:symbol "direct_mix" ;
|
||||||
|
lv2:name "Direct";
|
||||||
|
lv2:default -40.0 ;
|
||||||
|
lv2:minimum -40.0 ;
|
||||||
|
lv2:maximum 20.0 ;
|
||||||
|
units:unit units:db ;
|
||||||
|
|
||||||
|
lv2:scalePoint [
|
||||||
|
rdfs:label "-INF" ;
|
||||||
|
rdf:value -40.0
|
||||||
|
];
|
||||||
|
rdfs:comment "Direct mix. Set to minimum to disable." ;
|
||||||
|
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:InputPort ,
|
||||||
|
lv2:ControlPort ;
|
||||||
|
|
||||||
|
lv2:index 2 ;
|
||||||
|
lv2:symbol "reverb_mix" ;
|
||||||
|
lv2:name "Reverb";
|
||||||
|
|
||||||
|
lv2:default -10.0 ;
|
||||||
|
lv2:minimum -40.0 ;
|
||||||
|
lv2:maximum 20.0 ;
|
||||||
|
units:unit units:db ;
|
||||||
|
|
||||||
|
lv2:scalePoint [
|
||||||
|
rdfs:label "-INF" ;
|
||||||
|
rdf:value -40.0
|
||||||
|
];
|
||||||
|
rdfs:comment "Reverb mix. Set to minimum to disable." ;
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:InputPort ,
|
||||||
|
lv2:ControlPort ;
|
||||||
|
|
||||||
|
lv2:index 3 ;
|
||||||
|
lv2:symbol "predelay" ;
|
||||||
|
lv2:name "Predelay";
|
||||||
|
|
||||||
|
lv2:default 1 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 1 ;
|
||||||
|
|
||||||
|
lv2:portProperty lv2:enumeration ;
|
||||||
|
|
||||||
|
lv2:scalePoint [
|
||||||
|
rdfs:label "Remove" ;
|
||||||
|
rdf:value 0.0;
|
||||||
|
],
|
||||||
|
[
|
||||||
|
rdfs:label "Keep" ;
|
||||||
|
rdf:value 1.0;
|
||||||
|
];
|
||||||
|
rdfs:comment "Removes leading silence in impulse when set to false." ;
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
a lv2:OutputPort ,
|
||||||
|
lv2:ControlPort ;
|
||||||
|
|
||||||
|
lv2:index 4 ;
|
||||||
|
lv2:symbol "loading_state" ;
|
||||||
|
lv2:name "Status";
|
||||||
|
|
||||||
|
lv2:portProperty lv2:enumeration ;
|
||||||
|
lv2:default 0.0 ;
|
||||||
|
lv2:minimum 0.0 ;
|
||||||
|
lv2:maximum 5.0 ;
|
||||||
|
|
||||||
|
# NotLoaded = 0,
|
||||||
|
# Idle = 1,
|
||||||
|
# Error = 2,
|
||||||
|
# SentRequest = 3,
|
||||||
|
# GotResponse = 4,
|
||||||
|
# CleaningUp = 5,
|
||||||
|
|
||||||
|
lv2:scalePoint [
|
||||||
|
rdfs:label "Not loaded" ;
|
||||||
|
rdf:value 0.0
|
||||||
|
],[
|
||||||
|
rdfs:label "Ready" ;
|
||||||
|
rdf:value 1.0
|
||||||
|
],[
|
||||||
|
rdfs:label "Error" ;
|
||||||
|
rdf:value 2.0
|
||||||
|
],[
|
||||||
|
rdfs:label "Loading" ;
|
||||||
|
rdf:value 3.0
|
||||||
|
],[
|
||||||
|
rdfs:label "Loading" ;
|
||||||
|
rdf:value 4.0
|
||||||
|
],[
|
||||||
|
rdfs:label "Loading" ;
|
||||||
|
rdf:value 5.0
|
||||||
|
];
|
||||||
|
rdfs:comment "Plugin status." ;
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:AudioPort ,
|
||||||
|
lv2:InputPort ;
|
||||||
|
lv2:index 5 ;
|
||||||
|
lv2:symbol "in" ;
|
||||||
|
lv2:name "In"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a lv2:AudioPort ,
|
||||||
|
lv2:OutputPort ;
|
||||||
|
lv2:index 6 ;
|
||||||
|
lv2:symbol "out" ;
|
||||||
|
lv2:name "Out"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
a atom:AtomPort ,
|
||||||
|
lv2:InputPort;
|
||||||
|
atom:bufferType atom:Sequence ;
|
||||||
|
atom:supports patch:Message;
|
||||||
|
lv2:designation lv2:control ;
|
||||||
|
atom:supports patch:Message ;
|
||||||
|
|
||||||
|
lv2:index 7 ;
|
||||||
|
lv2:symbol "control" ;
|
||||||
|
lv2:name "Control" ;
|
||||||
|
rdfs:comment "Control" ;
|
||||||
|
] , [
|
||||||
|
a atom:AtomPort ,
|
||||||
|
lv2:OutputPort ;
|
||||||
|
atom:bufferType atom:Sequence ;
|
||||||
|
atom:supports patch:Message;
|
||||||
|
lv2:designation lv2:control ;
|
||||||
|
lv2:index 8;
|
||||||
|
lv2:symbol "notify" ;
|
||||||
|
lv2:name "Notify" ;
|
||||||
|
rdfs:comment "Notification" ;
|
||||||
|
]
|
||||||
|
.
|
||||||
|
|
||||||
|
|
||||||
|
<http://two-play.com/plugins/toob-convolution-reverb> pipedal_ui:ui toobConvolutionReverb:ui .
|
||||||
|
|
||||||
|
toobConvolutionReverb:ui
|
||||||
|
a pipedal_ui:ui ;
|
||||||
|
pipedal_ui:fileProperties
|
||||||
|
[
|
||||||
|
a pipedal_ui:fileProperty;
|
||||||
|
rdfs:label "Impulse File" ;
|
||||||
|
lv2:index 4 ;
|
||||||
|
pipedal_ui:patchProperty toobImpulse:impulseFile ;
|
||||||
|
pipedal_ui:directory "ReverbImpulseFiles";
|
||||||
|
pipedal_ui:fileTypes
|
||||||
|
[
|
||||||
|
a pipedal_ui:fileType;
|
||||||
|
rdfs:label "WAV File";
|
||||||
|
pipedal_ui:fileExtension ".wav";
|
||||||
|
];
|
||||||
|
];
|
||||||
|
ui:portNotification
|
||||||
|
[
|
||||||
|
lv2:symbol "loading_state";
|
||||||
|
ui:plugin <http://two-play.com/plugins/toob-convolution-reverb>;
|
||||||
|
ui:protocol ui:floatProtocol;
|
||||||
|
# pipedal_ui:style pipedal_ui:text ;
|
||||||
|
# pipedal_ui:redLevel 0;
|
||||||
|
# pipedal_ui:yellowLevel -12;
|
||||||
|
# ui:portIndex 3;
|
||||||
|
|
||||||
|
]
|
||||||
|
.
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
inputStage:filterGroup
|
inputStage:filterGroup
|
||||||
a param:ControlGroup ,
|
a param:ControlGroup ,
|
||||||
@@ -59,7 +59,7 @@ inputStage:filterGroup
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
|
|
||||||
mod:brand "TooB";
|
mod:brand "TooB";
|
||||||
mod:label "Input Stage";
|
mod:label "Input Stage";
|
||||||
|
|||||||
@@ -1,624 +0,0 @@
|
|||||||
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
|
||||||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
|
||||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
||||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
||||||
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
|
|
||||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
|
||||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
|
||||||
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
|
||||||
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
|
|
||||||
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
|
|
||||||
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
|
|
||||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
|
||||||
@prefix mod: <http://moddevices.com/ns/mod#> .
|
|
||||||
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
|
|
||||||
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
|
|
||||||
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
|
|
||||||
|
|
||||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
|
||||||
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
|
|
||||||
@prefix plug: <http://two-play.com/plugins/toob-power-stage#> .
|
|
||||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
||||||
@prefix state: <http://lv2plug.in/ns/ext/state#> .
|
|
||||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
|
||||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
||||||
@prefix modgui: <http://moddevices.com/ns/modgui#> .
|
|
||||||
@prefix pstage: <http://two-play.com/plugins/toob-power-stage#> .
|
|
||||||
|
|
||||||
|
|
||||||
pstage:stage1
|
|
||||||
a param:ControlGroup ,
|
|
||||||
pg:InputGroup ;
|
|
||||||
lv2:name "Stage 1" ;
|
|
||||||
lv2:symbol "stage1" .
|
|
||||||
|
|
||||||
pstage:stage2
|
|
||||||
a param:ControlGroup ,
|
|
||||||
pg:InputGroup ;
|
|
||||||
lv2:name "Stage 2" ;
|
|
||||||
lv2:symbol "stage2" .
|
|
||||||
|
|
||||||
|
|
||||||
pstage:stage3
|
|
||||||
a param:ControlGroup ,
|
|
||||||
pg:InputGroup ;
|
|
||||||
lv2:name "Stage 3" ;
|
|
||||||
lv2:symbol "stage3" .
|
|
||||||
|
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-power-stage#uiState>
|
|
||||||
a lv2:Parameter ;
|
|
||||||
rdfs:label "uiState" ;
|
|
||||||
rdfs:range atom:Vector .
|
|
||||||
|
|
||||||
|
|
||||||
<http://two-play.com/rerdavies#me>
|
|
||||||
a foaf:Person ;
|
|
||||||
foaf:name "Robin Davies" ;
|
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
|
||||||
foaf:homepage <http://two-play.com/> .
|
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-power-stage>
|
|
||||||
a lv2:Plugin ,
|
|
||||||
lv2:SimulatorPlugin ;
|
|
||||||
|
|
||||||
doap:name "TooB Power Stage";
|
|
||||||
|
|
||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
|
||||||
lv2:minorVersion 0 ;
|
|
||||||
lv2:microVersion 15 ;
|
|
||||||
|
|
||||||
mod:brand "TooB";
|
|
||||||
mod:label "Power Stage";
|
|
||||||
lv2:requiredFeature urid:map ;
|
|
||||||
lv2:optionalFeature lv2:hardRTCapable;
|
|
||||||
lv2:extensionData state:interface;
|
|
||||||
patch:readable
|
|
||||||
<http://two-play.com/plugins/toob-power-stage#uiState>;
|
|
||||||
|
|
||||||
rdfs:comment """
|
|
||||||
The TooB Power Stage provides flexible and nuanced emulations of guitar amp power stages.
|
|
||||||
The goal of TooB Power Stage is not to inflexibly emulate existing tube power stages, but instead,
|
|
||||||
to be better.
|
|
||||||
|
|
||||||
TooB Power Stage provides 3 consecutive gain stages, each of which can be overdriven to varying
|
|
||||||
degrees. Power transformer sag is emulated using the SAG and SAGD controls, which emulate the
|
|
||||||
volume and distortion effects introduced when power transformer voltage sags.
|
|
||||||
|
|
||||||
Each gain section expects an input level that is nominally in the range of 12db to 0db.
|
|
||||||
It's ok if the levels are slightly over 0db, but they should not be massively over. Use the
|
|
||||||
Trim control to adjust input signal levels.
|
|
||||||
|
|
||||||
The Gain controls use oversampled atan wave shaping to emulate the effect of overdriving
|
|
||||||
gain stages. Atan wave shaping provides rich even-order harmonics similar to those produced by a tube
|
|
||||||
amp power stage. A modest amount of gain will produce the characteristically "warm" sound of a tube
|
|
||||||
gain stage. More extreme settings produce harder overdrive.
|
|
||||||
|
|
||||||
Judicious use of the LO CUT and HI CUT controls have interesting and subtle effects on the overall sound.
|
|
||||||
You are encouraged to play with these controls to see what happens. Tightening up low and high frequencies,
|
|
||||||
in early gain sections can allow you to drive the signal much harder in later gain sections without totally
|
|
||||||
breaking up. Real amps will usually cut low frequency response at somewhere between 80Hz (Fender)
|
|
||||||
and 140Hz (Marshall).
|
|
||||||
|
|
||||||
General experience seems to suggest that gradually increasing the gain in each section allows you to drive
|
|
||||||
the signal harder without introducing undesirable effects in the output. However, there definitely interesting
|
|
||||||
and subtle differences in tone that can be acheived by not adhering to that rule of thumb.
|
|
||||||
|
|
||||||
The SAG and SAGD controls are important controls. You need to undertand how they work. SAG controls how much
|
|
||||||
the overall volume of the output decreases as the transform sags. It provides the subtle "compression" that
|
|
||||||
tube amps provide, which is quite different from the compression provided by compressors. SAGD controls
|
|
||||||
the amount of additional distortion that occurs as the gain stages receive reduced voltage. It provides
|
|
||||||
the "forgiveness" of a good tube amp. Specifically, because SAGD operates in a feedback loop that runs through
|
|
||||||
all three gain stages. Increasing the distortion in the gain stages increases the power use of the gain stages;
|
|
||||||
increasing the power use of the gain stages increases the effect of SAG and SAGD which has an opposing effect
|
|
||||||
on power use. The net result is that using SAG and SAGD causes signals to break up more gradually
|
|
||||||
(hence the perceived "forgiveness"). The effects are non-linear, so not easy to describe completely. You
|
|
||||||
are strongly urged to experiment with SAG and SAGD settings in order to get best tone results.
|
|
||||||
|
|
||||||
The TooB Power Stage design is based on an experimental design, that unexpectedly produced extraordinarily
|
|
||||||
good amp tones. To be perfectly honest, we have not completely explored the sonic possibilities of the
|
|
||||||
TooB Power Stage. What's known so far: it produces exceptionally pretty clean tones; it can be driven really
|
|
||||||
hard, and seems to preserve an unusual amount of detail in the signal even when being driven hard; and it
|
|
||||||
can definitely produce signals that have many of the most prized features of tube amplifiers: warmth, detail,
|
|
||||||
subtle "compression" without coloration, and forgiving break up.
|
|
||||||
|
|
||||||
Explore!
|
|
||||||
|
|
||||||
The TooB Input Stage is part of the TooB Amp Emulation Toolkit. Typically, you would
|
|
||||||
use the following plugins in the following order.
|
|
||||||
|
|
||||||
- TooB Input Stage
|
|
||||||
|
|
||||||
- TooB Tone Stack
|
|
||||||
|
|
||||||
- Toob Power Stage
|
|
||||||
|
|
||||||
- Toob Cabinet Simulator
|
|
||||||
""";
|
|
||||||
lv2:port
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 0 ;
|
|
||||||
pg:group pstage:stage1 ;
|
|
||||||
lv2:symbol "trim1" ;
|
|
||||||
lv2:name "Trim";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum -20.0 ;
|
|
||||||
lv2:maximum 20.0 ;
|
|
||||||
units:unit units:db ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "+20" ;
|
|
||||||
rdf:value 20.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "+10" ;
|
|
||||||
rdf:value 10.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "0" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "-10" ;
|
|
||||||
rdf:value -10.0
|
|
||||||
], [
|
|
||||||
rdfs:label "-20" ;
|
|
||||||
rdf:value -20.0
|
|
||||||
];
|
|
||||||
rdfs:comment "Trim input level (stage 1)" ;
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 1 ;
|
|
||||||
pg:group pstage:stage1 ;
|
|
||||||
lv2:symbol "gain1" ;
|
|
||||||
lv2:name "Gain";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0 ;
|
|
||||||
rdfs:comment "Gain (stage 1)"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
pg:group pstage:stage1 ;
|
|
||||||
|
|
||||||
lv2:index 2 ;
|
|
||||||
lv2:symbol "shape1" ;
|
|
||||||
lv2:name "Shape1";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0;
|
|
||||||
lv2:rangeStep 2;
|
|
||||||
|
|
||||||
lv2:portProperty lv2:enumeration ;
|
|
||||||
rdfs:comment "Wave Shape" ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "atan" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rdfs:label "triode" ;
|
|
||||||
rdf:value 1.0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
pg:group pstage:stage1 ;
|
|
||||||
|
|
||||||
lv2:index 3 ;
|
|
||||||
lv2:symbol "bias1" ;
|
|
||||||
lv2:name "Bias1";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum -2.0 ;
|
|
||||||
lv2:maximum 2.0;
|
|
||||||
],
|
|
||||||
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 4 ;
|
|
||||||
pg:group pstage:stage1 ;
|
|
||||||
lv2:symbol "locut1" ;
|
|
||||||
lv2:name "Lo Cut";
|
|
||||||
lv2:default 30.0 ;
|
|
||||||
lv2:minimum 30.0 ;
|
|
||||||
lv2:maximum 300.0 ;
|
|
||||||
units:unit units:hz;
|
|
||||||
lv2:designation param:cutoffFrequency ;
|
|
||||||
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "OFF" ;
|
|
||||||
rdf:value 30.0
|
|
||||||
];
|
|
||||||
rdfs:comment "Low frequency cutoff (stage 1). Set to minimum to disable." ;
|
|
||||||
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 5 ;
|
|
||||||
pg:group pstage:stage1 ;
|
|
||||||
lv2:symbol "hicut1" ;
|
|
||||||
lv2:name "Hi Cut";
|
|
||||||
lv2:default 19000.0 ;
|
|
||||||
lv2:minimum 1000.0 ;
|
|
||||||
lv2:maximum 19000.0;
|
|
||||||
units:unit units:hz;
|
|
||||||
lv2:designation param:cutoffFrequency ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "OFF" ;
|
|
||||||
rdf:value 19000.0
|
|
||||||
];
|
|
||||||
rdfs:comment "High cutoff frequency (stage 1). Set to maximum to disable." ;
|
|
||||||
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 6 ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
lv2:symbol "gain2_enable" ;
|
|
||||||
lv2:name "Enable2";
|
|
||||||
lv2:default 1 ;
|
|
||||||
lv2:minimum 0 ;
|
|
||||||
lv2:maximum 1;
|
|
||||||
lv2:portProperty lv2:toggled ;
|
|
||||||
rdfs:comment "Enable gain stage 2" ;
|
|
||||||
],
|
|
||||||
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 7 ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
lv2:symbol "trim2" ;
|
|
||||||
lv2:name "Trim";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum -20.0 ;
|
|
||||||
lv2:maximum 20.0 ;
|
|
||||||
units:unit units:db ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "+20" ;
|
|
||||||
rdf:value 20.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "+10" ;
|
|
||||||
rdf:value 10.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "0" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "-10" ;
|
|
||||||
rdf:value -10.0
|
|
||||||
], [
|
|
||||||
rdfs:label "-20" ;
|
|
||||||
rdf:value -20.0
|
|
||||||
];
|
|
||||||
rdfs:comment "Trim input (stage 2)" ;
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 8 ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
lv2:symbol "gain2" ;
|
|
||||||
lv2:name "Gain";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0 ;
|
|
||||||
rdfs:comment "Gain (stage 2)"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
|
|
||||||
lv2:index 9 ;
|
|
||||||
lv2:symbol "shape2" ;
|
|
||||||
lv2:name "Shape2";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0;
|
|
||||||
lv2:rangeStep 2;
|
|
||||||
|
|
||||||
lv2:portProperty lv2:enumeration ;
|
|
||||||
rdfs:comment "Wave Shape" ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "atan" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rdfs:label "triode" ;
|
|
||||||
rdf:value 1.0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
|
|
||||||
lv2:index 10 ;
|
|
||||||
lv2:symbol "bias2" ;
|
|
||||||
lv2:name "Bias2";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum -2.0 ;
|
|
||||||
lv2:maximum 2.0;
|
|
||||||
],
|
|
||||||
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 11 ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
lv2:symbol "locut2" ;
|
|
||||||
lv2:name "Lo Cut";
|
|
||||||
lv2:default 30.0 ;
|
|
||||||
lv2:minimum 30.0 ;
|
|
||||||
lv2:maximum 300.0 ;
|
|
||||||
units:unit units:hz;
|
|
||||||
lv2:designation param:cutoffFrequency ;
|
|
||||||
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "OFF" ;
|
|
||||||
rdf:value 30.0
|
|
||||||
]
|
|
||||||
;
|
|
||||||
rdfs:comment "Low frequency cutoff (stage 2). Set to minimum to disable." ;
|
|
||||||
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 12 ;
|
|
||||||
pg:group pstage:stage2 ;
|
|
||||||
lv2:symbol "hicut2" ;
|
|
||||||
lv2:name "Hi Cut";
|
|
||||||
lv2:default 19000.0 ;
|
|
||||||
lv2:minimum 1000.0 ;
|
|
||||||
lv2:maximum 19000.0;
|
|
||||||
units:unit units:hz;
|
|
||||||
lv2:designation param:cutoffFrequency ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "OFF" ;
|
|
||||||
rdf:value 19000.0
|
|
||||||
];
|
|
||||||
rdfs:comment "High cutoff frequency (stage 2). Set to maximum to disable." ;
|
|
||||||
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 13 ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
lv2:symbol "gain3_enable" ;
|
|
||||||
lv2:name "Enable3";
|
|
||||||
lv2:default 1 ;
|
|
||||||
lv2:minimum 0 ;
|
|
||||||
lv2:maximum 1;
|
|
||||||
lv2:portProperty lv2:toggled ;
|
|
||||||
rdfs:comment "Enable gain stage 3" ;
|
|
||||||
],
|
|
||||||
|
|
||||||
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 14 ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
lv2:symbol "trim3" ;
|
|
||||||
lv2:name "Trim";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum -20.0 ;
|
|
||||||
lv2:maximum 20.0 ;
|
|
||||||
units:unit units:db ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "+20" ;
|
|
||||||
rdf:value 20.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "+10" ;
|
|
||||||
rdf:value 10.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "0" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "-10" ;
|
|
||||||
rdf:value -10.0
|
|
||||||
], [
|
|
||||||
rdfs:label "-20" ;
|
|
||||||
rdf:value -20.0
|
|
||||||
];
|
|
||||||
rdfs:comment "Trim input level (stage 3)." ;
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 15 ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
lv2:symbol "gain3" ;
|
|
||||||
lv2:name "Gain";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0 ;
|
|
||||||
rdfs:comment "Gain (stage 3)."
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
|
|
||||||
lv2:index 16 ;
|
|
||||||
lv2:symbol "shape3" ;
|
|
||||||
lv2:name "Shape3";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0;
|
|
||||||
lv2:rangeStep 2;
|
|
||||||
|
|
||||||
lv2:portProperty lv2:enumeration ;
|
|
||||||
rdfs:comment "Wave Shape" ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "atan" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rdfs:label "triode" ;
|
|
||||||
rdf:value 1.0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
|
|
||||||
lv2:index 17 ;
|
|
||||||
lv2:symbol "bias3" ;
|
|
||||||
lv2:name "Bias3";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum -2.0 ;
|
|
||||||
lv2:maximum 2.0;
|
|
||||||
],
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 18 ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
lv2:symbol "locut3" ;
|
|
||||||
lv2:name "Lo Cut";
|
|
||||||
lv2:default 30.0 ;
|
|
||||||
lv2:minimum 30.0 ;
|
|
||||||
lv2:maximum 300.0 ;
|
|
||||||
units:unit units:hz;
|
|
||||||
lv2:designation param:cutoffFrequency ;
|
|
||||||
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "OFF" ;
|
|
||||||
rdf:value 30.0
|
|
||||||
];
|
|
||||||
rdfs:comment "Low frequency cutoff (stage 3). Set to minimum to disable." ;
|
|
||||||
|
|
||||||
], [
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 19 ;
|
|
||||||
pg:group pstage:stage3 ;
|
|
||||||
lv2:symbol "hicut3" ;
|
|
||||||
lv2:name "Hi Cut";
|
|
||||||
lv2:default 19000.0 ;
|
|
||||||
lv2:minimum 1000.0 ;
|
|
||||||
lv2:maximum 19000.0;
|
|
||||||
units:unit units:hz;
|
|
||||||
lv2:designation param:cutoffFrequency ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "OFF" ;
|
|
||||||
rdf:value 19000
|
|
||||||
];
|
|
||||||
rdfs:comment "High cutoff frequency (stage 3). Set to minimum to disable." ;
|
|
||||||
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 20 ;
|
|
||||||
lv2:symbol "sag" ;
|
|
||||||
lv2:name "Sag";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0;
|
|
||||||
rdfs:comment "Transformer sag (volume)." ;
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 21 ;
|
|
||||||
lv2:symbol "sagd" ;
|
|
||||||
lv2:name "Sag D";
|
|
||||||
lv2:default 0.0 ;
|
|
||||||
lv2:minimum 0.0 ;
|
|
||||||
lv2:maximum 1.0;
|
|
||||||
rdfs:comment "Transformer sag (distortion)." ;
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 22 ;
|
|
||||||
lv2:symbol "master" ;
|
|
||||||
lv2:name "Master";
|
|
||||||
lv2:default -0.0 ;
|
|
||||||
lv2:minimum -60.0 ;
|
|
||||||
lv2:maximum 30.0;
|
|
||||||
units:unit units:db;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "-60db" ;
|
|
||||||
rdf:value -60.0
|
|
||||||
] , [
|
|
||||||
rdfs:label "0db" ;
|
|
||||||
rdf:value 0.0
|
|
||||||
], [
|
|
||||||
rdfs:label "30db" ;
|
|
||||||
rdf:value -30.0
|
|
||||||
];
|
|
||||||
rdfs:comment "Master volume." ;
|
|
||||||
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:AudioPort ,
|
|
||||||
lv2:InputPort ;
|
|
||||||
lv2:index 23 ;
|
|
||||||
lv2:symbol "in" ;
|
|
||||||
lv2:name "In"
|
|
||||||
] , [
|
|
||||||
a lv2:AudioPort ,
|
|
||||||
lv2:OutputPort ;
|
|
||||||
lv2:index 24 ;
|
|
||||||
lv2:symbol "out" ;
|
|
||||||
lv2:name "Out"
|
|
||||||
],[
|
|
||||||
a atom:AtomPort ,
|
|
||||||
lv2:InputPort;
|
|
||||||
atom:bufferType atom:Sequence ;
|
|
||||||
atom:supports patch:Message;
|
|
||||||
lv2:designation lv2:control ;
|
|
||||||
lv2:index 25 ;
|
|
||||||
lv2:symbol "control" ;
|
|
||||||
lv2:name "Control" ;
|
|
||||||
rdfs:comment "Plugin to GUI communication" ;
|
|
||||||
] , [
|
|
||||||
a atom:AtomPort ,
|
|
||||||
lv2:OutputPort ;
|
|
||||||
atom:bufferType atom:Sequence ;
|
|
||||||
atom:supports patch:Message;
|
|
||||||
lv2:designation lv2:control ;
|
|
||||||
lv2:index 26 ;
|
|
||||||
lv2:symbol "notify" ;
|
|
||||||
lv2:name "Notify" ;
|
|
||||||
rdfs:comment "Plugin to GUI communication" ;
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 27 ;
|
|
||||||
lv2:symbol "sagf" ;
|
|
||||||
lv2:name "SagF";
|
|
||||||
lv2:default 13.0 ;
|
|
||||||
lv2:minimum 5.0 ;
|
|
||||||
lv2:maximum 25.0;
|
|
||||||
rdfs:comment "Sag filter Fc." ;
|
|
||||||
]
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ pstage:stage3
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-power-stage-2>
|
<http://two-play.com/plugins/toob-power-stage-2>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -67,7 +67,7 @@ pstage:stage3
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 1 ;
|
lv2:microVersion 14 ;
|
||||||
|
|
||||||
mod:brand "TooB";
|
mod:brand "TooB";
|
||||||
mod:label "Power Stage";
|
mod:label "Power Stage";
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-spectrum>
|
<http://two-play.com/plugins/toob-spectrum>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment "TooB spectrum analyzer" ;
|
rdfs:comment "TooB spectrum analyzer" ;
|
||||||
|
|
||||||
mod:brand "TooB";
|
mod:brand "TooB";
|
||||||
@@ -91,37 +91,7 @@ Displays a spectral plot of the input signal.
|
|||||||
a lv2:InputPort ,
|
a lv2:InputPort ,
|
||||||
lv2:ControlPort ;
|
lv2:ControlPort ;
|
||||||
|
|
||||||
lv2:index 4 ;
|
lv2:index 4 ;
|
||||||
lv2:symbol "blockSize" ;
|
|
||||||
lv2:name "Block Size";
|
|
||||||
lv2:default 4096.0 ;
|
|
||||||
lv2:minimum 1024.0 ;
|
|
||||||
lv2:maximum 8192.0;
|
|
||||||
|
|
||||||
lv2:portProperty lv2:enumeration ;
|
|
||||||
rdfs:comment "Wave Shape" ;
|
|
||||||
lv2:scalePoint [
|
|
||||||
rdfs:label "1024" ;
|
|
||||||
rdf:value 1024.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rdfs:label "2048" ;
|
|
||||||
rdf:value 2048.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rdfs:label "4096" ;
|
|
||||||
rdf:value 4096.0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rdfs:label "8192" ;
|
|
||||||
rdf:value 8192.0
|
|
||||||
];
|
|
||||||
],
|
|
||||||
[
|
|
||||||
a lv2:InputPort ,
|
|
||||||
lv2:ControlPort ;
|
|
||||||
|
|
||||||
lv2:index 5 ;
|
|
||||||
lv2:symbol "minF" ;
|
lv2:symbol "minF" ;
|
||||||
lv2:name "Min F";
|
lv2:name "Min F";
|
||||||
lv2:default 60.0 ;
|
lv2:default 60.0 ;
|
||||||
@@ -135,7 +105,7 @@ Displays a spectral plot of the input signal.
|
|||||||
a lv2:InputPort ,
|
a lv2:InputPort ,
|
||||||
lv2:ControlPort ;
|
lv2:ControlPort ;
|
||||||
|
|
||||||
lv2:index 6 ;
|
lv2:index 5 ;
|
||||||
lv2:symbol "maxF" ;
|
lv2:symbol "maxF" ;
|
||||||
lv2:name "Max F";
|
lv2:name "Max F";
|
||||||
lv2:default 16000.0 ;
|
lv2:default 16000.0 ;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-tone-stack>
|
<http://two-play.com/plugins/toob-tone-stack>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment """
|
rdfs:comment """
|
||||||
The TooB Tone Stack emulates the tone controls of either a 59' Bassman or a JCM8000 guitar amplifier.
|
The TooB Tone Stack emulates the tone controls of either a 59' Bassman or a JCM8000 guitar amplifier.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -28,7 +28,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-chorus>
|
<http://two-play.com/plugins/toob-chorus>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 1 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment """
|
rdfs:comment """
|
||||||
Emulation of Boss CE-2 Chorus.
|
Emulation of Boss CE-2 Chorus.
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-delay>
|
<http://two-play.com/plugins/toob-delay>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment """
|
rdfs:comment """
|
||||||
A straighforward no-frills digital delay.
|
A straighforward no-frills digital delay.
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-freeverb>
|
<http://two-play.com/plugins/toob-freeverb>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment """
|
rdfs:comment """
|
||||||
ToobFreeverb is an Lv2 implementation of the famous Freeverb reverb effect.
|
ToobFreeverb is an Lv2 implementation of the famous Freeverb reverb effect.
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ toobml:filterGroup
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-ml>
|
<http://two-play.com/plugins/toob-ml>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -47,7 +47,7 @@ toobml:filterGroup
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 13 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment """
|
rdfs:comment """
|
||||||
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
|
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
|
||||||
using artifical intelligence/machine learning modelling of real amplifiers.
|
using artifical intelligence/machine learning modelling of real amplifiers.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
a foaf:Person ;
|
a foaf:Person ;
|
||||||
foaf:name "Robin Davies" ;
|
foaf:name "Robin Davies" ;
|
||||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||||
foaf:homepage <http://two-play.com/> .
|
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-tuner>
|
<http://two-play.com/plugins/toob-tuner>
|
||||||
a lv2:Plugin ,
|
a lv2:Plugin ,
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||||
lv2:minorVersion 0 ;
|
lv2:minorVersion 0 ;
|
||||||
lv2:microVersion 1 ;
|
lv2:microVersion 14 ;
|
||||||
rdfs:comment """
|
rdfs:comment """
|
||||||
A chromatic guitar tuner.
|
A chromatic guitar tuner.
|
||||||
""" ;
|
""" ;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
|||||||
|
Toob Convolution Reverb's impulse files were provided by the OpenAir project.
|
||||||
|
https://www.openair.hosted.york.ac.uk/?page_id=571, under CC-By-4.0 licenses.
|
||||||
|
|
||||||
|
|
||||||
|
Arthur Sykes Rymer Auditorium.wav
|
||||||
|
---------------------------------------
|
||||||
|
By: www.openairlib.net, University of York
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=425
|
||||||
|
|
||||||
|
|
||||||
|
Jack Lyons Hall, University of York.wav
|
||||||
|
---------------------------------------
|
||||||
|
By: www.openairlib.net, Audiolab University of York, Alex Duffell, Aishwarya Sridhar, Zhong Li
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=571
|
||||||
|
|
||||||
|
|
||||||
|
Koli National Park - Winter.wav
|
||||||
|
---------------------------------------
|
||||||
|
By: www.openairlib.net, Audio Lab University of York, Andrew Chadwick, Simon Shelley
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=584
|
||||||
|
|
||||||
|
|
||||||
|
St. Margaret’s Church.wav
|
||||||
|
---------------------------------------
|
||||||
|
By: www.openairlib.net, AudioLab University of York, www.ncem.co.uk
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=702
|
||||||
|
|
||||||
|
|
||||||
|
Genesis 6 Studio Live Room.wav
|
||||||
|
---------------------------------------
|
||||||
|
By: www.openairlib.net
|
||||||
|
|
||||||
|
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Source: https://www.openair.hosted.york.ac.uk/?page_id=483
|
||||||
Binary file not shown.
@@ -7,6 +7,11 @@
|
|||||||
lv2:binary <ToobAmp.so> ;
|
lv2:binary <ToobAmp.so> ;
|
||||||
rdfs:seeAlso <ToneStack.ttl> .
|
rdfs:seeAlso <ToneStack.ttl> .
|
||||||
|
|
||||||
|
<http://two-play.com/plugins/toob-convolution-reverb> a lv2:Plugin ;
|
||||||
|
lv2:binary <ToobAmp.so> ;
|
||||||
|
rdfs:seeAlso <ConvolutionReverb.ttl> .
|
||||||
|
|
||||||
|
|
||||||
<http://two-play.com/plugins/toob-ml> a lv2:Plugin ;
|
<http://two-play.com/plugins/toob-ml> a lv2:Plugin ;
|
||||||
lv2:binary <ToobAmp.so> ;
|
lv2:binary <ToobAmp.so> ;
|
||||||
rdfs:seeAlso <ToobML.ttl> .
|
rdfs:seeAlso <ToobML.ttl> .
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ add_custom_command(
|
|||||||
src/Banks.tsx
|
src/Banks.tsx
|
||||||
src/PluginPresetsDialog.tsx
|
src/PluginPresetsDialog.tsx
|
||||||
src/BankDialog.tsx
|
src/BankDialog.tsx
|
||||||
src/UploadDialog.tsx
|
src/UploadPresetDialog.tsx
|
||||||
src/ToobPowerStage2View.tsx
|
src/ToobPowerStage2View.tsx
|
||||||
src/index.tsx
|
src/index.tsx
|
||||||
src/Units.tsx
|
src/Units.tsx
|
||||||
|
|||||||
@@ -12,3 +12,6 @@
|
|||||||
padding-left: 36px;
|
padding-left: 36px;
|
||||||
border-bottom: 1px #CCC solid
|
border-bottom: 1px #CCC solid
|
||||||
}
|
}
|
||||||
|
div {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -665,7 +665,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
size="large">
|
size="large">
|
||||||
<MenuButton />
|
<MenuButton />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<div style={{ flex: "1 1 1px" }} />
|
|
||||||
<div style={{ flex: "0 1 400px", minWidth: 100 }}>
|
<div style={{ flex: "0 1 400px", minWidth: 100 }}>
|
||||||
<PresetSelector />
|
<PresetSelector />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import React from 'react';
|
|||||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
|
||||||
|
|
||||||
import {PedalboardItem, PedalboardSplitItem} from './Pedalboard';
|
import { PedalboardItem, PedalboardSplitItem } from './Pedalboard';
|
||||||
import PluginControlView from './PluginControlView';
|
import PluginControlView from './PluginControlView';
|
||||||
import SplitControlView from './SplitControlView';
|
import SplitControlView from './SplitControlView';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@@ -29,10 +29,10 @@ import IControlViewFactory from './IControlViewFactory';
|
|||||||
import ToobInputStageViewFactory from './ToobInputStageView';
|
import ToobInputStageViewFactory from './ToobInputStageView';
|
||||||
import ToobToneStackViewFactory from './ToobToneStackView';
|
import ToobToneStackViewFactory from './ToobToneStackView';
|
||||||
import ToobCabSimViewFactory from './ToobCabSimView';
|
import ToobCabSimViewFactory from './ToobCabSimView';
|
||||||
import ToobPowerStage2Factory from './ToobPowerStage2View';
|
import ToobPowerStage2Factory from './ToobPowerStage2View';
|
||||||
import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView';
|
import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView';
|
||||||
import ToobMLViewFactory from './ToobMLView';
|
import ToobMLViewFactory from './ToobMLView';
|
||||||
import {ToobTunerViewFactory,GxTunerViewFactory} from './GxTunerView';
|
import { ToobTunerViewFactory, GxTunerViewFactory } from './GxTunerView';
|
||||||
|
|
||||||
|
|
||||||
let pluginFactories: IControlViewFactory[] = [
|
let pluginFactories: IControlViewFactory[] = [
|
||||||
@@ -47,32 +47,32 @@ let pluginFactories: IControlViewFactory[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export function GetControlView(pedalboardItem?: PedalboardItem| null): React.ReactNode
|
export function GetControlView(pedalboardItem?: PedalboardItem | null): React.ReactNode {
|
||||||
{
|
|
||||||
let model: PiPedalModel = PiPedalModelFactory.getInstance();
|
let model: PiPedalModel = PiPedalModelFactory.getInstance();
|
||||||
|
|
||||||
if (!pedalboardItem) {
|
if (!pedalboardItem) {
|
||||||
return (<div/>);
|
return (<div />);
|
||||||
}
|
}
|
||||||
if (pedalboardItem.isSplit())
|
if (pedalboardItem.isStart() || pedalboardItem.isEnd()) {
|
||||||
{
|
return (
|
||||||
|
<PluginControlView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pedalboardItem.isSplit()) {
|
||||||
return (
|
return (
|
||||||
<SplitControlView item={pedalboardItem as PedalboardSplitItem} instanceId={pedalboardItem!.instanceId}
|
<SplitControlView item={pedalboardItem as PedalboardSplitItem} instanceId={pedalboardItem!.instanceId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < pluginFactories.length; ++i)
|
for (let i = 0; i < pluginFactories.length; ++i) {
|
||||||
{
|
|
||||||
let factory = pluginFactories[i];
|
let factory = pluginFactories[i];
|
||||||
if (factory.uri === pedalboardItem.uri)
|
if (factory.uri === pedalboardItem.uri) {
|
||||||
{
|
return factory.Create(model, pedalboardItem);
|
||||||
return factory.Create(model,pedalboardItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let uiPlugin = model.getUiPlugin(pedalboardItem.uri);
|
let uiPlugin = model.getUiPlugin(pedalboardItem.uri);
|
||||||
if (!uiPlugin)
|
if (!uiPlugin) {
|
||||||
{
|
<div style={{ paddingLeft: 40, paddingRight: 40 }}>
|
||||||
<div style={{paddingLeft: 40, paddingRight: 40}}>
|
|
||||||
<Typography color="error" variant="h6" >Missing plugin.</Typography>
|
<Typography color="error" variant="h6" >Missing plugin.</Typography>
|
||||||
<Typography>The plugin '{pedalboardItem.pluginName}' ({pedalboardItem.uri}) is not currently installed.</Typography>
|
<Typography>The plugin '{pedalboardItem.pluginName}' ({pedalboardItem.uri}) is not currently installed.</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,8 +36,12 @@ const LONG_PRESS_TIME_MS = 250;
|
|||||||
|
|
||||||
const styles = (theme: Theme) => createStyles({
|
const styles = (theme: Theme) => createStyles({
|
||||||
frame: {
|
frame: {
|
||||||
position: "relative",
|
width: "100%",
|
||||||
margin: "12px"
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
position: "relative"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -479,7 +483,7 @@ const Draggable =
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={{ transform: "" }}
|
<div className={this.props.classes.frame} style={{ transform: "" }}
|
||||||
onPointerDown={this.onPointerDown}
|
onPointerDown={this.onPointerDown}
|
||||||
onPointerMove={this.onPointerMove}
|
onPointerMove={this.onPointerMove}
|
||||||
onPointerCancel={this.onPointerCancel}
|
onPointerCancel={this.onPointerCancel}
|
||||||
|
|||||||
@@ -29,14 +29,16 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|||||||
|
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import OldDeleteIcon from './OldDeleteIcon';
|
||||||
|
|
||||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
import { PiPedalFileProperty } from './Lv2Plugin';
|
import { PiPedalFileProperty } from './Lv2Plugin';
|
||||||
import ButtonBase from '@mui/material/ButtonBase';
|
import ButtonBase from '@mui/material/ButtonBase';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import DialogEx from './DialogEx';
|
import DialogEx from './DialogEx';
|
||||||
|
import UploadFileDialog from './UploadFileDialog';
|
||||||
|
import OkCancelDialog from './OkCancelDialog';
|
||||||
|
|
||||||
export interface FilePropertyDialogProps {
|
export interface FilePropertyDialogProps {
|
||||||
open: boolean,
|
open: boolean,
|
||||||
@@ -51,6 +53,8 @@ export interface FilePropertyDialogState {
|
|||||||
selectedFile: string;
|
selectedFile: string;
|
||||||
hasSelection: boolean;
|
hasSelection: boolean;
|
||||||
files: string[];
|
files: string[];
|
||||||
|
openUploadFileDialog: boolean;
|
||||||
|
openConfirmDeleteDialog: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePropertyDialogProps, FilePropertyDialogState> {
|
export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePropertyDialogProps, FilePropertyDialogState> {
|
||||||
@@ -66,7 +70,9 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
|||||||
fullScreen: false,
|
fullScreen: false,
|
||||||
selectedFile: props.selectedFile,
|
selectedFile: props.selectedFile,
|
||||||
hasSelection: false,
|
hasSelection: false,
|
||||||
files: []
|
files: [],
|
||||||
|
openUploadFileDialog: false,
|
||||||
|
openConfirmDeleteDialog: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this.requestFiles();
|
this.requestFiles();
|
||||||
@@ -74,32 +80,22 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
|||||||
private mounted: boolean = false;
|
private mounted: boolean = false;
|
||||||
private model: PiPedalModel;
|
private model: PiPedalModel;
|
||||||
|
|
||||||
private lastFileProperty: PiPedalFileProperty = new PiPedalFileProperty();
|
|
||||||
|
|
||||||
private requestFiles() {
|
private requestFiles() {
|
||||||
if (!this.props.open)
|
if (!this.props.open) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.props.fileProperty.directory === "")
|
if (this.props.fileProperty.directory === "") {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.lastFileProperty === this.props.fileProperty)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lastFileProperty = this.props.fileProperty;
|
|
||||||
|
|
||||||
this.model.requestFileList(this.props.fileProperty)
|
this.model.requestFileList(this.props.fileProperty)
|
||||||
.then((files) => {
|
.then((files) => {
|
||||||
if (this.mounted)
|
if (this.mounted) {
|
||||||
{
|
this.setState({ files: files, hasSelection: this.isFileInList(files, this.state.selectedFile) });
|
||||||
this.setState({files: files,hasSelection: this.isFileInList(files,this.state.selectedFile)});
|
}
|
||||||
}
|
}).catch((error) => {
|
||||||
}).catch((error)=>{
|
this.model.showAlert(error.toString())
|
||||||
this.model.showAlert(error.toString())
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowSizeChanged(width: number, height: number): void {
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
@@ -116,11 +112,17 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
|||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
}
|
}
|
||||||
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, snapshot?: any): void {
|
componentDidUpdate(prevProps: Readonly<FilePropertyDialogProps>, prevState: Readonly<FilePropertyDialogState>, snapshot?: any): void {
|
||||||
super.componentDidUpdate?.(prevProps,prevState,snapshot);
|
super.componentDidUpdate?.(prevProps, prevState, snapshot);
|
||||||
if (prevProps.fileProperty !== this.props.fileProperty)
|
if (prevProps.open !== this.props.open)
|
||||||
{
|
{
|
||||||
|
if (this.props.open)
|
||||||
|
{
|
||||||
|
this.requestFiles()
|
||||||
|
}
|
||||||
|
} else if (prevProps.fileProperty !== this.props.fileProperty) {
|
||||||
this.requestFiles()
|
this.requestFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fileNameOnly(path: string): string {
|
private fileNameOnly(path: string): string {
|
||||||
@@ -139,7 +141,7 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isFileInList(files: string[],file: string) {
|
private isFileInList(files: string[], file: string) {
|
||||||
|
|
||||||
let hasSelection = false;
|
let hasSelection = false;
|
||||||
for (var listFile of files) {
|
for (var listFile of files) {
|
||||||
@@ -152,7 +154,52 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSelect(selectedFile: string) {
|
onSelect(selectedFile: string) {
|
||||||
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(this.state.files,selectedFile) })
|
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(this.state.files, selectedFile) })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDelete() {
|
||||||
|
this.setState({openConfirmDeleteDialog: true});
|
||||||
|
}
|
||||||
|
handleConfirmDelete()
|
||||||
|
{
|
||||||
|
this.setState({openConfirmDeleteDialog: false});
|
||||||
|
if (this.state.hasSelection)
|
||||||
|
{
|
||||||
|
let selectedFile = this.state.selectedFile;
|
||||||
|
let position = -1;
|
||||||
|
for (let i = 0; i < this.state.files.length;++i)
|
||||||
|
{
|
||||||
|
let file = this.state.files[i];
|
||||||
|
if (file === selectedFile)
|
||||||
|
{
|
||||||
|
position = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let newSelection = "";
|
||||||
|
if (position >= 0 && position < this.state.files.length-1)
|
||||||
|
{
|
||||||
|
newSelection = this.state.files[position+1];
|
||||||
|
} else if (position === this.state.files.length-1)
|
||||||
|
{
|
||||||
|
if (position !== 0) {
|
||||||
|
newSelection = this.state.files[position-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model.deleteUserFile(this.state.selectedFile)
|
||||||
|
.then(
|
||||||
|
()=> {
|
||||||
|
this.setState({selectedFile: newSelection,hasSelection: newSelection !== ""});
|
||||||
|
this.requestFiles();
|
||||||
|
}
|
||||||
|
).catch(
|
||||||
|
(e: any)=>
|
||||||
|
{
|
||||||
|
this.model.showAlert(e + "");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapKey: number = 0;
|
mapKey: number = 0;
|
||||||
@@ -160,63 +207,90 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent<FilePr
|
|||||||
this.mapKey = 0;
|
this.mapKey = 0;
|
||||||
return this.props.open &&
|
return this.props.open &&
|
||||||
(
|
(
|
||||||
<DialogEx onClose={() => this.props.onCancel()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
|
<DialogEx onClose={() => this.props.onCancel()} open={this.props.open} tag="FilePropertyDialog" fullWidth maxWidth="xl" style={{ height: "90%" }}
|
||||||
PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }}
|
PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }}
|
||||||
>
|
>
|
||||||
<DialogTitle >
|
<DialogTitle >
|
||||||
<div>
|
<div>
|
||||||
<IconButton edge="start" color="inherit" onClick={()=> {this.props.onCancel();}} aria-label="back"
|
<IconButton edge="start" color="inherit" onClick={() => { this.props.onCancel(); }} aria-label="back"
|
||||||
>
|
>
|
||||||
<ArrowBackIcon fontSize="small" style={{opacity: "0.6"}} />
|
<ArrowBackIcon fontSize="small" style={{ opacity: "0.6" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography display="inline" >{this.props.fileProperty.name}</Typography>
|
<Typography display="inline" >{this.props.fileProperty.name}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}> </div>
|
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}> </div>
|
||||||
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row nowrap", overflowX: "auto", overflowY: "visible" }}>
|
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row nowrap", overflowX: "auto", overflowY: "visible" }}>
|
||||||
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start",paddingLeft: 16,paddingTop:16 }}>
|
<div style={{ flex: "1 1 100%", display: "flex", flexFlow: "column wrap", justifyContent: "start", alignItems: "flex-start", paddingLeft: 16, paddingTop: 16 }}>
|
||||||
{
|
{
|
||||||
this.state.files.map(
|
this.state.files.map(
|
||||||
(value: string, index: number) => {
|
(value: string, index: number) => {
|
||||||
let selected = value === this.state.selectedFile;
|
let selected = value === this.state.selectedFile;
|
||||||
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
|
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
|
||||||
return (
|
return (
|
||||||
<ButtonBase key={ this.mapKey++ }
|
<ButtonBase key={this.mapKey++}
|
||||||
style={{ width: "320px", flex: "0 0 48px", position: "relative"}}
|
style={{ width: "320px", flex: "0 0 48px", position: "relative" }}
|
||||||
onClick={() => this.onSelect(value)}
|
onClick={() => this.onSelect(value)}
|
||||||
>
|
>
|
||||||
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
|
<div style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
|
||||||
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
|
<div style={{ display: "flex", flexFlow: "row nowrap", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
|
||||||
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
||||||
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
|
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}>{this.fileNameOnly(value)}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}> </div>
|
|
||||||
<DialogActions style={{ justifyContent: "stretch" }}>
|
|
||||||
<div style={{ display: "flex", width: "100%", flexFlow: "row nowrap" }}>
|
|
||||||
<Button style={{ flex: "0 0 auto" }} startIcon={<FileUploadIcon />}>
|
|
||||||
<input hidden accept="audio/x-wav" name="Upload Impuse File" type="file" multiple />
|
|
||||||
Upload
|
|
||||||
</Button>
|
|
||||||
<IconButton style={{visibility: (this.state.hasSelection? "visible": "hidden")}} aria-label="delete selected file" component="label" color="primary" >
|
|
||||||
<DeleteIcon fontSize='small' />
|
|
||||||
</IconButton>
|
|
||||||
<div style={{ flex: "1 1 auto" }}> </div>
|
|
||||||
<Button onClick={() => {this.props.onCancel();}} aria-label="cancel">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button style={{ flex: "0 0 auto" }} onClick={() => {this.props.onOk(this.props.fileProperty,this.state.selectedFile);}} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
|
|
||||||
Select
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogActions>
|
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}> </div>
|
||||||
</DialogEx>
|
<DialogActions style={{ justifyContent: "stretch" }}>
|
||||||
);
|
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||||
|
<IconButton style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
|
||||||
|
onClick={()=> this.handleDelete()} >
|
||||||
|
<OldDeleteIcon fontSize='small' />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
|
||||||
|
onClick={()=> { this.setState({openUploadFileDialog: true})}}
|
||||||
|
>
|
||||||
|
<div>Upload</div>
|
||||||
|
</Button>
|
||||||
|
<div style={{ flex: "1 1 auto" }}> </div>
|
||||||
|
|
||||||
|
<Button onClick={() => { this.props.onCancel(); }} aria-label="cancel">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button style={{ flex: "0 0 auto" }} onClick={() => { this.props.onOk(this.props.fileProperty, this.state.selectedFile); }} color="secondary" disabled={!this.state.hasSelection} aria-label="select">
|
||||||
|
Select
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
<UploadFileDialog
|
||||||
|
open={this.state.openUploadFileDialog}
|
||||||
|
onClose=
|
||||||
|
{
|
||||||
|
() => {
|
||||||
|
this.setState({ openUploadFileDialog: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploadPage={ "uploadUserFile?directory="+ encodeURIComponent(this.props.fileProperty.directory)}
|
||||||
|
onUploaded={()=> { this.requestFiles();}}
|
||||||
|
fileProperty={this.props.fileProperty}
|
||||||
|
|
||||||
|
|
||||||
|
/>
|
||||||
|
<OkCancelDialog open={this.state.openConfirmDeleteDialog}
|
||||||
|
text="Are you sure you want to delete the selected file?"
|
||||||
|
okButtonText="Delete"
|
||||||
|
onOk={()=>this.handleConfirmDelete()}
|
||||||
|
onClose={()=>{
|
||||||
|
this.setState({openConfirmDeleteDialog: false});
|
||||||
|
}}
|
||||||
|
|
||||||
|
/>
|
||||||
|
</DialogEx>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+191
-80
@@ -17,7 +17,7 @@
|
|||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import React, { ReactNode, SyntheticEvent, CSSProperties } from 'react';
|
import React, { ReactNode, SyntheticEvent, CSSProperties, Fragment, ReactElement } from 'react';
|
||||||
|
|
||||||
import { PiPedalModel, PiPedalModelFactory, FavoritesList } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory, FavoritesList } from './PiPedalModel';
|
||||||
import { UiPlugin, PluginType } from './Lv2Plugin';
|
import { UiPlugin, PluginType } from './Lv2Plugin';
|
||||||
@@ -42,7 +42,6 @@ import SearchFilter from './SearchFilter';
|
|||||||
import { FixedSizeGrid } from 'react-window';
|
import { FixedSizeGrid } from 'react-window';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
||||||
import Slide, { SlideProps } from '@mui/material/Slide';
|
|
||||||
import { createStyles, Theme } from '@mui/material/styles';
|
import { createStyles, Theme } from '@mui/material/styles';
|
||||||
import { WithStyles, withStyles } from '@mui/styles';
|
import { WithStyles, withStyles } from '@mui/styles';
|
||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
@@ -102,10 +101,10 @@ const pluginGridStyles = (theme: Theme) => createStyles({
|
|||||||
height: "100%'"
|
height: "100%'"
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
marginTop: "8px",
|
marginTop: "4px",
|
||||||
marginBottom: "8px",
|
marginBottom: "4px",
|
||||||
marginLeft: "12px",
|
marginLeft: "8px",
|
||||||
marginRight: "12px",
|
marginRight: "8px",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
@@ -118,7 +117,7 @@ const pluginGridStyles = (theme: Theme) => createStyles({
|
|||||||
},
|
},
|
||||||
content2: {
|
content2: {
|
||||||
display: "flex", flexDirection: "column", flex: "1 1 auto", width: "100%",
|
display: "flex", flexDirection: "column", flex: "1 1 auto", width: "100%",
|
||||||
paddingLeft: 16, whiteSpace: "nowrap"
|
paddingLeft: 12, whiteSpace: "nowrap", textOverflow: "ellipsis"
|
||||||
|
|
||||||
},
|
},
|
||||||
favoriteDecoration: {
|
favoriteDecoration: {
|
||||||
@@ -134,7 +133,8 @@ const pluginGridStyles = (theme: Theme) => createStyles({
|
|||||||
opacity: "0.6"
|
opacity: "0.6"
|
||||||
},
|
},
|
||||||
iconBorder: {
|
iconBorder: {
|
||||||
flex: "0 0 auto"
|
flex: "0 0 auto",
|
||||||
|
paddingTop: "4px"
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
width: "100%"
|
width: "100%"
|
||||||
@@ -173,14 +173,11 @@ type PluginGridState = {
|
|||||||
grid_cell_columns: number,
|
grid_cell_columns: number,
|
||||||
minimumItemWidth: number,
|
minimumItemWidth: number,
|
||||||
favoritesList: FavoritesList
|
favoritesList: FavoritesList
|
||||||
|
gridItems: UiPlugin[];
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Transition = React.forwardRef(function Transition(
|
|
||||||
props: SlideProps, ref: React.Ref<unknown>
|
|
||||||
) {
|
|
||||||
return (<Slide direction="up" ref={ref} {...props} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LoadPluginDialog =
|
export const LoadPluginDialog =
|
||||||
withStyles(pluginGridStyles, { withTheme: true })(
|
withStyles(pluginGridStyles, { withTheme: true })(
|
||||||
@@ -210,7 +207,9 @@ export const LoadPluginDialog =
|
|||||||
grid_cell_width: this.getCellWidth(window.innerWidth),
|
grid_cell_width: this.getCellWidth(window.innerWidth),
|
||||||
grid_cell_columns: this.getCellColumns(window.innerWidth),
|
grid_cell_columns: this.getCellColumns(window.innerWidth),
|
||||||
minimumItemWidth: props.minimumItemWidth ? props.minimumItemWidth : 220,
|
minimumItemWidth: props.minimumItemWidth ? props.minimumItemWidth : 220,
|
||||||
favoritesList: this.model.favorites.get()
|
favoritesList: this.model.favorites.get(),
|
||||||
|
gridItems: this.getFilteredPlugins("", filterType_, this.model.favorites.get())
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateWindowSize = this.updateWindowSize.bind(this);
|
this.updateWindowSize = this.updateWindowSize.bind(this);
|
||||||
@@ -219,6 +218,8 @@ export const LoadPluginDialog =
|
|||||||
this.handleSearchStringReady = this.handleSearchStringReady.bind(this);
|
this.handleSearchStringReady = this.handleSearchStringReady.bind(this);
|
||||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||||
this.handleFavoritesChanged = this.handleFavoritesChanged.bind(this);
|
this.handleFavoritesChanged = this.handleFavoritesChanged.bind(this);
|
||||||
|
|
||||||
|
this.requestScrollTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
nominal_column_width: number = 250;
|
nominal_column_width: number = 250;
|
||||||
@@ -252,9 +253,9 @@ export const LoadPluginDialog =
|
|||||||
|
|
||||||
let newValue = searchInput.value + e.key;
|
let newValue = searchInput.value + e.key;
|
||||||
searchInput.value = newValue; // add the key. to the value.
|
searchInput.value = newValue; // add the key. to the value.
|
||||||
|
this.handleSearchStringChanged(newValue);
|
||||||
searchInput.focus();
|
searchInput.focus();
|
||||||
this.setState({
|
this.setState({
|
||||||
search_string: newValue,
|
|
||||||
search_collapsed: false
|
search_collapsed: false
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -275,9 +276,12 @@ export const LoadPluginDialog =
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFavoritesChanged() {
|
handleFavoritesChanged() {
|
||||||
|
let favorites = this.model.favorites.get();
|
||||||
|
this.requestScrollTo();
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
favoritesList: this.model.favorites.get()
|
favoritesList: favorites,
|
||||||
|
gridItems: this.getFilteredPlugins(null, null, favorites)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -285,7 +289,12 @@ export const LoadPluginDialog =
|
|||||||
this.updateWindowSize();
|
this.updateWindowSize();
|
||||||
window.addEventListener('resize', this.updateWindowSize);
|
window.addEventListener('resize', this.updateWindowSize);
|
||||||
this.model.favorites.addOnChangedHandler(this.handleFavoritesChanged);
|
this.model.favorites.addOnChangedHandler(this.handleFavoritesChanged);
|
||||||
this.setState({ favoritesList: this.model.favorites.get() });
|
let favorites = this.model.favorites.get();
|
||||||
|
this.setState({
|
||||||
|
favoritesList: favorites,
|
||||||
|
gridItems: this.getFilteredPlugins(null, null, favorites)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -297,7 +306,15 @@ export const LoadPluginDialog =
|
|||||||
componentDidUpdate(oldProps: PluginGridProps) {
|
componentDidUpdate(oldProps: PluginGridProps) {
|
||||||
if (oldProps.open !== this.props.open) {
|
if (oldProps.open !== this.props.open) {
|
||||||
if (this.props.open) {
|
if (this.props.open) {
|
||||||
this.setState({ search_string: "", search_collapsed: true });
|
// reset state now.
|
||||||
|
if (this.state.search_string !== "" || !this.state.search_collapsed) {
|
||||||
|
this.setState({
|
||||||
|
search_string: "",
|
||||||
|
search_collapsed: true,
|
||||||
|
gridItems: this.getFilteredPlugins("", null, null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.requestScrollTo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,17 +325,26 @@ export const LoadPluginDialog =
|
|||||||
}
|
}
|
||||||
|
|
||||||
onFilterChange(e: any) {
|
onFilterChange(e: any) {
|
||||||
let value = e.target.value as PluginType;
|
let filterValue = e.target.value as PluginType;
|
||||||
|
|
||||||
window.localStorage.setItem(FILTER_STORAGE_KEY, value as string);
|
window.localStorage.setItem(FILTER_STORAGE_KEY, filterValue as string);
|
||||||
|
|
||||||
this.setState({ filterType: value });
|
this.requestScrollTo();
|
||||||
|
this.setState({
|
||||||
|
filterType: filterValue,
|
||||||
|
gridItems: this.getFilteredPlugins(null, filterValue, null)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
onClearFilter(): void {
|
onClearFilter(): void {
|
||||||
let value = PluginType.Plugin;
|
let value = PluginType.Plugin;
|
||||||
window.localStorage.setItem(FILTER_STORAGE_KEY, value as string);
|
window.localStorage.setItem(FILTER_STORAGE_KEY, value as string);
|
||||||
|
if (this.state.filterType !== value) {
|
||||||
this.setState({ filterType: value });
|
this.requestScrollTo();
|
||||||
|
this.setState({
|
||||||
|
filterType: value,
|
||||||
|
gridItems: this.getFilteredPlugins(null, value, null)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectItem(item: number): void {
|
selectItem(item: number): void {
|
||||||
@@ -389,11 +415,9 @@ export const LoadPluginDialog =
|
|||||||
// let selectedUri = this.state.selected_uri;
|
// let selectedUri = this.state.selected_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
vst3_indicator(uiPlugin? : UiPlugin)
|
vst3_indicator(uiPlugin?: UiPlugin) {
|
||||||
{
|
if (uiPlugin?.is_vst3) {
|
||||||
if (uiPlugin?.is_vst3)
|
return (<img alt="vst" src="/img/vst.svg" style={{ marginLeft: 8, height: 22, opacity: 0.6, position: "relative", top: -1 }} />)
|
||||||
{
|
|
||||||
return (<img alt="vst" src="/img/vst.svg" style={{marginLeft: 8,height: 22, opacity: 0.6,position: "relative",top: -1}} />)
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -404,14 +428,36 @@ export const LoadPluginDialog =
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
info_string(uiPlugin?: UiPlugin): string {
|
info_string(uiPlugin?: UiPlugin): ReactElement {
|
||||||
if (uiPlugin === undefined) return "";
|
if (!uiPlugin) {
|
||||||
let result = uiPlugin.name;
|
return (<Fragment />);
|
||||||
if (uiPlugin.author_name !== "") {
|
} else {
|
||||||
result += ", " + uiPlugin.author_name;
|
let stereoIndicator = this.stereo_indicator(uiPlugin)
|
||||||
|
if (uiPlugin.author_name !== "") {
|
||||||
|
if (uiPlugin.author_homepage !== "") {
|
||||||
|
return (<Fragment>
|
||||||
|
<div style={{flex: "0 1 auto"}}>
|
||||||
|
<Typography variant='body2' color="textSecondary" noWrap>{uiPlugin.name + ",\u00A0"}</Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{flex: "0 1 auto"}}>
|
||||||
|
<a href={uiPlugin.author_homepage} target="_blank" >
|
||||||
|
<Typography variant='body2' color="textSecondary" noWrap>{uiPlugin.author_name}</Typography>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Fragment>);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return (<Fragment>
|
||||||
|
<Typography variant='body2' color="textSecondary" noWrap>{uiPlugin.name + ", " + uiPlugin.author_name}</Typography>
|
||||||
|
</Fragment>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Typography variant='body2' color="textSecondary" noWrap>{uiPlugin?.name ?? ""}</Typography>
|
||||||
|
</Fragment>);
|
||||||
|
|
||||||
}
|
}
|
||||||
result += this.stereo_indicator(uiPlugin);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
createFilterChildren(result: ReactNode[], classNode: PluginClass, level: number): void {
|
createFilterChildren(result: ReactNode[], classNode: PluginClass, level: number): void {
|
||||||
for (let i = 0; i < classNode.children.length; ++i) {
|
for (let i = 0; i < classNode.children.length; ++i) {
|
||||||
@@ -432,29 +478,35 @@ export const LoadPluginDialog =
|
|||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
getFilteredPlugins(): UiPlugin[] {
|
getFilteredPlugins(searchString: string | null, filterType: PluginType | null, favoritesList: FavoritesList | null): UiPlugin[] {
|
||||||
|
if (searchString === null) {
|
||||||
|
searchString = this.state.search_string;
|
||||||
|
}
|
||||||
|
if (filterType === null) {
|
||||||
|
filterType = this.state.filterType;
|
||||||
|
}
|
||||||
|
if (favoritesList === null) {
|
||||||
|
favoritesList = this.state.favoritesList;
|
||||||
|
}
|
||||||
let plugins = this.model.ui_plugins.get();
|
let plugins = this.model.ui_plugins.get();
|
||||||
let searchString = this.state.search_string;
|
|
||||||
|
|
||||||
let results: { score: number; plugin: UiPlugin }[] = [];
|
let results: { score: number; plugin: UiPlugin }[] = [];
|
||||||
let searchFilter = new SearchFilter(searchString);
|
let searchFilter = new SearchFilter(searchString);
|
||||||
let filterType = this.state.filterType;
|
|
||||||
let rootClass = this.model.plugin_classes.get();
|
let rootClass = this.model.plugin_classes.get();
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < plugins.length; ++i) {
|
for (let i = 0; i < plugins.length; ++i) {
|
||||||
let plugin = plugins[i];
|
let plugin = plugins[i];
|
||||||
if (filterType === PluginType.Plugin || rootClass.is_type_of(filterType, plugin.plugin_type)) {
|
if (filterType === PluginType.Plugin || rootClass.is_type_of(filterType, plugin.plugin_type)) {
|
||||||
let score:number = 0;
|
let score: number = 0;
|
||||||
if (plugin.is_vst3)
|
if (plugin.is_vst3) {
|
||||||
{
|
score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name, "vst3");
|
||||||
score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name,"vst3");
|
|
||||||
} else {
|
} else {
|
||||||
score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name);
|
score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score !== 0) {
|
if (score !== 0) {
|
||||||
if (this.state.favoritesList[plugin.uri]) {
|
if (favoritesList[plugin.uri]) {
|
||||||
score += 32768;
|
score += 32768;
|
||||||
}
|
}
|
||||||
results.push({ score: score, plugin: plugin });
|
results.push({ score: score, plugin: plugin });
|
||||||
@@ -476,9 +528,38 @@ export const LoadPluginDialog =
|
|||||||
changedSearchString?: string = undefined;
|
changedSearchString?: string = undefined;
|
||||||
hSearchTimeout?: NodeJS.Timeout;
|
hSearchTimeout?: NodeJS.Timeout;
|
||||||
|
|
||||||
|
private scrollToRequested = false;
|
||||||
|
requestScrollTo() {
|
||||||
|
this.scrollToRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScrollToCallback(element: FixedSizeGrid): void {
|
||||||
|
if (element) {
|
||||||
|
if (this.scrollToRequested) {
|
||||||
|
this.scrollToRequested = false;
|
||||||
|
let position = -1;
|
||||||
|
let gridItems = this.state.gridItems;
|
||||||
|
for (let i = 0; i < gridItems.length; ++i) {
|
||||||
|
if (this.state.selected_uri === gridItems[i].uri) {
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position != -1) {
|
||||||
|
element.scrollToItem({ rowIndex: Math.floor(position / this.state.grid_cell_columns) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
handleSearchStringReady() {
|
handleSearchStringReady() {
|
||||||
if (this.changedSearchString !== undefined) {
|
if (this.changedSearchString !== undefined) {
|
||||||
this.setState({ search_string: this.changedSearchString });
|
this.requestScrollTo();
|
||||||
|
this.setState({
|
||||||
|
search_string: this.changedSearchString,
|
||||||
|
gridItems: this.getFilteredPlugins(this.changedSearchString, null, null)
|
||||||
|
});
|
||||||
this.changedSearchString = undefined;
|
this.changedSearchString = undefined;
|
||||||
}
|
}
|
||||||
this.hSearchTimeout = undefined;
|
this.hSearchTimeout = undefined;
|
||||||
@@ -498,14 +579,15 @@ export const LoadPluginDialog =
|
|||||||
}
|
}
|
||||||
renderItem(row: number, column: number): React.ReactNode {
|
renderItem(row: number, column: number): React.ReactNode {
|
||||||
let item: number = (row) * this.gridColumnCount + (column);
|
let item: number = (row) * this.gridColumnCount + (column);
|
||||||
|
let gridItems = this.state.gridItems;
|
||||||
if (item >= this.gridItems.length) {
|
if (item >= gridItems.length) {
|
||||||
return (<div />);
|
return (<div />);
|
||||||
}
|
}
|
||||||
let value = this.gridItems[item];
|
let value = gridItems[item];
|
||||||
|
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let isFavorite: boolean = this.state.favoritesList[value.uri] ?? false;
|
let isFavorite: boolean = this.state.favoritesList[value.uri] ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={value.uri}
|
<div key={value.uri}
|
||||||
onDoubleClick={(e) => { this.onDoubleClick(e, value.uri) }}
|
onDoubleClick={(e) => { this.onDoubleClick(e, value.uri) }}
|
||||||
@@ -520,16 +602,19 @@ export const LoadPluginDialog =
|
|||||||
<PluginIcon pluginType={value.plugin_type} pluginUri={value.uri} size={24} />
|
<PluginIcon pluginType={value.plugin_type} pluginUri={value.uri} size={24} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.content2}>
|
<div className={classes.content2}>
|
||||||
<div className={classes.label} >
|
<div className={classes.label} style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center" }} >
|
||||||
<Typography color="textPrimary" noWrap sx={{ display: "inline" }} >
|
|
||||||
{value.name}
|
|
||||||
{
|
|
||||||
isFavorite && (
|
|
||||||
<StarBorderIcon sx={{ color: "#C80", fontSize: 16 }} />
|
|
||||||
|
|
||||||
)
|
<Typography color="textPrimary" noWrap sx={{ display: "block", flex: "0 1 auto", }} >
|
||||||
}
|
{value.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{
|
||||||
|
isFavorite && (
|
||||||
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
|
<StarBorderIcon sx={{ color: "#C80", fontSize: 16, marginRight: "2px" }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Typography color="textSecondary" noWrap>
|
<Typography color="textSecondary" noWrap>
|
||||||
{value.plugin_display_type} {this.stereo_indicator(value)}
|
{value.plugin_display_type} {this.stereo_indicator(value)}
|
||||||
@@ -549,7 +634,6 @@ export const LoadPluginDialog =
|
|||||||
if (!pluginUri) return;
|
if (!pluginUri) return;
|
||||||
this.model.setFavorite(pluginUri, isFavorite);
|
this.model.setFavorite(pluginUri, isFavorite);
|
||||||
}
|
}
|
||||||
gridItems: UiPlugin[] = [];
|
|
||||||
gridColumnCount: number = 1;
|
gridColumnCount: number = 1;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -565,7 +649,6 @@ export const LoadPluginDialog =
|
|||||||
if (this.state.client_width < 500) {
|
if (this.state.client_width < 500) {
|
||||||
showSearchIcon = this.state.search_collapsed
|
showSearchIcon = this.state.search_collapsed
|
||||||
}
|
}
|
||||||
this.gridItems = this.getFilteredPlugins();
|
|
||||||
let gridColumnCount = this.state.grid_cell_columns;
|
let gridColumnCount = this.state.grid_cell_columns;
|
||||||
this.gridColumnCount = gridColumnCount;
|
this.gridColumnCount = gridColumnCount;
|
||||||
let isFavorite = this.state.favoritesList[this.state.selected_uri ?? ""];
|
let isFavorite = this.state.favoritesList[this.state.selected_uri ?? ""];
|
||||||
@@ -574,7 +657,7 @@ export const LoadPluginDialog =
|
|||||||
<Dialog
|
<Dialog
|
||||||
onKeyPress={(e) => { this.handleKeyPress(e); }}
|
onKeyPress={(e) => { this.handleKeyPress(e); }}
|
||||||
fullScreen={true}
|
fullScreen={true}
|
||||||
TransitionComponent={Transition}
|
TransitionComponent={undefined}
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
open={this.props.open}
|
open={this.props.open}
|
||||||
scroll="body"
|
scroll="body"
|
||||||
@@ -603,13 +686,34 @@ export const LoadPluginDialog =
|
|||||||
this.handleSearchStringChanged(text);
|
this.handleSearchStringChanged(text);
|
||||||
}}
|
}}
|
||||||
onClearFilterClick={() => {
|
onClearFilterClick={() => {
|
||||||
this.setState({ search_collapsed: true, search_string: "" });
|
if (this.state.search_string !== "") {
|
||||||
|
this.requestScrollTo();
|
||||||
|
this.setState({
|
||||||
|
search_collapsed: true,
|
||||||
|
search_string: "",
|
||||||
|
gridItems: this.getFilteredPlugins("", null, null)
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.setState({ search_collapsed: true });
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (this.state.search_collapsed) {
|
if (this.state.search_collapsed) {
|
||||||
this.setState({ search_collapsed: false });
|
this.setState({ search_collapsed: false });
|
||||||
} else {
|
} else {
|
||||||
this.setState({ search_collapsed: true, search_string: "" });
|
|
||||||
|
if (this.state.search_string !== "") {
|
||||||
|
this.requestScrollTo();
|
||||||
|
this.setState({
|
||||||
|
search_collapsed: true,
|
||||||
|
search_string: "",
|
||||||
|
gridItems: this.getFilteredPlugins("", null, null)
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.setState({ search_collapsed: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
@@ -624,13 +728,13 @@ export const LoadPluginDialog =
|
|||||||
)}
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 160px",marginRight: 24 }} >
|
<div style={{ flex: "0 0 160px", marginRight: 24 }} >
|
||||||
|
|
||||||
<TextField select variant="standard"
|
<TextField select variant="standard"
|
||||||
defaultValue={this.state.filterType}
|
defaultValue={this.state.filterType}
|
||||||
key={this.state.filterType}
|
key={this.state.filterType}
|
||||||
onChange={(e) => { this.onFilterChange(e); }}
|
onChange={(e) => { this.onFilterChange(e); }}
|
||||||
sx={{ minWidth: 160}}
|
sx={{ minWidth: 160 }}
|
||||||
>
|
>
|
||||||
{this.createFilterOptions()}
|
{this.createFilterOptions()}
|
||||||
</TextField>
|
</TextField>
|
||||||
@@ -643,22 +747,33 @@ export const LoadPluginDialog =
|
|||||||
flex: "1 1 100px",
|
flex: "1 1 100px",
|
||||||
}} >
|
}} >
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{(arg: { height: number, width: number }) => {
|
{(arg) => {
|
||||||
|
if ((!arg.width) || (!arg.height)) {
|
||||||
|
return (<div></div>);
|
||||||
|
}
|
||||||
|
let width = arg.width ?? 1;
|
||||||
|
let height = arg.height ?? 1;
|
||||||
|
let gridItems = this.state.gridItems;
|
||||||
|
|
||||||
|
let scrollRef = (grid: FixedSizeGrid) => { this.handleScrollToCallback(grid); }
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FixedSizeGrid
|
<FixedSizeGrid
|
||||||
width={arg.width}
|
ref={scrollRef}
|
||||||
|
width={width}
|
||||||
columnCount={this.gridColumnCount}
|
columnCount={this.gridColumnCount}
|
||||||
columnWidth={(arg.width - 40) / this.gridColumnCount}
|
columnWidth={(width - 40) / this.gridColumnCount}
|
||||||
height={arg.height}
|
height={height}
|
||||||
rowHeight={64}
|
rowHeight={64}
|
||||||
overscanRowCount={10}
|
overscanRowCount={10}
|
||||||
rowCount={Math.ceil(this.gridItems.length / this.gridColumnCount)}
|
rowCount={Math.ceil(gridItems.length / this.gridColumnCount)}
|
||||||
itemKey={(args: { columnIndex: number, data: any, rowIndex: number }) => {
|
itemKey={(args: { columnIndex: number, data: any, rowIndex: number }) => {
|
||||||
let index = args.columnIndex + this.gridColumnCount * args.rowIndex;
|
let index = args.columnIndex + this.gridColumnCount * args.rowIndex;
|
||||||
if (index >= this.gridItems.length) {
|
if (index >= gridItems.length) {
|
||||||
return "blank-" + args.columnIndex + "-" + args.rowIndex;
|
return "blank-" + args.columnIndex + "-" + args.rowIndex;
|
||||||
}
|
}
|
||||||
let plugin = this.gridItems[index];
|
let plugin = gridItems[index];
|
||||||
return plugin.uri + "-" + args.rowIndex + "-" + args.columnIndex;
|
return plugin.uri + "-" + args.rowIndex + "-" + args.columnIndex;
|
||||||
|
|
||||||
}}
|
}}
|
||||||
@@ -680,11 +795,9 @@ export const LoadPluginDialog =
|
|||||||
{(this.state.client_width >= NARROW_DISPLAY_THRESHOLD) ? (
|
{(this.state.client_width >= NARROW_DISPLAY_THRESHOLD) ? (
|
||||||
<DialogActions style={{ flex: "0 0 auto" }} >
|
<DialogActions style={{ flex: "0 0 auto" }} >
|
||||||
<div className={classes.bottom}>
|
<div className={classes.bottom}>
|
||||||
<div style={{ display: "flex", justifyContent: "start", alignItems: "center", flex: "1 1 auto", height: "100%", overflow: "hidden" }} >
|
<div style={{ display: "flex", textOverflow: "ellipsis", justifyContent: "start", alignItems: "center", flex: "1 1 auto", height: "100%", overflow: "hidden" }} >
|
||||||
<PluginInfoDialog plugin_uri={this.state.selected_uri ?? ""} />
|
<PluginInfoDialog plugin_uri={this.state.selected_uri ?? ""} />
|
||||||
<Typography display='block' variant='body2' color="textPrimary" noWrap >
|
{this.info_string(selectedPlugin)}
|
||||||
{this.info_string(selectedPlugin)}
|
|
||||||
</Typography>
|
|
||||||
{this.vst3_indicator(selectedPlugin)}
|
{this.vst3_indicator(selectedPlugin)}
|
||||||
<div style={{
|
<div style={{
|
||||||
color: isFavorite ? "#F80" : "#CCC", display: (this.state.selected_uri !== "" ? "block" : "none"),
|
color: isFavorite ? "#F80" : "#CCC", display: (this.state.selected_uri !== "" ? "block" : "none"),
|
||||||
@@ -708,12 +821,10 @@ export const LoadPluginDialog =
|
|||||||
<div style={{ display: "flex", justifyContent: "start", alignItems: "center", flex: "1 1 auto", overflow: "hidden" }} >
|
<div style={{ display: "flex", justifyContent: "start", alignItems: "center", flex: "1 1 auto", overflow: "hidden" }} >
|
||||||
<PluginInfoDialog plugin_uri={this.state.selected_uri ?? ""} />
|
<PluginInfoDialog plugin_uri={this.state.selected_uri ?? ""} />
|
||||||
<div style={{ width: 1, height: 48 }} />
|
<div style={{ width: 1, height: 48 }} />
|
||||||
<Typography display='block' variant='body2' color="textPrimary" noWrap >
|
{this.info_string(selectedPlugin)}
|
||||||
{this.info_string(selectedPlugin)}
|
|
||||||
</Typography>
|
|
||||||
<div style={{
|
<div style={{
|
||||||
color: isFavorite ? "#F80" : "#CCC", display: (this.state.selected_uri ? "block" : "block"),
|
color: isFavorite ? "#F80" : "#CCC", display: (this.state.selected_uri ? "block" : "block"),
|
||||||
position: "relative", top: -5
|
position: "relative"
|
||||||
}}>
|
}}>
|
||||||
<IconButton color="inherit" aria-label="Set as favorite"
|
<IconButton color="inherit" aria-label="Set as favorite"
|
||||||
onClick={() => { this.setFavorite(this.state.selected_uri, !isFavorite); }}
|
onClick={() => { this.setFavorite(this.state.selected_uri, !isFavorite); }}
|
||||||
@@ -722,11 +833,11 @@ export const LoadPluginDialog =
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.bottom}>
|
<div className={classes.bottom} style={{height: 64}}>
|
||||||
<div style={{ flex: "1 1 1px" }} />
|
<div style={{ flex: "1 1 1px" }} />
|
||||||
<div style={{ position: "relative", flex: "0 1 auto", display: "flex", alignItems: "center" }}>
|
<div style={{ position: "relative", flex: "0 1 auto", display: "flex", alignItems: "center" }}>
|
||||||
<Button onClick={this.handleCancel} style={{ width: 120, height: 48 }} >Cancel</Button>
|
<Button onClick={this.handleCancel} style={{ height: 48 }} >Cancel</Button>
|
||||||
<Button onClick={this.handleOk} color="secondary" disabled={selectedPlugin === null} style={{ width: 120, height: 48 }} >SELECT</Button>
|
<Button onClick={this.handleOk} color="secondary" disabled={selectedPlugin === null} style={{ height: 48 }} >SELECT</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
+72
-22
@@ -167,6 +167,37 @@ export class PiPedalFileProperty {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static getFileExtension(name: string): string {
|
||||||
|
let pos = name.lastIndexOf('.');
|
||||||
|
let filenamePos = name.lastIndexOf('/') +1;
|
||||||
|
filenamePos = Math.max(name.lastIndexOf('\\')+1);
|
||||||
|
filenamePos = Math.max(name.lastIndexOf(':')+1);
|
||||||
|
if (pos < filenamePos)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos !== -1) {
|
||||||
|
return name.substring(pos);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
wantsFile(filename: string) : boolean {
|
||||||
|
if (this.fileTypes.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let extension = PiPedalFileProperty.getFileExtension(filename);
|
||||||
|
for (let fileType of this.fileTypes)
|
||||||
|
{
|
||||||
|
if (fileType.fileExtension === extension )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
name: string = "";
|
name: string = "";
|
||||||
fileTypes: PiPedalFileType[] = [];
|
fileTypes: PiPedalFileType[] = [];
|
||||||
patchProperty: string = "";
|
patchProperty: string = "";
|
||||||
@@ -290,7 +321,7 @@ export enum PluginType {
|
|||||||
export enum ControlType {
|
export enum ControlType {
|
||||||
Dial,
|
Dial,
|
||||||
OnOffSwitch,
|
OnOffSwitch,
|
||||||
Toggle,
|
ABSwitch,
|
||||||
Select
|
Select
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +331,7 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
this.symbol = input.symbol;
|
this.symbol = input.symbol;
|
||||||
this.name = input.name;
|
this.name = input.name;
|
||||||
this.index = input.index;
|
this.index = input.index;
|
||||||
|
this.is_input = input.is_input;
|
||||||
this.min_value = input.min_value;
|
this.min_value = input.min_value;
|
||||||
this.max_value = input.max_value;
|
this.max_value = input.max_value;
|
||||||
this.default_value = input.default_value;
|
this.default_value = input.default_value;
|
||||||
@@ -320,33 +352,55 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
this.is_program_controller = input.is_program_controller? true: false;
|
this.is_program_controller = input.is_program_controller? true: false;
|
||||||
this.custom_units = input.custom_units ?? "";
|
this.custom_units = input.custom_units ?? "";
|
||||||
|
|
||||||
this.controlType = ControlType.Dial;
|
|
||||||
|
|
||||||
if (this.is_bypass)
|
if (this.is_bypass)
|
||||||
{
|
{
|
||||||
this.not_on_gui = true;
|
this.not_on_gui = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.controlType = ControlType.Dial;
|
||||||
|
|
||||||
if (this.enumeration_property && this.scale_points.length === 2)
|
if (this.isValidEnumeration())
|
||||||
{
|
|
||||||
this.controlType = ControlType.Toggle;
|
|
||||||
} else {
|
|
||||||
if (this.min_value === 0 && this.max_value === 1)
|
|
||||||
{
|
|
||||||
if (this.toggled_property || this.integer_property || this.range_steps === 2)
|
|
||||||
{
|
|
||||||
this.controlType = ControlType.OnOffSwitch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.controlType === ControlType.Dial && this.enumeration_property)
|
|
||||||
{
|
{
|
||||||
this.controlType = ControlType.Select;
|
this.controlType = ControlType.Select;
|
||||||
|
if (this.scale_points.length === 2)
|
||||||
|
{
|
||||||
|
this.controlType = ControlType.ABSwitch;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.toggled_property || (this.integer_property && this.min_value === 0 && this.max_value === 1))
|
||||||
|
{
|
||||||
|
this.controlType = ControlType.OnOffSwitch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private hasScalePoint(value: number): boolean {
|
||||||
|
for (let scale_point of this.scale_points)
|
||||||
|
{
|
||||||
|
if (scale_point.value === value) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private isValidEnumeration() : boolean {
|
||||||
|
if (this.enumeration_property) return true;
|
||||||
|
if (this.toggled_property && this.min_value === 0 && this.max_value === 1)
|
||||||
|
{
|
||||||
|
if (this.hasScalePoint(this.min_value) && this.hasScalePoint(this.max_value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.integer_property && this.min_value === 0 && this.max_value === 1)
|
||||||
|
{
|
||||||
|
if (this.hasScalePoint(this.min_value) && this.hasScalePoint(this.max_value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
controlType: ControlType = ControlType.Dial; // non-serializable.
|
controlType: ControlType = ControlType.Dial; // non-serializable.
|
||||||
|
|
||||||
@@ -361,6 +415,7 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
symbol: string = "";
|
symbol: string = "";
|
||||||
name: string = "";
|
name: string = "";
|
||||||
index: number = -1;
|
index: number = -1;
|
||||||
|
is_input: boolean = true;
|
||||||
min_value: number = 0;
|
min_value: number = 0;
|
||||||
max_value: number = 1;
|
max_value: number = 1;
|
||||||
default_value:number = 0.5;
|
default_value:number = 0.5;
|
||||||
@@ -406,7 +461,7 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAbToggle(): boolean {
|
isAbToggle(): boolean {
|
||||||
return this.controlType === ControlType.Toggle;
|
return this.controlType === ControlType.ABSwitch;
|
||||||
}
|
}
|
||||||
isSelect() : boolean {
|
isSelect() : boolean {
|
||||||
return this.controlType === ControlType.Select;
|
return this.controlType === ControlType.Select;
|
||||||
@@ -426,9 +481,6 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
if (range > 1) range = 1;
|
if (range > 1) range = 1;
|
||||||
if (range < 0) range = 0;
|
if (range < 0) range = 0;
|
||||||
|
|
||||||
if (this.range_steps !== 0) {
|
|
||||||
range = Math.round(range*this.range_steps)/this.range_steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
@@ -438,9 +490,7 @@ export class UiControl implements Deserializable<UiControl> {
|
|||||||
if (range > 1) range = 1;
|
if (range > 1) range = 1;
|
||||||
|
|
||||||
if (this.toggled_property) return range === 0? 0: 1;
|
if (this.toggled_property) return range === 0? 0: 1;
|
||||||
if (this.range_steps !== 0) {
|
|
||||||
range = Math.round(range * this.range_steps) / this.range_steps;
|
|
||||||
}
|
|
||||||
let value = range * (this.max_value - this.min_value) + this.min_value;
|
let value = range * (this.max_value - this.min_value) + this.min_value;
|
||||||
if (this.integer_property || this.enumeration_property) {
|
if (this.integer_property || this.enumeration_property) {
|
||||||
value = Math.round(value);
|
value = Math.round(value);
|
||||||
|
|||||||
+90
-63
@@ -45,6 +45,7 @@ import PluginInfoDialog from './PluginInfoDialog';
|
|||||||
import { GetControlView } from './ControlViewFactory';
|
import { GetControlView } from './ControlViewFactory';
|
||||||
import MidiBindingsDialog from './MidiBindingsDialog';
|
import MidiBindingsDialog from './MidiBindingsDialog';
|
||||||
import PluginPresetSelector from './PluginPresetSelector';
|
import PluginPresetSelector from './PluginPresetSelector';
|
||||||
|
import SaveIconOutline from '@mui/icons-material/Save';
|
||||||
|
|
||||||
|
|
||||||
const SPLIT_CONTROLBAR_THRESHHOLD = 650;
|
const SPLIT_CONTROLBAR_THRESHHOLD = 650;
|
||||||
@@ -75,7 +76,7 @@ const styles = ({ palette }: Theme) => createStyles({
|
|||||||
flex: "0 0 auto", width: "100%", height: 48
|
flex: "0 0 auto", width: "100%", height: 48
|
||||||
},
|
},
|
||||||
splitControlBar: {
|
splitControlBar: {
|
||||||
flex: "0 0 48px", width: "100%", paddingLeft: 24, paddingRight: 16, paddingBottom: 16
|
flex: "0 0 64px", width: "100%", paddingLeft: 24, paddingRight: 16, paddingBottom: 16
|
||||||
},
|
},
|
||||||
controlContent: {
|
controlContent: {
|
||||||
flex: "1 1 auto", width: "100%", overflowY: "auto", minHeight: 240
|
flex: "1 1 auto", width: "100%", overflowY: "auto", minHeight: 240
|
||||||
@@ -83,8 +84,8 @@ const styles = ({ palette }: Theme) => createStyles({
|
|||||||
controlContentSmall: {
|
controlContentSmall: {
|
||||||
flex: "0 0 162px", width: "100%", height: 162, overflowY: "hidden",
|
flex: "0 0 162px", width: "100%", height: 162, overflowY: "hidden",
|
||||||
},
|
},
|
||||||
title: { fontSize: "1.1em", fontWeight: 700, marginRight: 8 },
|
title: { fontSize: "1.1em", fontWeight: 700, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 },
|
||||||
author: { fontWeight: 500, marginRight: 8 }
|
author: { fontWeight: 500, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap",opacity: 0.75 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ interface MainState {
|
|||||||
horizontalScrollLayout: boolean;
|
horizontalScrollLayout: boolean;
|
||||||
showMidiBindingsDialog: boolean;
|
showMidiBindingsDialog: boolean;
|
||||||
screenHeight: number;
|
screenHeight: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ export const MainPage =
|
|||||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||||
showMidiBindingsDialog: false,
|
showMidiBindingsDialog: false,
|
||||||
screenHeight: this.windowSize.height
|
screenHeight: this.windowSize.height
|
||||||
|
|
||||||
};
|
};
|
||||||
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
||||||
this.onPedalDoubleClick = this.onPedalDoubleClick.bind(this);
|
this.onPedalDoubleClick = this.onPedalDoubleClick.bind(this);
|
||||||
@@ -193,16 +194,17 @@ export const MainPage =
|
|||||||
}
|
}
|
||||||
onPedalboardChanged(value: Pedalboard) {
|
onPedalboardChanged(value: Pedalboard) {
|
||||||
let selectedItem = -1;
|
let selectedItem = -1;
|
||||||
if (value.hasItem(this.state.selectedPedal))
|
if (this.state.selectedPedal == Pedalboard.START_CONTROL || this.state.selectedPedal == Pedalboard.END_CONTROL) {
|
||||||
{
|
selectedItem = this.state.selectedPedal;
|
||||||
|
} else if (value.hasItem(this.state.selectedPedal)) {
|
||||||
selectedItem = this.state.selectedPedal;
|
selectedItem = this.state.selectedPedal;
|
||||||
} else {
|
} else {
|
||||||
selectedItem = value.getFirstSelectableItem();
|
selectedItem = value.getFirstSelectableItem();
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
pedalboard: value,
|
pedalboard: value,
|
||||||
selectedPedal: selectedItem
|
selectedPedal: selectedItem
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onDeletePedal(instanceId: number): void {
|
onDeletePedal(instanceId: number): void {
|
||||||
let result = this.model.deletePedalboardPedal(instanceId);
|
let result = this.model.deletePedalboardPedal(instanceId);
|
||||||
@@ -274,6 +276,15 @@ export const MainPage =
|
|||||||
|
|
||||||
let pedalboard = this.model.pedalboard.get();
|
let pedalboard = this.model.pedalboard.get();
|
||||||
if (!pedalboard) return null;
|
if (!pedalboard) return null;
|
||||||
|
|
||||||
|
if (selectedId === Pedalboard.START_CONTROL) // synthetic input volume item.
|
||||||
|
{
|
||||||
|
return pedalboard.makeStartItem();
|
||||||
|
} else if (selectedId === Pedalboard.END_CONTROL) // synthetic output volume.
|
||||||
|
{
|
||||||
|
return pedalboard.makeEndItem();
|
||||||
|
}
|
||||||
|
|
||||||
let it = pedalboard.itemsGenerator();
|
let it = pedalboard.itemsGenerator();
|
||||||
if (!selectedId) return null;
|
if (!selectedId) return null;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -297,7 +308,7 @@ export const MainPage =
|
|||||||
}
|
}
|
||||||
getSelectedUri(): string {
|
getSelectedUri(): string {
|
||||||
let pedalboardItem = this.getSelectedPedalboardItem();
|
let pedalboardItem = this.getSelectedPedalboardItem();
|
||||||
if (pedalboardItem == null) return "";
|
if (pedalboardItem === null) return "";
|
||||||
return pedalboardItem.uri;
|
return pedalboardItem.uri;
|
||||||
}
|
}
|
||||||
titleBar(pedalboardItem: PedalboardItem | null): React.ReactNode {
|
titleBar(pedalboardItem: PedalboardItem | null): React.ReactNode {
|
||||||
@@ -311,7 +322,13 @@ export const MainPage =
|
|||||||
title = "";
|
title = "";
|
||||||
} else if (pedalboardItem.isSplit()) {
|
} else if (pedalboardItem.isSplit()) {
|
||||||
title = "Split";
|
title = "Split";
|
||||||
} else {
|
} else if (pedalboardItem.isSyntheticItem()) {
|
||||||
|
title = pedalboardItem.pluginName ?? "#error";
|
||||||
|
author = "";
|
||||||
|
presetsUri = "";
|
||||||
|
pluginUri = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
|
let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
|
||||||
if (!uiPlugin) {
|
if (!uiPlugin) {
|
||||||
missing = true;
|
missing = true;
|
||||||
@@ -327,46 +344,41 @@ export const MainPage =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
if (missing)
|
if (missing) {
|
||||||
{
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
flex: "1 0 auto", overflow: "hidden", marginRight: 8,
|
flex: "1 0 auto", overflow: "hidden", marginRight: 8, minWidth: 0,
|
||||||
display: "flex", flexDirection: "row", flexWrap: "nowrap",
|
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flex: "0 1 auto" }}>
|
<div style={{ flex: "0 1 auto",minWidth: 0 }}>
|
||||||
<span style={{ whiteSpace: "nowrap", color: "#800000" }}>
|
<span style={{ color: "#800000" }}>
|
||||||
<span className={classes.title}>{title}</span>
|
<span className={classes.title}>{title}</span>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
flex: "1 0 auto", overflow: "hidden", marginRight: 8,
|
flex: "1 1 auto", minWidth: 0,overflow: "hidden", marginRight: 8,
|
||||||
display: "flex", flexDirection: "row", flexWrap: "nowrap",
|
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flex: "0 1 auto" }}>
|
<div style={{ flex: "0 1 auto", minWidth: 0,overflow: "hidden",textOverflow: "ellipsis" }}>
|
||||||
<span style={{ whiteSpace: "nowrap" }}>
|
|
||||||
<span className={classes.title}>{title}</span>
|
<span className={classes.title}>{title}</span>
|
||||||
</span>
|
|
||||||
<span style={{ whiteSpace: "nowrap" }}>
|
|
||||||
<span className={classes.author}>{author}</span>
|
<span className={classes.author}>{author}</span>
|
||||||
</span>
|
</div>
|
||||||
|
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
|
||||||
|
<PluginInfoDialog plugin_uri={pluginUri} />
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
|
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
|
);
|
||||||
<PluginInfoDialog plugin_uri={pluginUri} />
|
|
||||||
</div>
|
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
|
||||||
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +391,9 @@ export const MainPage =
|
|||||||
let bypassVisible = false;
|
let bypassVisible = false;
|
||||||
let bypassChecked = false;
|
let bypassChecked = false;
|
||||||
let canDelete = false;
|
let canDelete = false;
|
||||||
let canAdd = false;
|
let canInsert = false;
|
||||||
|
let canAppend = false;
|
||||||
|
let canLoad = true;
|
||||||
let instanceId = -1;
|
let instanceId = -1;
|
||||||
let missing = false;
|
let missing = false;
|
||||||
let pluginUri = "#error";
|
let pluginUri = "#error";
|
||||||
@@ -388,13 +402,26 @@ export const MainPage =
|
|||||||
canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId);
|
canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId);
|
||||||
instanceId = pedalboardItem.instanceId;
|
instanceId = pedalboardItem.instanceId;
|
||||||
if (pedalboardItem.isEmpty()) {
|
if (pedalboardItem.isEmpty()) {
|
||||||
canAdd = true;
|
canInsert = true;
|
||||||
|
canAppend = true;
|
||||||
|
|
||||||
|
} else if (pedalboardItem.isStart()) {
|
||||||
|
canAppend = true;
|
||||||
|
canDelete = false;
|
||||||
|
canLoad = false;
|
||||||
|
} else if (pedalboardItem.isEnd()) {
|
||||||
|
canInsert = true;
|
||||||
|
canDelete = false;
|
||||||
|
canLoad = false;
|
||||||
} else if (pedalboardItem.isSplit()) {
|
} else if (pedalboardItem.isSplit()) {
|
||||||
canAdd = true;
|
canInsert = true;
|
||||||
|
canAppend = true;
|
||||||
|
canLoad = false;
|
||||||
} else {
|
} else {
|
||||||
pluginUri = pedalboardItem.uri;
|
pluginUri = pedalboardItem.uri;
|
||||||
uiPlugin = this.model.getUiPlugin(pluginUri);
|
uiPlugin = this.model.getUiPlugin(pluginUri);
|
||||||
canAdd = true;
|
canInsert = true;
|
||||||
|
canAppend = true;
|
||||||
if (uiPlugin) {
|
if (uiPlugin) {
|
||||||
bypassVisible = true;
|
bypassVisible = true;
|
||||||
bypassChecked = pedalboardItem.isEnabled;
|
bypassChecked = pedalboardItem.isEnabled;
|
||||||
@@ -418,7 +445,7 @@ export const MainPage =
|
|||||||
<div className={classes.separator} />
|
<div className={classes.separator} />
|
||||||
<div className={classes.controlToolBar}>
|
<div className={classes.controlToolBar}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: "flex", flexFlow: "row nowrap", alignItems: "center", justifyContent: "center",
|
display: "flex", flexFlow: "row nowrap", alignItems: "center", justifyContent: "center", minWidth: 0,
|
||||||
width: "100%", height: 48, paddingLeft: 16, paddingRight: 16
|
width: "100%", height: 48, paddingLeft: 16, paddingRight: 16
|
||||||
}} >
|
}} >
|
||||||
<div style={{ flex: "0 0 auto", width: 80 }} >
|
<div style={{ flex: "0 0 auto", width: 80 }} >
|
||||||
@@ -432,7 +459,7 @@ export const MainPage =
|
|||||||
<div style={{ flex: "1 1 1px" }}>
|
<div style={{ flex: "1 1 1px" }}>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 auto", display: canAdd ? "block" : "none", paddingRight: 8 }}>
|
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}>
|
||||||
<IconButton onClick={(e) => { this.onAddClick(e) }} size="large">
|
<IconButton onClick={(e) => { this.onAddClick(e) }} size="large">
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -444,11 +471,11 @@ export const MainPage =
|
|||||||
onClose={() => this.handleAddClose()}
|
onClose={() => this.handleAddClose()}
|
||||||
TransitionComponent={Fade}
|
TransitionComponent={Fade}
|
||||||
>
|
>
|
||||||
<MenuItem onClick={() => this.onInsertPedal(instanceId)}>Insert pedal</MenuItem>
|
{canInsert && (<MenuItem onClick={() => this.onInsertPedal(instanceId)}>Insert pedal</MenuItem>)}
|
||||||
<MenuItem onClick={() => this.onAppendPedal(instanceId)}>Append pedal</MenuItem>
|
{canAppend && (<MenuItem onClick={() => this.onAppendPedal(instanceId)}>Append pedal</MenuItem>)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<MenuItem onClick={() => this.onInsertSplit(instanceId)}>Insert split</MenuItem>
|
{canInsert && (<MenuItem onClick={() => this.onInsertSplit(instanceId)}>Insert split</MenuItem>)}
|
||||||
<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>
|
{canAppend && (<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
|
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
|
||||||
@@ -464,9 +491,9 @@ export const MainPage =
|
|||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={this.onLoadClick}
|
onClick={this.onLoadClick}
|
||||||
disabled={this.state.selectedPedal === -1 || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
disabled={this.state.selectedPedal === -1 || (!canLoad) || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
||||||
startIcon={<InputIcon />}
|
startIcon={<InputIcon />}
|
||||||
style={{ borderRadius: 24, paddingLeft: 18, paddingRight: 18, textTransform: "none"}}
|
style={{ textTransform: "none" }}
|
||||||
>
|
>
|
||||||
Load
|
Load
|
||||||
</Button>
|
</Button>
|
||||||
@@ -493,19 +520,19 @@ export const MainPage =
|
|||||||
<div className={horizontalScrollLayout ? classes.controlContentSmall : classes.controlContent}>
|
<div className={horizontalScrollLayout ? classes.controlContentSmall : classes.controlContent}>
|
||||||
{
|
{
|
||||||
missing ? (
|
missing ? (
|
||||||
<div style={{marginLeft: 100,marginTop: 20}}>
|
<div style={{ marginLeft: 100, marginTop: 20 }}>
|
||||||
<Typography variant="body1" paragraph={true}>Error: Plugin is not installed.</Typography>
|
<Typography variant="body1" paragraph={true}>Error: Plugin is not installed.</Typography>
|
||||||
<Typography noWrap variant="body2" paragraph={true}>{pluginUri}</Typography>
|
<Typography noWrap variant="body2" paragraph={true}>{pluginUri}</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
):
|
) :
|
||||||
(
|
(
|
||||||
GetControlView(pedalboardItem)
|
GetControlView(pedalboardItem)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<MidiBindingsDialog open={this.state.showMidiBindingsDialog}
|
<MidiBindingsDialog open={this.state.showMidiBindingsDialog}
|
||||||
onClose={()=> this.setState({showMidiBindingsDialog: false} ) }
|
onClose={() => this.setState({ showMidiBindingsDialog: false })}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
(this.state.loadDialogOpen) && (
|
(this.state.loadDialogOpen) && (
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
import { createSvgIcon } from '@mui/material/utils';
|
||||||
|
|
||||||
|
const OldDeleteIcon = createSvgIcon(
|
||||||
|
<path d="M20,4h-4.5l-1-1h-5l-1,1H4v2h1v13c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V6h1V4z M17,19H7V6h10V19z M9 8h2v8h-2z M13,8h2v8h-2z"/>,
|
||||||
|
"Delete",
|
||||||
|
);
|
||||||
|
|
||||||
|
export default OldDeleteIcon;
|
||||||
@@ -24,7 +24,6 @@ import MidiBinding from './MidiBinding';
|
|||||||
const SPLIT_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Split";
|
const SPLIT_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Split";
|
||||||
const EMPTY_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Empty";
|
const EMPTY_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Empty";
|
||||||
|
|
||||||
|
|
||||||
interface Deserializable<T> {
|
interface Deserializable<T> {
|
||||||
deserialize(input: any): T;
|
deserialize(input: any): T;
|
||||||
}
|
}
|
||||||
@@ -92,6 +91,10 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSyntheticItem(): boolean {
|
||||||
|
return this.instanceId === Pedalboard.START_CONTROL
|
||||||
|
|| this.instanceId === Pedalboard.END_CONTROL;
|
||||||
|
}
|
||||||
getInstanceId() : number {
|
getInstanceId() : number {
|
||||||
if (this.instanceId === undefined)
|
if (this.instanceId === undefined)
|
||||||
{
|
{
|
||||||
@@ -105,6 +108,12 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
|||||||
isSplit(): boolean {
|
isSplit(): boolean {
|
||||||
return this.uri === SPLIT_PEDALBOARD_ITEM_URI;
|
return this.uri === SPLIT_PEDALBOARD_ITEM_URI;
|
||||||
}
|
}
|
||||||
|
isStart(): boolean {
|
||||||
|
return this.uri === Pedalboard.START_PEDALBOARD_ITEM_URI;
|
||||||
|
}
|
||||||
|
isEnd(): boolean {
|
||||||
|
return this.uri === Pedalboard.END_PEDALBOARD_ITEM_URI;
|
||||||
|
}
|
||||||
|
|
||||||
getControl(key: string): ControlValue {
|
getControl(key: string): ControlValue {
|
||||||
for (let i = 0; i < this.controlValues.length; ++i) {
|
for (let i = 0; i < this.controlValues.length; ++i) {
|
||||||
@@ -186,7 +195,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
|||||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||||
midiBindings: MidiBinding[] = [];
|
midiBindings: MidiBinding[] = [];
|
||||||
vstState: string = "";
|
vstState: string = "";
|
||||||
lv2State: any = {};
|
lv2State: [boolean,any] = [false,{}]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -253,8 +262,15 @@ export class PedalboardSplitItem extends PedalboardItem {
|
|||||||
|
|
||||||
export class Pedalboard implements Deserializable<Pedalboard> {
|
export class Pedalboard implements Deserializable<Pedalboard> {
|
||||||
|
|
||||||
|
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
|
||||||
|
static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume.
|
||||||
|
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
||||||
|
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
||||||
|
|
||||||
deserialize(input: any): Pedalboard {
|
deserialize(input: any): Pedalboard {
|
||||||
this.name = input.name;
|
this.name = input.name;
|
||||||
|
this.input_volume_db = input.input_volume_db;
|
||||||
|
this.output_volume_db = input.output_volume_db;
|
||||||
this.items = PedalboardItem.deserializeArray(input.items);
|
this.items = PedalboardItem.deserializeArray(input.items);
|
||||||
this.nextInstanceId = input.nextInstanceId ?? -1;
|
this.nextInstanceId = input.nextInstanceId ?? -1;
|
||||||
return this;
|
return this;
|
||||||
@@ -264,6 +280,8 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
return new Pedalboard().deserialize(this);
|
return new Pedalboard().deserialize(this);
|
||||||
}
|
}
|
||||||
name: string = "";
|
name: string = "";
|
||||||
|
input_volume_db: number = 0;
|
||||||
|
output_volume_db: number = 0;
|
||||||
items: PedalboardItem[] = [];
|
items: PedalboardItem[] = [];
|
||||||
nextInstanceId: number = -1;
|
nextInstanceId: number = -1;
|
||||||
|
|
||||||
@@ -316,6 +334,27 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
makeStartItem(): PedalboardItem {
|
||||||
|
let result = new PedalboardItem();
|
||||||
|
result.pluginName = "Input";
|
||||||
|
result.instanceId = Pedalboard.START_CONTROL;
|
||||||
|
result.uri = Pedalboard.START_PEDALBOARD_ITEM_URI;
|
||||||
|
result.isEnabled = true;
|
||||||
|
result.controlValues = [new ControlValue("volume_db",this.input_volume_db)];
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
makeEndItem(): PedalboardItem {
|
||||||
|
let result = new PedalboardItem();
|
||||||
|
result.pluginName = "Output";
|
||||||
|
result.instanceId = Pedalboard.END_CONTROL;
|
||||||
|
result.uri = Pedalboard.END_PEDALBOARD_ITEM_URI;
|
||||||
|
result.isEnabled = true;
|
||||||
|
result.controlValues = [new ControlValue("volume_db",this.output_volume_db)];
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
getItem(instanceId: number): PedalboardItem {
|
getItem(instanceId: number): PedalboardItem {
|
||||||
let it = this.itemsGenerator();
|
let it = this.itemsGenerator();
|
||||||
while (true)
|
while (true)
|
||||||
@@ -561,7 +600,7 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
throw new PiPedalArgumentError("instanceId not found.");
|
throw new PiPedalArgumentError("instanceId not found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean)
|
private _addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean)
|
||||||
{
|
{
|
||||||
for (let i = 0; i < items.length; ++i)
|
for (let i = 0; i < items.length; ++i)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,14 +33,16 @@ import Draggable from './Draggable'
|
|||||||
import Rect from './Rect';
|
import Rect from './Rect';
|
||||||
import {PiPedalStateError} from './PiPedalError';
|
import {PiPedalStateError} from './PiPedalError';
|
||||||
import Utility from './Utility'
|
import Utility from './Utility'
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType,
|
Pedalboard, PedalboardItem, PedalboardSplitItem, SplitType
|
||||||
} from './Pedalboard';
|
} from './Pedalboard';
|
||||||
|
|
||||||
const START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
|
const START_CONTROL = Pedalboard.START_CONTROL;
|
||||||
const END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
|
const END_CONTROL = Pedalboard.END_CONTROL;
|
||||||
|
|
||||||
|
|
||||||
|
const START_PEDALBOARD_ITEM_URI = Pedalboard.START_PEDALBOARD_ITEM_URI;
|
||||||
|
const END_PEDALBOARD_ITEM_URI = Pedalboard.END_PEDALBOARD_ITEM_URI;
|
||||||
|
|
||||||
const ENABLED_CONNECTOR_COLOR = "#666";
|
const ENABLED_CONNECTOR_COLOR = "#666";
|
||||||
const DISABLED_CONNECTOR_COLOR = "#CCC";
|
const DISABLED_CONNECTOR_COLOR = "#CCC";
|
||||||
@@ -77,26 +79,6 @@ const pedalboardStyles = (theme: Theme) => createStyles({
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
|
|
||||||
},
|
|
||||||
startItem: {
|
|
||||||
position: "absolute",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
|
|
||||||
width: CELL_WIDTH,
|
|
||||||
height: CELL_HEIGHT
|
|
||||||
|
|
||||||
},
|
|
||||||
endItem: {
|
|
||||||
position: "absolute",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
|
|
||||||
width: CELL_WIDTH,
|
|
||||||
height: CELL_HEIGHT
|
|
||||||
|
|
||||||
},
|
},
|
||||||
splitItem: {
|
splitItem: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -125,7 +107,13 @@ const pedalboardStyles = (theme: Theme) => createStyles({
|
|||||||
right: 0,
|
right: 0,
|
||||||
top: 0
|
top: 0
|
||||||
},
|
},
|
||||||
|
buttonDraggable: {
|
||||||
|
display: "flex",
|
||||||
|
alignItem: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%"
|
||||||
|
},
|
||||||
pedalItem: {
|
pedalItem: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
width: CELL_WIDTH,
|
width: CELL_WIDTH,
|
||||||
@@ -150,6 +138,23 @@ const pedalboardStyles = (theme: Theme) => createStyles({
|
|||||||
border: "1pt #666 solid",
|
border: "1pt #666 solid",
|
||||||
borderRadius: 6
|
borderRadius: 6
|
||||||
},
|
},
|
||||||
|
borderlessIconFrame: {
|
||||||
|
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
|
||||||
|
background: "transparent",
|
||||||
|
marginLeft: (CELL_WIDTH - FRAME_SIZE) / 2,
|
||||||
|
marginRight: (CELL_WIDTH - FRAME_SIZE) / 2,
|
||||||
|
marginTop: (CELL_HEIGHT - FRAME_SIZE) / 2,
|
||||||
|
marginBottom: (CELL_HEIGHT - FRAME_SIZE) / 2,
|
||||||
|
width: FRAME_SIZE,
|
||||||
|
height: FRAME_SIZE,
|
||||||
|
border: "0pt #666 solid",
|
||||||
|
borderRadius: 6
|
||||||
|
},
|
||||||
|
|
||||||
pedalIcon: {
|
pedalIcon: {
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
@@ -632,7 +637,7 @@ const PedalboardView =
|
|||||||
if (bounds.height < TWO_ROW_HEIGHT) {
|
if (bounds.height < TWO_ROW_HEIGHT) {
|
||||||
|
|
||||||
let extra = Math.floor((TWO_ROW_HEIGHT - Math.ceil(bounds.height))/2);
|
let extra = Math.floor((TWO_ROW_HEIGHT - Math.ceil(bounds.height))/2);
|
||||||
this.offsetLayout_(layoutItems, Math.floor(-bounds.y + extra ));
|
this.offsetLayout_(layoutItems, Math.floor(-bounds.y + extra/2 ));
|
||||||
bounds.height += extra;
|
bounds.height += extra;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -677,7 +682,6 @@ const PedalboardView =
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX set keys on output objects !!
|
|
||||||
renderConnector(output: ReactNode[], item: PedalLayout, enabled: boolean): void {
|
renderConnector(output: ReactNode[], item: PedalLayout, enabled: boolean): void {
|
||||||
// let classes = this.props.classes;
|
// let classes = this.props.classes;
|
||||||
let x_ = item.bounds.x + CELL_WIDTH / 2;
|
let x_ = item.bounds.x + CELL_WIDTH / 2;
|
||||||
@@ -688,14 +692,14 @@ const PedalboardView =
|
|||||||
|
|
||||||
if (numberOfOutputs === 2) {
|
if (numberOfOutputs === 2) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={svgPath} stroke={color} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={svgPath} stroke={color} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
output.push((
|
output.push((
|
||||||
<path d={svgPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={svgPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
} else if (numberOfOutputs === 1) {
|
} else if (numberOfOutputs === 1) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={svgPath} stroke={color} strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={svgPath} stroke={color} strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -718,18 +722,18 @@ const PedalboardView =
|
|||||||
let bottomStartPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_, yBottom).lineTo(x_ + CELL_WIDTH, yBottom).toString();
|
let bottomStartPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_, yBottom).lineTo(x_ + CELL_WIDTH, yBottom).toString();
|
||||||
|
|
||||||
if (item.numberOfInputs === 2 && item.topChildren[0].numberOfInputs === 2) {
|
if (item.numberOfInputs === 2 && item.topChildren[0].numberOfInputs === 2) {
|
||||||
output.push((<path d={topStartPath} stroke={topColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />));
|
output.push((<path key={this.renderKey++} d={topStartPath} stroke={topColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />));
|
||||||
output.push((<path d={topStartPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />));
|
output.push((<path key={this.renderKey++} d={topStartPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />));
|
||||||
} else if (item.numberOfInputs !== 0 && item.topChildren[0].numberOfInputs !== 0) {
|
} else if (item.numberOfInputs !== 0 && item.topChildren[0].numberOfInputs !== 0) {
|
||||||
output.push((<path d={topStartPath} stroke={topColor} strokeWidth={SVG_STROKE_WIDTH} />));
|
output.push((<path key={this.renderKey++} d={topStartPath} stroke={topColor} strokeWidth={SVG_STROKE_WIDTH} />));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.numberOfInputs === 2 && item.bottomChildren[0].numberOfInputs === 2) {
|
if (item.numberOfInputs === 2 && item.bottomChildren[0].numberOfInputs === 2) {
|
||||||
output.push((<path d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />));
|
output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />));
|
||||||
output.push((<path d={bottomStartPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />));
|
output.push((<path key={this.renderKey++} d={bottomStartPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />));
|
||||||
|
|
||||||
} else if (item.numberOfInputs !== 0 && item.bottomChildren[0].numberOfInputs !== 0) {
|
} else if (item.numberOfInputs !== 0 && item.bottomChildren[0].numberOfInputs !== 0) {
|
||||||
output.push((<path d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STROKE_WIDTH} />));
|
output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STROKE_WIDTH} />));
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastTop = item.topChildren[item.topChildren.length - 1];
|
let lastTop = item.topChildren[item.topChildren.length - 1];
|
||||||
@@ -813,26 +817,26 @@ const PedalboardView =
|
|||||||
// display stereo strokes with cutoff line.
|
// display stereo strokes with cutoff line.
|
||||||
if (firstPathStereo) {
|
if (firstPathStereo) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
output.push((
|
output.push((
|
||||||
<path d={firstPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={firstPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
} else if (!firstPathAbsent) {
|
} else if (!firstPathAbsent) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (secondPathStereo) {
|
if (secondPathStereo) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
output.push((
|
output.push((
|
||||||
<path d={secondPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={secondPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
} else if (!secondPathAbsent) {
|
} else if (!secondPathAbsent) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,32 +844,32 @@ const PedalboardView =
|
|||||||
// stereo strokes merge.
|
// stereo strokes merge.
|
||||||
if (firstPathStereo) {
|
if (firstPathStereo) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
} else if (!firstPathAbsent) {
|
} else if (!firstPathAbsent) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (secondPathStereo) {
|
if (secondPathStereo) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
} else if (!secondPathAbsent) {
|
} else if (!secondPathAbsent) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw stereo inner lines.
|
// draw stereo inner lines.
|
||||||
if (firstPathStereo) {
|
if (firstPathStereo) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={firstPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={firstPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (secondPathStereo) {
|
if (secondPathStereo) {
|
||||||
output.push((
|
output.push((
|
||||||
<path d={secondPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={secondPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -874,10 +878,10 @@ const PedalboardView =
|
|||||||
{
|
{
|
||||||
// stereo output of L/R splitter
|
// stereo output of L/R splitter
|
||||||
output.push((
|
output.push((
|
||||||
<path d={thirdPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={thirdPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
output.push((
|
output.push((
|
||||||
<path d={thirdPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
<path key={this.renderKey++} d={thirdPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
@@ -898,25 +902,24 @@ const PedalboardView =
|
|||||||
throw new PiPedalStateError("scroll container not found.");
|
throw new PiPedalStateError("scroll container not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
pedalButton(instanceId: number, iconUrl: string, draggable: boolean, enabled: boolean): ReactNode {
|
pedalButton(instanceId: number, iconUrl: string, draggable: boolean, enabled: boolean,hasBorder: boolean = true): ReactNode {
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
return (
|
return (
|
||||||
<div className={classes.iconFrame} onContextMenu={(e) => { e.preventDefault(); }}>
|
<div className={hasBorder? classes.iconFrame : classes.borderlessIconFrame} onContextMenu={(e) => { e.preventDefault(); }}>
|
||||||
|
|
||||||
<ButtonBase style={{ width: "100%", height: "100%" }}
|
<ButtonBase style={{ width: "100%", height: "100%" }}
|
||||||
onClick={(e) => { this.onItemClick(e, instanceId); }}
|
onClick={(e) => { this.onItemClick(e, instanceId); }}
|
||||||
onDoubleClick={(e: SyntheticEvent) => { this.onItemDoubleClick(e, instanceId); }}
|
onDoubleClick={(e: SyntheticEvent) => { this.onItemDoubleClick(e, instanceId); }}
|
||||||
onContextMenu={(e: SyntheticEvent) => { this.onItemLongClick(e, instanceId); }}
|
onContextMenu={(e: SyntheticEvent) => { this.onItemLongClick(e, instanceId); }}
|
||||||
>
|
>
|
||||||
<SelectHoverBackground selected={instanceId === this.props.selectedId} showHover={true} />
|
<SelectHoverBackground selected={instanceId === this.props.selectedId} showHover={true} borderRadius={6} >
|
||||||
<Draggable draggable={draggable} getScrollContainer={() => this.getScrollContainer()}
|
<Draggable draggable={draggable} getScrollContainer={() => this.getScrollContainer()}
|
||||||
onDragEnd={(x, y) => { this.onDragEnd(instanceId, x, y) }}
|
onDragEnd={(x, y) => { this.onDragEnd(instanceId, x, y) }}
|
||||||
>
|
>
|
||||||
<div style={{ width: "100%", height: "100%" }}>
|
|
||||||
<img src={iconUrl} className={classes.pedalIcon} alt="Pedal" draggable={false}
|
<img src={iconUrl} className={classes.pedalIcon} alt="Pedal" draggable={false}
|
||||||
style={{opacity: enabled? 0.99: 0.6}} />
|
style={{opacity: enabled? 0.99: 0.6}} />
|
||||||
</div>
|
</Draggable>
|
||||||
</Draggable>
|
</SelectHoverBackground>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -945,6 +948,7 @@ const PedalboardView =
|
|||||||
let outputs: ReactNode[] = [];
|
let outputs: ReactNode[] = [];
|
||||||
this.renderConnectors(outputs, layoutChain, true, false);
|
this.renderConnectors(outputs, layoutChain, true, false);
|
||||||
return (
|
return (
|
||||||
|
<div style={{width: layoutSize.width, height: layoutSize.height, overflow: "hidden"}}>
|
||||||
<svg width={layoutSize.width} height={layoutSize.height}
|
<svg width={layoutSize.width} height={layoutSize.height}
|
||||||
xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}>
|
xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}>
|
||||||
<g fill="none">
|
<g fill="none">
|
||||||
@@ -954,6 +958,7 @@ const PedalboardView =
|
|||||||
</g>
|
</g>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -973,19 +978,25 @@ const PedalboardView =
|
|||||||
let item = v.value;
|
let item = v.value;
|
||||||
switch (item.uri) {
|
switch (item.uri) {
|
||||||
case START_PEDALBOARD_ITEM_URI:
|
case START_PEDALBOARD_ITEM_URI:
|
||||||
result.push(<div className={classes.startItem} style={{ left: item.bounds.x, top: item.bounds.y }} >
|
result.push(<div key={this.renderKey++} className={classes.splitItem} style={{ left: item.bounds.x, top: item.bounds.y, width: item.bounds.width }} >
|
||||||
<img src={item.iconUrl} className={classes.pedalIcon} alt="" draggable={false} />
|
<div className={classes.splitStart} >
|
||||||
|
|
||||||
|
{this.pedalButton(START_CONTROL, item.iconUrl, false,true,false)}
|
||||||
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
break;
|
break;
|
||||||
case END_PEDALBOARD_ITEM_URI:
|
case END_PEDALBOARD_ITEM_URI:
|
||||||
result.push(<div className={classes.endItem} style={{ left: item.bounds.x, top: item.bounds.y }} >
|
result.push(<div key={this.renderKey++} className={classes.splitItem} style={{ left: item.bounds.x, top: item.bounds.y, width: item.bounds.width }} >
|
||||||
<img src={item.iconUrl} className={classes.pedalIcon} alt="" draggable={false} />
|
<div className={classes.splitStart} >
|
||||||
|
|
||||||
|
{this.pedalButton(END_CONTROL, item.iconUrl, false,true,false)}
|
||||||
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (item.isSplitter()) {
|
if (item.isSplitter()) {
|
||||||
|
|
||||||
result.push(<div className={classes.splitItem} style={{ left: item.bounds.x, top: item.bounds.y, width: item.bounds.width }} >
|
result.push(<div key={this.renderKey++} className={classes.splitItem} style={{ left: item.bounds.x, top: item.bounds.y, width: item.bounds.width }} >
|
||||||
<div className={classes.splitStart} >
|
<div className={classes.splitStart} >
|
||||||
{this.pedalButton(item.pedalItem?.instanceId ?? -1, this.getSplitterIcon(item), false,true)}
|
{this.pedalButton(item.pedalItem?.instanceId ?? -1, this.getSplitterIcon(item), false,true)}
|
||||||
</div>
|
</div>
|
||||||
@@ -993,7 +1004,7 @@ const PedalboardView =
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
result.push(
|
result.push(
|
||||||
<div style={{
|
<div key={this.renderKey++} style={{
|
||||||
display: "flex", justifyContent: "flex-start", alignItems: "flex-start",
|
display: "flex", justifyContent: "flex-start", alignItems: "flex-start",
|
||||||
position: "absolute", left: item.bounds.x, width: CELL_WIDTH, top: item.bounds.bottom - 12, paddingLeft: 2, paddingRight: 2
|
position: "absolute", left: item.bounds.x, width: CELL_WIDTH, top: item.bounds.bottom - 12, paddingLeft: 2, paddingRight: 2
|
||||||
}}>
|
}}>
|
||||||
@@ -1002,7 +1013,7 @@ const PedalboardView =
|
|||||||
>{item.pedalItem?.pluginName}</Typography>
|
>{item.pedalItem?.pluginName}</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
result.push(<div className={classes.pedalItem} style={{ left: item.bounds.x, top: item.bounds.y }} >
|
result.push(<div key={this.renderKey++} className={classes.pedalItem} style={{ left: item.bounds.x, top: item.bounds.y }} >
|
||||||
{this.pedalButton(item.pedalItem?.instanceId ?? -1, item.iconUrl, !item.isEmpty(), item.pedalItem?.isEnabled ?? false)}
|
{this.pedalButton(item.pedalItem?.instanceId ?? -1, item.iconUrl, !item.isEmpty(), item.pedalItem?.isEnabled ?? false)}
|
||||||
|
|
||||||
</div>);
|
</div>);
|
||||||
@@ -1151,10 +1162,10 @@ const PedalboardView =
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentLayout?: PedalLayout[];
|
currentLayout?: PedalLayout[];
|
||||||
|
private renderKey: number = 0;
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
|
this.renderKey = 0;
|
||||||
let layoutChain = makeChain(this.model, this.state.pedalboard?.items);
|
let layoutChain = makeChain(this.model, this.state.pedalboard?.items);
|
||||||
let start = PedalLayout.Start();
|
let start = PedalLayout.Start();
|
||||||
let end = PedalLayout.End();
|
let end = PedalLayout.End();
|
||||||
|
|||||||
+182
-151
@@ -38,6 +38,7 @@ import GovernorSettings from './GovernorSettings';
|
|||||||
import WifiChannel from './WifiChannel';
|
import WifiChannel from './WifiChannel';
|
||||||
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
||||||
import { AndroidHostInterface, FakeAndroidHost } from './AndroidHost';
|
import { AndroidHostInterface, FakeAndroidHost } from './AndroidHost';
|
||||||
|
import Xxx from './XxxSnippet';
|
||||||
|
|
||||||
|
|
||||||
export enum State {
|
export enum State {
|
||||||
@@ -338,153 +339,6 @@ export interface FavoritesList {
|
|||||||
[url: string]: boolean;
|
[url: string]: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export interface PiPedalModel {
|
|
||||||
// clientId: number;
|
|
||||||
// countryCodes: Object;
|
|
||||||
// debug: boolean;
|
|
||||||
// serverVersion?: PiPedalVersion;
|
|
||||||
// errorMessage: ObservableProperty<string>;
|
|
||||||
// alertMessage: ObservableProperty<string>;
|
|
||||||
// showStatusMonitor: ObservableProperty<boolean>;
|
|
||||||
// setShowStatusMonitor(show: boolean): void;
|
|
||||||
// state: ObservableProperty<State>;
|
|
||||||
// visibilityState: ObservableProperty<VisibilityState>;
|
|
||||||
|
|
||||||
// ui_plugins: ObservableProperty<UiPlugin[]>;
|
|
||||||
// plugin_classes: ObservableProperty<PluginClass>;
|
|
||||||
// jackConfiguration: ObservableProperty<JackConfiguration>;
|
|
||||||
// jackSettings: ObservableProperty<JackChannelSelection>;
|
|
||||||
// banks: ObservableProperty<BankIndex>;
|
|
||||||
// jackServerSettings: ObservableProperty<JackServerSettings>;
|
|
||||||
// wifiConfigSettings: ObservableProperty<WifiConfigSettings>;
|
|
||||||
// wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings>;
|
|
||||||
// governorSettings: ObservableProperty<GovernorSettings>;
|
|
||||||
// favorites: ObservableProperty<FavoritesList>;
|
|
||||||
|
|
||||||
// presets: ObservableProperty<PresetIndex>;
|
|
||||||
// systemMidiBindings: ObservableProperty<MidiBinding[]>;
|
|
||||||
|
|
||||||
|
|
||||||
// zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined>;
|
|
||||||
|
|
||||||
// setJackServerSettings(jackServerSettings: JackServerSettings): void;
|
|
||||||
// setMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
|
|
||||||
|
|
||||||
|
|
||||||
// getUiPlugin(uri: string): UiPlugin | null;
|
|
||||||
|
|
||||||
// pedalboard: ObservableProperty<Pedalboard>;
|
|
||||||
// loadPedalboardPlugin(itemId: number, selectedUri: string): number;
|
|
||||||
|
|
||||||
// setPedalboardControl(instanceId: number, key: string, value: number): void;
|
|
||||||
// setPedalboardItemEnabled(instanceId: number, value: boolean): void;
|
|
||||||
// previewPedalboardValue(instanceId: number, key: string, value: number): void;
|
|
||||||
// deletePedalboardPedal(instanceId: number): number | null;
|
|
||||||
|
|
||||||
// movePedalboardItem(fromInstanceId: number, toInstanceId: number): void;
|
|
||||||
// movePedalboardItemToStart(fromInstanceId: number): void;
|
|
||||||
// movePedalboardItemToEnd(fromInstanceId: number): void;
|
|
||||||
// movePedalboardItemBefore(fromInstanceId: number, toInstanceId: number): void;
|
|
||||||
// movePedalboardItemAfter(fromInstanceId: number, toInstanceId: number): void;
|
|
||||||
|
|
||||||
// addPedalboardItem(instanceId: number, append: boolean): number;
|
|
||||||
// addPedalboardSplitItem(instanceId: number, append: boolean): number;
|
|
||||||
// setPedalboardItemEmpty(instanceId: number): number;
|
|
||||||
|
|
||||||
// saveCurrentPreset(): void;
|
|
||||||
// saveCurrentPresetAs(newName: string): void;
|
|
||||||
|
|
||||||
// saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): void;
|
|
||||||
|
|
||||||
// loadPreset(instanceId: number): void;
|
|
||||||
// updatePresets(presets: PresetIndex): Promise<void>;
|
|
||||||
// deletePresetItem(instanceId: number): Promise<number>;
|
|
||||||
// renamePresetItem(instanceId: number, name: string): Promise<void>;
|
|
||||||
// copyPreset(fromId: number, toId: number): Promise<number>;
|
|
||||||
// duplicatePreset(instanceId: number): Promise<number>;
|
|
||||||
|
|
||||||
// moveBank(from: number, to: number): Promise<void>;
|
|
||||||
// deleteBankItem(instanceId: number): Promise<number>;
|
|
||||||
|
|
||||||
|
|
||||||
// showAlert(message: string): void;
|
|
||||||
|
|
||||||
// setJackSettings(jackSettings: JackChannelSelection): void;
|
|
||||||
|
|
||||||
// svgImgUrl(svgImage: string): string;
|
|
||||||
|
|
||||||
// removeVuSubscription(handle: VuSubscriptionHandle): void;
|
|
||||||
// addVuSubscription(instanceId: number, vuChangedHandler: VuChangedHandler): VuSubscriptionHandle;
|
|
||||||
|
|
||||||
// monitorPort(instanceid: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle;
|
|
||||||
// unmonitorPort(handle: MonitorPortHandle): void;
|
|
||||||
|
|
||||||
// getPatchProperty<Type = any>(instanceId: number, uri: string): Promise<Type>;
|
|
||||||
// setPatchProperty(instanceId: number, uri: string, value: any): Promise<boolean>;
|
|
||||||
|
|
||||||
// renameBank(instanceId: number, newName: string): Promise<void>;
|
|
||||||
// saveBankAs(instanceId: number, newName: string): Promise<number>;
|
|
||||||
// openBank(bankId: number): Promise<void>;
|
|
||||||
|
|
||||||
// getJackStatus(): Promise<JackHostStatus>;
|
|
||||||
|
|
||||||
// getPluginPresets(uri: string): Promise<PluginUiPresets>;
|
|
||||||
// loadPluginPreset(pluginInstanceId: number, presetInstanceId: number): void;
|
|
||||||
// updatePluginPresets(uri: string, presets: PluginUiPresets): Promise<void>;
|
|
||||||
// duplicatePluginPreset(uri: string, instanceId: number): Promise<number>;
|
|
||||||
|
|
||||||
// setSystemMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
|
|
||||||
|
|
||||||
// shutdown(): Promise<void>;
|
|
||||||
// restart(): Promise<void>;
|
|
||||||
|
|
||||||
// listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle;
|
|
||||||
// cancelListenForMidiEvent(listenHandle: ListenHandle): void;
|
|
||||||
|
|
||||||
// addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle
|
|
||||||
// removeControlValueChangeListener(handle: ControlValueChangedHandle): void;
|
|
||||||
|
|
||||||
// addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler): PluginPresetsChangedHandle;
|
|
||||||
// removePluginPresetsChangedListener(handle: PluginPresetsChangedHandle): void;
|
|
||||||
|
|
||||||
// listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle;
|
|
||||||
// cancelListenForAtomOutput(listenHandle: ListenHandle): void;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// download(targetType: string, isntanceId: number | string): void;
|
|
||||||
|
|
||||||
// uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise<number>;
|
|
||||||
// uploadBank(file: File, uploadAfter: number): Promise<number>;
|
|
||||||
|
|
||||||
// setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise<void>;
|
|
||||||
// setWifiDirectConfigSettings(wifiDirectConfigSettings: WifiDirectConfigSettings): Promise<void>;
|
|
||||||
// setGovernorSettings(governor: string): Promise<void>;
|
|
||||||
|
|
||||||
// getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
|
||||||
|
|
||||||
// getAlsaDevices(): Promise<AlsaDeviceInfo[]>;
|
|
||||||
|
|
||||||
// zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void;
|
|
||||||
|
|
||||||
// onPreviousZoomedControl(): void;
|
|
||||||
// onNextZoomedControl(): void;
|
|
||||||
// clearZoomedControl(): void;
|
|
||||||
|
|
||||||
// setFavorite(pluginUrl: string, isFavorite: boolean): void;
|
|
||||||
|
|
||||||
// isAndroidHosted(): boolean;
|
|
||||||
// showAndroidDonationActivity(): void;
|
|
||||||
// getAndroidHostVersion(): string;
|
|
||||||
// chooseNewDevice(): void;
|
|
||||||
|
|
||||||
// isOnboarding(): boolean;
|
|
||||||
// setOnboarding(value: boolean): void;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise<string[]>;
|
|
||||||
// };
|
|
||||||
|
|
||||||
export class PiPedalModel //implements PiPedalModel
|
export class PiPedalModel //implements PiPedalModel
|
||||||
{
|
{
|
||||||
@@ -608,6 +462,14 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
controlChangedBody.value,
|
controlChangedBody.value,
|
||||||
false // do NOT notify the server of the change.
|
false // do NOT notify the server of the change.
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else if (message === "onOutputVolumeChanged") {
|
||||||
|
let value = body as number;
|
||||||
|
this._setOutputVolume(value,false);
|
||||||
|
}
|
||||||
|
else if (message === "onInputVolumeChanged") {
|
||||||
|
let value = body as number;
|
||||||
|
this._setInputVolume(value,false);
|
||||||
} else if (message === "onLv2StateChanged") {
|
} else if (message === "onLv2StateChanged") {
|
||||||
let instanceId = body as number;
|
let instanceId = body as number;
|
||||||
this.onLv2StateChanged(instanceId);
|
this.onLv2StateChanged(instanceId);
|
||||||
@@ -755,6 +617,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
requestPluginClasses(): Promise<boolean> {
|
requestPluginClasses(): Promise<boolean> {
|
||||||
const myRequest = new Request(this.varRequest('plugin_classes.json'));
|
const myRequest = new Request(this.varRequest('plugin_classes.json'));
|
||||||
return fetch(myRequest)
|
return fetch(myRequest)
|
||||||
@@ -881,7 +744,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxUploadSize: number = 512 * 1024;
|
maxFileUploadSize: number = 100000000;
|
||||||
|
maxPresetUploadSize: number = 512 * 1024;
|
||||||
debug: boolean = false;
|
debug: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
@@ -895,7 +759,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
)
|
)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.max_upload_size) {
|
if (data.max_upload_size) {
|
||||||
this.maxUploadSize = data.max_upload_size;
|
this.maxPresetUploadSize = data.max_upload_size;
|
||||||
}
|
}
|
||||||
if (data.fakeAndroid) {
|
if (data.fakeAndroid) {
|
||||||
this.androidHost = new FakeAndroidHost();
|
this.androidHost = new FakeAndroidHost();
|
||||||
@@ -1295,13 +1159,93 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
};
|
};
|
||||||
this.webSocket?.send("setPatchProperty", body);
|
this.webSocket?.send("setPatchProperty", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInputVolume(volume_db: number) : void {
|
||||||
|
this._setInputVolume(volume_db,true);
|
||||||
|
}
|
||||||
|
previewInputVolume(volume_db: number) : void {
|
||||||
|
nullCast(this.webSocket).send("previewInputVolume",volume_db);
|
||||||
|
|
||||||
|
}
|
||||||
|
previewOutputVolume(volume_db: number) : void {
|
||||||
|
nullCast(this.webSocket).send("previewOutputVolume",volume_db);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setInputVolume(volume_db: number, notifyServer: boolean) : void {
|
||||||
|
let changed: boolean = false;
|
||||||
|
|
||||||
|
let pedalboard = this.pedalboard.get();
|
||||||
|
if (pedalboard.input_volume_db != volume_db)
|
||||||
|
{
|
||||||
|
let newPedalboard = pedalboard.clone();
|
||||||
|
newPedalboard.input_volume_db = volume_db;
|
||||||
|
this.pedalboard.set(newPedalboard);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
if (notifyServer) {
|
||||||
|
nullCast(this.webSocket).send("setInputVolume",volume_db);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
||||||
|
let item = this._controlValueChangeItems[i];
|
||||||
|
if (Pedalboard.START_CONTROL === item.instanceId) {
|
||||||
|
item.onValueChanged("volume_db", volume_db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutputVolume(volume_db: number, notifyServer: boolean) : void
|
||||||
|
{
|
||||||
|
this._setOutputVolume(volume_db,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setOutputVolume(volume_db: number, notifyServer: boolean) : void {
|
||||||
|
let changed: boolean = false;
|
||||||
|
|
||||||
|
let pedalboard = this.pedalboard.get();
|
||||||
|
if (pedalboard.output_volume_db != volume_db)
|
||||||
|
{
|
||||||
|
let newPedalboard = pedalboard.clone();
|
||||||
|
newPedalboard.output_volume_db = volume_db;
|
||||||
|
this.pedalboard.set(newPedalboard);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
if (notifyServer) {
|
||||||
|
nullCast(this.webSocket).send("setOutputVolume",volume_db);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
||||||
|
let item = this._controlValueChangeItems[i];
|
||||||
|
if (Pedalboard.END_CONTROL === item.instanceId) {
|
||||||
|
item.onValueChanged("volume_db", volume_db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
|
private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
|
||||||
let pedalboard = this.pedalboard.get();
|
let pedalboard = this.pedalboard.get();
|
||||||
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
||||||
let newPedalboard = pedalboard.clone();
|
let newPedalboard = pedalboard.clone();
|
||||||
|
|
||||||
|
if (instanceId === Pedalboard.START_CONTROL && key == "volume_db")
|
||||||
|
{
|
||||||
|
this._setInputVolume(value,notifyServer);
|
||||||
|
return;
|
||||||
|
} else if (instanceId === Pedalboard.END_CONTROL)
|
||||||
|
{
|
||||||
|
this._setOutputVolume(value,notifyServer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let item = newPedalboard.getItem(instanceId);
|
let item = newPedalboard.getItem(instanceId);
|
||||||
let changed = item.setControlValue(key, value);
|
let changed = item.setControlValue(key, value);
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.pedalboard.set(newPedalboard);
|
this.pedalboard.set(newPedalboard);
|
||||||
if (notifyServer) {
|
if (notifyServer) {
|
||||||
@@ -1427,6 +1371,16 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
// mouse is down. Don't update EVERYBODY, but we must change
|
// mouse is down. Don't update EVERYBODY, but we must change
|
||||||
// the control on the running audio plugin.
|
// the control on the running audio plugin.
|
||||||
// TODO: respect "expensive" port attribute.
|
// TODO: respect "expensive" port attribute.
|
||||||
|
if (instanceId === Pedalboard.START_CONTROL && key == "volume_db")
|
||||||
|
{
|
||||||
|
this.previewInputVolume(value);
|
||||||
|
return;
|
||||||
|
} else if (instanceId === Pedalboard.END_CONTROL)
|
||||||
|
{
|
||||||
|
this.previewOutputVolume(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._setServerControl("previewControl", instanceId, key, value);
|
this._setServerControl("previewControl", instanceId, key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1642,6 +1596,12 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
.request<string[]>('requestFileList', piPedalFileProperty);
|
.request<string[]>('requestFileList', piPedalFileProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteUserFile(fileName: string) : Promise<boolean>
|
||||||
|
{
|
||||||
|
return nullCast(this.webSocket).request<boolean>('deleteUserFile',fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise<number> {
|
saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise<number> {
|
||||||
// default behaviour is to save after the currently selected preset.
|
// default behaviour is to save after the currently selected preset.
|
||||||
let request: any = {
|
let request: any = {
|
||||||
@@ -2101,10 +2061,66 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
||||||
|
let result = new Promise<string>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (file.size > this.maxFileUploadSize) {
|
||||||
|
reject("File is too large.");
|
||||||
|
}
|
||||||
|
let url = this.varServerUrl + uploadPage;
|
||||||
|
let parsedUrl = new URL(url);
|
||||||
|
let fileNameOnly = file.name.split('/').pop()?.split('\\')?.pop();
|
||||||
|
let query = parsedUrl.search;
|
||||||
|
if (query.length === 0)
|
||||||
|
{
|
||||||
|
query += '?';
|
||||||
|
} else {
|
||||||
|
query += '&';
|
||||||
|
}
|
||||||
|
if (!fileNameOnly)
|
||||||
|
{
|
||||||
|
reject("Invalid filename.");
|
||||||
|
}
|
||||||
|
query += "filename=" + encodeURIComponent(fileNameOnly??"");
|
||||||
|
parsedUrl.search = query;
|
||||||
|
url = parsedUrl.toString();
|
||||||
|
fetch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: file,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
},
|
||||||
|
signal: abortController?.signal
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response: Response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
reject("Upload failed. " + response.statusText);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
resolve(json as string);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject("Upload failed. " + error);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
} catch (error) {
|
||||||
|
reject("Upload failed. " + error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise<number> {
|
uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise<number> {
|
||||||
let result = new Promise<number>((resolve, reject) => {
|
let result = new Promise<number>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (file.size > this.maxUploadSize) {
|
if (file.size > this.maxPresetUploadSize) {
|
||||||
reject("File is too large.");
|
reject("File is too large.");
|
||||||
}
|
}
|
||||||
let url = this.varServerUrl + uploadPage;
|
let url = this.varServerUrl + uploadPage;
|
||||||
@@ -2146,7 +2162,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let result = new Promise<number>((resolve, reject) => {
|
let result = new Promise<number>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
console.log("File: " + file.name + " Size: " + file.size);
|
console.log("File: " + file.name + " Size: " + file.size);
|
||||||
if (file.size > this.maxUploadSize) {
|
if (file.size > this.maxPresetUploadSize) {
|
||||||
reject("File is too large.");
|
reject("File is too large.");
|
||||||
}
|
}
|
||||||
let url = this.varServerUrl + "uploadBank";
|
let url = this.varServerUrl + "uploadBank";
|
||||||
@@ -2387,6 +2403,11 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
++ix;
|
++ix;
|
||||||
if (ix >= uiPlugin.controls.length) return;
|
if (ix >= uiPlugin.controls.length) return;
|
||||||
|
while (!uiPlugin.controls[ix].is_input)
|
||||||
|
{
|
||||||
|
++ix;
|
||||||
|
if (ix >= uiPlugin.controls.length) return;
|
||||||
|
}
|
||||||
|
|
||||||
this.zoomUiControl(currentUiControl.source, currentUiControl.instanceId, uiPlugin.controls[ix]);
|
this.zoomUiControl(currentUiControl.source, currentUiControl.instanceId, uiPlugin.controls[ix]);
|
||||||
}
|
}
|
||||||
@@ -2416,6 +2437,11 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
--ix;
|
--ix;
|
||||||
if (ix < 0) return;
|
if (ix < 0) return;
|
||||||
|
while (!uiPlugin.controls[ix].is_input)
|
||||||
|
{
|
||||||
|
--ix;
|
||||||
|
if (ix < 0) return;
|
||||||
|
}
|
||||||
|
|
||||||
this.zoomUiControl(currentUiControl.source, currentUiControl.instanceId, uiPlugin.controls[ix]);
|
this.zoomUiControl(currentUiControl.source, currentUiControl.instanceId, uiPlugin.controls[ix]);
|
||||||
}
|
}
|
||||||
@@ -2471,6 +2497,11 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns the ID of the new preset.
|
||||||
|
newPresetItem(createAfter: number): Promise<number>
|
||||||
|
{
|
||||||
|
return nullCast(this.webSocket).request<number>("newPreset");
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Robin Davies
|
// Copyright (c) 2022 Robin Davies
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
// this software and associated documentation files (the "Software"), to deal in
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { WithStyles } from '@mui/styles';
|
|||||||
import createStyles from '@mui/styles/createStyles';
|
import createStyles from '@mui/styles/createStyles';
|
||||||
import withStyles from '@mui/styles/withStyles';
|
import withStyles from '@mui/styles/withStyles';
|
||||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
import { UiPlugin, UiControl, PiPedalFileProperty} from './Lv2Plugin';
|
import { UiPlugin, UiControl, PiPedalFileProperty,ScalePoint} from './Lv2Plugin';
|
||||||
import {
|
import {
|
||||||
Pedalboard, PedalboardItem, ControlValue
|
Pedalboard, PedalboardItem, ControlValue
|
||||||
} from './Pedalboard';
|
} from './Pedalboard';
|
||||||
@@ -37,6 +37,8 @@ import FullScreenIME from './FullScreenIME';
|
|||||||
import FilePropertyControl from './FilePropertyControl';
|
import FilePropertyControl from './FilePropertyControl';
|
||||||
import FilePropertyDialog from './FilePropertyDialog';
|
import FilePropertyDialog from './FilePropertyDialog';
|
||||||
import JsonAtom from './JsonAtom';
|
import JsonAtom from './JsonAtom';
|
||||||
|
import PluginOutputControl from './PluginOutputControl';
|
||||||
|
import Units from './Units';
|
||||||
|
|
||||||
|
|
||||||
export const StandardItemSize = { width: 80, height: 110 };
|
export const StandardItemSize = { width: 80, height: 110 };
|
||||||
@@ -45,6 +47,37 @@ export const StandardItemSize = { width: 80, height: 110 };
|
|||||||
|
|
||||||
const LANDSCAPE_HEIGHT_BREAK = 500;
|
const LANDSCAPE_HEIGHT_BREAK = 500;
|
||||||
|
|
||||||
|
|
||||||
|
function makeIoPluginInfo(name: string, uri: string) : UiPlugin {
|
||||||
|
let result = new UiPlugin();
|
||||||
|
result.name = name;
|
||||||
|
result.uri = uri;
|
||||||
|
let volumeControl = new UiControl();
|
||||||
|
volumeControl.name = "Volume";
|
||||||
|
volumeControl.symbol = "volume_db";
|
||||||
|
volumeControl.index = 0;
|
||||||
|
volumeControl.is_input = true;
|
||||||
|
|
||||||
|
volumeControl.min_value = -60;
|
||||||
|
volumeControl.max_value = 30;
|
||||||
|
volumeControl.default_value = 0;
|
||||||
|
volumeControl.units = Units.db;
|
||||||
|
volumeControl.scale_points = [
|
||||||
|
new ScalePoint().deserialize({label: "-INF",value: -60})
|
||||||
|
];
|
||||||
|
result.controls = [
|
||||||
|
volumeControl
|
||||||
|
];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let startPluginInfo: UiPlugin =
|
||||||
|
makeIoPluginInfo("Input",Pedalboard.START_PEDALBOARD_ITEM_URI);
|
||||||
|
|
||||||
|
let endPluginInfo: UiPlugin =
|
||||||
|
makeIoPluginInfo("Output",Pedalboard.END_PEDALBOARD_ITEM_URI);
|
||||||
|
|
||||||
|
|
||||||
const styles = (theme: Theme) => createStyles({
|
const styles = (theme: Theme) => createStyles({
|
||||||
frame: {
|
frame: {
|
||||||
display: "block",
|
display: "block",
|
||||||
@@ -255,6 +288,7 @@ const PluginControlView =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onWindowSizeChanged(width: number, height: number): void {
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
this.setState({ landscapeGrid: height < LANDSCAPE_HEIGHT_BREAK });
|
this.setState({ landscapeGrid: height < LANDSCAPE_HEIGHT_BREAK });
|
||||||
}
|
}
|
||||||
@@ -296,6 +330,13 @@ const PluginControlView =
|
|||||||
}
|
}
|
||||||
makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode {
|
makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode {
|
||||||
let symbol = uiControl.symbol;
|
let symbol = uiControl.symbol;
|
||||||
|
if (!uiControl.is_input)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<PluginOutputControl instanceId={this.props.instanceId} uiControl={uiControl} />
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let controlValue: ControlValue | undefined = undefined;
|
let controlValue: ControlValue | undefined = undefined;
|
||||||
for (let i = 0; i < controlValues.length; ++i) {
|
for (let i = 0; i < controlValues.length; ++i) {
|
||||||
@@ -435,10 +476,26 @@ const PluginControlView =
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static startPluginInfo: UiPlugin =
|
||||||
|
makeIoPluginInfo("Input",Pedalboard.START_PEDALBOARD_ITEM_URI);
|
||||||
|
|
||||||
|
static endPluginInfo: UiPlugin =
|
||||||
|
makeIoPluginInfo("Output",Pedalboard.END_PEDALBOARD_ITEM_URI);
|
||||||
|
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId);
|
let pedalboardItem: PedalboardItem;
|
||||||
|
let pedalboard = this.model.pedalboard.get();
|
||||||
|
if (this.props.instanceId == Pedalboard.START_CONTROL)
|
||||||
|
{
|
||||||
|
pedalboardItem = pedalboard.makeStartItem();
|
||||||
|
} else if (this.props.instanceId == Pedalboard.END_CONTROL)
|
||||||
|
{
|
||||||
|
pedalboardItem = pedalboard.makeEndItem();
|
||||||
|
} else {
|
||||||
|
pedalboardItem = pedalboard.getItem(this.props.instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
if (!pedalboardItem)
|
if (!pedalboardItem)
|
||||||
return (<div className={classes.frame} ></div>);
|
return (<div className={classes.frame} ></div>);
|
||||||
@@ -446,7 +503,18 @@ const PluginControlView =
|
|||||||
let controlValues = pedalboardItem.controlValues;
|
let controlValues = pedalboardItem.controlValues;
|
||||||
|
|
||||||
|
|
||||||
let plugin: UiPlugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri));
|
let plugin: UiPlugin;
|
||||||
|
if (pedalboardItem.isStart())
|
||||||
|
{
|
||||||
|
plugin = startPluginInfo;
|
||||||
|
controlValues = [new ControlValue("volume_db",pedalboard.input_volume_db)];
|
||||||
|
} else if (pedalboardItem.isEnd())
|
||||||
|
{
|
||||||
|
plugin = endPluginInfo;
|
||||||
|
controlValues = [new ControlValue("volume_db",pedalboard.output_volume_db)];
|
||||||
|
} else {
|
||||||
|
plugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
controlValues = this.filterNotOnGui(controlValues, plugin);
|
controlValues = this.filterNotOnGui(controlValues, plugin);
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
// Copyright (c) 2023 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Theme } from '@mui/material/styles';
|
||||||
|
import { WithStyles } from '@mui/styles';
|
||||||
|
import createStyles from '@mui/styles/createStyles';
|
||||||
|
import withStyles from '@mui/styles/withStyles';
|
||||||
|
import { UiControl } from './Lv2Plugin';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory, MonitorPortHandle } from './PiPedalModel';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => createStyles({
|
||||||
|
frame: {
|
||||||
|
position: "relative",
|
||||||
|
margin: "12px"
|
||||||
|
},
|
||||||
|
switchTrack: {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 14 / 2,
|
||||||
|
zIndex: -1,
|
||||||
|
transition: theme.transitions.create(['opacity', 'background-color'], {
|
||||||
|
duration: theme.transitions.duration.shortest
|
||||||
|
}),
|
||||||
|
backgroundColor: theme.palette.secondary.main,
|
||||||
|
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
|
||||||
|
},
|
||||||
|
displayValue: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 4,
|
||||||
|
textAlign: "center",
|
||||||
|
background: "white",
|
||||||
|
color: "#666",
|
||||||
|
// zIndex: -1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export interface PluginOutputControlProps extends WithStyles<typeof styles> {
|
||||||
|
uiControl: UiControl;
|
||||||
|
instanceId: number;
|
||||||
|
}
|
||||||
|
type PluginOutputControlState = {
|
||||||
|
hasValue: boolean;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PluginOutputControl =
|
||||||
|
withStyles(styles, { withTheme: true })(
|
||||||
|
class extends Component<PluginOutputControlProps, PluginOutputControlState>
|
||||||
|
{
|
||||||
|
|
||||||
|
private model: PiPedalModel;
|
||||||
|
private vuRef: React.RefObject<HTMLDivElement>;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(props: PluginOutputControlProps) {
|
||||||
|
super(props);
|
||||||
|
this.vuRef = React.createRef<HTMLDivElement>();
|
||||||
|
this.state = {
|
||||||
|
hasValue: false,
|
||||||
|
value: 0
|
||||||
|
};
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private mounted: boolean = false;
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (super.componentWillUnmount) {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
}
|
||||||
|
this.mounted = false;
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.mounted = true;
|
||||||
|
this.subscribe();
|
||||||
|
if (super.componentDidMount) {
|
||||||
|
super.componentDidMount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VU_HEIGHT = 48-2;
|
||||||
|
private animationHandle: number | undefined = undefined;
|
||||||
|
|
||||||
|
private updateValue(value: number) {
|
||||||
|
if (this.vuRef.current) {
|
||||||
|
let control = this.props.uiControl;
|
||||||
|
let range = (value-control.min_value)/(control.max_value-control.min_value);
|
||||||
|
let top = this.VU_HEIGHT-range*this.VU_HEIGHT;
|
||||||
|
|
||||||
|
if (!this.animationHandle)
|
||||||
|
{
|
||||||
|
this.animationHandle = requestAnimationFrame(
|
||||||
|
()=> {
|
||||||
|
if (this.vuRef.current)
|
||||||
|
{
|
||||||
|
this.vuRef.current.style.marginTop = top+"px";
|
||||||
|
}
|
||||||
|
this.animationHandle = undefined;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ hasValue: true, value: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private monitorPortHandle?: MonitorPortHandle;
|
||||||
|
|
||||||
|
private subscribe(): void {
|
||||||
|
this.unsubscribe();
|
||||||
|
this.monitorPortHandle = this.model.monitorPort(
|
||||||
|
this.props.instanceId,
|
||||||
|
this.props.uiControl.symbol,
|
||||||
|
1 / 15.0, // update rate.
|
||||||
|
(value) => {
|
||||||
|
this.updateValue(value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
private unsubscribe(): void {
|
||||||
|
if (this.monitorPortHandle) {
|
||||||
|
this.model.unmonitorPort(this.monitorPortHandle)
|
||||||
|
this.monitorPortHandle = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isShortSelect(control: UiControl) {
|
||||||
|
for (let scale_point of control.scale_points) {
|
||||||
|
if (scale_point.label.length > 12) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDisplayValue(uiControl: UiControl | undefined, value: number): string {
|
||||||
|
if (!uiControl) return "";
|
||||||
|
|
||||||
|
return uiControl.formatDisplayValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
let control: UiControl = this.props.uiControl;
|
||||||
|
|
||||||
|
|
||||||
|
let text = "";
|
||||||
|
if (this.state.hasValue) {
|
||||||
|
let value = this.state.value;
|
||||||
|
if (control.isOnOffSwitch()) {
|
||||||
|
text = value === 0 ? "OFF" : "ON";
|
||||||
|
} else {
|
||||||
|
text = this.formatDisplayValue(control, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isSelect = control.isSelect();
|
||||||
|
|
||||||
|
let item_width: number | undefined = isSelect ? 160 : 80;
|
||||||
|
if (isSelect) {
|
||||||
|
if (this.isShortSelect(control)) {
|
||||||
|
item_width = 80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (control.isDial()) {
|
||||||
|
item_width = undefined;
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", width: item_width, margin: 8, height: 98 }}>
|
||||||
|
{/* TITLE SECTION */}
|
||||||
|
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
|
||||||
|
<Typography variant="caption" display="block" style={{
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center"
|
||||||
|
}}> {control.name}</Typography>
|
||||||
|
</div>
|
||||||
|
{/* CONTROL SECTION */}
|
||||||
|
|
||||||
|
<div style={{ flex: "1 1 auto", display: "flex", justifyContent: "center", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||||
|
<div style={{ width: 6, height: 48, background: "#000", }}>
|
||||||
|
<div style={{ height: 46, overflow: "hidden", position: "absolute", margin: 1 }}>
|
||||||
|
<div ref={this.vuRef} style={{ width: 4, height: 44, background: "#0C0", }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* LABEL/EDIT SECTION*, strictly a placeholder for visual alignment purposes.*/}
|
||||||
|
<div style={{ flex: "0 0 auto", position: "relative", width: 30, height: 27 }}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", width: item_width, margin: 8, height: 98 }}>
|
||||||
|
{/* TITLE SECTION */}
|
||||||
|
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8, marginLeft: 0, marginRight: 0 }}>
|
||||||
|
<Typography variant="caption" display="block" style={{
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "left"
|
||||||
|
}}> {control.name}</Typography>
|
||||||
|
</div>
|
||||||
|
{/* CONTROL SECTION */}
|
||||||
|
|
||||||
|
<div style={{ flex: "1 1 auto", display: "flex", justifyContent: "start", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||||
|
<Typography variant="caption" display="block" noWrap style={{ width: "100%" }}>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* LABEL/EDIT SECTION*, strictly a placeholder for visual alignment purposes.*/}
|
||||||
|
<div style={{ flex: "0 0 auto", position: "relative", width: 40, height: 27 }}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PluginOutputControl;
|
||||||
@@ -33,7 +33,7 @@ import ListItemText from '@mui/material/ListItemText';
|
|||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Fade from '@mui/material/Fade';
|
import Fade from '@mui/material/Fade';
|
||||||
import UploadDialog from './UploadDialog';
|
import UploadPresetDialog from './UploadPresetDialog';
|
||||||
import {PluginUiPresets,PluginUiPreset} from './PluginPreset';
|
import {PluginUiPresets,PluginUiPreset} from './PluginPreset';
|
||||||
|
|
||||||
import SelectHoverBackground from './SelectHoverBackground';
|
import SelectHoverBackground from './SelectHoverBackground';
|
||||||
@@ -67,7 +67,7 @@ interface PluginPresetsDialogState {
|
|||||||
renameOpen: boolean;
|
renameOpen: boolean;
|
||||||
|
|
||||||
moreMenuAnchorEl: HTMLElement | null;
|
moreMenuAnchorEl: HTMLElement | null;
|
||||||
openUploadDialog: boolean;
|
openUploadPresetDialog: boolean;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -138,7 +138,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
selectedItem: -1,
|
selectedItem: -1,
|
||||||
renameOpen: false,
|
renameOpen: false,
|
||||||
moreMenuAnchorEl: null,
|
moreMenuAnchorEl: null,
|
||||||
openUploadDialog: false
|
openUploadPresetDialog: false
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
handleUploadPresets() {
|
handleUploadPresets() {
|
||||||
this.handleMoreClose();
|
this.handleMoreClose();
|
||||||
this.setState({ openUploadDialog: true });
|
this.setState({ openUploadPresetDialog: true });
|
||||||
|
|
||||||
}
|
}
|
||||||
handleMoreClose(): void {
|
handleMoreClose(): void {
|
||||||
@@ -466,14 +466,14 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
|
|||||||
</DraggableGrid>
|
</DraggableGrid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UploadDialog
|
<UploadPresetDialog
|
||||||
title="Upload Plugin Presets"
|
title="Upload Plugin Presets"
|
||||||
extension='.piPluginPresets'
|
extension='.piPluginPresets'
|
||||||
uploadPage='uploadPluginPresets'
|
uploadPage='uploadPluginPresets'
|
||||||
onUploaded={(instanceId) => {} }
|
onUploaded={(instanceId) => {} }
|
||||||
uploadAfter={this.state.selectedItem}
|
uploadAfter={this.state.selectedItem}
|
||||||
open={this.state.openUploadDialog}
|
open={this.state.openUploadPresetDialog}
|
||||||
onClose={() => { this.setState({ openUploadDialog: false }) }} />
|
onClose={() => { this.setState({ openUploadPresetDialog: false }) }} />
|
||||||
|
|
||||||
</DialogEx>
|
</DialogEx>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import ListItemText from '@mui/material/ListItemText';
|
|||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Fade from '@mui/material/Fade';
|
import Fade from '@mui/material/Fade';
|
||||||
import UploadDialog from './UploadDialog';
|
import UploadPresetDialog from './UploadPresetDialog';
|
||||||
|
|
||||||
import SelectHoverBackground from './SelectHoverBackground';
|
import SelectHoverBackground from './SelectHoverBackground';
|
||||||
import CloseIcon from '@mui/icons-material//Close';
|
import CloseIcon from '@mui/icons-material//Close';
|
||||||
@@ -64,7 +64,7 @@ interface PresetDialogState {
|
|||||||
renameOpen: boolean;
|
renameOpen: boolean;
|
||||||
|
|
||||||
moreMenuAnchorEl: HTMLElement | null;
|
moreMenuAnchorEl: HTMLElement | null;
|
||||||
openUploadDialog: boolean;
|
openUploadPresetDialog: boolean;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -137,7 +137,7 @@ const PresetDialog = withStyles(styles, { withTheme: true })(
|
|||||||
selectedItem: presets.selectedInstanceId,
|
selectedItem: presets.selectedInstanceId,
|
||||||
renameOpen: false,
|
renameOpen: false,
|
||||||
moreMenuAnchorEl: null,
|
moreMenuAnchorEl: null,
|
||||||
openUploadDialog: false
|
openUploadPresetDialog: false
|
||||||
|
|
||||||
};
|
};
|
||||||
this.handlePresetsChanged = this.handlePresetsChanged.bind(this);
|
this.handlePresetsChanged = this.handlePresetsChanged.bind(this);
|
||||||
@@ -162,7 +162,7 @@ const PresetDialog = withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
handleUploadPreset() {
|
handleUploadPreset() {
|
||||||
this.handleMoreClose();
|
this.handleMoreClose();
|
||||||
this.setState({ openUploadDialog: true });
|
this.setState({ openUploadPresetDialog: true });
|
||||||
|
|
||||||
}
|
}
|
||||||
handleMoreClose(): void {
|
handleMoreClose(): void {
|
||||||
@@ -445,14 +445,14 @@ const PresetDialog = withStyles(styles, { withTheme: true })(
|
|||||||
</DraggableGrid>
|
</DraggableGrid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UploadDialog
|
<UploadPresetDialog
|
||||||
title='Upload preset'
|
title='Upload preset'
|
||||||
extension='.piPreset'
|
extension='.piPreset'
|
||||||
uploadPage='uploadPreset'
|
uploadPage='uploadPreset'
|
||||||
onUploaded={(instanceId) => this.setState({selectedItem: instanceId}) }
|
onUploaded={(instanceId) => this.setState({selectedItem: instanceId}) }
|
||||||
uploadAfter={this.state.selectedItem}
|
uploadAfter={this.state.selectedItem}
|
||||||
open={this.state.openUploadDialog}
|
open={this.state.openUploadPresetDialog}
|
||||||
onClose={() => { this.setState({ openUploadDialog: false }) }} />
|
onClose={() => { this.setState({ openUploadPresetDialog: false }) }} />
|
||||||
|
|
||||||
</DialogEx>
|
</DialogEx>
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import Fade from '@mui/material/Fade';
|
|||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import RenameDialog from './RenameDialog'
|
import RenameDialog from './RenameDialog'
|
||||||
import Select from '@mui/material/Select';
|
import Select from '@mui/material/Select';
|
||||||
import UploadDialog from './UploadDialog';
|
import UploadPresetDialog from './UploadPresetDialog';
|
||||||
|
|
||||||
|
|
||||||
interface PresetSelectorProps extends WithStyles<typeof styles> {
|
interface PresetSelectorProps extends WithStyles<typeof styles> {
|
||||||
@@ -55,7 +55,7 @@ interface PresetSelectorState {
|
|||||||
renameDialogDefaultName: string;
|
renameDialogDefaultName: string;
|
||||||
renameDialogActionName: string;
|
renameDialogActionName: string;
|
||||||
renameDialogOnOk?: (name: string) => void;
|
renameDialogOnOk?: (name: string) => void;
|
||||||
openUploadDialog: boolean;
|
openUploadPresetDialog: boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ const PresetSelector =
|
|||||||
renameDialogDefaultName: "",
|
renameDialogDefaultName: "",
|
||||||
renameDialogActionName: "",
|
renameDialogActionName: "",
|
||||||
renameDialogOnOk: undefined,
|
renameDialogOnOk: undefined,
|
||||||
openUploadDialog: false
|
openUploadPresetDialog: false
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -127,7 +127,7 @@ const PresetSelector =
|
|||||||
this.handlePresetsMenuClose();
|
this.handlePresetsMenuClose();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.setState({ openUploadDialog: true });
|
this.setState({ openUploadPresetDialog: true });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ const PresetSelector =
|
|||||||
return this.model.saveCurrentPresetAs(newName);
|
return this.model.saveCurrentPresetAs(newName);
|
||||||
})
|
})
|
||||||
.then((newInstanceId) => {
|
.then((newInstanceId) => {
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.showError(error);
|
this.showError(error);
|
||||||
@@ -179,6 +179,22 @@ const PresetSelector =
|
|||||||
})
|
})
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
handlePresetsMenuNew(e: SyntheticEvent): void {
|
||||||
|
this.handlePresetsMenuClose();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
let currentPresets = this.model.presets.get();
|
||||||
|
let item = currentPresets.getItem(currentPresets.selectedInstanceId);
|
||||||
|
if (item == null) return;
|
||||||
|
let name = item.name;
|
||||||
|
this.model.newPresetItem(currentPresets.selectedInstanceId)
|
||||||
|
.then((instanceId) => {
|
||||||
|
this.model.loadPreset(instanceId);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
showError(error: string) {
|
showError(error: string) {
|
||||||
this.model.showAlert(error);
|
this.model.showAlert(error);
|
||||||
@@ -282,13 +298,24 @@ const PresetSelector =
|
|||||||
let presets = this.state.presets;
|
let presets = this.state.presets;
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
return (
|
return (
|
||||||
<div style={{ marginLeft: 12, display: "flex", flexDirection: "row",
|
<div style={{
|
||||||
justifyContent: "left", flexWrap: "nowrap", alignItems: "center", height: "100%", position: "relative" }}>
|
marginLeft: 12, display: "flex", flexDirection: "row",
|
||||||
|
justifyContent: "left", flexWrap: "nowrap", alignItems: "center", height: "100%", position: "relative"
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
|
<IconButton
|
||||||
|
style={{ flex: "0 0 auto", opacity: this.state.presets.presetChanged ? 1.0 : 0.0 }}
|
||||||
|
onClick={(e) => { this.handleSave(); }}
|
||||||
|
size="large">
|
||||||
|
<SaveIconOutline style={{ opacity: 0.75 }} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ flex: "1 1 auto", minWidth: 60, maxWidth: 300, position: "relative", paddingRight: 12 }} >
|
<div style={{ flex: "1 1 auto", minWidth: 60, maxWidth: 300, position: "relative", paddingRight: 12 }} >
|
||||||
{true ? (
|
{true ? (
|
||||||
<Select variant="standard"
|
<Select variant="standard"
|
||||||
className={classes.select}
|
className={classes.select}
|
||||||
style={{ width: "100%", position: "relative", top: 0}} disabled={!this.state.enabled}
|
style={{ width: "100%", position: "relative", top: 0 }} disabled={!this.state.enabled}
|
||||||
onChange={(e, extra) => this.handleChange(e, extra)}
|
onChange={(e, extra) => this.handleChange(e, extra)}
|
||||||
onClose={(e) => this.handleSelectClose(e)}
|
onClose={(e) => this.handleSelectClose(e)}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
@@ -331,14 +358,6 @@ const PresetSelector =
|
|||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
|
||||||
<IconButton
|
|
||||||
style={{ flex: "0 0 auto", opacity: this.state.presets.presetChanged ? 1.0 : 0.0 }}
|
|
||||||
onClick={(e) => { this.handleSave(); }}
|
|
||||||
size="large">
|
|
||||||
<SaveIconOutline style={{ opacity: 0.75 }} />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{ flex: "0 0 auto" }}
|
style={{ flex: "0 0 auto" }}
|
||||||
@@ -356,6 +375,7 @@ const PresetSelector =
|
|||||||
<MenuItem onClick={(e) => this.handlePresetsMenuSave(e)}>Save preset</MenuItem>
|
<MenuItem onClick={(e) => this.handlePresetsMenuSave(e)}>Save preset</MenuItem>
|
||||||
<MenuItem onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>
|
<MenuItem onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>
|
||||||
<MenuItem onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>
|
<MenuItem onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>
|
||||||
|
<MenuItem onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
<MenuItem onClick={(e) => { this.handleDownloadPreset(e); }} >Download preset</MenuItem>
|
<MenuItem onClick={(e) => { this.handleDownloadPreset(e); }} >Download preset</MenuItem>
|
||||||
<MenuItem onClick={(e) => { this.handleUploadPreset(e) }}>Upload preset</MenuItem>
|
<MenuItem onClick={(e) => { this.handleUploadPreset(e) }}>Upload preset</MenuItem>
|
||||||
@@ -369,14 +389,14 @@ const PresetSelector =
|
|||||||
acceptActionName={this.state.renameDialogActionName}
|
acceptActionName={this.state.renameDialogActionName}
|
||||||
onClose={() => this.handleRenameDialogClose()}
|
onClose={() => this.handleRenameDialogClose()}
|
||||||
onOk={(name: string) => this.handleRenameDialogOk(name)} />
|
onOk={(name: string) => this.handleRenameDialogOk(name)} />
|
||||||
<UploadDialog
|
<UploadPresetDialog
|
||||||
title='Upload preset'
|
title='Upload preset'
|
||||||
extension='.piPreset'
|
extension='.piPreset'
|
||||||
uploadPage='uploadPreset'
|
uploadPage='uploadPreset'
|
||||||
onUploaded={(instanceId) => { this.model.loadPreset(instanceId); }}
|
onUploaded={(instanceId) => { this.model.loadPreset(instanceId); }}
|
||||||
open={this.state.openUploadDialog}
|
open={this.state.openUploadPresetDialog}
|
||||||
uploadAfter={-1}
|
uploadAfter={-1}
|
||||||
onClose={() => { this.setState({ openUploadDialog: false }) }} />
|
onClose={() => { this.setState({ openUploadPresetDialog: false }) }} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import { Component, SyntheticEvent } from 'react';
|
import React,{ Component, SyntheticEvent } from 'react';
|
||||||
import { Theme } from '@mui/material/styles';
|
import { Theme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
|
||||||
@@ -54,11 +54,12 @@ const styles = ({ palette }: Theme) => createStyles({
|
|||||||
interface HoverProps extends WithStyles<typeof styles> {
|
interface HoverProps extends WithStyles<typeof styles> {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
showHover?: boolean;
|
showHover?: boolean;
|
||||||
|
borderRadius?: number;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HoverState {
|
interface HoverState {
|
||||||
hovering: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -69,44 +70,53 @@ export const SelectHoverBackground =
|
|||||||
constructor(props: HoverProps) {
|
constructor(props: HoverProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
hovering: false
|
|
||||||
};
|
};
|
||||||
|
this.selfRef = React.createRef<HTMLDivElement>();
|
||||||
|
this.hoverElementRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
this.handleMouseOver = this.handleMouseOver.bind(this);
|
||||||
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseEnter(e: SyntheticEvent): void {
|
handleMouseOver(e: SyntheticEvent): void {
|
||||||
if (this.props.showHover??true)
|
console.log("onMouseOver")
|
||||||
{
|
if (this.props.showHover ?? true) {
|
||||||
this.setHovering(true);
|
if (this.hoverElementRef.current)
|
||||||
|
{
|
||||||
|
this.hoverElementRef.current.style.display = "block";
|
||||||
|
} else {
|
||||||
|
console.log("No hoverElementRef")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
handleMouseLeave(e: SyntheticEvent): void {
|
handleMouseLeave(e: SyntheticEvent): void {
|
||||||
if (this.props.showHover??true)
|
if (this.props.showHover ?? true) {
|
||||||
{
|
if (this.hoverElementRef.current)
|
||||||
this.setHovering(false);
|
{
|
||||||
}
|
this.hoverElementRef.current.style.display = "none";
|
||||||
|
} else {
|
||||||
}
|
console.log("No hoverElementRef")
|
||||||
|
}
|
||||||
|
|
||||||
setHovering(value: boolean): void {
|
|
||||||
if (this.state.hovering !== value) {
|
|
||||||
this.setState({ hovering: value });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private selfRef: React.RefObject<HTMLDivElement>;
|
||||||
|
private hoverElementRef: React.RefObject<HTMLDivElement>;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let isSelected = this.props.selected;
|
let isSelected = this.props.selected;
|
||||||
let hovering = this.state.hovering && (this.props.showHover??true);
|
return (<div className={classes.frame} ref={this.selfRef}
|
||||||
return (<div className={classes.frame}
|
onMouseOver={(e) => { this.handleMouseOver(e); }} onMouseLeave={(e) => { this.handleMouseLeave(e) }}
|
||||||
onMouseEnter={(e) => { this.handleMouseEnter(e); }} onMouseLeave={(e) => { this.handleMouseLeave(e) }}
|
|
||||||
>
|
>
|
||||||
<div className={classes.selected} style={{ display: (isSelected ? "block" : "none") }} />
|
<div className={classes.selected} style={{ display: (isSelected ? "block" : "none"), borderRadius: this.props.borderRadius }} />
|
||||||
<div className={classes.hover} style={{ display: (hovering ? "block" : "none") }} />
|
<div ref={this.hoverElementRef} className={classes.hover} style={{ display: "none", borderRadius: this.props.borderRadius }} />
|
||||||
|
{
|
||||||
|
this.props.children
|
||||||
|
}
|
||||||
</div>);
|
</div>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,414 @@
|
|||||||
|
// Copyright (c) 2022 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
|
||||||
|
import DialogEx from './DialogEx';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { PiPedalFileProperty } from './Lv2Plugin';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
|
||||||
|
// const PRESET_EXTENSION = ".piPreset";
|
||||||
|
// const BANK_EXTENSION = ".piBank";
|
||||||
|
// const PLUGIN_PRESETS_EXTENSION = ".piPluginPresets";
|
||||||
|
|
||||||
|
export interface UploadFileDialogProps {
|
||||||
|
open: boolean,
|
||||||
|
onClose: () => void,
|
||||||
|
onUploaded: (fileName: string) => void,
|
||||||
|
uploadPage: string,
|
||||||
|
fileProperty: PiPedalFileProperty
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FileUploadStatus {
|
||||||
|
Ready,
|
||||||
|
Uploading,
|
||||||
|
Uploaded,
|
||||||
|
Error,
|
||||||
|
Cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextId = 0;
|
||||||
|
|
||||||
|
class FileUploadEntry {
|
||||||
|
constructor(file: File) {
|
||||||
|
this.id = nextId++;
|
||||||
|
this.file = file;
|
||||||
|
this.abortController = undefined;
|
||||||
|
this.status = FileUploadStatus.Ready;
|
||||||
|
this.statusMessage = "";
|
||||||
|
}
|
||||||
|
status: FileUploadStatus
|
||||||
|
statusMessage: string;
|
||||||
|
file: File;
|
||||||
|
abortController?: AbortController;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileUploadDisplayEntry {
|
||||||
|
constructor(fileUploadEntry: FileUploadEntry) {
|
||||||
|
this.name = fileUploadEntry.file.name;
|
||||||
|
this.status = fileUploadEntry.status;
|
||||||
|
this.statusMessage = fileUploadEntry.statusMessage;
|
||||||
|
this.id = fileUploadEntry.id;
|
||||||
|
}
|
||||||
|
name: string;
|
||||||
|
status: FileUploadStatus;
|
||||||
|
statusMessage: string;
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface UploadFileDialogState {
|
||||||
|
fullScreen: boolean;
|
||||||
|
okEnabled: boolean;
|
||||||
|
files: FileUploadDisplayEntry[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFileDialogProps, UploadFileDialogState> {
|
||||||
|
|
||||||
|
model: PiPedalModel;
|
||||||
|
|
||||||
|
constructor(props: UploadFileDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
fullScreen: false,
|
||||||
|
okEnabled: true,
|
||||||
|
files: []
|
||||||
|
};
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
|
||||||
|
}
|
||||||
|
mounted: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
this.setState({ fullScreen: height < 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
super.componentDidMount();
|
||||||
|
this.mounted = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
this.mounted = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Readonly<UploadFileDialogProps>, prevState: Readonly<UploadFileDialogState>, snapshot?: any): void {
|
||||||
|
if (prevProps.open !== this.props.open) {
|
||||||
|
if (this.props.open) {
|
||||||
|
this.uploadList = [];
|
||||||
|
this.setState({ files: [], okEnabled: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose(): void {
|
||||||
|
this.uploadList = [];
|
||||||
|
this.setState({ files: [] });
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel() {
|
||||||
|
for (let upload of this.uploadList) {
|
||||||
|
if (upload.status === FileUploadStatus.Ready || upload.status === FileUploadStatus.Uploading) {
|
||||||
|
upload.status = FileUploadStatus.Cancelled;
|
||||||
|
upload.statusMessage = "Cancelled.";
|
||||||
|
}
|
||||||
|
if (upload.abortController) {
|
||||||
|
upload.abortController.abort();
|
||||||
|
upload.abortController = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
this.handleClose();
|
||||||
|
}
|
||||||
|
handleOk(): void {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUploadDisplayList() {
|
||||||
|
let result: FileUploadDisplayEntry[] = [];
|
||||||
|
let uploadsPending = false;
|
||||||
|
for (let upload of this.uploadList) {
|
||||||
|
result.push(new FileUploadDisplayEntry(upload));
|
||||||
|
if (upload.status === FileUploadStatus.Ready || upload.status === FileUploadStatus.Uploading) {
|
||||||
|
uploadsPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
this.setState({ files: result, okEnabled: !uploadsPending });
|
||||||
|
}
|
||||||
|
|
||||||
|
private uploading = false;
|
||||||
|
private wantsScrollTo = false;
|
||||||
|
|
||||||
|
private uploadList: FileUploadEntry[] = [];
|
||||||
|
|
||||||
|
async uploadFiles(files: FileList) {
|
||||||
|
for (let i = 0; i < files.length; ++i) {
|
||||||
|
let file = files.item(i);
|
||||||
|
if (file) {
|
||||||
|
this.uploadList.push(new FileUploadEntry(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.wantsScrollTo = true;
|
||||||
|
if (this.uploading) {
|
||||||
|
this.updateUploadDisplayList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.uploading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < this.uploadList.length; ++i) {
|
||||||
|
let upload = this.uploadList[i];
|
||||||
|
if (upload.status !== FileUploadStatus.Ready) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
upload.status = FileUploadStatus.Uploading;
|
||||||
|
upload.statusMessage = "Uploading...";
|
||||||
|
this.wantsScrollTo = true;
|
||||||
|
this.updateUploadDisplayList();
|
||||||
|
try {
|
||||||
|
upload.abortController = new AbortController();
|
||||||
|
let filename = await this.model.uploadFile(this.props.uploadPage, this.uploadList[i].file, "application/octet-stream", upload.abortController);
|
||||||
|
this.props.onUploaded(filename);
|
||||||
|
upload.status = FileUploadStatus.Uploaded;
|
||||||
|
upload.statusMessage = "Uploaded.";
|
||||||
|
upload.abortController = undefined;
|
||||||
|
} catch (error: any) {
|
||||||
|
// @ts-ignore: TS2367 // No overlap between FileUploadStatus.Updating and FileUploadStatus.Cancelled
|
||||||
|
if (upload.status !== FileUploadStatus.Cancelled) {
|
||||||
|
upload.status = FileUploadStatus.Error;
|
||||||
|
if (error instanceof Error) {
|
||||||
|
upload.statusMessage = (error as Error).message;
|
||||||
|
} else if (typeof error === "string") {
|
||||||
|
upload.statusMessage = error as string;
|
||||||
|
} else {
|
||||||
|
throw new Error("Unrecgnized exception type: " + error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.model.showAlert(error + "");
|
||||||
|
};
|
||||||
|
this.updateUploadDisplayList();
|
||||||
|
this.uploading = false;
|
||||||
|
}
|
||||||
|
handleDrop(e: React.DragEvent<HTMLDivElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
e.currentTarget.style.background = "";
|
||||||
|
|
||||||
|
if (this.wantsTransfer(e.dataTransfer)) {
|
||||||
|
this.uploadFiles(e.dataTransfer.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private wantsFile(file: File | null) {
|
||||||
|
if (file === null) return false;
|
||||||
|
return this.props.fileProperty.wantsFile(file.name);
|
||||||
|
|
||||||
|
}
|
||||||
|
private wantsTransfer(fileList: DataTransfer): boolean {
|
||||||
|
if (fileList.files.length === 0) return false;
|
||||||
|
|
||||||
|
console.log("File count: " + fileList.files.length);
|
||||||
|
for (let i = 0; i < fileList.files.length; ++i) {
|
||||||
|
let file = fileList.files[i];
|
||||||
|
if (this.wantsFile(file)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
handleDragOver(e: React.DragEvent<HTMLDivElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
e.currentTarget.style.background = "#D8D8D8";
|
||||||
|
}
|
||||||
|
|
||||||
|
handleButtonSelect(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
if (e.currentTarget.files) {
|
||||||
|
this.uploadFiles(e.currentTarget.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasFileList() {
|
||||||
|
return this.state.files.length !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon(status: FileUploadStatus) {
|
||||||
|
let size = 14;
|
||||||
|
|
||||||
|
let style = { width: size, height: size, opacity: 0.75 };
|
||||||
|
let top = 0;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case FileUploadStatus.Error:
|
||||||
|
return (<div style={{ position: "relative", top: top }}><ErrorIcon color="error" style={style} /></div>);
|
||||||
|
case FileUploadStatus.Uploaded:
|
||||||
|
return (<div style={{ position: "relative", top: top }}><CheckCircleOutlineIcon color="success" style={style}/></div >);
|
||||||
|
case FileUploadStatus.Uploading:
|
||||||
|
return (<CircularProgress size={size } style={{ margin: 2 }} />);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (<SvgIcon style={{ width: size, height: size, }} />);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeBrowserAcceptList() {
|
||||||
|
let fileTypes = this.props.fileProperty.fileTypes;
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < fileTypes.length; ++i) {
|
||||||
|
if (result.length !== 0) {
|
||||||
|
result += ',';
|
||||||
|
}
|
||||||
|
result += fileTypes[i].fileExtension;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogEx tag="UploadFileDialog" open={this.props.open} fullWidth onClose={() => this.handleClose()}
|
||||||
|
fullScreen={this.state.fullScreen}
|
||||||
|
style={{ userSelect: "none" }}
|
||||||
|
>
|
||||||
|
<DialogTitle >
|
||||||
|
<div>
|
||||||
|
<IconButton edge="start" color="inherit" onClick={() => { this.handleCancel(); }} aria-label="back"
|
||||||
|
>
|
||||||
|
<ArrowBackIcon fontSize="small" style={{ opacity: "0.6" }} />
|
||||||
|
</IconButton>
|
||||||
|
<Typography display="inline" >Upload</Typography>
|
||||||
|
</div>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent style={{ paddingBottom: 0 }}>
|
||||||
|
<div style={{
|
||||||
|
width: "100%", height: 140, marginBottom: 0,
|
||||||
|
border: "2px dashed #CCC",
|
||||||
|
borderRadius: "10px",
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
padding: 0
|
||||||
|
}}
|
||||||
|
onDragEnter={(e) => { this.handleDragOver(e); }}
|
||||||
|
onDragLeave={(e) => { e.currentTarget.style.background = ""; e.preventDefault(); }}
|
||||||
|
onDragOver={(e) => { this.handleDragOver(e); }}
|
||||||
|
onDrop={(e) => {
|
||||||
|
this.handleDrop(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!this.hasFileList() && (
|
||||||
|
<Typography noWrap color="textSecondary" align="center" display="block" variant="caption" style={{ width: "100%", marginTop: 20, verticalAlign: "middle" }} >Drop files here</Typography>
|
||||||
|
)}
|
||||||
|
{this.hasFileList() && (
|
||||||
|
<div style={{ width: "100%", height: "100%", overflow: "auto" }}>
|
||||||
|
<table style={{ margin: 8, }}>
|
||||||
|
{
|
||||||
|
this.state.files.map(
|
||||||
|
(upload) => {
|
||||||
|
let myRef: ((arg: HTMLDivElement) => void) | undefined = undefined;
|
||||||
|
if (this.wantsScrollTo && upload.status === FileUploadStatus.Uploading) {
|
||||||
|
myRef = (refElement: HTMLDivElement | null) => {
|
||||||
|
if (refElement && this.wantsScrollTo) {
|
||||||
|
let options: ScrollIntoViewOptions = {
|
||||||
|
block: "center"
|
||||||
|
};
|
||||||
|
refElement.scrollIntoView(options);
|
||||||
|
this.wantsScrollTo = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td style={{ verticalAlign: "middle" }}>
|
||||||
|
<div ref={myRef} style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center" }}>
|
||||||
|
{this.getIcon(upload.status)}
|
||||||
|
<Typography variant="caption" noWrap display="block" style={{ marginLeft: 4 }}> {upload.name} </Typography>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style={{ verticalAlign: "middle" }}>
|
||||||
|
<Typography variant="caption" noWrap display="block" style={{ marginLeft: 8 }}> {upload.statusMessage} </Typography>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</DialogContent >
|
||||||
|
|
||||||
|
<DialogActions >
|
||||||
|
<Button
|
||||||
|
component="label" color="primary" style={{ marginLeft: 16, width: 120, flex: "0 0 auto" }}
|
||||||
|
>
|
||||||
|
Select Files
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
hidden
|
||||||
|
accept={this.makeBrowserAcceptList()}
|
||||||
|
multiple
|
||||||
|
onChange={(e) => this.handleButtonSelect(e)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<div style={{ flex: "1 1 20px" }} />
|
||||||
|
{
|
||||||
|
this.state.okEnabled ?
|
||||||
|
(
|
||||||
|
<Button onClick={() => this.handleClose()} color="secondary">
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<Button onClick={() => this.handleCancel()} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</DialogActions>
|
||||||
|
</DialogEx>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
// const BANK_EXTENSION = ".piBank";
|
// const BANK_EXTENSION = ".piBank";
|
||||||
// const PLUGIN_PRESETS_EXTENSION = ".piPluginPresets";
|
// const PLUGIN_PRESETS_EXTENSION = ".piPluginPresets";
|
||||||
|
|
||||||
export interface UploadDialogProps {
|
export interface UploadPresetDialogProps {
|
||||||
open: boolean,
|
open: boolean,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
uploadAfter: number,
|
uploadAfter: number,
|
||||||
@@ -44,15 +44,15 @@ export interface UploadDialogProps {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UploadDialogState {
|
export interface UploadPresetDialogState {
|
||||||
fullScreen: boolean;
|
fullScreen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class UploadDialog extends ResizeResponsiveComponent<UploadDialogProps, UploadDialogState> {
|
export default class UploadPresetDialog extends ResizeResponsiveComponent<UploadPresetDialogProps, UploadPresetDialogState> {
|
||||||
|
|
||||||
model: PiPedalModel;
|
model: PiPedalModel;
|
||||||
|
|
||||||
constructor(props: UploadDialogProps) {
|
constructor(props: UploadPresetDialogProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
fullScreen: false
|
fullScreen: false
|
||||||
@@ -157,7 +157,7 @@ export default class UploadDialog extends ResizeResponsiveComponent<UploadDialog
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogEx tag="UploadDialog" open={this.props.open} fullWidth onClose={() => this.handleClose()}
|
<DialogEx tag="UploadPresetDialog" open={this.props.open} fullWidth onClose={() => this.handleClose()}
|
||||||
fullScreen={this.state.fullScreen}
|
fullScreen={this.state.fullScreen}
|
||||||
style={{userSelect: "none"}}
|
style={{userSelect: "none"}}
|
||||||
>
|
>
|
||||||
+1
-1
@@ -49,7 +49,7 @@ src/GxTunerControl.tsx
|
|||||||
src/Banks.tsx
|
src/Banks.tsx
|
||||||
src/PluginPresetsDialog.tsx
|
src/PluginPresetsDialog.tsx
|
||||||
src/BankDialog.tsx
|
src/BankDialog.tsx
|
||||||
src/UploadDialog.tsx
|
src/UploadPresetDialog.tsx
|
||||||
src/ToobPowerStage2View.tsx
|
src/ToobPowerStage2View.tsx
|
||||||
src/index.tsx
|
src/index.tsx
|
||||||
src/Units.tsx
|
src/Units.tsx
|
||||||
|
|||||||
+62
-15
@@ -501,6 +501,20 @@ private:
|
|||||||
this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
|
this->realtimeActivePedalboard->SetControlValue(body.effectIndex, body.controlIndex, body.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case RingBufferCommand::SetInputVolume:
|
||||||
|
{
|
||||||
|
SetVolumeBody body;
|
||||||
|
realtimeReader.readComplete(&body);
|
||||||
|
this->realtimeActivePedalboard->SetInputVolume(body.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RingBufferCommand::SetOutputVolume:
|
||||||
|
{
|
||||||
|
SetVolumeBody body;
|
||||||
|
realtimeReader.readComplete(&body);
|
||||||
|
this->realtimeActivePedalboard->SetOutputVolume(body.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case RingBufferCommand::ParameterRequest:
|
case RingBufferCommand::ParameterRequest:
|
||||||
{
|
{
|
||||||
RealtimePatchPropertyRequest *pRequest = nullptr;
|
RealtimePatchPropertyRequest *pRequest = nullptr;
|
||||||
@@ -809,7 +823,7 @@ private:
|
|||||||
{
|
{
|
||||||
if (this->realtimeVuBuffers != nullptr)
|
if (this->realtimeVuBuffers != nullptr)
|
||||||
{
|
{
|
||||||
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes);
|
pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes,inputBuffers,outputBuffers);
|
||||||
|
|
||||||
vuSamplesRemaining -= nframes;
|
vuSamplesRemaining -= nframes;
|
||||||
if (vuSamplesRemaining <= 0)
|
if (vuSamplesRemaining <= 0)
|
||||||
@@ -1332,6 +1346,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetControlValue(uint64_t instanceId, const std::string &symbol, float value)
|
void SetControlValue(uint64_t instanceId, const std::string &symbol, float value)
|
||||||
@@ -1350,8 +1365,23 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void SetInputVolume(float value) {
|
||||||
|
std::lock_guard guard(mutex);
|
||||||
|
if (active && this->currentPedalboard)
|
||||||
|
{
|
||||||
|
hostWriter.SetInputVolume(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetOutputVolume(float value) {
|
||||||
|
std::lock_guard guard(mutex);
|
||||||
|
if (active && this->currentPedalboard)
|
||||||
|
{
|
||||||
|
hostWriter.SetOutputVolume(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
|
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
|
||||||
{
|
{
|
||||||
@@ -1372,21 +1402,33 @@ public:
|
|||||||
{
|
{
|
||||||
int64_t instanceId = instanceIds[i];
|
int64_t instanceId = instanceIds[i];
|
||||||
auto effect = this->currentPedalboard->GetEffect(instanceId);
|
auto effect = this->currentPedalboard->GetEffect(instanceId);
|
||||||
if (!effect)
|
if (effect)
|
||||||
{
|
{
|
||||||
throw PiPedalStateException("Effect not found.");
|
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
|
||||||
|
vuConfig->enabledIndexes.push_back(index);
|
||||||
|
VuUpdate v;
|
||||||
|
v.instanceId_ = instanceId;
|
||||||
|
// Display mono VUs if a stereo device is being fed identical L/R inputs.
|
||||||
|
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
||||||
|
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
|
||||||
|
|
||||||
|
vuConfig->vuUpdateWorkingData.push_back(v);
|
||||||
|
vuConfig->vuUpdateResponseData.push_back(v);
|
||||||
|
} else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) {
|
||||||
|
int index = (int)instanceId;
|
||||||
|
VuUpdate v;
|
||||||
|
vuConfig->enabledIndexes.push_back(index);
|
||||||
|
v.instanceId_ = instanceId;
|
||||||
|
if (instanceId == Pedalboard::INPUT_VOLUME_ID)
|
||||||
|
{
|
||||||
|
v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1;
|
||||||
|
} else {
|
||||||
|
v.isStereoInput_ = v.isStereoOutput_ =this->pHost->GetNumberOfOutputAudioChannels() > 1;
|
||||||
|
}
|
||||||
|
vuConfig->vuUpdateWorkingData.push_back(v);
|
||||||
|
vuConfig->vuUpdateResponseData.push_back(v);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
|
|
||||||
vuConfig->enabledIndexes.push_back(index);
|
|
||||||
VuUpdate v;
|
|
||||||
v.instanceId_ = instanceId;
|
|
||||||
// Display mono VUs if a stereo device is being fed identical L/R inputs.
|
|
||||||
v.isStereoInput_ = effect->GetNumberOfInputAudioPorts() != 1 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1);
|
|
||||||
v.isStereoOutput_ = effect->GetNumberOfOutputAudioPorts() != 1;
|
|
||||||
|
|
||||||
vuConfig->vuUpdateWorkingData.push_back(v);
|
|
||||||
vuConfig->vuUpdateResponseData.push_back(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->hostWriter.SetVuSubscriptions(vuConfig);
|
this->hostWriter.SetVuSubscriptions(vuConfig);
|
||||||
@@ -1653,7 +1695,12 @@ void AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
|||||||
IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
|
IEffect*effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
|
||||||
if (effect != nullptr)
|
if (effect != nullptr)
|
||||||
{
|
{
|
||||||
effect->GetLv2State(const_cast<Lv2PluginState*>(&pedalboardItem.lv2State()));
|
try {
|
||||||
|
effect->GetLv2State(const_cast<Lv2PluginState*>(&pedalboardItem.lv2State()));
|
||||||
|
} catch(const std::exception &e)
|
||||||
|
{
|
||||||
|
Lv2Log::warning(SS(pedalboardItem.pluginName() << ": " << e.what()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -207,7 +207,9 @@ public:
|
|||||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
||||||
|
|
||||||
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
|
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
|
||||||
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> & values) = 0;
|
virtual void SetInputVolume(float value) = 0;
|
||||||
|
virtual void SetOutputVolume(float value) = 0;
|
||||||
|
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) = 0;
|
||||||
virtual void SetBypass(uint64_t instanceId, bool enabled) = 0;
|
virtual void SetBypass(uint64_t instanceId, bool enabled) = 0;
|
||||||
|
|
||||||
virtual bool IsOpen() const = 0;
|
virtual bool IsOpen() const = 0;
|
||||||
|
|||||||
@@ -43,12 +43,17 @@ float AutoLilvNode::AsFloat(float defaultValue)
|
|||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
int AutoLilvNode::AsInt(int defaultValue)
|
int32_t AutoLilvNode::AsInt(int defaultValue)
|
||||||
{
|
{
|
||||||
if (node == nullptr)
|
if (node == nullptr)
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
if (lilv_node_is_int(node))
|
if (lilv_node_is_int(node))
|
||||||
return lilv_node_as_int(node);
|
return lilv_node_as_int(node);
|
||||||
|
if (lilv_node_is_float(node))
|
||||||
|
{
|
||||||
|
return (int32_t)lilv_node_as_float(node);
|
||||||
|
}
|
||||||
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
bool AutoLilvNode::AsBool(bool defaultValue)
|
bool AutoLilvNode::AsBool(bool defaultValue)
|
||||||
|
|||||||
+76
-7
@@ -37,9 +37,9 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
AutoLilvNode(const LilvNode *node)
|
AutoLilvNode(const LilvNode *node)
|
||||||
: node(const_cast<LilvNode*>(node))
|
: node(const_cast<LilvNode *>(node))
|
||||||
{
|
{
|
||||||
isConst = true;
|
isConst = true;
|
||||||
}
|
}
|
||||||
AutoLilvNode(LilvNode *node)
|
AutoLilvNode(LilvNode *node)
|
||||||
: node(node)
|
: node(node)
|
||||||
@@ -66,15 +66,17 @@ namespace pipedal
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
operator LilvNode**() {
|
operator LilvNode **()
|
||||||
|
{
|
||||||
Free();
|
Free();
|
||||||
this->isConst = false;
|
this->isConst = false;
|
||||||
return &node;
|
return &node;
|
||||||
}
|
}
|
||||||
operator const LilvNode**() {
|
operator const LilvNode **()
|
||||||
|
{
|
||||||
Free();
|
Free();
|
||||||
this->isConst = true;
|
this->isConst = true;
|
||||||
return (const LilvNode**)&node;
|
return (const LilvNode **)&node;
|
||||||
}
|
}
|
||||||
AutoLilvNode &operator=(AutoLilvNode &&other)
|
AutoLilvNode &operator=(AutoLilvNode &&other)
|
||||||
{
|
{
|
||||||
@@ -83,7 +85,7 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
|
|
||||||
float AsFloat(float defaultValue = 0);
|
float AsFloat(float defaultValue = 0);
|
||||||
int AsInt(int defaultValue = 0);
|
int32_t AsInt(int defaultValue = 0);
|
||||||
bool AsBool(bool defaultValue = false);
|
bool AsBool(bool defaultValue = false);
|
||||||
std::string AsUri();
|
std::string AsUri();
|
||||||
std::string AsString();
|
std::string AsString();
|
||||||
@@ -99,4 +101,71 @@ namespace pipedal
|
|||||||
LilvNode *node = nullptr;
|
LilvNode *node = nullptr;
|
||||||
bool isConst = true;
|
bool isConst = true;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
class AutoLilvNodes
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
AutoLilvNodes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
AutoLilvNodes(const LilvNodes *nodes)
|
||||||
|
: nodes(const_cast<LilvNodes *>(nodes))
|
||||||
|
{
|
||||||
|
isConst = true;
|
||||||
|
}
|
||||||
|
AutoLilvNodes(LilvNodes *nodes)
|
||||||
|
: nodes(nodes)
|
||||||
|
{
|
||||||
|
isConst = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
~AutoLilvNodes() { Free(); }
|
||||||
|
|
||||||
|
operator const LilvNodes *()
|
||||||
|
{
|
||||||
|
return this->nodes;
|
||||||
|
}
|
||||||
|
operator bool()
|
||||||
|
{
|
||||||
|
return this->nodes != nullptr;
|
||||||
|
}
|
||||||
|
const LilvNodes *Get() { return nodes; }
|
||||||
|
|
||||||
|
AutoLilvNodes &operator=(LilvNodes *node)
|
||||||
|
{
|
||||||
|
Free();
|
||||||
|
this->nodes = nodes;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator LilvNodes **()
|
||||||
|
{
|
||||||
|
Free();
|
||||||
|
this->isConst = false;
|
||||||
|
return &nodes;
|
||||||
|
}
|
||||||
|
operator const LilvNodes **()
|
||||||
|
{
|
||||||
|
Free();
|
||||||
|
this->isConst = true;
|
||||||
|
return (const LilvNodes **)&nodes;
|
||||||
|
}
|
||||||
|
AutoLilvNodes &operator=(AutoLilvNodes &&other)
|
||||||
|
{
|
||||||
|
std::swap(this->nodes, other.nodes);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Free()
|
||||||
|
{
|
||||||
|
if (nodes != nullptr && !isConst)
|
||||||
|
lilv_nodes_free(nodes);
|
||||||
|
nodes = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
LilvNodes *nodes = nullptr;
|
||||||
|
bool isConst = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set (PIPEDAL_SOURCES
|
set (PIPEDAL_SOURCES
|
||||||
|
DbDezipper.hpp DbDezipper.cpp
|
||||||
|
WebServerLog.hpp
|
||||||
|
|
||||||
util.hpp util.cpp
|
util.hpp util.cpp
|
||||||
PiPedalUI.hpp
|
PiPedalUI.hpp
|
||||||
PiPedalUI.cpp
|
PiPedalUI.cpp
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
* All rights reserved.
|
||||||
|
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DbDezipper.hpp"
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
|
static const int SEGMENT_SIZE = 64;
|
||||||
|
|
||||||
|
void DbDezipper::SetSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
this->sampleRate = sampleRate;
|
||||||
|
this->dbPerSegment = 96/rate * SEGMENT_SIZE / sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbDezipper::SetRate(float seconds)
|
||||||
|
{
|
||||||
|
this->rate = seconds;
|
||||||
|
this->dbPerSegment = 96/rate * SEGMENT_SIZE / sampleRate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DbDezipper::SetMinDb(float minDb)
|
||||||
|
{
|
||||||
|
this->minDb = minDb;
|
||||||
|
if (targetDb < minDb)
|
||||||
|
{
|
||||||
|
targetDb = minDb;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
if (currentDb < minDb)
|
||||||
|
{
|
||||||
|
currentDb = minDb;
|
||||||
|
x = targetX = 0;
|
||||||
|
dx = 0;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbDezipper::NextSegment()
|
||||||
|
{
|
||||||
|
if (targetDb == currentDb)
|
||||||
|
{
|
||||||
|
x = targetX;
|
||||||
|
dx = 0;
|
||||||
|
if (targetDb <= minDb)
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
count = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (targetDb < currentDb)
|
||||||
|
{
|
||||||
|
currentDb -= dbPerSegment;
|
||||||
|
if (currentDb < targetDb)
|
||||||
|
{
|
||||||
|
currentDb = targetDb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentDb += dbPerSegment;
|
||||||
|
if (currentDb > targetDb)
|
||||||
|
{
|
||||||
|
currentDb = targetDb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->targetX = db2a(currentDb);
|
||||||
|
this->dx = (targetX - x) / SEGMENT_SIZE;
|
||||||
|
this->count = SEGMENT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbDezipper::Reset(float db)
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
if (db <= minDb)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
} else {
|
||||||
|
value = db2a(db);
|
||||||
|
}
|
||||||
|
x = targetX = value;
|
||||||
|
dx = 0;
|
||||||
|
currentDb = db;
|
||||||
|
targetDb = db;
|
||||||
|
count = -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
* All rights reserved.
|
||||||
|
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PiPedalMath.hpp"
|
||||||
|
|
||||||
|
namespace pipedal
|
||||||
|
{
|
||||||
|
class DbDezipper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void SetSampleRate(double rate);
|
||||||
|
void SetRate(float seconds);
|
||||||
|
void Reset(float db = -96);
|
||||||
|
void SetMinDb(float db = -96);
|
||||||
|
|
||||||
|
void SetTarget(float db)
|
||||||
|
{
|
||||||
|
if (db < minDb)
|
||||||
|
db = minDb;
|
||||||
|
if (db != targetDb)
|
||||||
|
{
|
||||||
|
targetDb = db;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsIdle() const { return count < 0;}
|
||||||
|
inline float Tick()
|
||||||
|
{
|
||||||
|
if (count >= 0)
|
||||||
|
{
|
||||||
|
if (count-- <= 0)
|
||||||
|
{
|
||||||
|
NextSegment();
|
||||||
|
}
|
||||||
|
float result = x;
|
||||||
|
x += dx;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float minDb = -96;
|
||||||
|
double sampleRate = 44100;
|
||||||
|
float rate = 0.1;
|
||||||
|
float targetDb = -96;
|
||||||
|
float currentDb = -96;
|
||||||
|
float targetX = 0;
|
||||||
|
float x = 0;
|
||||||
|
float dx = 0;
|
||||||
|
int32_t count = -1;
|
||||||
|
float dbPerSegment;
|
||||||
|
void NextSegment();
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -55,6 +55,7 @@ namespace pipedal
|
|||||||
void JackInitialize(); // from jack server instance.
|
void JackInitialize(); // from jack server instance.
|
||||||
~JackConfiguration();
|
~JackConfiguration();
|
||||||
bool isValid() const { return isValid_;}
|
bool isValid() const { return isValid_;}
|
||||||
|
void isValid(bool value) { isValid_ = value; }
|
||||||
bool isOnboarding() const { return isOnboarding_;}
|
bool isOnboarding() const { return isOnboarding_;}
|
||||||
void isOnboarding(bool value) { isOnboarding_ = value; }
|
void isOnboarding(bool value) { isOnboarding_ = value; }
|
||||||
bool isRestarting() const { return isRestarting_; }
|
bool isRestarting() const { return isRestarting_; }
|
||||||
|
|||||||
+26
-5
@@ -148,7 +148,16 @@ void Lv2Effect::RestoreState(const PedalboardItem&pedalboardItem)
|
|||||||
// Restore state if present.
|
// Restore state if present.
|
||||||
if (this->stateInterface)
|
if (this->stateInterface)
|
||||||
{
|
{
|
||||||
this->stateInterface->Restore(pedalboardItem.lv2State());
|
try {
|
||||||
|
if (pedalboardItem.lv2State().isValid_)
|
||||||
|
{
|
||||||
|
this->stateInterface->Restore(pedalboardItem.lv2State());
|
||||||
|
}
|
||||||
|
} catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
std::string name = pedalboardItem.pluginName();
|
||||||
|
Lv2Log::warning(SS(name << ": " << e.what()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +622,7 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
|||||||
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||||
{
|
{
|
||||||
|
|
||||||
// frame_offset = ev->time.frames; // not really interested.
|
auto frame_offset = ev->time.frames; // not really interested.
|
||||||
|
|
||||||
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
||||||
{
|
{
|
||||||
@@ -649,9 +658,21 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
|||||||
bool Lv2Effect::GetLv2State(Lv2PluginState*state)
|
bool Lv2Effect::GetLv2State(Lv2PluginState*state)
|
||||||
{
|
{
|
||||||
if (!this->stateInterface) return false;
|
if (!this->stateInterface) return false;
|
||||||
|
try {
|
||||||
*state = this->stateInterface->Save();
|
if (this->stateInterface == nullptr)
|
||||||
return true;
|
{
|
||||||
|
state->Erase();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*state = this->stateInterface->Save();
|
||||||
|
state->isValid_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception&e)
|
||||||
|
{
|
||||||
|
state->Erase();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ namespace pipedal
|
|||||||
class Lv2Effect : public IEffect
|
class Lv2Effect : public IEffect
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::unique_ptr<StateInterface> stateInterface;
|
std::unique_ptr<StateInterface> stateInterface;
|
||||||
void RestoreState(const PedalboardItem&pedalboardItem);
|
void RestoreState(const PedalboardItem&pedalboardItem);
|
||||||
|
|
||||||
|
|||||||
+73
-29
@@ -210,6 +210,15 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
|
|||||||
{
|
{
|
||||||
this->pHost = pHost;
|
this->pHost = pHost;
|
||||||
|
|
||||||
|
inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
|
||||||
|
outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
|
||||||
|
inputVolume.SetMinDb(-60);
|
||||||
|
outputVolume.SetMinDb(-60);
|
||||||
|
|
||||||
|
inputVolume.SetTarget(pedalboard.input_volume_db());
|
||||||
|
outputVolume.SetTarget(pedalboard.output_volume_db());
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
|
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
|
||||||
{
|
{
|
||||||
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
|
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
|
||||||
@@ -368,21 +377,32 @@ static void Copy(float *input, float *output, uint32_t samples)
|
|||||||
bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter)
|
bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter)
|
||||||
{
|
{
|
||||||
this->ringBufferWriter = ringBufferWriter;
|
this->ringBufferWriter = ringBufferWriter;
|
||||||
for (int i = 0; i < this->pedalboardInputBuffers.size(); ++i)
|
for (size_t i = 0; i < this->pedalboardInputBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
if (inputBuffers[i] == nullptr)
|
if (inputBuffers[i] == nullptr)
|
||||||
return false;
|
{
|
||||||
Copy(inputBuffers[i], this->pedalboardInputBuffers[i], samples);
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < samples; ++i)
|
||||||
|
{
|
||||||
|
float volume = this->inputVolume.Tick();
|
||||||
|
for (int c = 0; c < this->pedalboardInputBuffers.size(); ++c)
|
||||||
|
{
|
||||||
|
this->pedalboardInputBuffers[c][i] = inputBuffers[c][i]*volume;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < this->processActions.size(); ++i)
|
for (int i = 0; i < this->processActions.size(); ++i)
|
||||||
{
|
{
|
||||||
processActions[i](samples);
|
processActions[i](samples);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < this->pedalboardOutputBuffers.size(); ++i)
|
for (size_t i = 0; i < samples; ++i)
|
||||||
{
|
{
|
||||||
if (outputBuffers[i] == nullptr)
|
float volume = outputVolume.Tick();
|
||||||
return false;
|
for (size_t c = 0; c < this->pedalboardOutputBuffers.size(); ++c)
|
||||||
Copy(this->pedalboardOutputBuffers[i], outputBuffers[i], samples);
|
{
|
||||||
|
outputBuffers[c][i] = this->pedalboardOutputBuffers[c][i]*volume;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -404,35 +424,59 @@ void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled)
|
|||||||
effect->SetBypass(enabled);
|
effect->SetBypass(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples)
|
void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**inputBuffers, float**outputBuffers)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
|
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
|
||||||
{
|
{
|
||||||
int index = vuConfiguration->enabledIndexes[i];
|
int index = vuConfiguration->enabledIndexes[i];
|
||||||
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
|
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
|
||||||
|
if (index == Pedalboard::INPUT_VOLUME_ID)
|
||||||
|
{
|
||||||
|
if (this->pedalboardInputBuffers.size() > 1)
|
||||||
|
{
|
||||||
|
GetInputBuffers();
|
||||||
|
pUpdate->AccumulateInputs(inputBuffers[0],inputBuffers[1],samples);
|
||||||
|
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]),&(this->pedalboardInputBuffers[1][0]),samples); // after outputVolume applied.
|
||||||
|
} else {
|
||||||
|
pUpdate->AccumulateInputs(inputBuffers[0],samples);
|
||||||
|
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]),samples); // after input volume applied.
|
||||||
|
}
|
||||||
|
} else if (index == Pedalboard::OUTPUT_VOLUME_ID)
|
||||||
|
{
|
||||||
|
if (this->pedalboardOutputBuffers.size() > 1)
|
||||||
|
{
|
||||||
|
pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]),&(this->pedalboardOutputBuffers[1][0]),samples);
|
||||||
|
pUpdate->AccumulateOutputs(outputBuffers[0],outputBuffers[1],samples);
|
||||||
|
} else {
|
||||||
|
pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]),samples);
|
||||||
|
pUpdate->AccumulateOutputs(outputBuffers[0],samples);
|
||||||
|
}
|
||||||
|
|
||||||
auto effect = this->realtimeEffects[index];
|
}
|
||||||
|
else {
|
||||||
|
auto effect = this->realtimeEffects[index];
|
||||||
|
|
||||||
if (effect->GetNumberOfInputAudioPorts() == 1)
|
if (effect->GetNumberOfInputAudioPorts() == 1)
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
|
pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateInputs(
|
pUpdate->AccumulateInputs(
|
||||||
effect->GetAudioInputBuffer(0),
|
effect->GetAudioInputBuffer(0),
|
||||||
effect->GetAudioInputBuffer(1), samples);
|
effect->GetAudioInputBuffer(1), samples);
|
||||||
}
|
}
|
||||||
if (effect->GetNumberOfOutputAudioPorts() == 1)
|
if (effect->GetNumberOfOutputAudioPorts() == 1)
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
|
pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pUpdate->AccumulateOutputs(
|
pUpdate->AccumulateOutputs(
|
||||||
effect->GetAudioOutputBuffer(0),
|
effect->GetAudioOutputBuffer(0),
|
||||||
effect->GetAudioOutputBuffer(1),
|
effect->GetAudioOutputBuffer(1),
|
||||||
samples);
|
samples);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <lv2/urid.lv2/urid.h>
|
#include <lv2/urid.lv2/urid.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include "DbDezipper.hpp"
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
@@ -37,6 +38,9 @@ class RealtimeRingBufferWriter;
|
|||||||
class Lv2Pedalboard {
|
class Lv2Pedalboard {
|
||||||
IHost *pHost = nullptr;
|
IHost *pHost = nullptr;
|
||||||
|
|
||||||
|
DbDezipper inputVolume;
|
||||||
|
DbDezipper outputVolume;
|
||||||
|
|
||||||
BufferPool bufferPool;
|
BufferPool bufferPool;
|
||||||
std::vector<float*> pedalboardInputBuffers;
|
std::vector<float*> pedalboardInputBuffers;
|
||||||
std::vector<float*> pedalboardOutputBuffers;
|
std::vector<float*> pedalboardOutputBuffers;
|
||||||
@@ -133,9 +137,11 @@ public:
|
|||||||
|
|
||||||
int GetControlIndex(uint64_t instanceId,const std::string&symbol);
|
int GetControlIndex(uint64_t instanceId,const std::string&symbol);
|
||||||
void SetControlValue(int effectIndex, int portIndex, float value);
|
void SetControlValue(int effectIndex, int portIndex, float value);
|
||||||
|
void SetInputVolume(float value) { this->inputVolume.SetTarget(value);}
|
||||||
|
void SetOutputVolume(float value) { this->outputVolume.SetTarget(value);}
|
||||||
void SetBypass(int effectIndex,bool enabled);
|
void SetBypass(int effectIndex,bool enabled);
|
||||||
|
|
||||||
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples);
|
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**inputBuffers, float**outputBuffers);
|
||||||
|
|
||||||
float GetControlOutputValue(int effectIndex, int portIndex);
|
float GetControlOutputValue(int effectIndex, int portIndex);
|
||||||
|
|
||||||
|
|||||||
+2
-13
@@ -165,19 +165,6 @@ Pedalboard Pedalboard::MakeDefault()
|
|||||||
// copy insanity. but it happens so rarely.
|
// copy insanity. but it happens so rarely.
|
||||||
Pedalboard result;
|
Pedalboard result;
|
||||||
|
|
||||||
auto split = result.MakeSplit();
|
|
||||||
split.topChain().push_back(result.MakeEmptyItem());
|
|
||||||
split.bottomChain().push_back(result.MakeEmptyItem());
|
|
||||||
|
|
||||||
|
|
||||||
result.items().push_back(result.MakeEmptyItem());
|
|
||||||
result.items().push_back(result.MakeEmptyItem());
|
|
||||||
result.items().push_back(result.MakeEmptyItem());
|
|
||||||
|
|
||||||
result.items().push_back(split);
|
|
||||||
|
|
||||||
result.items().push_back(result.MakeEmptyItem());
|
|
||||||
result.items().push_back(result.MakeEmptyItem());
|
|
||||||
result.items().push_back(result.MakeEmptyItem());
|
result.items().push_back(result.MakeEmptyItem());
|
||||||
result.name("Default Preset");
|
result.name("Default Preset");
|
||||||
|
|
||||||
@@ -218,6 +205,8 @@ JSON_MAP_END()
|
|||||||
|
|
||||||
JSON_MAP_BEGIN(Pedalboard)
|
JSON_MAP_BEGIN(Pedalboard)
|
||||||
JSON_MAP_REFERENCE(Pedalboard,name)
|
JSON_MAP_REFERENCE(Pedalboard,name)
|
||||||
|
JSON_MAP_REFERENCE(Pedalboard,input_volume_db)
|
||||||
|
JSON_MAP_REFERENCE(Pedalboard,output_volume_db)
|
||||||
JSON_MAP_REFERENCE(Pedalboard,items)
|
JSON_MAP_REFERENCE(Pedalboard,items)
|
||||||
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|||||||
+8
-1
@@ -90,7 +90,7 @@ public:
|
|||||||
ControlValue*GetControlValue(const std::string&symbol);
|
ControlValue*GetControlValue(const std::string&symbol);
|
||||||
|
|
||||||
bool hasLv2State() const {
|
bool hasLv2State() const {
|
||||||
return lv2State_.values_.size() != 0;
|
return lv2State_.isValid_ != 0;
|
||||||
}
|
}
|
||||||
GETTER_SETTER(instanceId)
|
GETTER_SETTER(instanceId)
|
||||||
GETTER_SETTER_REF(uri)
|
GETTER_SETTER_REF(uri)
|
||||||
@@ -130,11 +130,16 @@ public:
|
|||||||
|
|
||||||
class Pedalboard {
|
class Pedalboard {
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
float input_volume_db_ = 0;
|
||||||
|
float output_volume_db_ = 0;
|
||||||
|
|
||||||
std::vector<PedalboardItem> items_;
|
std::vector<PedalboardItem> items_;
|
||||||
uint64_t nextInstanceId_ = 0;
|
uint64_t nextInstanceId_ = 0;
|
||||||
uint64_t NextInstanceId() { return ++nextInstanceId_; }
|
uint64_t NextInstanceId() { return ++nextInstanceId_; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
||||||
|
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
||||||
bool SetControlValue(long pedalItemId, const std::string &symbol, float value);
|
bool SetControlValue(long pedalItemId, const std::string &symbol, float value);
|
||||||
bool SetItemEnabled(long pedalItemId, bool enabled);
|
bool SetItemEnabled(long pedalItemId, bool enabled);
|
||||||
|
|
||||||
@@ -146,6 +151,8 @@ public:
|
|||||||
|
|
||||||
GETTER_SETTER_REF(name)
|
GETTER_SETTER_REF(name)
|
||||||
GETTER_SETTER_VEC(items)
|
GETTER_SETTER_VEC(items)
|
||||||
|
GETTER_SETTER(input_volume_db)
|
||||||
|
GETTER_SETTER(output_volume_db)
|
||||||
|
|
||||||
|
|
||||||
DECLARE_JSON_MAP(Pedalboard);
|
DECLARE_JSON_MAP(Pedalboard);
|
||||||
|
|||||||
+137
-25
@@ -137,7 +137,7 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
|
|||||||
storage.SetConfigRoot(configuration.GetDocRoot());
|
storage.SetConfigRoot(configuration.GetDocRoot());
|
||||||
storage.SetDataRoot(configuration.GetLocalStoragePath());
|
storage.SetDataRoot(configuration.GetLocalStoragePath());
|
||||||
storage.Initialize();
|
storage.Initialize();
|
||||||
lv2Host.SetPluginStoragePath(storage.GetPluginStorageDirectory());
|
lv2Host.SetPluginStoragePath(storage.GetPluginAudioFileDirectory());
|
||||||
|
|
||||||
this->systemMidiBindings = storage.GetSystemMidiBindings();
|
this->systemMidiBindings = storage.GetSystemMidiBindings();
|
||||||
|
|
||||||
@@ -233,7 +233,30 @@ void PiPedalModel::Load()
|
|||||||
this->jackConfiguration = this->jackConfiguration.JackInitialize();
|
this->jackConfiguration = this->jackConfiguration.JackInitialize();
|
||||||
#else
|
#else
|
||||||
this->jackServerSettings = storage.GetJackServerSettings();
|
this->jackServerSettings = storage.GetJackServerSettings();
|
||||||
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
|
if (jackServerSettings.IsValid())
|
||||||
|
{
|
||||||
|
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
|
||||||
|
if (jackServerSettings.IsValid())
|
||||||
|
{
|
||||||
|
for (size_t retry = 0; retry < 5; ++retry)
|
||||||
|
{
|
||||||
|
// retry until the device comes online.
|
||||||
|
sleep(3);
|
||||||
|
Lv2Log::info("Retrying AlsaInitialize");
|
||||||
|
jackConfiguration.isValid(true);
|
||||||
|
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
|
||||||
|
if (jackConfiguration.isValid())
|
||||||
|
{
|
||||||
|
Lv2Log::info("Retry succeeded.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (this->jackConfiguration.isValid())
|
if (this->jackConfiguration.isValid())
|
||||||
{
|
{
|
||||||
@@ -312,9 +335,12 @@ void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
|
void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
|
||||||
{
|
{
|
||||||
|
|
||||||
IEffect *effect = lv2Pedalboard->GetEffect(pedalItemId);
|
IEffect *effect = lv2Pedalboard->GetEffect(pedalItemId);
|
||||||
if (effect->IsVst3())
|
if (effect->IsVst3())
|
||||||
{
|
{
|
||||||
@@ -354,6 +380,67 @@ void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::SetInputVolume(float value)
|
||||||
|
{
|
||||||
|
PreviewInputVolume(value);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
this->pedalboard.input_volume_db(value);
|
||||||
|
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||||
|
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||||
|
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||||
|
{
|
||||||
|
t[i] = this->subscribers[i];
|
||||||
|
}
|
||||||
|
size_t n = this->subscribers.size();
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
t[i]->OnInputVolumeChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] t;
|
||||||
|
|
||||||
|
this->SetPresetChanged(-1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void PiPedalModel::SetOutputVolume(float value)
|
||||||
|
{
|
||||||
|
PreviewOutputVolume(value);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
this->pedalboard.output_volume_db(value);
|
||||||
|
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||||
|
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||||
|
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||||
|
{
|
||||||
|
t[i] = this->subscribers[i];
|
||||||
|
}
|
||||||
|
size_t n = this->subscribers.size();
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
t[i]->OnOutputVolumeChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] t;
|
||||||
|
|
||||||
|
this->SetPresetChanged(-1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void PiPedalModel::PreviewInputVolume(float value)
|
||||||
|
{
|
||||||
|
audioHost->SetInputVolume(value);
|
||||||
|
}
|
||||||
|
void PiPedalModel::PreviewOutputVolume(float value)
|
||||||
|
{
|
||||||
|
audioHost->SetOutputVolume(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
|
void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -613,17 +700,12 @@ void PiPedalModel::UpdatePluginPresets(const PluginUiPresets &pluginPresets)
|
|||||||
}
|
}
|
||||||
int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &name)
|
int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &name)
|
||||||
{
|
{
|
||||||
auto *item = this->pedalboard.GetItem(instanceId);
|
PedalboardItem *item = this->pedalboard.GetItem(instanceId);
|
||||||
if (!item)
|
if (!item)
|
||||||
{
|
{
|
||||||
throw PiPedalException("Plugin not found.");
|
throw PiPedalException("Plugin not found.");
|
||||||
}
|
}
|
||||||
std::map<std::string, float> values;
|
uint64_t presetId = storage.SavePluginPreset(name, *item);
|
||||||
for (const auto &controlValue : item->controlValues())
|
|
||||||
{
|
|
||||||
values[controlValue.key()] = controlValue.value();
|
|
||||||
}
|
|
||||||
uint64_t presetId = storage.SavePluginPreset(item->uri(), name, values);
|
|
||||||
FirePluginPresetsChanged(item->uri());
|
FirePluginPresetsChanged(item->uri());
|
||||||
return presetId;
|
return presetId;
|
||||||
}
|
}
|
||||||
@@ -1250,7 +1332,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
|
|||||||
for (int i = 0; i < activeVuSubscriptions.size(); ++i)
|
for (int i = 0; i < activeVuSubscriptions.size(); ++i)
|
||||||
{
|
{
|
||||||
auto instanceId = activeVuSubscriptions[i].instanceid;
|
auto instanceId = activeVuSubscriptions[i].instanceid;
|
||||||
if (pedalboard.HasItem(instanceId))
|
if (pedalboard.HasItem(instanceId) || instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID)
|
||||||
{
|
{
|
||||||
addedInstances.insert(activeVuSubscriptions[i].instanceid);
|
addedInstances.insert(activeVuSubscriptions[i].instanceid);
|
||||||
}
|
}
|
||||||
@@ -1629,26 +1711,34 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
|
|||||||
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(pluginInstanceId);
|
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(pluginInstanceId);
|
||||||
if (pedalboardItem != nullptr)
|
if (pedalboardItem != nullptr)
|
||||||
{
|
{
|
||||||
std::vector<ControlValue> controlValues = storage.GetPluginPresetValues(pedalboardItem->uri(), presetInstanceId);
|
PluginPresetValues presetValues = storage.GetPluginPresetValues(pedalboardItem->uri(), presetInstanceId);
|
||||||
audioHost->SetPluginPreset(pluginInstanceId, controlValues);
|
// if the plugin has state, we have to rebuild the pedalboard, since setting state is not thread-safe.
|
||||||
|
|
||||||
for (size_t i = 0; i < controlValues.size(); ++i)
|
for (auto&control : presetValues.controls)
|
||||||
{
|
{
|
||||||
const ControlValue &value = controlValues[i];
|
this->pedalboard.SetControlValue(pluginInstanceId, control.key(), control.value());
|
||||||
this->pedalboard.SetControlValue(pluginInstanceId, value.key(), value.value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
if (!presetValues.state.isValid_)
|
||||||
for (size_t i = 0; i < subscribers.size(); ++i)
|
|
||||||
{
|
{
|
||||||
t[i] = this->subscribers[i];
|
audioHost->SetPluginPreset(pluginInstanceId, presetValues.controls);
|
||||||
|
|
||||||
|
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||||
|
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||||
|
{
|
||||||
|
t[i] = this->subscribers[i];
|
||||||
|
}
|
||||||
|
size_t n = this->subscribers.size();
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
t[i]->OnLoadPluginPreset(pluginInstanceId, presetValues.controls);
|
||||||
|
}
|
||||||
|
delete[] t;
|
||||||
|
} else {
|
||||||
|
pedalboardItem->lv2State(presetValues.state);
|
||||||
|
FirePedalboardChanged(-1); // does a complete reload of both client and audio server.
|
||||||
}
|
}
|
||||||
size_t n = this->subscribers.size();
|
this->SetPresetChanged(-1, true);
|
||||||
for (size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
t[i]->OnLoadPluginPreset(pluginInstanceId, controlValues);
|
|
||||||
}
|
|
||||||
delete[] t;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1875,3 +1965,25 @@ std::vector<std::string> PiPedalModel::GetFileList(const UiFileProperty &filePro
|
|||||||
return std::vector<std::string>(); // don't disclose to users what the problem is.
|
return std::vector<std::string>(); // don't disclose to users what the problem is.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::DeleteSampleFile(const std::filesystem::path &fileName)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
storage.DeleteSampleFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody)
|
||||||
|
{
|
||||||
|
return storage.UploadUserFile(directory,patchProperty,filename,fileBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t PiPedalModel::CreateNewPreset()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
return storage.CreateNewPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ namespace pipedal
|
|||||||
virtual int64_t GetClientId() = 0;
|
virtual int64_t GetClientId() = 0;
|
||||||
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
||||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||||
|
virtual void OnInputVolumeChanged(float value) = 0;
|
||||||
|
virtual void OnOutputVolumeChanged(float value) = 0;
|
||||||
|
|
||||||
virtual void OnLv2StateChanged(int64_t pedalItemId) = 0;
|
virtual void OnLv2StateChanged(int64_t pedalItemId) = 0;
|
||||||
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
||||||
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
|
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
|
||||||
@@ -215,6 +218,13 @@ namespace pipedal
|
|||||||
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||||
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||||
|
|
||||||
|
void SetInputVolume(float value);
|
||||||
|
void SetOutputVolume(float value);
|
||||||
|
void PreviewInputVolume(float value);
|
||||||
|
void PreviewOutputVolume(float value);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||||
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||||
|
|
||||||
@@ -310,6 +320,10 @@ namespace pipedal
|
|||||||
void SetFavorites(const std::map<std::string, bool> &favorites);
|
void SetFavorites(const std::map<std::string, bool> &favorites);
|
||||||
|
|
||||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
||||||
|
|
||||||
|
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||||
|
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody);
|
||||||
|
uint64_t CreateNewPreset();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pipedal.
|
} // namespace pipedal.
|
||||||
+152
-39
@@ -31,6 +31,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include "Ipv6Helpers.hpp"
|
#include "Ipv6Helpers.hpp"
|
||||||
#include "Promise.hpp"
|
#include "Promise.hpp"
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "AdminClient.hpp"
|
#include "AdminClient.hpp"
|
||||||
#include "WifiConfigSettings.hpp"
|
#include "WifiConfigSettings.hpp"
|
||||||
@@ -418,16 +419,45 @@ private:
|
|||||||
|
|
||||||
struct PortMonitorSubscription
|
struct PortMonitorSubscription
|
||||||
{
|
{
|
||||||
|
PortMonitorSubscription(
|
||||||
|
int64_t subscriptionHandle,
|
||||||
|
int64_t instanceId,
|
||||||
|
const std::string& key)
|
||||||
|
:subscriptionHandle(subscriptionHandle),
|
||||||
|
instanceId(instanceId),
|
||||||
|
key(key)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
~PortMonitorSubscription() {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
PortMonitorSubscription() {}
|
||||||
|
PortMonitorSubscription(const PortMonitorSubscription&) = delete;
|
||||||
|
PortMonitorSubscription& operator=(const PortMonitorSubscription&) = delete;
|
||||||
|
void Close() {
|
||||||
|
std::lock_guard lock {pmMutex};
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::mutex pmMutex;
|
||||||
|
|
||||||
|
bool closed = false;
|
||||||
int64_t subscriptionHandle = -1;
|
int64_t subscriptionHandle = -1;
|
||||||
int64_t instanceId = -1;
|
int64_t instanceId = -1;
|
||||||
std::string key;
|
std::string key;
|
||||||
|
|
||||||
float lastSentValue = 0;
|
static constexpr float INVALID_VALUE = std::numeric_limits<float>::max();
|
||||||
|
float lastValue = INVALID_VALUE;
|
||||||
|
float currentValue = INVALID_VALUE;
|
||||||
|
|
||||||
bool pendingValue = false;
|
bool pendingValue = false;
|
||||||
bool waitingForAck = false;
|
bool waitingForAck = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<PortMonitorSubscription> activePortMonitors;
|
std::mutex activePortMonitorsMutex;
|
||||||
|
std::vector<std::shared_ptr<PortMonitorSubscription>> activePortMonitors;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual int64_t GetClientId() { return clientId; }
|
virtual int64_t GetClientId() { return clientId; }
|
||||||
@@ -436,7 +466,7 @@ public:
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
||||||
{
|
{
|
||||||
model.UnmonitorPort(activePortMonitors[i].subscriptionHandle);
|
model.UnmonitorPort(activePortMonitors[i]->subscriptionHandle);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
|
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -701,22 +731,48 @@ public:
|
|||||||
Reply(replyTo, "error", what);
|
Reply(replyTo, "error", what);
|
||||||
}
|
}
|
||||||
|
|
||||||
PortMonitorSubscription *getPortMonitorSubscription(uint64_t subscriptionId)
|
std::shared_ptr<PortMonitorSubscription> getPortMonitorSubscription(uint64_t subscriptionId)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
std::shared_ptr<PortMonitorSubscription> result;
|
||||||
{
|
{
|
||||||
auto *portMonitor = &activePortMonitors[i];
|
std::lock_guard lock (activePortMonitorsMutex);
|
||||||
if (portMonitor->subscriptionHandle == subscriptionId)
|
|
||||||
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
||||||
{
|
{
|
||||||
return portMonitor;
|
if (activePortMonitors[i]->subscriptionHandle == subscriptionId)
|
||||||
|
{
|
||||||
|
result = activePortMonitors[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendMonitorPortMessage(PortMonitorSubscription *subscription, float value)
|
void SendMonitorPortMessage(std::shared_ptr<PortMonitorSubscription> &subscription, float value)
|
||||||
{
|
{
|
||||||
|
// running on RT_output thread, or on Socket thread.
|
||||||
|
{
|
||||||
|
std::lock_guard lock {subscription->pmMutex};
|
||||||
|
if (subscription->closed) return;
|
||||||
|
if (value == subscription->currentValue)
|
||||||
|
return;
|
||||||
|
if (subscription->waitingForAck)
|
||||||
|
{
|
||||||
|
subscription->pendingValue = true;
|
||||||
|
subscription->currentValue = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscription->currentValue = value;
|
||||||
|
subscription->lastValue = value;
|
||||||
|
subscription->waitingForAck = true;
|
||||||
|
}
|
||||||
|
SendMonitorPortMessage_Inner(subscription,value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// post-sync send.
|
||||||
|
void SendMonitorPortMessage_Inner(std::shared_ptr<PortMonitorSubscription> &subscription, float value)
|
||||||
|
{
|
||||||
auto subscriptionHandle_ = subscription->subscriptionHandle;
|
auto subscriptionHandle_ = subscription->subscriptionHandle;
|
||||||
MonitorResultBody body;
|
MonitorResultBody body;
|
||||||
body.subscriptionHandle_ = subscriptionHandle_;
|
body.subscriptionHandle_ = subscriptionHandle_;
|
||||||
@@ -726,17 +782,30 @@ public:
|
|||||||
body,
|
body,
|
||||||
[this, subscriptionHandle_](const bool &result)
|
[this, subscriptionHandle_](const bool &result)
|
||||||
{
|
{
|
||||||
PortMonitorSubscription *subscription = getPortMonitorSubscription(subscriptionHandle_);
|
// running on PiPedalSocket thread.
|
||||||
if (subscription == nullptr) return;
|
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
|
||||||
|
if (!subscription) return;
|
||||||
|
|
||||||
if (subscription->pendingValue)
|
float value;
|
||||||
{
|
{
|
||||||
subscription->pendingValue = false;
|
std::unique_lock lock {subscription->pmMutex};
|
||||||
SendMonitorPortMessage(subscription, subscription->lastSentValue);
|
if (subscription->closed) return;
|
||||||
}
|
if (subscription->pendingValue)
|
||||||
else
|
{
|
||||||
{
|
value = subscription->currentValue;
|
||||||
subscription->waitingForAck = false;
|
subscription->lastValue = value;
|
||||||
|
subscription->pendingValue = false;
|
||||||
|
subscription->waitingForAck = true;
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
SendMonitorPortMessage_Inner(subscription, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subscription->waitingForAck = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[](const std::exception &e)
|
[](const std::exception &e)
|
||||||
@@ -753,31 +822,19 @@ public:
|
|||||||
body.updateRate_,
|
body.updateRate_,
|
||||||
[this](int64_t subscriptionHandle_, float value)
|
[this](int64_t subscriptionHandle_, float value)
|
||||||
{
|
{
|
||||||
PortMonitorSubscription *subscription = getPortMonitorSubscription(subscriptionHandle_);
|
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
|
||||||
|
|
||||||
if (subscription)
|
if (subscription)
|
||||||
{
|
{
|
||||||
if (subscription->waitingForAck)
|
SendMonitorPortMessage(subscription, value);
|
||||||
{
|
|
||||||
if (subscription->lastSentValue != value)
|
|
||||||
{
|
|
||||||
subscription->lastSentValue = value;
|
|
||||||
subscription->pendingValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
subscription->lastSentValue = value;
|
|
||||||
|
|
||||||
subscription->waitingForAck = true;
|
|
||||||
SendMonitorPortMessage(subscription, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
activePortMonitors.push_back(
|
{
|
||||||
PortMonitorSubscription{subscriptionHandle, body.instanceId_, body.key_});
|
std::lock_guard lock(activePortMonitorsMutex);
|
||||||
|
activePortMonitors.push_back(std::make_shared<PortMonitorSubscription>(subscriptionHandle, body.instanceId_, body.key_));
|
||||||
|
}
|
||||||
|
|
||||||
this->Reply(replyTo, "monitorPort", subscriptionHandle);
|
this->Reply(replyTo, "monitorPort", subscriptionHandle);
|
||||||
}
|
}
|
||||||
@@ -831,7 +888,37 @@ public:
|
|||||||
ControlChangedBody message;
|
ControlChangedBody message;
|
||||||
pReader->read(&message);
|
pReader->read(&message);
|
||||||
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||||
|
} else if (message == "setControl")
|
||||||
|
{
|
||||||
|
ControlChangedBody message;
|
||||||
|
pReader->read(&message);
|
||||||
|
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||||
|
}
|
||||||
|
else if (message == "setInputVolume")
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
pReader->read(&value);
|
||||||
|
this->model.SetInputVolume(value);
|
||||||
}
|
}
|
||||||
|
else if (message == "setOutputVolume")
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
pReader->read(&value);
|
||||||
|
this->model.SetOutputVolume(value);
|
||||||
|
}
|
||||||
|
else if (message == "previewInputVolume")
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
pReader->read(&value);
|
||||||
|
this->model.PreviewInputVolume(value);
|
||||||
|
}
|
||||||
|
else if (message == "previewOutputVolume")
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
pReader->read(&value);
|
||||||
|
this->model.PreviewOutputVolume(value);
|
||||||
|
}
|
||||||
|
|
||||||
else if (message == "listenForMidiEvent")
|
else if (message == "listenForMidiEvent")
|
||||||
{
|
{
|
||||||
ListenForMidiEventBody body;
|
ListenForMidiEventBody body;
|
||||||
@@ -1233,11 +1320,14 @@ public:
|
|||||||
pReader->read(&subscriptionHandle);
|
pReader->read(&subscriptionHandle);
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
std::lock_guard guard(activePortMonitorsMutex);
|
||||||
for (auto i = this->activePortMonitors.begin(); i != this->activePortMonitors.end(); ++i)
|
for (auto i = this->activePortMonitors.begin(); i != this->activePortMonitors.end(); ++i)
|
||||||
{
|
{
|
||||||
if ((*i).subscriptionHandle == subscriptionHandle)
|
if ((*i)->subscriptionHandle == subscriptionHandle)
|
||||||
{
|
{
|
||||||
|
auto subscription = (*i);
|
||||||
|
subscription->Close();
|
||||||
|
|
||||||
this->activePortMonitors.erase(i);
|
this->activePortMonitors.erase(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1311,6 +1401,19 @@ public:
|
|||||||
pReader->read(&fileProperty);
|
pReader->read(&fileProperty);
|
||||||
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
||||||
this->Reply(replyTo,"requestFileList",list);
|
this->Reply(replyTo,"requestFileList",list);
|
||||||
|
}
|
||||||
|
else if (message == "newPreset")
|
||||||
|
{
|
||||||
|
int64_t presetId = this->model.CreateNewPreset();
|
||||||
|
this->Reply(replyTo,"newPreset",presetId);
|
||||||
|
}
|
||||||
|
else if (message == "deleteUserFile")
|
||||||
|
{
|
||||||
|
std::string fileName;
|
||||||
|
pReader->read(&fileName);
|
||||||
|
|
||||||
|
this->model.DeleteSampleFile(fileName);
|
||||||
|
this->Reply(replyTo,"deleteUserFile",true);
|
||||||
}
|
}
|
||||||
else if (message == "setOnboarding")
|
else if (message == "setOnboarding")
|
||||||
{
|
{
|
||||||
@@ -1518,6 +1621,16 @@ private:
|
|||||||
body.value_ = value;
|
body.value_ = value;
|
||||||
Send("onControlChanged", body);
|
Send("onControlChanged", body);
|
||||||
}
|
}
|
||||||
|
virtual void OnInputVolumeChanged(float value)
|
||||||
|
{
|
||||||
|
ControlChangedBody body;
|
||||||
|
Send("onInputVolumeChanged", value);
|
||||||
|
}
|
||||||
|
virtual void OnOutputVolumeChanged(float value)
|
||||||
|
{
|
||||||
|
ControlChangedBody body;
|
||||||
|
Send("onOutputVolumeChanged", value);
|
||||||
|
}
|
||||||
|
|
||||||
class DeferredValue
|
class DeferredValue
|
||||||
{
|
{
|
||||||
|
|||||||
+70
-18
@@ -39,7 +39,7 @@ PiPedalUI::PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::files
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
UiFileProperty::ptr fileUI = std::make_shared<UiFileProperty>(pHost, fileNode, resourcePath);
|
UiFileProperty::ptr fileUI = std::make_shared<UiFileProperty>(pHost, fileNode, resourcePath);
|
||||||
this->fileProperites_.push_back(std::move(fileUI));
|
this->fileProperties_.push_back(std::move(fileUI));
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
@@ -69,18 +69,18 @@ PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
|
|||||||
{
|
{
|
||||||
auto pWorld = pHost->getWorld();
|
auto pWorld = pHost->getWorld();
|
||||||
|
|
||||||
AutoLilvNode name = lilv_world_get(
|
AutoLilvNode label = lilv_world_get(
|
||||||
pWorld,
|
pWorld,
|
||||||
node,
|
node,
|
||||||
pHost->lilvUris.lv2Core__name,
|
pHost->lilvUris.rdfs__label,
|
||||||
nullptr);
|
nullptr);
|
||||||
if (name)
|
if (label)
|
||||||
{
|
{
|
||||||
this->name_ = name.AsString();
|
this->label_ = label.AsString();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw std::logic_error("pipedal_ui:fileType is missing name property.");
|
throw std::logic_error("pipedal_ui:fileType is missing label property.");
|
||||||
}
|
}
|
||||||
AutoLilvNode fileExtension = lilv_world_get(
|
AutoLilvNode fileExtension = lilv_world_get(
|
||||||
pWorld,
|
pWorld,
|
||||||
@@ -103,7 +103,7 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
|
|||||||
AutoLilvNode name = lilv_world_get(
|
AutoLilvNode name = lilv_world_get(
|
||||||
pWorld,
|
pWorld,
|
||||||
node,
|
node,
|
||||||
pHost->lilvUris.lv2Core__name,
|
pHost->lilvUris.lv2core__name,
|
||||||
nullptr);
|
nullptr);
|
||||||
if (name)
|
if (name)
|
||||||
{
|
{
|
||||||
@@ -113,6 +113,19 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
|
|||||||
{
|
{
|
||||||
this->name_ = "File";
|
this->name_ = "File";
|
||||||
}
|
}
|
||||||
|
AutoLilvNode index = lilv_world_get(
|
||||||
|
pWorld,
|
||||||
|
node,
|
||||||
|
pHost->lilvUris.lv2core__index,
|
||||||
|
nullptr);
|
||||||
|
if (index)
|
||||||
|
{
|
||||||
|
this->index_ = name.AsInt();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->index_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
AutoLilvNode directory = lilv_world_get(
|
AutoLilvNode directory = lilv_world_get(
|
||||||
pWorld,
|
pWorld,
|
||||||
@@ -192,11 +205,31 @@ bool UiFileProperty::IsDirectoryNameValid(const std::string &value)
|
|||||||
{
|
{
|
||||||
if (value.length() == 0)
|
if (value.length() == 0)
|
||||||
return false;
|
return false;
|
||||||
return IsAlphaNumeric(value);
|
if (value.find_first_of('/') != std::string::npos)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.find_first_of('\\') != std::string::npos)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.find_first_of("::") != std::string::npos)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.find_first_of(":") != std::string::npos)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UiFileProperty::IsValidExtension(const std::string &extension) const
|
bool UiFileProperty::IsValidExtension(const std::string &extension) const
|
||||||
{
|
{
|
||||||
|
if (fileTypes_.size() == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto &fileType : fileTypes_)
|
for (auto &fileType : fileTypes_)
|
||||||
{
|
{
|
||||||
@@ -221,39 +254,57 @@ UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node)
|
|||||||
// ]
|
// ]
|
||||||
LilvWorld *pWorld = pHost->getWorld();
|
LilvWorld *pWorld = pHost->getWorld();
|
||||||
|
|
||||||
AutoLilvNode portIndex = lilv_world_get(pWorld,node,pHost->lilvUris.ui__portIndex,nullptr);
|
AutoLilvNode portIndex = lilv_world_get(pWorld, node, pHost->lilvUris.ui__portIndex, nullptr);
|
||||||
if (!portIndex)
|
if (!portIndex)
|
||||||
{
|
{
|
||||||
this->portIndex_ = -1;
|
this->portIndex_ = -1;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this->portIndex_ = (uint32_t)lilv_node_as_int(portIndex);
|
this->portIndex_ = (uint32_t)lilv_node_as_int(portIndex);
|
||||||
}
|
}
|
||||||
AutoLilvNode symbol = lilv_world_get(pWorld,node,pHost->lilvUris.lv2__symbol,nullptr);
|
AutoLilvNode symbol = lilv_world_get(pWorld, node, pHost->lilvUris.lv2__symbol, nullptr);
|
||||||
if (!symbol)
|
if (!symbol)
|
||||||
{
|
{
|
||||||
this->symbol_ = "";
|
this->symbol_ = "";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this->symbol_ = symbol.AsString();
|
this->symbol_ = symbol.AsString();
|
||||||
}
|
}
|
||||||
AutoLilvNode plugin = lilv_world_get(pWorld,node,pHost->lilvUris.ui__plugin,nullptr);
|
AutoLilvNode plugin = lilv_world_get(pWorld, node, pHost->lilvUris.ui__plugin, nullptr);
|
||||||
if (!plugin)
|
if (!plugin)
|
||||||
{
|
{
|
||||||
this->plugin_ = "";
|
this->plugin_ = "";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this->plugin_ = plugin.AsUri();
|
this->plugin_ = plugin.AsUri();
|
||||||
}
|
}
|
||||||
AutoLilvNode protocol = lilv_world_get(pWorld,node,pHost->lilvUris.ui__protocol,nullptr);
|
AutoLilvNode protocol = lilv_world_get(pWorld, node, pHost->lilvUris.ui__protocol, nullptr);
|
||||||
if (!protocol)
|
if (!protocol)
|
||||||
{
|
{
|
||||||
this->protocol_ = "";
|
this->protocol_ = "";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this->protocol_ = protocol.AsUri();
|
this->protocol_ = protocol.AsUri();
|
||||||
}
|
}
|
||||||
if (this->portIndex_ == -1 &&this->symbol_ == "")
|
if (this->portIndex_ == -1 && this->symbol_ == "")
|
||||||
{
|
{
|
||||||
pHost->LogWarning("ui:portNotification specifies neither a ui:portIndex nor an lv2:symbol.");
|
pHost->LogWarning("ui:portNotification specifies neither a ui:portIndex nor an lv2:symbol.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UiFileProperty::UiFileProperty(const std::string &name, const std::string &patchProperty, const std::string &directory)
|
||||||
|
: name_(name),
|
||||||
|
patchProperty_(patchProperty),
|
||||||
|
directory_(directory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
PiPedalUI::PiPedalUI(std::vector<UiFileProperty::ptr> &&fileProperties)
|
||||||
|
{
|
||||||
|
this->fileProperties_ = std::move(fileProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_MAP_BEGIN(UiPortNotification)
|
JSON_MAP_BEGIN(UiPortNotification)
|
||||||
@@ -264,12 +315,13 @@ JSON_MAP_REFERENCE(UiPortNotification, protocol)
|
|||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
JSON_MAP_BEGIN(PiPedalFileType)
|
JSON_MAP_BEGIN(PiPedalFileType)
|
||||||
JSON_MAP_REFERENCE(PiPedalFileType, name)
|
JSON_MAP_REFERENCE(PiPedalFileType, label)
|
||||||
JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
|
JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
JSON_MAP_BEGIN(UiFileProperty)
|
JSON_MAP_BEGIN(UiFileProperty)
|
||||||
JSON_MAP_REFERENCE(UiFileProperty, name)
|
JSON_MAP_REFERENCE(UiFileProperty, name)
|
||||||
|
JSON_MAP_REFERENCE(UiFileProperty, index)
|
||||||
JSON_MAP_REFERENCE(UiFileProperty, directory)
|
JSON_MAP_REFERENCE(UiFileProperty, directory)
|
||||||
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
|
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
|
||||||
JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
|
JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
|
||||||
|
|||||||
+10
-4
@@ -57,7 +57,7 @@ namespace pipedal {
|
|||||||
|
|
||||||
class PiPedalFileType {
|
class PiPedalFileType {
|
||||||
private:
|
private:
|
||||||
std::string name_;
|
std::string label_;
|
||||||
std::string fileExtension_;
|
std::string fileExtension_;
|
||||||
public:
|
public:
|
||||||
PiPedalFileType() { }
|
PiPedalFileType() { }
|
||||||
@@ -65,7 +65,7 @@ namespace pipedal {
|
|||||||
|
|
||||||
static std::vector<PiPedalFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
|
static std::vector<PiPedalFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
|
||||||
|
|
||||||
const std::string& name() const { return name_;}
|
const std::string& label() const { return label_;}
|
||||||
const std::string &fileExtension() const { return fileExtension_; }
|
const std::string &fileExtension() const { return fileExtension_; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -94,6 +94,7 @@ namespace pipedal {
|
|||||||
class UiFileProperty {
|
class UiFileProperty {
|
||||||
private:
|
private:
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
std::int64_t index_ = -1;
|
||||||
std::string directory_;
|
std::string directory_;
|
||||||
std::vector<PiPedalFileType> fileTypes_;
|
std::vector<PiPedalFileType> fileTypes_;
|
||||||
std::string patchProperty_;
|
std::string patchProperty_;
|
||||||
@@ -101,9 +102,11 @@ namespace pipedal {
|
|||||||
using ptr = std::shared_ptr<UiFileProperty>;
|
using ptr = std::shared_ptr<UiFileProperty>;
|
||||||
UiFileProperty() { }
|
UiFileProperty() { }
|
||||||
UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
|
UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
|
||||||
|
UiFileProperty(const std::string&name, const std::string&patchProperty,const std::string &directory);
|
||||||
|
|
||||||
|
|
||||||
const std::string &name() const { return name_; }
|
const std::string &name() const { return name_; }
|
||||||
|
int64_t index() const { return index_; }
|
||||||
const std::string &directory() const { return directory_; }
|
const std::string &directory() const { return directory_; }
|
||||||
|
|
||||||
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
|
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
|
||||||
@@ -120,9 +123,12 @@ namespace pipedal {
|
|||||||
public:
|
public:
|
||||||
using ptr = std::shared_ptr<PiPedalUI>;
|
using ptr = std::shared_ptr<PiPedalUI>;
|
||||||
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
|
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
|
||||||
|
|
||||||
|
PiPedalUI(std::vector<UiFileProperty::ptr> &&fileProperites);
|
||||||
|
|
||||||
const std::vector<UiFileProperty::ptr>& fileProperties() const
|
const std::vector<UiFileProperty::ptr>& fileProperties() const
|
||||||
{
|
{
|
||||||
return fileProperites_;
|
return fileProperties_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
|
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
|
||||||
@@ -140,7 +146,7 @@ namespace pipedal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<UiFileProperty::ptr> fileProperites_;
|
std::vector<UiFileProperty::ptr> fileProperties_;
|
||||||
std::vector<UiPortNotification::ptr> portNotifications_;
|
std::vector<UiPortNotification::ptr> portNotifications_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+245
-109
@@ -66,26 +66,31 @@ using namespace pipedal;
|
|||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
static const char *LV2_AUDIO_PORT = P_LV2_CORE_URI "AudioPort";
|
|
||||||
static const char *LV2_PLUGIN = P_LV2_CORE_URI "Plugin";
|
// in ttl files, but not header files.
|
||||||
static const char *LV2_CONTROL_PORT = P_LV2_CORE_URI "ControlPort";
|
|
||||||
static const char *LV2_INPUT_PORT = P_LV2_CORE_URI "InputPort";
|
|
||||||
static const char *LV2_OUTPUT_PORT = P_LV2_CORE_URI "OutputPort";
|
|
||||||
static const char *LV2_INTEGER = P_LV2_CORE_URI "integer";
|
|
||||||
static const char *LV2_ENUMERATION = P_LV2_CORE_URI "enumeration";
|
|
||||||
static const char *LV2_PORT_LOGARITHMIC = P_LV2_PPROPS "logarithmic";
|
|
||||||
static const char *LV2_PORT_RANGE_STEPS = P_LV2_PPROPS "rangeSteps";
|
|
||||||
static const char *LV2_PORT_TRIGGER = P_LV2_PPROPS "trigger";
|
|
||||||
static const char *LV2_PORT_DISPLAY_PRIORITY = P_LV2_PPROPS "displayPriority";
|
|
||||||
static const char *LV2_MIDI_PLUGIN = "http://lv2plug.in/ns/lv2core#MIDIPlugin";
|
static const char *LV2_MIDI_PLUGIN = "http://lv2plug.in/ns/lv2core#MIDIPlugin";
|
||||||
|
|
||||||
static const char *LV2_ATOM_PORT = "http://lv2plug.in/ns/ext/atom#AtomPort";
|
// static const char *LV2_AUDIO_PORT = P_LV2_CORE_URI "AudioPort";
|
||||||
static const char *LV2_ATOM_SEQUENCE = "http://lv2plug.in/ns/ext/atom#Sequence";
|
// static const char *LV2_PLUGIN = P_LV2_CORE_URI "Plugin";
|
||||||
static const char *LV2_ATOM_SOUND = "http://lv2plug.in/ns/ext/atom#Sound";
|
// static const char *LV2_CONTROL_PORT = P_LV2_CORE_URI "ControlPort";
|
||||||
static const char *LV2_ATOM_VECTOR = "http://lv2plug.in/ns/ext/atom#Vector";
|
// static const char *LV2_INPUT_PORT = P_LV2_CORE_URI "InputPort";
|
||||||
static const char *LV2_ATOM_STRING = "http://lv2plug.in/ns/ext/atom#String";
|
// static const char *LV2_OUTPUT_PORT = P_LV2_CORE_URI "OutputPort";
|
||||||
static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
|
// static const char *LV2_INTEGER = P_LV2_CORE_URI "integer";
|
||||||
static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
|
// static const char *LV2_ENUMERATION = P_LV2_CORE_URI "enumeration";
|
||||||
|
// static const char *LV2_PORT_LOGARITHMIC = P_LV2_PPROPS "logarithmic";
|
||||||
|
// static const char *LV2_PORT_RANGE_STEPS = P_LV2_PPROPS "rangeSteps";
|
||||||
|
// static const char *LV2_PORT_TRIGGER = P_LV2_PPROPS "trigger";
|
||||||
|
// static const char *LV2_PORT_DISPLAY_PRIORITY = P_LV2_PPROPS "displayPriority";
|
||||||
|
|
||||||
|
|
||||||
|
// static const char *LV2_ATOM_PORT = "http://lv2plug.in/ns/ext/atom#AtomPort";
|
||||||
|
// static const char *LV2_ATOM_SEQUENCE = "http://lv2plug.in/ns/ext/atom#Sequence";
|
||||||
|
// static const char *LV2_ATOM_SOUND = "http://lv2plug.in/ns/ext/atom#Sound";
|
||||||
|
// static const char *LV2_ATOM_VECTOR = "http://lv2plug.in/ns/ext/atom#Vector";
|
||||||
|
// static const char *LV2_ATOM_STRING = "http://lv2plug.in/ns/ext/atom#String";
|
||||||
|
// static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
|
||||||
|
// static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
|
||||||
|
|
||||||
class PluginHost::Urids
|
class PluginHost::Urids
|
||||||
{
|
{
|
||||||
@@ -117,25 +122,35 @@ void PluginHost::SetConfiguration(const PiPedalConfiguration &configuration)
|
|||||||
|
|
||||||
void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||||
{
|
{
|
||||||
rdfsComment = lilv_new_uri(pWorld, PluginHost::RDFS_COMMENT_URI);
|
rdfs__Comment = lilv_new_uri(pWorld, PluginHost::RDFS__comment);
|
||||||
logarithic_uri = lilv_new_uri(pWorld, LV2_PORT_LOGARITHMIC);
|
rdfs__range = lilv_new_uri(pWorld,PluginHost::RDFS__range);
|
||||||
display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY);
|
port_logarithmic = lilv_new_uri(pWorld, LV2_PORT_PROPS__logarithmic);
|
||||||
range_steps_uri = lilv_new_uri(pWorld, LV2_PORT_RANGE_STEPS);
|
port__display_priority = lilv_new_uri(pWorld, LV2_PORT_PROPS__displayPriority);
|
||||||
integer_property_uri = lilv_new_uri(pWorld, LV2_INTEGER);
|
port_range_steps = lilv_new_uri(pWorld, LV2_PORT_PROPS__rangeSteps);
|
||||||
enumeration_property_uri = lilv_new_uri(pWorld, LV2_ENUMERATION);
|
integer_property_uri = lilv_new_uri(pWorld, LV2_CORE__integer);
|
||||||
toggle_property_uri = lilv_new_uri(pWorld, LV2_CORE__toggled);
|
enumeration_property_uri = lilv_new_uri(pWorld, LV2_CORE__enumeration);
|
||||||
not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
|
core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled);
|
||||||
midiEventNode = lilv_new_uri(pWorld, LV2_MIDI_EVENT);
|
core__connectionOptional = lilv_new_uri(pWorld,LV2_CORE__connectionOptional);
|
||||||
designationNode = lilv_new_uri(pWorld, LV2_DESIGNATION);
|
portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
|
||||||
portGroupUri = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
|
midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent);
|
||||||
unitsUri = lilv_new_uri(pWorld, LV2_UNITS__unit);
|
core__designation = lilv_new_uri(pWorld, LV2_CORE__designation);
|
||||||
bufferType_uri = lilv_new_uri(pWorld, LV2_ATOM__bufferType);
|
portgroups__group = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
|
||||||
pset_Preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset);
|
units__unit = lilv_new_uri(pWorld, LV2_UNITS__unit);
|
||||||
rdfs_label = lilv_new_uri(pWorld, LILV_NS_RDFS "label");
|
|
||||||
symbolUri = lilv_new_uri(pWorld, LV2_CORE__symbol);
|
invada_units__unit = lilv_new_uri(pWorld, "http://lv2plug.in/ns/extension/units#unit"); // a typo in invada plugin ttl files.
|
||||||
nameUri = lilv_new_uri(pWorld, LV2_CORE__name);
|
invada_portprops__logarithmic = lilv_new_uri(pWorld, "http://lv2plug.in/ns/dev/extportinfo#logarithmic"); // a typo in invada plugin ttl files.
|
||||||
|
|
||||||
|
atom__bufferType = lilv_new_uri(pWorld, LV2_ATOM__bufferType);
|
||||||
|
atom__Path = lilv_new_uri(pWorld, LV2_ATOM__Path);
|
||||||
|
presets__preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset);
|
||||||
|
rdfs__label = lilv_new_uri(pWorld, LILV_NS_RDFS "label");
|
||||||
|
|
||||||
|
lv2core__symbol = lilv_new_uri(pWorld, LV2_CORE__symbol);
|
||||||
|
lv2core__name = lilv_new_uri(pWorld, LV2_CORE__name);
|
||||||
|
lv2core__index = lilv_new_uri(pWorld, LV2_CORE__index);
|
||||||
|
lv2core__Parameter = lilv_new_uri(pWorld,LV2_CORE_PREFIX "Parameter");
|
||||||
|
|
||||||
|
|
||||||
lv2Core__name = lilv_new_uri(pWorld, LV2_CORE__name);
|
|
||||||
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
|
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
|
||||||
pipedalUI__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
|
pipedalUI__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
|
||||||
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
|
pipedalUI__patchProperty = lilv_new_uri(pWorld, PIPEDAL_UI__patchProperty);
|
||||||
@@ -173,27 +188,30 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
|||||||
time_speed = lilv_new_uri(pWorld, LV2_TIME__speed);
|
time_speed = lilv_new_uri(pWorld, LV2_TIME__speed);
|
||||||
|
|
||||||
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
|
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
|
||||||
|
|
||||||
|
patch__writable = lilv_new_uri(pWorld,LV2_PATCH__writable);
|
||||||
|
patch__readable = lilv_new_uri(pWorld,LV2_PATCH__readable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginHost::LilvUris::Free()
|
void PluginHost::LilvUris::Free()
|
||||||
{
|
{
|
||||||
rdfsComment.Free();
|
rdfs__Comment.Free();
|
||||||
logarithic_uri.Free();
|
port_logarithmic.Free();
|
||||||
display_priority_uri.Free();
|
port__display_priority.Free();
|
||||||
range_steps_uri.Free();
|
port_range_steps.Free();
|
||||||
integer_property_uri.Free();
|
integer_property_uri.Free();
|
||||||
enumeration_property_uri.Free();
|
enumeration_property_uri.Free();
|
||||||
toggle_property_uri.Free();
|
core__toggled.Free();
|
||||||
not_on_gui_property_uri.Free();
|
portprops__not_on_gui_property_uri.Free();
|
||||||
midiEventNode.Free();
|
midi__event.Free();
|
||||||
designationNode.Free();
|
core__designation.Free();
|
||||||
portGroupUri.Free();
|
portgroups__group.Free();
|
||||||
unitsUri.Free();
|
units__unit.Free();
|
||||||
bufferType_uri.Free();
|
atom__bufferType.Free();
|
||||||
pset_Preset.Free();
|
presets__preset.Free();
|
||||||
rdfs_label.Free();
|
rdfs__label.Free();
|
||||||
symbolUri.Free();
|
lv2core__symbol.Free();
|
||||||
nameUri.Free();
|
lv2core__name.Free();
|
||||||
|
|
||||||
time_Position.Free();
|
time_Position.Free();
|
||||||
time_barBeat.Free();
|
time_barBeat.Free();
|
||||||
@@ -574,13 +592,18 @@ static std::vector<std::string> nodeAsStringArray(const LilvNodes *nodes)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *PluginHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
|
const char *PluginHost::RDFS__comment = "http://www.w3.org/2000/01/rdf-schema#"
|
||||||
"comment";
|
"comment";
|
||||||
|
|
||||||
|
const char *PluginHost::RDFS__range = "http://www.w3.org/2000/01/rdf-schema#"
|
||||||
|
"range";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
LilvNode *PluginHost::get_comment(const std::string &uri)
|
LilvNode *PluginHost::get_comment(const std::string &uri)
|
||||||
{
|
{
|
||||||
AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
|
AutoLilvNode uriNode = lilv_new_uri(pWorld, uri.c_str());
|
||||||
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr);
|
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfs__Comment, nullptr);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +614,7 @@ static bool ports_sort_compare(std::shared_ptr<Lv2PortInfo> &p1, const std::shar
|
|||||||
|
|
||||||
bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin)
|
bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin)
|
||||||
{
|
{
|
||||||
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset);
|
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.presets__preset);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
LILV_FOREACH(nodes, iNode, nodes)
|
LILV_FOREACH(nodes, iNode, nodes)
|
||||||
{
|
{
|
||||||
@@ -601,9 +624,65 @@ bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plu
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host,const LilvPlugin*pPlugin)
|
||||||
|
{
|
||||||
|
// example:
|
||||||
|
|
||||||
|
// <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>
|
||||||
|
// a lv2:Parameter;
|
||||||
|
// rdfs:label "Model";
|
||||||
|
// rdfs:range atom:Path.
|
||||||
|
// ...
|
||||||
|
// patch:writable <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>;
|
||||||
|
|
||||||
|
LilvWorld* pWorld = lv2Host->getWorld();
|
||||||
|
AutoLilvNode pluginUri = lilv_plugin_get_uri(pPlugin);
|
||||||
|
AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld,pluginUri,lv2Host->lilvUris.patch__writable,nullptr);
|
||||||
|
|
||||||
|
std::vector<UiFileProperty::ptr> fileProperties;
|
||||||
|
|
||||||
|
LILV_FOREACH(nodes, iNode, patchWritables)
|
||||||
|
{
|
||||||
|
AutoLilvNode propertyUri = lilv_nodes_get(patchWritables, iNode);
|
||||||
|
if (propertyUri)
|
||||||
|
{
|
||||||
|
// isA lv2:Parameter?
|
||||||
|
if (lilv_world_ask(pWorld,propertyUri,lv2Host->lilvUris.isA,lv2Host->lilvUris.lv2core__Parameter))
|
||||||
|
{
|
||||||
|
// rfs:range atom:Path?
|
||||||
|
if (lilv_world_ask(pWorld,propertyUri,lv2Host->lilvUris.rdfs__range,lv2Host->lilvUris.atom__Path))
|
||||||
|
{
|
||||||
|
AutoLilvNode label = lilv_world_get(pWorld,propertyUri,lv2Host->lilvUris.rdfs__label,nullptr);
|
||||||
|
std::string strLabel = label.AsString();
|
||||||
|
if (strLabel.length() != 0)
|
||||||
|
{
|
||||||
|
std::filesystem::path path = this->bundle_path();
|
||||||
|
path = path.parent_path();
|
||||||
|
std::string lv2DirectoryName = path.filename().string();
|
||||||
|
// we have a valid path property!
|
||||||
|
fileProperties.push_back(
|
||||||
|
std::make_shared<UiFileProperty>(
|
||||||
|
strLabel,propertyUri.AsUri(),lv2DirectoryName)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (fileProperties.size() != 0)
|
||||||
|
{
|
||||||
|
return std::make_shared<PiPedalUI>(std::move(fileProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::shared_ptr<PiPedalUI>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
|
Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
|
||||||
{
|
{
|
||||||
|
|
||||||
AutoLilvNode bundleUriNode = lilv_plugin_get_bundle_uri(pPlugin);
|
AutoLilvNode bundleUriNode = lilv_plugin_get_bundle_uri(pPlugin);
|
||||||
if (!bundleUriNode)
|
if (!bundleUriNode)
|
||||||
throw std::logic_error("Invalid bundle uri.");
|
throw std::logic_error("Invalid bundle uri.");
|
||||||
@@ -698,15 +777,39 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
|||||||
if (pipedalUINode)
|
if (pipedalUINode)
|
||||||
{
|
{
|
||||||
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
|
this->piPedalUI_ = std::make_shared<PiPedalUI>(lv2Host, pipedalUINode, std::filesystem::path(bundlePath));
|
||||||
|
} else {
|
||||||
|
// look for
|
||||||
|
this->piPedalUI_ = FindWritablePathProperties(lv2Host,pPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nInputs = 0;
|
||||||
|
for (size_t i = 0; i < ports_.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = ports_[i];
|
||||||
|
if (port->is_audio_port() && port->is_input())
|
||||||
|
{
|
||||||
|
if (nInputs >= 2 && !port->connection_optional())
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++nInputs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int nOutputs = 0;
|
||||||
|
for (size_t i = 0; i < ports_.size(); ++i)
|
||||||
|
{
|
||||||
|
auto port = ports_[i];
|
||||||
|
if (port->is_audio_port() && port->is_output())
|
||||||
|
{
|
||||||
|
if (nOutputs >= 2 && !port->connection_optional())
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++nOutputs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// xxx lilv_world_get(pWorld,pluginUri,);
|
|
||||||
// for (auto&portInfo: ports_)
|
|
||||||
// {
|
|
||||||
// if (portInfo->is_control_port() && portInfo->is_output())
|
|
||||||
// {
|
|
||||||
// std::cout << "Dbg: " << "Has an output control port. " << this->uri_ << std::endl;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
this->is_valid_ = isValid;
|
this->is_valid_ = isValid;
|
||||||
}
|
}
|
||||||
@@ -724,9 +827,11 @@ std::vector<std::string> supportedFeatures = {
|
|||||||
LV2_STATE__loadDefaultState,
|
LV2_STATE__loadDefaultState,
|
||||||
LV2_STATE__makePath,
|
LV2_STATE__makePath,
|
||||||
LV2_STATE__mapPath,
|
LV2_STATE__mapPath,
|
||||||
|
LV2_CORE__inPlaceBroken,
|
||||||
|
|
||||||
// UI features that we can ignore, since we won't load their ui.
|
// UI features that we can ignore, since we won't load their ui.
|
||||||
"http://lv2plug.in/ns/extensions/ui#makeResident",
|
"http://lv2plug.in/ns/extensions/ui#makeResident",
|
||||||
|
"http://lv2plug.in/ns/ext/port-props#supportsStrictBounds"
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -798,9 +903,12 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
|||||||
if (default_value_ < min_value_)
|
if (default_value_ < min_value_)
|
||||||
default_value_ = min_value_;
|
default_value_ = min_value_;
|
||||||
|
|
||||||
this->is_logarithmic_ = lilv_port_has_property(plugin, pPort, host->lilvUris.logarithic_uri);
|
this->is_logarithmic_ = lilv_port_has_property(plugin, pPort, host->lilvUris.port_logarithmic);
|
||||||
|
|
||||||
NodesAutoFree priority_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.display_priority_uri);
|
// typo in invada plugins.
|
||||||
|
this->is_logarithmic_ |= lilv_port_has_property(plugin, pPort, host->lilvUris.invada_portprops__logarithmic);
|
||||||
|
|
||||||
|
NodesAutoFree priority_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.port__display_priority);
|
||||||
|
|
||||||
this->display_priority_ = -1;
|
this->display_priority_ = -1;
|
||||||
if (priority_nodes)
|
if (priority_nodes)
|
||||||
@@ -812,7 +920,7 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NodesAutoFree range_steps_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.range_steps_uri);
|
NodesAutoFree range_steps_nodes = lilv_port_get_value(plugin, pPort, host->lilvUris.port_range_steps);
|
||||||
this->range_steps_ = 0;
|
this->range_steps_ = 0;
|
||||||
if (range_steps_nodes)
|
if (range_steps_nodes)
|
||||||
{
|
{
|
||||||
@@ -826,9 +934,9 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
|||||||
|
|
||||||
this->enumeration_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.enumeration_property_uri);
|
this->enumeration_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.enumeration_property_uri);
|
||||||
|
|
||||||
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.toggle_property_uri);
|
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris.core__toggled);
|
||||||
|
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris.portprops__not_on_gui_property_uri);
|
||||||
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris.not_on_gui_property_uri);
|
this->connection_optional_ = lilv_port_has_property(plugin,pPort,host->lilvUris.core__connectionOptional);
|
||||||
|
|
||||||
LilvScalePoints *pScalePoints = lilv_port_get_scale_points(plugin, pPort);
|
LilvScalePoints *pScalePoints = lilv_port_get_scale_points(plugin, pPort);
|
||||||
LILV_FOREACH(scale_points, iSP, pScalePoints)
|
LILV_FOREACH(scale_points, iSP, pScalePoints)
|
||||||
@@ -844,30 +952,50 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
|||||||
|
|
||||||
std::sort(scale_points_.begin(), scale_points_.end(), scale_points_sort_compare);
|
std::sort(scale_points_.begin(), scale_points_.end(), scale_points_sort_compare);
|
||||||
|
|
||||||
is_input_ = is_a(host, LV2_INPUT_PORT);
|
is_input_ = is_a(host, LV2_CORE__InputPort);
|
||||||
is_output_ = is_a(host, LV2_OUTPUT_PORT);
|
is_output_ = is_a(host, LV2_CORE__OutputPort);
|
||||||
|
|
||||||
is_control_port_ = is_a(host, LV2_CORE__ControlPort);
|
is_control_port_ = is_a(host, LV2_CORE__ControlPort);
|
||||||
is_audio_port_ = is_a(host, LV2_CORE__AudioPort);
|
is_audio_port_ = is_a(host, LV2_CORE__AudioPort);
|
||||||
is_atom_port_ = is_a(host, LV2_ATOM_PORT);
|
is_atom_port_ = is_a(host, LV2_ATOM__AtomPort);
|
||||||
is_cv_port_ = is_a(host, LV2_CORE__CVPort);
|
is_cv_port_ = is_a(host, LV2_CORE__CVPort);
|
||||||
|
|
||||||
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midiEventNode);
|
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midi__event);
|
||||||
supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.time_Position);
|
supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.time_Position);
|
||||||
|
|
||||||
AutoLilvNode designationValue = lilv_port_get(plugin, pPort, host->lilvUris.designationNode);
|
AutoLilvNode designationValue = lilv_port_get(plugin, pPort, host->lilvUris.core__designation);
|
||||||
designation_ = nodeAsString(designationValue);
|
designation_ = nodeAsString(designationValue);
|
||||||
|
|
||||||
AutoLilvNode portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris.portGroupUri);
|
AutoLilvNode portGroup_value = lilv_port_get(plugin, pPort, host->lilvUris.portgroups__group);
|
||||||
port_group_ = nodeAsString(portGroup_value);
|
port_group_ = nodeAsString(portGroup_value);
|
||||||
|
|
||||||
AutoLilvNode unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.unitsUri);
|
AutoLilvNode unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.units__unit);
|
||||||
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
|
if (unitsValueUri)
|
||||||
|
{
|
||||||
|
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
|
||||||
|
} else {
|
||||||
|
// invada plugins use the wrong URI.
|
||||||
|
AutoLilvNode invadaUnitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.invada_units__unit);
|
||||||
|
if (invadaUnitsValueUri)
|
||||||
|
{
|
||||||
|
std::string uri = nodeAsString(invadaUnitsValueUri);
|
||||||
|
static const char*INCORRECT_URI = "http://lv2plug.in/ns/extension/units#";
|
||||||
|
static const char*CORRECT_URI = "http://lv2plug.in/ns/extensions/units#";
|
||||||
|
if (uri.starts_with(INCORRECT_URI))
|
||||||
|
{
|
||||||
|
uri = uri.replace(0,strlen(INCORRECT_URI),CORRECT_URI);
|
||||||
|
}
|
||||||
|
this->units_ = UriToUnits(uri);
|
||||||
|
} else {
|
||||||
|
this->units(Units::none);
|
||||||
|
}
|
||||||
|
|
||||||
AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment);
|
}
|
||||||
|
|
||||||
|
AutoLilvNode commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfs__Comment);
|
||||||
this->comment_ = nodeAsString(commentNode);
|
this->comment_ = nodeAsString(commentNode);
|
||||||
|
|
||||||
AutoLilvNode bufferType = lilv_port_get(plugin, pPort, host->lilvUris.bufferType_uri);
|
AutoLilvNode bufferType = lilv_port_get(plugin, pPort, host->lilvUris.atom__bufferType);
|
||||||
|
|
||||||
this->buffer_type_ = "";
|
this->buffer_type_ = "";
|
||||||
if (bufferType)
|
if (bufferType)
|
||||||
@@ -962,11 +1090,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
|||||||
}
|
}
|
||||||
for (auto port : plugin->ports())
|
for (auto port : plugin->ports())
|
||||||
{
|
{
|
||||||
|
|
||||||
if (port->is_input())
|
if (port->is_input())
|
||||||
{
|
{
|
||||||
if (port->is_control_port() && port->is_input())
|
if (port->is_control_port())
|
||||||
{
|
{
|
||||||
controls_.push_back(Lv2PluginUiControlPort(plugin, port.get()));
|
controls_.push_back(Lv2PluginUiPort(plugin, port.get()));
|
||||||
}
|
}
|
||||||
else if (port->is_atom_port())
|
else if (port->is_atom_port())
|
||||||
{
|
{
|
||||||
@@ -982,7 +1111,11 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
|||||||
}
|
}
|
||||||
else if (port->is_output())
|
else if (port->is_output())
|
||||||
{
|
{
|
||||||
if (port->is_atom_port() && port->supports_midi())
|
if (port->is_control_port())
|
||||||
|
{
|
||||||
|
controls_.push_back(Lv2PluginUiPort(plugin, port.get()));
|
||||||
|
}
|
||||||
|
else if (port->is_atom_port() && port->supports_midi())
|
||||||
{
|
{
|
||||||
this->has_midi_output_ = true;
|
this->has_midi_output_ = true;
|
||||||
}
|
}
|
||||||
@@ -1007,7 +1140,7 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
|||||||
|
|
||||||
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
|
std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
|
||||||
{
|
{
|
||||||
return this->GetPluginClass(LV2_PLUGIN);
|
return this->GetPluginClass(LV2_CORE__Plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
|
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
|
||||||
@@ -1078,7 +1211,7 @@ std::vector<ControlValue> PluginHost::LoadFactoryPluginPreset(
|
|||||||
throw PiPedalStateException("No such plugin.");
|
throw PiPedalStateException("No such plugin.");
|
||||||
}
|
}
|
||||||
|
|
||||||
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.pset_Preset);
|
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.presets__preset);
|
||||||
LILV_FOREACH(nodes, i, presets)
|
LILV_FOREACH(nodes, i, presets)
|
||||||
{
|
{
|
||||||
const LilvNode *preset = lilv_nodes_get(presets, i);
|
const LilvNode *preset = lilv_nodes_get(presets, i);
|
||||||
@@ -1154,7 +1287,7 @@ PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
|
|||||||
PluginPresets result;
|
PluginPresets result;
|
||||||
result.pluginUri_ = pluginUri;
|
result.pluginUri_ = pluginUri;
|
||||||
|
|
||||||
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.pset_Preset);
|
LilvNodes *presets = lilv_plugin_get_related(plugin, lilvUris.presets__preset);
|
||||||
LILV_FOREACH(nodes, i, presets)
|
LILV_FOREACH(nodes, i, presets)
|
||||||
{
|
{
|
||||||
const LilvNode *preset = lilv_nodes_get(presets, i);
|
const LilvNode *preset = lilv_nodes_get(presets, i);
|
||||||
@@ -1171,7 +1304,7 @@ PluginPresets PluginHost::GetFactoryPluginPresets(const std::string &pluginUri)
|
|||||||
|
|
||||||
if (!cbData.failed)
|
if (!cbData.failed)
|
||||||
{
|
{
|
||||||
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, std::move(label), std::move(controlValues)));
|
result.presets_.push_back(PluginPreset(result.nextInstanceId_++, label, controlValues,Lv2PluginState()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1216,9 +1349,9 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
|
|||||||
|
|
||||||
this->uri_ = groupUri;
|
this->uri_ = groupUri;
|
||||||
AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
|
AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
|
||||||
LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.symbolUri, nullptr);
|
LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.lv2core__symbol, nullptr);
|
||||||
symbol_ = nodeAsString(symbolNode);
|
symbol_ = nodeAsString(symbolNode);
|
||||||
LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.nameUri, nullptr);
|
LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris.lv2core__name, nullptr);
|
||||||
name_ = nodeAsString(nameNode);
|
name_ = nodeAsString(nameNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1269,6 +1402,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
|||||||
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
|
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
|
||||||
json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
|
json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
|
||||||
json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
|
json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
|
||||||
|
json_map::reference("connection_optional", &Lv2PortInfo::connection_optional_),
|
||||||
|
|
||||||
json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
|
json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
|
||||||
|
|
||||||
@@ -1330,29 +1464,31 @@ json_map::storage_type<Lv2PluginUiPortGroup> Lv2PluginUiPortGroup::jmap{{
|
|||||||
MAP_REF(Lv2PluginUiPortGroup, program_list_id),
|
MAP_REF(Lv2PluginUiPortGroup, program_list_id),
|
||||||
}};
|
}};
|
||||||
|
|
||||||
json_map::storage_type<Lv2PluginUiControlPort> Lv2PluginUiControlPort::jmap{{
|
json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
|
||||||
json_map::reference("symbol", &Lv2PluginUiControlPort::symbol_),
|
json_map::reference("symbol", &Lv2PluginUiPort::symbol_),
|
||||||
json_map::reference("name", &Lv2PluginUiControlPort::name_),
|
json_map::reference("name", &Lv2PluginUiPort::name_),
|
||||||
MAP_REF(Lv2PluginUiControlPort, index),
|
MAP_REF(Lv2PluginUiPort, index),
|
||||||
MAP_REF(Lv2PluginUiControlPort, min_value),
|
MAP_REF(Lv2PluginUiPort, is_input),
|
||||||
MAP_REF(Lv2PluginUiControlPort, max_value),
|
MAP_REF(Lv2PluginUiPort, min_value),
|
||||||
MAP_REF(Lv2PluginUiControlPort, default_value),
|
MAP_REF(Lv2PluginUiPort, max_value),
|
||||||
MAP_REF(Lv2PluginUiControlPort, is_logarithmic),
|
MAP_REF(Lv2PluginUiPort, default_value),
|
||||||
MAP_REF(Lv2PluginUiControlPort, display_priority),
|
MAP_REF(Lv2PluginUiPort, is_logarithmic),
|
||||||
|
MAP_REF(Lv2PluginUiPort, display_priority),
|
||||||
|
|
||||||
MAP_REF(Lv2PluginUiControlPort, range_steps),
|
MAP_REF(Lv2PluginUiPort, range_steps),
|
||||||
MAP_REF(Lv2PluginUiControlPort, integer_property),
|
MAP_REF(Lv2PluginUiPort, integer_property),
|
||||||
MAP_REF(Lv2PluginUiControlPort, enumeration_property),
|
MAP_REF(Lv2PluginUiPort, enumeration_property),
|
||||||
MAP_REF(Lv2PluginUiControlPort, not_on_gui),
|
MAP_REF(Lv2PluginUiPort, not_on_gui),
|
||||||
MAP_REF(Lv2PluginUiControlPort, toggled_property),
|
MAP_REF(Lv2PluginUiPort, toggled_property),
|
||||||
MAP_REF(Lv2PluginUiControlPort, scale_points),
|
MAP_REF(Lv2PluginUiPort, scale_points),
|
||||||
MAP_REF(Lv2PluginUiControlPort, port_group),
|
MAP_REF(Lv2PluginUiPort, port_group),
|
||||||
|
|
||||||
json_map::enum_reference("units", &Lv2PluginUiControlPort::units_, get_units_enum_converter()),
|
json_map::enum_reference("units", &Lv2PluginUiPort::units_, get_units_enum_converter()),
|
||||||
MAP_REF(Lv2PluginUiControlPort, comment),
|
MAP_REF(Lv2PluginUiPort, comment),
|
||||||
MAP_REF(Lv2PluginUiControlPort, is_bypass),
|
MAP_REF(Lv2PluginUiPort, is_bypass),
|
||||||
MAP_REF(Lv2PluginUiControlPort, is_program_controller),
|
MAP_REF(Lv2PluginUiPort, is_program_controller),
|
||||||
MAP_REF(Lv2PluginUiControlPort, custom_units),
|
MAP_REF(Lv2PluginUiPort, custom_units),
|
||||||
|
MAP_REF(Lv2PluginUiPort, connection_optional),
|
||||||
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
+57
-26
@@ -206,6 +206,7 @@ namespace pipedal
|
|||||||
bool is_audio_port_ = false;
|
bool is_audio_port_ = false;
|
||||||
bool is_atom_port_ = false;
|
bool is_atom_port_ = false;
|
||||||
bool is_cv_port_ = false;
|
bool is_cv_port_ = false;
|
||||||
|
bool connection_optional_ = false;
|
||||||
|
|
||||||
bool is_valid_ = false;
|
bool is_valid_ = false;
|
||||||
bool supports_midi_ = false;
|
bool supports_midi_ = false;
|
||||||
@@ -275,6 +276,7 @@ namespace pipedal
|
|||||||
LV2_PROPERTY_GETSET_SCALAR(is_control_port);
|
LV2_PROPERTY_GETSET_SCALAR(is_control_port);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(is_audio_port);
|
LV2_PROPERTY_GETSET_SCALAR(is_audio_port);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(is_atom_port);
|
LV2_PROPERTY_GETSET_SCALAR(is_atom_port);
|
||||||
|
LV2_PROPERTY_GETSET_SCALAR(connection_optional);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
|
LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(is_valid);
|
LV2_PROPERTY_GETSET_SCALAR(is_valid);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(supports_midi);
|
LV2_PROPERTY_GETSET_SCALAR(supports_midi);
|
||||||
@@ -338,6 +340,8 @@ namespace pipedal
|
|||||||
Lv2PluginInfo() {}
|
Lv2PluginInfo() {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::shared_ptr<PiPedalUI> FindWritablePathProperties(PluginHost *lv2Host,const LilvPlugin*pPlugin);
|
||||||
|
|
||||||
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
|
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
|
||||||
std::string bundle_path_;
|
std::string bundle_path_;
|
||||||
std::string uri_;
|
std::string uri_;
|
||||||
@@ -467,14 +471,20 @@ namespace pipedal
|
|||||||
static json_map::storage_type<Lv2PluginUiPortGroup> jmap;
|
static json_map::storage_type<Lv2PluginUiPortGroup> jmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Lv2PluginUiControlPort
|
class Lv2PluginUiPort
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Lv2PluginUiControlPort()
|
Lv2PluginUiPort()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
Lv2PluginUiControlPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
|
Lv2PluginUiPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
|
||||||
: symbol_(pPort->symbol()), index_(pPort->index()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()), default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()), is_logarithmic_(pPort->is_logarithmic()), integer_property_(pPort->integer_property()), enumeration_property_(pPort->enumeration_property()), toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()), comment_(pPort->comment()), units_(pPort->units())
|
: symbol_(pPort->symbol()), index_(pPort->index()),
|
||||||
|
is_input_(pPort->is_input()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()),
|
||||||
|
default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()),
|
||||||
|
is_logarithmic_(pPort->is_logarithmic()), integer_property_(pPort->integer_property()), enumeration_property_(pPort->enumeration_property()),
|
||||||
|
toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()),
|
||||||
|
comment_(pPort->comment()), units_(pPort->units()),
|
||||||
|
connection_optional_(pPort->connection_optional())
|
||||||
{
|
{
|
||||||
// Use symbols to index port groups, instead of uris.
|
// Use symbols to index port groups, instead of uris.
|
||||||
// symbols are guaranteed to be unique.
|
// symbols are guaranteed to be unique.
|
||||||
@@ -489,31 +499,40 @@ namespace pipedal
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is_bypass_ = name_ == "bypass"
|
||||||
|
|| name_ == "Bypass"
|
||||||
|
|| symbol_ == "bypass"
|
||||||
|
|| symbol_ == "Bypass";
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string symbol_;
|
std::string symbol_;
|
||||||
int index_;
|
int index_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
bool is_input_ = true;
|
||||||
float min_value_ = 0, max_value_ = 1, default_value_ = 0;
|
float min_value_ = 0, max_value_ = 1, default_value_ = 0;
|
||||||
int range_steps_ = 0;
|
|
||||||
int display_priority_ = -1;
|
|
||||||
bool is_logarithmic_ = false;
|
bool is_logarithmic_ = false;
|
||||||
|
int display_priority_ = -1;
|
||||||
|
|
||||||
|
int range_steps_ = 0;
|
||||||
bool integer_property_ = false;
|
bool integer_property_ = false;
|
||||||
bool enumeration_property_ = false;
|
bool enumeration_property_ = false;
|
||||||
bool toggled_property_ = false;
|
|
||||||
bool not_on_gui_ = false;
|
bool not_on_gui_ = false;
|
||||||
|
bool toggled_property_ = false;
|
||||||
std::vector<Lv2ScalePoint> scale_points_;
|
std::vector<Lv2ScalePoint> scale_points_;
|
||||||
std::string port_group_;
|
std::string port_group_;
|
||||||
|
|
||||||
Units units_ = Units::none;
|
Units units_ = Units::none;
|
||||||
std::string comment_;
|
std::string comment_;
|
||||||
bool is_bypass_ = false;
|
bool is_bypass_ = false;
|
||||||
bool is_program_controller_ = false;
|
bool is_program_controller_ = false;
|
||||||
std::string custom_units_;
|
std::string custom_units_;
|
||||||
|
bool connection_optional_ = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LV2_PROPERTY_GETSET(symbol);
|
LV2_PROPERTY_GETSET(symbol);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(index);
|
LV2_PROPERTY_GETSET_SCALAR(index);
|
||||||
|
LV2_PROPERTY_GETSET_SCALAR(is_input);
|
||||||
LV2_PROPERTY_GETSET(name);
|
LV2_PROPERTY_GETSET(name);
|
||||||
LV2_PROPERTY_GETSET(port_group);
|
LV2_PROPERTY_GETSET(port_group);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(min_value);
|
LV2_PROPERTY_GETSET_SCALAR(min_value);
|
||||||
@@ -532,9 +551,10 @@ namespace pipedal
|
|||||||
LV2_PROPERTY_GETSET_SCALAR(is_bypass);
|
LV2_PROPERTY_GETSET_SCALAR(is_bypass);
|
||||||
LV2_PROPERTY_GETSET_SCALAR(is_program_controller);
|
LV2_PROPERTY_GETSET_SCALAR(is_program_controller);
|
||||||
LV2_PROPERTY_GETSET(custom_units);
|
LV2_PROPERTY_GETSET(custom_units);
|
||||||
|
LV2_PROPERTY_GETSET(connection_optional);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static json_map::storage_type<Lv2PluginUiControlPort> jmap;
|
static json_map::storage_type<Lv2PluginUiPort> jmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Lv2PluginUiInfo
|
class Lv2PluginUiInfo
|
||||||
@@ -557,7 +577,7 @@ namespace pipedal
|
|||||||
std::string description_;
|
std::string description_;
|
||||||
bool is_vst3_ = false;
|
bool is_vst3_ = false;
|
||||||
|
|
||||||
std::vector<Lv2PluginUiControlPort> controls_;
|
std::vector<Lv2PluginUiPort> controls_;
|
||||||
std::vector<Lv2PluginUiPortGroup> port_groups_;
|
std::vector<Lv2PluginUiPortGroup> port_groups_;
|
||||||
std::vector<UiFileProperty::ptr> fileProperties_;
|
std::vector<UiFileProperty::ptr> fileProperties_;
|
||||||
std::vector<UiPortNotification::ptr> uiPortNotifications_;
|
std::vector<UiPortNotification::ptr> uiPortNotifications_;
|
||||||
@@ -601,7 +621,8 @@ namespace pipedal
|
|||||||
#endif
|
#endif
|
||||||
friend class pipedal::AutoLilvNode;
|
friend class pipedal::AutoLilvNode;
|
||||||
friend class pipedal::PiPedalUI;
|
friend class pipedal::PiPedalUI;
|
||||||
static const char *RDFS_COMMENT_URI;
|
static const char *RDFS__comment;
|
||||||
|
static const char *RDFS__range;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class LilvUris
|
class LilvUris
|
||||||
@@ -610,25 +631,31 @@ namespace pipedal
|
|||||||
void Initialize(LilvWorld *pWorld);
|
void Initialize(LilvWorld *pWorld);
|
||||||
void Free();
|
void Free();
|
||||||
|
|
||||||
AutoLilvNode rdfsComment;
|
AutoLilvNode rdfs__Comment;
|
||||||
AutoLilvNode logarithic_uri;
|
AutoLilvNode rdfs__range;
|
||||||
AutoLilvNode display_priority_uri;
|
AutoLilvNode port_logarithmic;
|
||||||
AutoLilvNode range_steps_uri;
|
AutoLilvNode port__display_priority;
|
||||||
|
AutoLilvNode port_range_steps;
|
||||||
AutoLilvNode integer_property_uri;
|
AutoLilvNode integer_property_uri;
|
||||||
AutoLilvNode enumeration_property_uri;
|
AutoLilvNode enumeration_property_uri;
|
||||||
AutoLilvNode toggle_property_uri;
|
AutoLilvNode core__toggled;
|
||||||
AutoLilvNode not_on_gui_property_uri;
|
AutoLilvNode core__connectionOptional;
|
||||||
AutoLilvNode midiEventNode;
|
AutoLilvNode portprops__not_on_gui_property_uri;
|
||||||
AutoLilvNode designationNode;
|
AutoLilvNode midi__event;
|
||||||
AutoLilvNode portGroupUri;
|
AutoLilvNode core__designation;
|
||||||
AutoLilvNode unitsUri;
|
AutoLilvNode portgroups__group;
|
||||||
AutoLilvNode bufferType_uri;
|
AutoLilvNode units__unit;
|
||||||
AutoLilvNode pset_Preset;
|
AutoLilvNode invada_units__unit; // typo in invada plugins.
|
||||||
AutoLilvNode rdfs_label;
|
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
|
||||||
AutoLilvNode symbolUri;
|
|
||||||
AutoLilvNode nameUri;
|
|
||||||
|
|
||||||
AutoLilvNode lv2Core__name;
|
AutoLilvNode atom__bufferType;
|
||||||
|
AutoLilvNode atom__Path;
|
||||||
|
AutoLilvNode presets__preset;
|
||||||
|
AutoLilvNode rdfs__label;
|
||||||
|
AutoLilvNode lv2core__symbol;
|
||||||
|
AutoLilvNode lv2core__name;
|
||||||
|
AutoLilvNode lv2core__index;
|
||||||
|
AutoLilvNode lv2core__Parameter;
|
||||||
AutoLilvNode pipedalUI__ui;
|
AutoLilvNode pipedalUI__ui;
|
||||||
AutoLilvNode pipedalUI__fileProperties;
|
AutoLilvNode pipedalUI__fileProperties;
|
||||||
AutoLilvNode pipedalUI__directory;
|
AutoLilvNode pipedalUI__directory;
|
||||||
@@ -657,6 +684,10 @@ namespace pipedal
|
|||||||
AutoLilvNode ui__peakProtocol;
|
AutoLilvNode ui__peakProtocol;
|
||||||
AutoLilvNode ui__portIndex;
|
AutoLilvNode ui__portIndex;
|
||||||
AutoLilvNode lv2__symbol;
|
AutoLilvNode lv2__symbol;
|
||||||
|
|
||||||
|
AutoLilvNode patch__writable;
|
||||||
|
AutoLilvNode patch__readable;
|
||||||
|
|
||||||
};
|
};
|
||||||
LilvUris lilvUris;
|
LilvUris lilvUris;
|
||||||
|
|
||||||
|
|||||||
+19
-8
@@ -25,6 +25,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "StateInterface.hpp"
|
#include "StateInterface.hpp"
|
||||||
|
#include "Pedalboard.hpp"
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
@@ -69,20 +70,30 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PluginPreset {
|
class PluginPreset {
|
||||||
public:
|
public:
|
||||||
PluginPreset() { }
|
PluginPreset() { }
|
||||||
PluginPreset(uint64_t instanceId, std::string&&label, std::map<std::string,float> && controlValues)
|
PluginPreset(uint64_t instanceId, const std::string&label,const PedalboardItem&pedalboardItem)
|
||||||
: instanceId_(instanceId),
|
: instanceId_(instanceId)
|
||||||
label_(std::forward<std::string>(label)),
|
, label_(label)
|
||||||
controlValues_(std::forward<std::map<std::string,float>>(controlValues))
|
, state_(pedalboardItem.lv2State())
|
||||||
{
|
{
|
||||||
|
for (auto & controlValue : pedalboardItem.controlValues())
|
||||||
|
{
|
||||||
|
this->controlValues_[controlValue.key()] = controlValue.value();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
PluginPreset(uint64_t instanceId, const std::string&label, const std::map<std::string,float> & controlValues)
|
PluginPreset(
|
||||||
|
uint64_t instanceId,
|
||||||
|
const std::string&label,
|
||||||
|
const std::map<std::string,
|
||||||
|
float> & controlValues,
|
||||||
|
const Lv2PluginState &state)
|
||||||
: instanceId_(instanceId),
|
: instanceId_(instanceId),
|
||||||
label_(label),
|
label_(label),
|
||||||
controlValues_(controlValues)
|
controlValues_((controlValues)),
|
||||||
|
state_(state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-22
@@ -30,35 +30,38 @@ namespace pipedal
|
|||||||
|
|
||||||
enum class RingBufferCommand : int64_t
|
enum class RingBufferCommand : int64_t
|
||||||
{
|
{
|
||||||
ReplaceEffect = 0,
|
Invalid = 0,
|
||||||
EffectReplaced = 1,
|
ReplaceEffect,
|
||||||
SetValue = 2,
|
EffectReplaced,
|
||||||
SetBypass = 3,
|
SetValue,
|
||||||
AudioStopped = 4,
|
SetBypass,
|
||||||
SetVuSubscriptions = 5,
|
AudioStopped,
|
||||||
FreeVuSubscriptions = 6,
|
SetVuSubscriptions,
|
||||||
SendVuUpdate = 7,
|
FreeVuSubscriptions,
|
||||||
AckVuUpdate = 8,
|
SendVuUpdate,
|
||||||
|
AckVuUpdate,
|
||||||
|
|
||||||
SetMonitorPortSubscription = 9,
|
SetMonitorPortSubscription,
|
||||||
FreeMonitorPortSubscription = 10,
|
FreeMonitorPortSubscription,
|
||||||
SendMonitorPortUpdate = 11,
|
SendMonitorPortUpdate,
|
||||||
AckMonitorPortUpdate = 12,
|
AckMonitorPortUpdate,
|
||||||
ParameterRequest = 13,
|
ParameterRequest,
|
||||||
ParameterRequestComplete = 14,
|
ParameterRequestComplete,
|
||||||
|
|
||||||
MidiValueChanged = 15,
|
MidiValueChanged,
|
||||||
|
|
||||||
OnMidiListen = 16,
|
OnMidiListen,
|
||||||
|
|
||||||
AtomOutput = 17,
|
AtomOutput,
|
||||||
|
|
||||||
MidiProgramChange = 18, // program change requested via midi.
|
MidiProgramChange, // program change requested via midi.
|
||||||
AckMidiProgramChange = 19,
|
AckMidiProgramChange,
|
||||||
|
|
||||||
NextMidiProgram = 20,
|
NextMidiProgram,
|
||||||
|
|
||||||
Lv2StateChanged = 21,
|
Lv2StateChanged,
|
||||||
|
SetInputVolume,
|
||||||
|
SetOutputVolume,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -154,6 +157,11 @@ namespace pipedal
|
|||||||
int controlIndex;
|
int controlIndex;
|
||||||
float value;
|
float value;
|
||||||
};
|
};
|
||||||
|
class SetVolumeBody
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
|
||||||
class Lv2Pedalboard;
|
class Lv2Pedalboard;
|
||||||
|
|
||||||
@@ -369,6 +377,19 @@ namespace pipedal
|
|||||||
body.value = value;
|
body.value = value;
|
||||||
write(RingBufferCommand::SetValue,body);
|
write(RingBufferCommand::SetValue,body);
|
||||||
}
|
}
|
||||||
|
void SetInputVolume(float value)
|
||||||
|
{
|
||||||
|
SetVolumeBody body;
|
||||||
|
body.value = value;
|
||||||
|
write(RingBufferCommand::SetInputVolume,body);
|
||||||
|
}
|
||||||
|
void SetOutputVolume(float value)
|
||||||
|
{
|
||||||
|
SetVolumeBody body;
|
||||||
|
body.value = value;
|
||||||
|
write(RingBufferCommand::SetOutputVolume,body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void FreeVuSubscriptions(RealtimeVuBuffers *configuration)
|
void FreeVuSubscriptions(RealtimeVuBuffers *configuration)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ void StateInterface::Restore(const Lv2PluginState &state)
|
|||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
throw std::logic_error(SS("State save failed. " << e.what()));
|
throw std::logic_error(SS("State restore failed. " << e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*static*/ const void *StateInterface::FnStateRetreiveFunction(
|
/*static*/ const void *StateInterface::FnStateRetreiveFunction(
|
||||||
@@ -199,10 +199,18 @@ const void *StateInterface::StateRetrieveFunction(
|
|||||||
|
|
||||||
void Lv2PluginState::write_json(json_writer &writer) const
|
void Lv2PluginState::write_json(json_writer &writer) const
|
||||||
{
|
{
|
||||||
|
writer.start_array();
|
||||||
|
writer.write(isValid_);
|
||||||
|
writer.write_raw(",");
|
||||||
writer.write(values_);
|
writer.write(values_);
|
||||||
|
writer.end_array();
|
||||||
}
|
}
|
||||||
void Lv2PluginState::read_json(json_reader &reader) {
|
void Lv2PluginState::read_json(json_reader &reader) {
|
||||||
|
reader.consume('[');
|
||||||
|
reader.read(&isValid_);
|
||||||
|
reader.consume(',');
|
||||||
reader.read(&values_);
|
reader.read(&values_);
|
||||||
|
reader.consume(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ namespace pipedal
|
|||||||
class Lv2PluginState: public JsonSerializable
|
class Lv2PluginState: public JsonSerializable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
bool isValid_ = false;
|
||||||
std::map<std::string,Lv2PluginStateEntry> values_;
|
std::map<std::string,Lv2PluginStateEntry> values_;
|
||||||
|
void Erase() {
|
||||||
|
isValid_ = false;
|
||||||
|
values_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
std::string ToString() const;
|
std::string ToString() const;
|
||||||
private:
|
private:
|
||||||
|
|||||||
+164
-8
@@ -264,7 +264,7 @@ std::filesystem::path Storage::GetPluginPresetsDirectory() const
|
|||||||
{
|
{
|
||||||
return this->dataRoot / "plugin_presets";
|
return this->dataRoot / "plugin_presets";
|
||||||
}
|
}
|
||||||
std::filesystem::path Storage::GetPluginStorageDirectory() const
|
std::filesystem::path Storage::GetPluginAudioFileDirectory() const
|
||||||
{
|
{
|
||||||
return this->dataRoot / "audio_uploads";
|
return this->dataRoot / "audio_uploads";
|
||||||
}
|
}
|
||||||
@@ -635,6 +635,29 @@ std::string Storage::GetPresetCopyName(const std::string &name)
|
|||||||
++copyNumber;
|
++copyNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t Storage::CreateNewPreset()
|
||||||
|
{
|
||||||
|
Pedalboard newPedalboard = Pedalboard::MakeDefault();
|
||||||
|
std::string name = "New";
|
||||||
|
if (currentBank.hasName(name))
|
||||||
|
{
|
||||||
|
int copyNumber = 2;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
name = SS("New (" << copyNumber << ")");
|
||||||
|
if (!currentBank.hasName(name))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++copyNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPedalboard.name(name);
|
||||||
|
return this->currentBank.addPreset(newPedalboard,-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
|
int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
|
||||||
{
|
{
|
||||||
auto &fromItem = this->currentBank.getItem(fromId);
|
auto &fromItem = this->currentBank.getItem(fromId);
|
||||||
@@ -1189,28 +1212,64 @@ PluginUiPresets Storage::GetPluginUiPresets(const std::string &pluginUri) const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ControlValue> Storage::GetPluginPresetValues(const std::string &pluginUri, uint64_t instanceId)
|
PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri, uint64_t instanceId)
|
||||||
{
|
{
|
||||||
auto presets = GetPluginPresets(pluginUri);
|
auto presets = GetPluginPresets(pluginUri);
|
||||||
for (const auto &preset : presets.presets_)
|
for (const auto &preset : presets.presets_)
|
||||||
{
|
{
|
||||||
if (preset.instanceId_ == instanceId)
|
if (preset.instanceId_ == instanceId)
|
||||||
{
|
{
|
||||||
std::vector<ControlValue> result;
|
PluginPresetValues result;
|
||||||
|
|
||||||
for (const auto &valuePair : preset.controlValues_)
|
for (const auto &valuePair : preset.controlValues_)
|
||||||
{
|
{
|
||||||
result.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
|
result.controls.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
|
||||||
}
|
}
|
||||||
|
result.state = preset.state_;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw PiPedalException("Plugin preset not found.");
|
throw PiPedalException("Plugin preset not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t Storage::SavePluginPreset(
|
||||||
|
const std::string &name,
|
||||||
|
const PedalboardItem &pedalboardItem)
|
||||||
|
{
|
||||||
|
const std::string&pluginUri = pedalboardItem.uri();
|
||||||
|
auto presets = GetPluginPresets(pluginUri);
|
||||||
|
uint64_t result = -1;
|
||||||
|
bool existing = false;
|
||||||
|
for (size_t i = 0; i < presets.presets_.size(); ++i)
|
||||||
|
{
|
||||||
|
auto &preset = presets.presets_[i];
|
||||||
|
if (preset.label_ == name)
|
||||||
|
{
|
||||||
|
existing = true;
|
||||||
|
result = preset.instanceId_;
|
||||||
|
presets.presets_[i] = PluginPreset(preset.instanceId_,preset.label_,pedalboardItem);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!existing)
|
||||||
|
{
|
||||||
|
result = presets.nextInstanceId_++;
|
||||||
|
presets.presets_.push_back(
|
||||||
|
PluginPreset(result,
|
||||||
|
name,
|
||||||
|
pedalboardItem));
|
||||||
|
}
|
||||||
|
this->SavePluginPresets(pluginUri, presets);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t Storage::SavePluginPreset(
|
uint64_t Storage::SavePluginPreset(
|
||||||
const std::string &pluginUri,
|
const std::string &pluginUri,
|
||||||
const std::string &name,
|
const std::string &name,
|
||||||
const std::map<std::string, float> &values)
|
float inputVolume,
|
||||||
|
float outputVolume,
|
||||||
|
const std::map<std::string, float> &values,
|
||||||
|
const Lv2PluginState &lv2State)
|
||||||
{
|
{
|
||||||
auto presets = GetPluginPresets(pluginUri);
|
auto presets = GetPluginPresets(pluginUri);
|
||||||
uint64_t result = -1;
|
uint64_t result = -1;
|
||||||
@@ -1220,8 +1279,10 @@ uint64_t Storage::SavePluginPreset(
|
|||||||
auto &preset = presets.presets_[i];
|
auto &preset = presets.presets_[i];
|
||||||
if (preset.label_ == name)
|
if (preset.label_ == name)
|
||||||
{
|
{
|
||||||
preset.controlValues_ = values;
|
|
||||||
existing = true;
|
existing = true;
|
||||||
|
preset.controlValues_ = values;
|
||||||
|
preset.state_ = lv2State;
|
||||||
|
|
||||||
result = preset.instanceId_;
|
result = preset.instanceId_;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1233,7 +1294,8 @@ uint64_t Storage::SavePluginPreset(
|
|||||||
PluginPreset(
|
PluginPreset(
|
||||||
result,
|
result,
|
||||||
name,
|
name,
|
||||||
values));
|
values,
|
||||||
|
lv2State));
|
||||||
}
|
}
|
||||||
this->SavePluginPresets(pluginUri, presets);
|
this->SavePluginPresets(pluginUri, presets);
|
||||||
return result;
|
return result;
|
||||||
@@ -1446,7 +1508,7 @@ std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty
|
|||||||
// if fileProperty has a user-accessible directory, push the entire file path.
|
// if fileProperty has a user-accessible directory, push the entire file path.
|
||||||
if (fileProperty.directory().size() != 0)
|
if (fileProperty.directory().size() != 0)
|
||||||
{
|
{
|
||||||
std::filesystem::path audioFileDirectory = this->GetPluginStorageDirectory() / fileProperty.directory();
|
std::filesystem::path audioFileDirectory = this->GetPluginAudioFileDirectory() / fileProperty.directory();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
|
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
|
||||||
@@ -1474,6 +1536,100 @@ std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Storage::IsValidSampleFile(const std::filesystem::path &fileName)
|
||||||
|
{
|
||||||
|
if (!fileName.is_absolute())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::filesystem::path audioFilePath = this->GetPluginAudioFileDirectory();
|
||||||
|
|
||||||
|
std::filesystem::path parentDirectory = fileName.parent_path();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!parentDirectory.has_parent_path())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string name = parentDirectory.filename().string();
|
||||||
|
if (name == ".." || name == ".")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (parentDirectory == audioFilePath)
|
||||||
|
return true;
|
||||||
|
parentDirectory = parentDirectory.parent_path();
|
||||||
|
if (parentDirectory.string().length() < audioFilePath.string().length())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
||||||
|
{
|
||||||
|
if (!IsValidSampleFile(fileName))
|
||||||
|
{
|
||||||
|
throw std::logic_error("Permission denied.");
|
||||||
|
}
|
||||||
|
if (!std::filesystem::exists(fileName))
|
||||||
|
{
|
||||||
|
throw std::logic_error("File not found.");
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::filesystem::remove(fileName);
|
||||||
|
}
|
||||||
|
catch (const std::exception &)
|
||||||
|
{
|
||||||
|
throw std::logic_error("Permission denied.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, const std::string &filename)
|
||||||
|
{
|
||||||
|
if (!UiFileProperty::IsDirectoryNameValid(directory))
|
||||||
|
{
|
||||||
|
throw std::logic_error("Permission denied.");
|
||||||
|
}
|
||||||
|
std::filesystem::path filePath{filename};
|
||||||
|
|
||||||
|
if (filePath.has_parent_path())
|
||||||
|
{
|
||||||
|
throw std::logic_error("Permission denied.");
|
||||||
|
}
|
||||||
|
std::filesystem::path result = this->GetPluginAudioFileDirectory() / directory / filename;
|
||||||
|
if (!this->IsValidSampleFile(result))
|
||||||
|
{
|
||||||
|
throw std::logic_error("Permission denied.");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
std::string Storage::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, const std::string &fileBody)
|
||||||
|
{
|
||||||
|
std::filesystem::path path;
|
||||||
|
if (directory.length() != 0)
|
||||||
|
{
|
||||||
|
path = this->MakeUserFilePath(directory, filename);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (patchProperty.length() == 0)
|
||||||
|
{
|
||||||
|
throw std::logic_error("Malformed request.");
|
||||||
|
}
|
||||||
|
throw std::logic_error("patchProperty directory not implemented.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::filesystem::create_directories(path.parent_path());
|
||||||
|
|
||||||
|
std::ofstream f(path, std::ios_base::trunc | std::ios_base::binary);
|
||||||
|
if (!f.is_open())
|
||||||
|
{
|
||||||
|
throw std::logic_error(SS("Can't create file " << path << "."));
|
||||||
|
}
|
||||||
|
f.write(fileBody.c_str(), fileBody.length());
|
||||||
|
}
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
|
||||||
JSON_MAP_BEGIN(UserSettings)
|
JSON_MAP_BEGIN(UserSettings)
|
||||||
JSON_MAP_REFERENCE(UserSettings, governor)
|
JSON_MAP_REFERENCE(UserSettings, governor)
|
||||||
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
|
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
|
||||||
|
|||||||
+29
-4
@@ -53,6 +53,12 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct PluginPresetValues {
|
||||||
|
|
||||||
|
std::vector<ControlValue> controls;
|
||||||
|
Lv2PluginState state;
|
||||||
|
};
|
||||||
|
|
||||||
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
|
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
|
||||||
|
|
||||||
class Storage {
|
class Storage {
|
||||||
@@ -100,7 +106,7 @@ public:
|
|||||||
void SetDataRoot(const std::filesystem::path& path);
|
void SetDataRoot(const std::filesystem::path& path);
|
||||||
void SetConfigRoot(const std::filesystem::path& path);
|
void SetConfigRoot(const std::filesystem::path& path);
|
||||||
|
|
||||||
std::filesystem::path GetPluginStorageDirectory() const;
|
std::filesystem::path GetPluginAudioFileDirectory() const;
|
||||||
|
|
||||||
std::vector<std::string> GetPedalboards();
|
std::vector<std::string> GetPedalboards();
|
||||||
|
|
||||||
@@ -132,6 +138,8 @@ public:
|
|||||||
int64_t DeletePreset(int64_t presetId);
|
int64_t DeletePreset(int64_t presetId);
|
||||||
bool RenamePreset(int64_t presetId, const std::string&name);
|
bool RenamePreset(int64_t presetId, const std::string&name);
|
||||||
int64_t CopyPreset(int64_t fromId, int64_t toId = -1);
|
int64_t CopyPreset(int64_t fromId, int64_t toId = -1);
|
||||||
|
int64_t CreateNewPreset();
|
||||||
|
|
||||||
|
|
||||||
void RenameBank(int64_t bankId, const std::string&newName);
|
void RenameBank(int64_t bankId, const std::string&newName);
|
||||||
int64_t SaveBankAs(int64_t bankId, const std::string&newName);
|
int64_t SaveBankAs(int64_t bankId, const std::string&newName);
|
||||||
@@ -166,13 +174,29 @@ private:
|
|||||||
void LoadPluginPresetIndex();
|
void LoadPluginPresetIndex();
|
||||||
void SavePluginPresetIndex();
|
void SavePluginPresetIndex();
|
||||||
std::filesystem::path GetPluginPresetPath(const std::string &pluginUri) const;
|
std::filesystem::path GetPluginPresetPath(const std::string &pluginUri) const;
|
||||||
|
bool IsValidSampleFile(const std::filesystem::path&fileName);
|
||||||
|
std::filesystem::path MakeUserFilePath(const std::string &directory, const std::string&filename);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool HasPluginPresets(const std::string&pluginUri) const;
|
bool HasPluginPresets(const std::string&pluginUri) const;
|
||||||
void SavePluginPresets(const std::string&pluginUri, const PluginPresets&presets);
|
void SavePluginPresets(const std::string&pluginUri, const PluginPresets&presets);
|
||||||
PluginPresets GetPluginPresets(const std::string&pluginUri) const;
|
PluginPresets GetPluginPresets(const std::string&pluginUri) const;
|
||||||
PluginUiPresets GetPluginUiPresets(const std::string&pluginUri) const;
|
PluginUiPresets GetPluginUiPresets(const std::string&pluginUri) const;
|
||||||
std::vector<ControlValue> GetPluginPresetValues(const std::string&pluginUri, uint64_t instanceId);
|
PluginPresetValues GetPluginPresetValues(const std::string&pluginUri, uint64_t instanceId);
|
||||||
uint64_t SavePluginPreset(const std::string&pluginUri, const std::string&name, const std::map<std::string,float> & values);
|
|
||||||
|
uint64_t SavePluginPreset(
|
||||||
|
const std::string&name,
|
||||||
|
const PedalboardItem&pedalboardEntry
|
||||||
|
);
|
||||||
|
|
||||||
|
uint64_t SavePluginPreset(
|
||||||
|
const std::string &pluginUri,
|
||||||
|
const std::string &name,
|
||||||
|
float inputVolume,
|
||||||
|
float outputVolume,
|
||||||
|
const std::map<std::string, float> &values,
|
||||||
|
const Lv2PluginState& lv2State);
|
||||||
|
|
||||||
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
|
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
|
||||||
uint64_t CopyPluginPreset(const std::string&pluginUri,uint64_t presetId);
|
uint64_t CopyPluginPreset(const std::string&pluginUri,uint64_t presetId);
|
||||||
|
|
||||||
@@ -183,7 +207,8 @@ public:
|
|||||||
bool GetShowStatusMonitor() const;
|
bool GetShowStatusMonitor() const;
|
||||||
void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
|
void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
|
||||||
std::vector<MidiBinding> GetSystemMidiBindings();
|
std::vector<MidiBinding> GetSystemMidiBindings();
|
||||||
|
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||||
|
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+45
-10
@@ -36,32 +36,46 @@ Units UriToUnits(const std::string &uri);
|
|||||||
|
|
||||||
std::map<Units,std::string> unitsToStringMap =
|
std::map<Units,std::string> unitsToStringMap =
|
||||||
{
|
{
|
||||||
|
//LV2_UNITS__conversion
|
||||||
|
//LV2_UNITS__name
|
||||||
CASE(none)
|
CASE(none)
|
||||||
CASE(unknown)
|
CASE(unknown)
|
||||||
|
|
||||||
CASE(bar)
|
CASE(bar)
|
||||||
CASE(beat)
|
CASE(beat)
|
||||||
CASE(bpm)
|
CASE(bpm)
|
||||||
CASE(cent)
|
CASE(cent)
|
||||||
CASE(cm)
|
CASE(cm)
|
||||||
|
CASE(coef)
|
||||||
CASE(db)
|
CASE(db)
|
||||||
|
CASE(degree)
|
||||||
|
CASE(frame)
|
||||||
CASE(hz)
|
CASE(hz)
|
||||||
|
CASE(inch)
|
||||||
CASE(khz)
|
CASE(khz)
|
||||||
CASE(km)
|
CASE(km)
|
||||||
CASE(m)
|
CASE(m)
|
||||||
CASE(mhz)
|
CASE(mhz)
|
||||||
CASE(midiNote)
|
CASE(midiNote)
|
||||||
|
CASE(mile)
|
||||||
CASE(min)
|
CASE(min)
|
||||||
|
CASE(mm)
|
||||||
CASE(ms)
|
CASE(ms)
|
||||||
CASE(pc)
|
CASE(oct)
|
||||||
CASE(s)
|
CASE(s)
|
||||||
|
CASE(pc)
|
||||||
CASE(semitone12TET)
|
CASE(semitone12TET)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const std::string emptyString;
|
||||||
const std::string& pipedal::UnitsToString(Units units)
|
const std::string& pipedal::UnitsToString(Units units)
|
||||||
{
|
{
|
||||||
return unitsToStringMap[units];
|
if (unitsToStringMap.find(units) != unitsToStringMap.end())
|
||||||
|
{
|
||||||
|
return unitsToStringMap[units];
|
||||||
|
} else {
|
||||||
|
return emptyString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#undef CASE
|
#undef CASE
|
||||||
|
|
||||||
@@ -76,17 +90,24 @@ std::map<std::string,Units> unitMap = {
|
|||||||
CASE(bpm)
|
CASE(bpm)
|
||||||
CASE(cent)
|
CASE(cent)
|
||||||
CASE(cm)
|
CASE(cm)
|
||||||
|
CASE(coef)
|
||||||
CASE(db)
|
CASE(db)
|
||||||
|
CASE(degree)
|
||||||
|
CASE(frame)
|
||||||
CASE(hz)
|
CASE(hz)
|
||||||
|
CASE(inch)
|
||||||
CASE(khz)
|
CASE(khz)
|
||||||
CASE(km)
|
CASE(km)
|
||||||
CASE(m)
|
CASE(m)
|
||||||
CASE(mhz)
|
CASE(mhz)
|
||||||
CASE(midiNote)
|
CASE(midiNote)
|
||||||
|
CASE(mile)
|
||||||
CASE(min)
|
CASE(min)
|
||||||
|
CASE(mm)
|
||||||
CASE(ms)
|
CASE(ms)
|
||||||
CASE(pc)
|
CASE(oct)
|
||||||
CASE(s)
|
CASE(s)
|
||||||
|
CASE(pc)
|
||||||
CASE(semitone12TET)
|
CASE(semitone12TET)
|
||||||
};
|
};
|
||||||
#undef CASE
|
#undef CASE
|
||||||
@@ -97,38 +118,52 @@ std::map<std::string,Units> unitMap = {
|
|||||||
|
|
||||||
|
|
||||||
std::map<std::string,Units> uriToUnitsMap = {
|
std::map<std::string,Units> uriToUnitsMap = {
|
||||||
|
{ "", Units::none,},
|
||||||
CASE(bar)
|
CASE(bar)
|
||||||
CASE(beat)
|
CASE(beat)
|
||||||
CASE(bpm)
|
CASE(bpm)
|
||||||
CASE(cent)
|
CASE(cent)
|
||||||
CASE(cm)
|
CASE(cm)
|
||||||
|
CASE(coef)
|
||||||
CASE(db)
|
CASE(db)
|
||||||
|
CASE(degree)
|
||||||
|
CASE(frame)
|
||||||
CASE(hz)
|
CASE(hz)
|
||||||
|
CASE(inch)
|
||||||
CASE(khz)
|
CASE(khz)
|
||||||
CASE(km)
|
CASE(km)
|
||||||
CASE(m)
|
CASE(m)
|
||||||
CASE(mhz)
|
CASE(mhz)
|
||||||
CASE(midiNote)
|
CASE(midiNote)
|
||||||
|
CASE(mile)
|
||||||
CASE(min)
|
CASE(min)
|
||||||
|
CASE(mm)
|
||||||
CASE(ms)
|
CASE(ms)
|
||||||
CASE(pc)
|
CASE(oct)
|
||||||
CASE(s)
|
CASE(s)
|
||||||
|
CASE(pc)
|
||||||
CASE(semitone12TET)
|
CASE(semitone12TET)
|
||||||
};
|
};
|
||||||
|
#undef CASE
|
||||||
|
|
||||||
|
|
||||||
Units pipedal::StringToUnits(const std::string &text)
|
Units pipedal::StringToUnits(const std::string &text)
|
||||||
{
|
{
|
||||||
return unitMap[text];
|
if (unitMap.find(text) != unitMap.end()) {
|
||||||
|
return unitMap[text];
|
||||||
|
}
|
||||||
|
return Units::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
Units pipedal::UriToUnits(const std::string &text)
|
Units pipedal::UriToUnits(const std::string &text)
|
||||||
{
|
{
|
||||||
if (text.length() == 0) return Units::none;
|
if (text.length() == 0) return Units::none;
|
||||||
auto result = uriToUnitsMap[text];
|
if (uriToUnitsMap.contains(text))
|
||||||
if (result == Units::none) return Units::unknown;
|
{
|
||||||
return result;
|
auto result = uriToUnitsMap[text];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return Units::unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -44,7 +44,14 @@ enum class Units {
|
|||||||
pc,
|
pc,
|
||||||
s,
|
s,
|
||||||
semitone12TET,
|
semitone12TET,
|
||||||
custom
|
custom,
|
||||||
|
degree,
|
||||||
|
coef,
|
||||||
|
frame,
|
||||||
|
inch,
|
||||||
|
mile,
|
||||||
|
mm,
|
||||||
|
oct,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -272,7 +272,7 @@ void Vst3Host::Private::UpdateControlInfo(IEditController *controller, Lv2Plugin
|
|||||||
Steinberg::Vst::ParameterInfo info;
|
Steinberg::Vst::ParameterInfo info;
|
||||||
tresult tErr = controller->getParameterInfo(param, info);
|
tresult tErr = controller->getParameterInfo(param, info);
|
||||||
|
|
||||||
Lv2PluginUiControlPort port;
|
Lv2PluginUiPort port;
|
||||||
port.index(param); // used for LV2 purposes.
|
port.index(param); // used for LV2 purposes.
|
||||||
port.symbol(SS(info.id)); // Used for VST3 purposes. convert to int to specifiy a vst control id.
|
port.symbol(SS(info.id)); // Used for VST3 purposes. convert to int to specifiy a vst control id.
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -26,7 +26,7 @@ namespace pipedal
|
|||||||
class VuUpdate
|
class VuUpdate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
uint64_t instanceId_ = 0;
|
int64_t instanceId_ = 0;
|
||||||
long sampleTime_ = 0;
|
long sampleTime_ = 0;
|
||||||
bool isStereoInput_ = false;
|
bool isStereoInput_ = false;
|
||||||
bool isStereoOutput_ = false;
|
bool isStereoOutput_ = false;
|
||||||
@@ -76,7 +76,6 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
AccumulateVu(&outputMaxValueL_,outputL,samples);
|
AccumulateVu(&outputMaxValueL_,outputL,samples);
|
||||||
AccumulateVu(&outputMaxValueR_,outputR,samples);
|
AccumulateVu(&outputMaxValueR_,outputR,samples);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DECLARE_JSON_MAP(VuUpdate);
|
DECLARE_JSON_MAP(VuUpdate);
|
||||||
|
|||||||
+92
-35
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#include <websocketpp/server.hpp>
|
#include <websocketpp/server.hpp>
|
||||||
|
|
||||||
|
#include "WebServerLog.hpp"
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@@ -37,6 +39,34 @@ using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
|||||||
const size_t MAX_READ_SIZE = 1 * 1024 * 204;
|
const size_t MAX_READ_SIZE = 1 * 1024 * 204;
|
||||||
using namespace boost;
|
using namespace boost;
|
||||||
|
|
||||||
|
class CustomPpConfig: public websocketpp::config::asio {
|
||||||
|
public:
|
||||||
|
typedef CustomPpConfig type;
|
||||||
|
typedef websocketpp::config::asio base;
|
||||||
|
|
||||||
|
|
||||||
|
static const size_t max_http_body_size = 100000000; //websocketpp::config::asio::max_http_body_size;
|
||||||
|
typedef pipedal_elog elog_type;
|
||||||
|
typedef pipedal_alog alog_type;
|
||||||
|
|
||||||
|
|
||||||
|
struct transport_config : public base::transport_config {
|
||||||
|
typedef type::concurrency_type concurrency_type;
|
||||||
|
typedef type::alog_type alog_type;
|
||||||
|
typedef type::elog_type elog_type;
|
||||||
|
typedef type::request_type request_type;
|
||||||
|
typedef type::response_type response_type;
|
||||||
|
typedef websocketpp::transport::asio::basic_socket::endpoint
|
||||||
|
socket_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef websocketpp::transport::asio::endpoint<transport_config>
|
||||||
|
transport_type;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
pipedal::last_modified(const std::filesystem::path &path)
|
pipedal::last_modified(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
@@ -149,7 +179,7 @@ namespace pipedal
|
|||||||
boost::asio::io_context *pIoContext = nullptr;
|
boost::asio::io_context *pIoContext = nullptr;
|
||||||
|
|
||||||
typedef websocketpp::connection_hdl connection_hdl;
|
typedef websocketpp::connection_hdl connection_hdl;
|
||||||
typedef websocketpp::server<websocketpp::config::asio> server;
|
typedef websocketpp::server<CustomPpConfig> server;
|
||||||
|
|
||||||
class HttpRequestImpl : public HttpRequest
|
class HttpRequestImpl : public HttpRequest
|
||||||
{
|
{
|
||||||
@@ -208,11 +238,17 @@ namespace pipedal
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// IWriteCallback
|
// IWriteCallback
|
||||||
virtual void close() { webSocket->close(websocketpp::close::status::normal, ""); }
|
virtual void close() {
|
||||||
|
webSocket->close(websocketpp::close::status::normal, "");
|
||||||
|
webSocket = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
virtual void writeCallback(const std::string &text)
|
virtual void writeCallback(const std::string &text)
|
||||||
{
|
{
|
||||||
webSocket->send(text, websocketpp::frame::opcode::text);
|
if (webSocket)
|
||||||
|
{
|
||||||
|
webSocket->send(text, websocketpp::frame::opcode::text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
virtual std::string getFromAddress() const
|
virtual std::string getFromAddress() const
|
||||||
{
|
{
|
||||||
@@ -222,12 +258,18 @@ namespace pipedal
|
|||||||
public:
|
public:
|
||||||
~WebSocketSession()
|
~WebSocketSession()
|
||||||
{
|
{
|
||||||
|
this->socketHandler = nullptr;
|
||||||
|
webSocket = nullptr;
|
||||||
|
pServer = nullptr;
|
||||||
Lv2Log::info("WebSocketSession closed.");
|
Lv2Log::info("WebSocketSession closed.");
|
||||||
}
|
}
|
||||||
using ptr = std::shared_ptr<WebSocketSession>;
|
using ptr = std::shared_ptr<WebSocketSession>;
|
||||||
WebSocketSession(WebServerImpl *pServer, server::connection_ptr &webSocket)
|
WebSocketSession(WebServerImpl *pServer, server::connection_ptr &webSocket)
|
||||||
: pServer(pServer),
|
: pServer(pServer),
|
||||||
webSocket(webSocket)
|
webSocket(webSocket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void Open()
|
||||||
{
|
{
|
||||||
uri requestUri(webSocket->get_uri()->str().c_str());
|
uri requestUri(webSocket->get_uri()->str().c_str());
|
||||||
fromAddress = SS(webSocket->get_socket().remote_endpoint());
|
fromAddress = SS(webSocket->get_socket().remote_endpoint());
|
||||||
@@ -276,48 +318,58 @@ namespace pipedal
|
|||||||
|
|
||||||
std::set<WebSocketSession::ptr, std::owner_less<WebSocketSession::ptr>> m_sessions;
|
std::set<WebSocketSession::ptr, std::owner_less<WebSocketSession::ptr>> m_sessions;
|
||||||
|
|
||||||
void on_session_closed(WebSocketSession::ptr session, connection_hdl hConnection)
|
void on_session_closed(WebSocketSession::ptr &session, connection_hdl hConnection)
|
||||||
{
|
{
|
||||||
m_sessions.erase(session);
|
m_sessions.erase(session);
|
||||||
|
session = nullptr; // probably delete here.
|
||||||
m_connections.erase(hConnection);
|
m_connections.erase(hConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotFound(server::connection_type &connection, const std::string &filename)
|
void NotFound(server::connection_type &connection, const std::string &filename)
|
||||||
{
|
{
|
||||||
// 404 error
|
try {
|
||||||
std::stringstream ss;
|
// 404 error
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
ss << "<!doctype html><html><head>"
|
ss << "<!doctype html><html><head>"
|
||||||
<< "<title>Error 404 (Resource not found)</title><body>"
|
<< "<title>Error 404 (Resource not found)</title><body>"
|
||||||
<< "<h1>Error 404</h1>"
|
<< "<h1>Error 404</h1>"
|
||||||
<< "<p>The requested URL " << HtmlHelper::HtmlEncode(filename) << " was not found on this server.</p>"
|
<< "<p>The requested URL " << HtmlHelper::HtmlEncode(filename) << " was not found on this server.</p>"
|
||||||
<< "</body></head></html>";
|
<< "</body></head></html>";
|
||||||
|
|
||||||
std::string body = ss.str();
|
std::string body = ss.str();
|
||||||
connection.set_body(body);
|
connection.set_body(body);
|
||||||
std::stringstream ssLen;
|
std::stringstream ssLen;
|
||||||
ssLen << body.length();
|
ssLen << body.length();
|
||||||
connection.replace_header(HttpField::content_length, ssLen.str());
|
connection.replace_header(HttpField::content_length, ssLen.str());
|
||||||
connection.set_status(websocketpp::http::status_code::not_found);
|
connection.set_status(websocketpp::http::status_code::not_found);
|
||||||
|
} catch (const std::exception&)
|
||||||
|
{
|
||||||
|
// ignored. Things weren't going well anyway.
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
void ServerError(server::connection_type &connection, const std::string &error)
|
void ServerError(server::connection_type &connection, const std::string &error)
|
||||||
{
|
{
|
||||||
// 404 error
|
try {
|
||||||
std::stringstream ss;
|
// 404 error
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
ss << "<!doctype html><html><head>"
|
ss << "<!doctype html><html><head>"
|
||||||
<< "<title>Error 500 (Server error)</title><body>"
|
<< "<title>Error 500 (Server error)</title><body>"
|
||||||
<< "<h1>Error 500</h1>"
|
<< "<h1>Error 500</h1>"
|
||||||
<< "<p>" << HtmlHelper::HtmlEncode(error) << "</p>"
|
<< "<p>" << HtmlHelper::HtmlEncode(error) << "</p>"
|
||||||
<< "</body></head></html>";
|
<< "</body></head></html>";
|
||||||
std::string body = ss.str();
|
std::string body = ss.str();
|
||||||
connection.set_body(body);
|
connection.set_body(body);
|
||||||
std::stringstream ssLen;
|
std::stringstream ssLen;
|
||||||
ssLen << body.length();
|
ssLen << body.length();
|
||||||
connection.replace_header(HttpField::content_length, ssLen.str());
|
connection.replace_header(HttpField::content_length, ssLen.str());
|
||||||
|
|
||||||
connection.set_status(websocketpp::http::status_code::internal_server_error);
|
connection.set_status(websocketpp::http::status_code::internal_server_error);
|
||||||
|
} catch (const std::exception&)
|
||||||
|
{
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -564,10 +616,16 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
m_connections.insert(hdl);
|
m_connections.insert(hdl);
|
||||||
|
|
||||||
server::connection_ptr webSocket = m_endpoint.get_con_from_hdl(hdl);
|
|
||||||
|
|
||||||
WebSocketSession::ptr socketSession = std::make_shared<WebSocketSession>(this, webSocket);
|
try {
|
||||||
m_sessions.insert(socketSession);
|
server::connection_ptr webSocket = m_endpoint.get_con_from_hdl(hdl);
|
||||||
|
WebSocketSession::ptr socketSession = std::make_shared<WebSocketSession>(this, webSocket);
|
||||||
|
socketSession->Open();
|
||||||
|
m_sessions.insert(socketSession);
|
||||||
|
} catch (const std::exception&e)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Failed to open session: %s", e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_fail(connection_hdl hdl)
|
void on_fail(connection_hdl hdl)
|
||||||
@@ -591,7 +649,6 @@ namespace pipedal
|
|||||||
m_endpoint.set_reuse_addr(true);
|
m_endpoint.set_reuse_addr(true);
|
||||||
|
|
||||||
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
|
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
|
||||||
// m_endpoint.set_access_channels(websocketpp::log::alevel::access_core);
|
|
||||||
m_endpoint.set_access_channels(websocketpp::log::alevel::fail);
|
m_endpoint.set_access_channels(websocketpp::log::alevel::fail);
|
||||||
|
|
||||||
m_endpoint.init_asio(&ioc);
|
m_endpoint.init_asio(&ioc);
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, Peter Thorson. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the WebSocket++ Project nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Based on websocketpp/logger/pipedal_logger.hpp, Copyright Peter Thorson.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Logger that redirects webserverpp messages to Lv2Log.
|
||||||
|
#ifndef WEBSOCKETPP_LOGGER_PIPEDAL_HPP
|
||||||
|
#define WEBSOCKETPP_LOGGER_PIPEDAL_HPP
|
||||||
|
|
||||||
|
|
||||||
|
/* Need a way to print a message to the log
|
||||||
|
*
|
||||||
|
* - timestamps
|
||||||
|
* - channels
|
||||||
|
* - thread safe
|
||||||
|
* - output to stdout or file
|
||||||
|
* - selective output channels, both compile time and runtime
|
||||||
|
* - named channels
|
||||||
|
* - ability to test whether a log message will be printed at compile time
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <string>
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
|
#include "ss.hpp"
|
||||||
|
#include <websocketpp/logger/levels.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class pipedal_elog {
|
||||||
|
public:
|
||||||
|
using level = websocketpp::log::level;
|
||||||
|
using elevel = websocketpp::log::elevel;
|
||||||
|
using channel_type_hint = websocketpp::log::channel_type_hint;
|
||||||
|
pipedal_elog()
|
||||||
|
: m_channels(0xffffffff)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destructor
|
||||||
|
~pipedal_elog() {}
|
||||||
|
|
||||||
|
/// Copy constructor
|
||||||
|
pipedal_elog(pipedal_elog const & other)
|
||||||
|
: m_channels(other.m_channels)
|
||||||
|
{}
|
||||||
|
|
||||||
|
pipedal_elog(level level,channel_type_hint::value hint = channel_type_hint::error)
|
||||||
|
{
|
||||||
|
this->m_channels = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
// no copy assignment operator because of const member variables
|
||||||
|
pipedal_elog & operator=(pipedal_elog const &) = delete;
|
||||||
|
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_
|
||||||
|
/// Move constructor
|
||||||
|
pipedal_elog(pipedal_elog && other)
|
||||||
|
: m_channels(other.m_channels)
|
||||||
|
{}
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
// no move assignment operator because of const member variables
|
||||||
|
pipedal_elog & operator=(pipedal_elog &&) = delete;
|
||||||
|
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
|
||||||
|
#endif // _WEBSOCKETPP_MOVE_SEMANTICS_
|
||||||
|
|
||||||
|
void set_channels(level channels) {
|
||||||
|
m_channels |= channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_channels(level channels) {
|
||||||
|
m_channels &= ~m_channels;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a string message to the given channel
|
||||||
|
/**
|
||||||
|
* @param channel The channel to write to
|
||||||
|
* @param msg The message to write
|
||||||
|
*/
|
||||||
|
void write(level channel, std::string const & msg) {
|
||||||
|
if (!dynamic_test(channel)) return;
|
||||||
|
switch (channel)
|
||||||
|
{
|
||||||
|
case elevel::devel:
|
||||||
|
case elevel::library:
|
||||||
|
Lv2Log::debug(msg);
|
||||||
|
break;
|
||||||
|
case elevel::info:
|
||||||
|
Lv2Log::info(msg);
|
||||||
|
break;
|
||||||
|
case elevel::warn:
|
||||||
|
Lv2Log::warning(msg);
|
||||||
|
break;
|
||||||
|
case elevel::rerror:
|
||||||
|
Lv2Log::error(msg);
|
||||||
|
break;
|
||||||
|
case elevel::fatal:
|
||||||
|
Lv2Log::error("Fatal error: " + msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a cstring message to the given channel
|
||||||
|
/**
|
||||||
|
* @param channel The channel to write to
|
||||||
|
* @param msg The message to write
|
||||||
|
*/
|
||||||
|
void write(level channel, char const * msg) {
|
||||||
|
write(channel,std::string(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool static_test(level channel) const {
|
||||||
|
return m_static_channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dynamic_test(level channel) {
|
||||||
|
return (m_channels) & channel != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
private:
|
||||||
|
static constexpr level m_static_channels = elevel::warn | elevel::rerror | elevel::fatal;
|
||||||
|
level m_channels;
|
||||||
|
};
|
||||||
|
|
||||||
|
class pipedal_alog {
|
||||||
|
public:
|
||||||
|
using level = websocketpp::log::level;
|
||||||
|
using alevel = websocketpp::log::alevel;
|
||||||
|
using channel_type_hint = websocketpp::log::channel_type_hint;
|
||||||
|
|
||||||
|
|
||||||
|
pipedal_alog()
|
||||||
|
:m_channels(alevel::fail)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
pipedal_alog(level level, channel_type_hint::value hint = channel_type_hint::access)
|
||||||
|
: m_channels(level)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
/// Destructor
|
||||||
|
~pipedal_alog() {}
|
||||||
|
|
||||||
|
/// Copy constructor
|
||||||
|
pipedal_alog(pipedal_alog const & other)
|
||||||
|
:m_channels(other.m_channels)
|
||||||
|
{}
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
// no copy assignment operator because of const member variables
|
||||||
|
pipedal_alog & operator=(pipedal_elog const &) = delete;
|
||||||
|
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_
|
||||||
|
/// Move constructor
|
||||||
|
pipedal_alog(pipedal_alog && other)
|
||||||
|
{}
|
||||||
|
|
||||||
|
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
// no move assignment operator because of const member variables
|
||||||
|
pipedal_alog & operator=(pipedal_elog &&) = delete;
|
||||||
|
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
||||||
|
|
||||||
|
#endif // _WEBSOCKETPP_MOVE_SEMANTICS_
|
||||||
|
|
||||||
|
void set_channels(level channels) {
|
||||||
|
m_channels |= channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_channels(level channels) {
|
||||||
|
m_channels = m_channels & ~channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a string message to the given channel
|
||||||
|
/**
|
||||||
|
* @param channel The channel to write to
|
||||||
|
* @param msg The message to write
|
||||||
|
*/
|
||||||
|
void write(level channel, std::string const & msg) {
|
||||||
|
if (dynamic_test(channel))
|
||||||
|
{
|
||||||
|
Lv2Log::info(SS('[' << alevel::channel_name(channel) << "] " << msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a cstring message to the given channel
|
||||||
|
/**
|
||||||
|
* @param channel The channel to write to
|
||||||
|
* @param msg The message to write
|
||||||
|
*/
|
||||||
|
void write(level channel, char const * msg) {
|
||||||
|
write(channel,std::string(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool static_test(level channel) const {
|
||||||
|
return (this->m_static_channels & channel) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dynamic_test(level channel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
private:
|
||||||
|
static constexpr level m_static_channels = alevel::access_core;
|
||||||
|
level m_channels;
|
||||||
|
};
|
||||||
|
|
||||||
|
} //namespace pipedal
|
||||||
|
|
||||||
|
#endif // WEBSOCKETPP_LOGGER_PIPEDAL_HPP
|
||||||
+49
-22
@@ -139,6 +139,9 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (segment == "uploadBank")
|
else if (segment == "uploadBank")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
} else if (segment == "uploadUserFile")
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -402,6 +405,37 @@ public:
|
|||||||
res.setContentLength(result.length());
|
res.setContentLength(result.length());
|
||||||
|
|
||||||
res.setBody(result);
|
res.setBody(result);
|
||||||
|
} else if (segment == "uploadUserFile")
|
||||||
|
{
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
|
||||||
|
const std::string &fileBody = req.body();
|
||||||
|
const std::string &directory = request_uri.query("directory");
|
||||||
|
const std::string &filename = request_uri.query("filename");
|
||||||
|
const std::string &patchProperty = request_uri.query("property");
|
||||||
|
|
||||||
|
|
||||||
|
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||||
|
{
|
||||||
|
throw PiPedalException("Malformed request.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
|
||||||
|
std::string outputFileName = std::filesystem::path(directory) / filename;
|
||||||
|
|
||||||
|
std::string path = this->model->UploadUserFile(directory,patchProperty,filename,fileBody);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(outputFileName);
|
||||||
|
std::string response = ss.str();
|
||||||
|
|
||||||
|
res.setContentLength(response.length());
|
||||||
|
res.setBody(response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -698,6 +732,21 @@ int main(int argc, char *argv[])
|
|||||||
model.GetAlsaDevices();
|
model.GetAlsaDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only accept signals on the main thread.
|
||||||
|
int sig;
|
||||||
|
sigset_t sigSet;
|
||||||
|
int s;
|
||||||
|
sigemptyset(&sigSet);
|
||||||
|
sigaddset(&sigSet, SIGINT);
|
||||||
|
sigaddset(&sigSet, SIGTERM);
|
||||||
|
sigaddset(&sigSet, SIGUSR1);
|
||||||
|
|
||||||
|
s = pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
|
||||||
|
if (s != 0)
|
||||||
|
{
|
||||||
|
throw std::logic_error("pthread_sigmask failed.");
|
||||||
|
}
|
||||||
|
|
||||||
#if JACK_HOST
|
#if JACK_HOST
|
||||||
if (systemd)
|
if (systemd)
|
||||||
{
|
{
|
||||||
@@ -767,28 +816,6 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
int sig;
|
|
||||||
sigset_t sigSet;
|
|
||||||
int s;
|
|
||||||
sigemptyset(&sigSet);
|
|
||||||
sigaddset(&sigSet, SIGINT);
|
|
||||||
sigaddset(&sigSet, SIGTERM);
|
|
||||||
sigaddset(&sigSet, SIGUSR1);
|
|
||||||
|
|
||||||
s = pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
|
|
||||||
if (s != 0)
|
|
||||||
{
|
|
||||||
throw std::logic_error("pthread_sigmask failed.");
|
|
||||||
}
|
|
||||||
if (s != 0)
|
|
||||||
{
|
|
||||||
throwSystemError(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
//signal(SIGINT, sig_handler);
|
|
||||||
//signal(SIGTERM, sig_handler);
|
|
||||||
//signal(SIGUSR1, sig_handler);
|
|
||||||
|
|
||||||
sigwait(&sigSet,&sig);
|
sigwait(&sigSet,&sig);
|
||||||
|
|
||||||
if (systemd)
|
if (systemd)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Description=${DESCRIPTION}
|
|||||||
After=network.target
|
After=network.target
|
||||||
After=sound.target
|
After=sound.target
|
||||||
After=local-fs.target
|
After=local-fs.target
|
||||||
|
After=avahi-daemon
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user