TooB Parametric EQ UI
This commit is contained in:
+1
-1
@@ -1769,7 +1769,7 @@ public:
|
||||
std::lock_guard guard(mutex);
|
||||
|
||||
this->currentPedalboard = pedalboard;
|
||||
if (active)
|
||||
if (active && pedalboard)
|
||||
{
|
||||
pedalboard->Activate();
|
||||
this->activePedalboards.push_back(pedalboard);
|
||||
|
||||
@@ -2040,6 +2040,11 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
|
||||
// calibration is OFF when upgrading.
|
||||
pedalboardItem->SetControlValue("inputCalibrationMode",0.0f);
|
||||
}
|
||||
pValue = pedalboardItem->GetControlValue("version");
|
||||
if (pValue == nullptr)
|
||||
{
|
||||
pedalboardItem->SetControlValue("version",0.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+16
-1
@@ -127,6 +127,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
core__designation = lilv_new_uri(pWorld, LV2_CORE__designation);
|
||||
portgroups__group = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
|
||||
units__unit = lilv_new_uri(pWorld, LV2_UNITS__unit);
|
||||
units__render = lilv_new_uri(pWorld, LV2_UNITS__render);
|
||||
|
||||
invada_units__unit = lilv_new_uri(pWorld, "http://lv2plug.in/ns/extension/units#unit"); // a typo in invada plugin ttl files.
|
||||
invada_portprops__logarithmic = lilv_new_uri(pWorld, "http://lv2plug.in/ns/dev/extportinfo#logarithmic"); // a typo in invada plugin ttl files.
|
||||
@@ -1235,6 +1236,19 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
||||
if (unitsValueUri)
|
||||
{
|
||||
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
|
||||
if (this->units_ == Units::unknown) {
|
||||
// try for a custom format string.
|
||||
AutoLilvNode render = lilv_world_get(
|
||||
pWorld,
|
||||
unitsValueUri,
|
||||
host->lilvUris->units__render,
|
||||
nullptr);
|
||||
if (render) {
|
||||
const char*strRender = lilv_node_as_string(render);
|
||||
custom_units_ = strRender;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1964,6 +1978,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
||||
MAP_REF(Lv2PortInfo, pipedal_ledColor),
|
||||
|
||||
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
|
||||
MAP_REF(Lv2PortInfo, custom_units),
|
||||
MAP_REF(Lv2PortInfo, comment)}};
|
||||
|
||||
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
||||
@@ -2041,10 +2056,10 @@ json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
|
||||
MAP_REF(Lv2PluginUiPort, pipedal_ledColor),
|
||||
|
||||
json_map::enum_reference("units", &Lv2PluginUiPort::units_, get_units_enum_converter()),
|
||||
MAP_REF(Lv2PluginUiPort, custom_units),
|
||||
MAP_REF(Lv2PluginUiPort, comment),
|
||||
MAP_REF(Lv2PluginUiPort, is_bypass),
|
||||
MAP_REF(Lv2PluginUiPort, is_program_controller),
|
||||
MAP_REF(Lv2PluginUiPort, custom_units),
|
||||
MAP_REF(Lv2PluginUiPort, connection_optional),
|
||||
|
||||
}};
|
||||
|
||||
+5
-1
@@ -233,6 +233,7 @@ namespace pipedal
|
||||
std::string designation_;
|
||||
bool is_bypass_ = false;
|
||||
Units units_ = Units::none;
|
||||
std::string custom_units_;
|
||||
std::string comment_;
|
||||
PiPedalUI::ptr piPedalUI_;
|
||||
|
||||
@@ -307,6 +308,7 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(port_group);
|
||||
LV2_PROPERTY_GETSET(comment);
|
||||
LV2_PROPERTY_GETSET_SCALAR(units);
|
||||
LV2_PROPERTY_GETSET(custom_units);
|
||||
LV2_PROPERTY_GETSET(pipedal_ledColor);
|
||||
|
||||
LV2_PROPERTY_GETSET(buffer_type);
|
||||
@@ -574,6 +576,7 @@ namespace pipedal
|
||||
trigger_property_(pPort->trigger_property()),
|
||||
pipedal_ledColor_(pPort->pipedal_ledColor()),
|
||||
comment_(pPort->comment()), units_(pPort->units()),
|
||||
custom_units_(pPort->custom_units()),
|
||||
connection_optional_(pPort->connection_optional())
|
||||
{
|
||||
// Use symbols to index port groups, instead of uris.
|
||||
@@ -626,10 +629,10 @@ namespace pipedal
|
||||
std::string pipedal_ledColor_;
|
||||
|
||||
Units units_ = Units::none;
|
||||
std::string custom_units_;
|
||||
std::string comment_;
|
||||
bool is_bypass_ = false;
|
||||
bool is_program_controller_ = false;
|
||||
std::string custom_units_;
|
||||
bool connection_optional_ = false;
|
||||
|
||||
public:
|
||||
@@ -768,6 +771,7 @@ namespace pipedal
|
||||
AutoLilvNode core__designation;
|
||||
AutoLilvNode portgroups__group;
|
||||
AutoLilvNode units__unit;
|
||||
AutoLilvNode units__render;
|
||||
AutoLilvNode invada_units__unit; // typo in invada plugins.
|
||||
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
add apt install expat
|
||||
|
||||
Versioning for legacy NAM presets!
|
||||
Convert CRVB presets!
|
||||
|
||||
double select on double-click/ok in file dialog.
|
||||
sudo apt install libbz2-dev
|
||||
Move status to pedalboard view.
|
||||
Default to dark theme.
|
||||
|
||||
|
||||
Carla Project icons.
|
||||
@@ -9,6 +15,7 @@ Exactly one underrun per seek in Toob Player
|
||||
|
||||
|
||||
json "skip" code doesn't work with complex objects (MidiChannelBinding specifically).
|
||||
json nan/inf is fatal.
|
||||
|
||||
- pipewire aux in?
|
||||
|
||||
|
||||
+68
-8
@@ -3,40 +3,59 @@
|
||||
find_program(NPM_COMMAND npm)
|
||||
|
||||
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(BUILD_REACT "echo")
|
||||
set(BUILD_REACT_ARGS Skipping react debug build)
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(BUILD_REACT "echo")
|
||||
set(BUILD_REACT_ARGS Skipping react debug build)
|
||||
else()
|
||||
set(BUILD_REACT npm)
|
||||
set(BUILD_REACT_ARGS run build)
|
||||
endif()
|
||||
|
||||
set (BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/vite/dist)
|
||||
set(BUILD_DIRECTORY ${PROJECT_SOURCE_DIR}/vite/dist)
|
||||
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${BUILD_DIRECTORY}/index.html
|
||||
COMMAND ${BUILD_REACT}
|
||||
ARGS ${BUILD_REACT_ARGS}
|
||||
ARGS ${BUILD_REACT_ARGS}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vite
|
||||
|
||||
DEPENDS
|
||||
DEPENDS
|
||||
# find public src *.json *.ts -type f >/tmp/tmp.txt
|
||||
public/serve.json
|
||||
public/fonts/Roboto-BoldItalic.woff2
|
||||
public/fonts/Roboto-MediumItalic.woff2
|
||||
public/fonts/Roboto-Thin.woff2
|
||||
public/fonts/Roboto-LightItalic.woff2
|
||||
public/fonts/epf/epf_lul-webfont.woff
|
||||
public/fonts/epf/stylesheet.css
|
||||
public/fonts/england-hand/stylesheet.css
|
||||
public/fonts/england-hand/england-webfont.woff
|
||||
public/fonts/comforta/Comfortaa-Bold.ttf
|
||||
public/fonts/comforta/Comfortaa-Light.ttf
|
||||
public/fonts/comforta/Comfortaa-Regular.ttf
|
||||
public/fonts/Roboto-Light.woff2
|
||||
public/fonts/Roboto-BlackItalic.woff2
|
||||
public/fonts/Roboto-ThinItalic.woff2
|
||||
public/fonts/cooper/cooperhewitt-book-webfont.woff2
|
||||
public/fonts/cooper/cooperhewitt-lightitalic-webfont.woff2
|
||||
public/fonts/cooper/cooperhewitt-bookitalic-webfont.woff2
|
||||
public/fonts/cooper/cooperhewitt-semibold-webfont.woff2
|
||||
public/fonts/cooper/cooperhewitt-light-webfont.woff2
|
||||
public/fonts/LICENSE.txt
|
||||
public/fonts/Roboto-Regular.woff2
|
||||
public/fonts/Roboto-Medium.woff2
|
||||
public/fonts/Roboto-Black.woff2
|
||||
public/fonts/Roboto-Bold.woff2
|
||||
public/fonts/Roboto-Italic.woff2
|
||||
public/fonts/pirulen/stylesheet.css
|
||||
public/fonts/pirulen/pirulen_rg-webfont.woff
|
||||
public/fonts/questrial/questrial-regular-webfont.woff
|
||||
public/fonts/questrial/stylesheet.css
|
||||
public/fonts/nexa/Nexa_Free_Bold-webfont.woff
|
||||
public/fonts/nexa/stylesheet.css
|
||||
public/robots.txt
|
||||
public/css/modGui.css
|
||||
public/css/roboto.css
|
||||
public/manifest.json
|
||||
public/logo192.png
|
||||
@@ -48,6 +67,7 @@ add_custom_command(
|
||||
public/img/fx_lr.svg
|
||||
public/img/fx_pitch.svg
|
||||
public/img/vst.svg
|
||||
public/img/missing_thumbnail.jpg
|
||||
public/img/ic_bank.svg
|
||||
public/img/fx_utility.svg
|
||||
public/img/fx_compressor.svg
|
||||
@@ -70,28 +90,35 @@ add_custom_command(
|
||||
public/img/fx_flanger2.svg
|
||||
public/img/fx_generator.svg
|
||||
public/img/Pi-Logo-3.png
|
||||
public/img/red-light-on.png
|
||||
public/img/fx_oscillator.svg
|
||||
public/img/ic_drawer_2.svg
|
||||
public/img/ic_presets_white.svg
|
||||
public/img/fx_chorus.svg
|
||||
public/img/settings_black_24dp.svg
|
||||
public/img/footswitch_strip.png
|
||||
public/img/BlackKnob.png
|
||||
public/img/fx_analyzer.svg
|
||||
public/img/old_delete_outline_white_24dp.svg
|
||||
public/img/fx_reverb.svg
|
||||
public/img/fx_instrument.svg
|
||||
public/img/audio-output.png
|
||||
public/img/ic_drawer_2.png
|
||||
public/img/cx_mono.svg
|
||||
public/img/fx_split_b.svg
|
||||
public/img/default_album.jpg
|
||||
public/img/fx_filter_hp.svg
|
||||
public/img/fx_parametric_eq.svg
|
||||
public/img/fx_filter.svg
|
||||
public/img/fx_error.svg
|
||||
public/img/cx_stereo.svg
|
||||
public/img/fx_modulator.svg
|
||||
public/img/red-light-off.png
|
||||
public/img/delete_outline_black_24dp.svg
|
||||
public/img/fx_gate.svg
|
||||
public/img/vst.png
|
||||
public/img/fx_function.svg
|
||||
public/img/audio-input.png
|
||||
public/img/fx_terminal.svg
|
||||
public/img/fx_plugin.svg
|
||||
public/img/fx_distortion.svg
|
||||
@@ -109,30 +136,46 @@ add_custom_command(
|
||||
public/logo512.png
|
||||
public/sample_lv2_plugins.json
|
||||
src/pipedal/SplitUiControls.tsx
|
||||
src/pipedal/IconButtonEx.tsx
|
||||
src/pipedal/JackHostStatus.tsx
|
||||
src/pipedal/FontTest.tsx
|
||||
src/pipedal/DraggableButtonBase.tsx
|
||||
src/pipedal/AlsaSequencer.tsx
|
||||
src/pipedal/VirtualKeyboardHandler.tsx
|
||||
src/pipedal/TimebaseselectorDialog.tsx
|
||||
src/pipedal/JackStatusView.tsx
|
||||
src/pipedal/Pedal.tsx
|
||||
src/pipedal/PiPedalError.tsx
|
||||
src/pipedal/AudioFileMetadata.tsx
|
||||
src/pipedal/FilePropertyControl.tsx
|
||||
src/pipedal/PerformanceView.tsx
|
||||
src/pipedal/ChannelBindingsHelpDialog.tsx
|
||||
src/pipedal/UseWindowSize.tsx
|
||||
src/pipedal/TemporaryDrawer.tsx
|
||||
src/pipedal/OkCancelDialog.tsx
|
||||
src/pipedal/SystemMidiBindingView.tsx
|
||||
src/pipedal/WifiConfigSettings.tsx
|
||||
src/pipedal/Timebase.tsx
|
||||
src/pipedal/LinkEx.tsx
|
||||
src/pipedal/react-app-env.d.ts
|
||||
src/pipedal/FileUtils.tsx
|
||||
src/pipedal/UpdateDialog.tsx
|
||||
src/pipedal/PresetSelector.tsx
|
||||
src/pipedal/OkDialog.tsx
|
||||
src/pipedal/JackServerSettings.tsx
|
||||
src/pipedal/LoopDialog.tsx
|
||||
src/pipedal/ToobNamView.tsx
|
||||
src/pipedal/SearchControl.tsx
|
||||
src/pipedal/Tone3000AuthComplete.tsx
|
||||
src/pipedal/ObservableEvent.tsx
|
||||
src/pipedal/WindowScale.tsx
|
||||
src/pipedal/DialogStack.tsx
|
||||
src/pipedal/ToobToneStackView.tsx
|
||||
src/pipedal/TextFieldEx.tsx
|
||||
src/pipedal/AppThemed.css
|
||||
src/pipedal/ScreenOrientation.tsx
|
||||
src/pipedal/PluginIcon.tsx
|
||||
src/pipedal/ControlSlider.tsx
|
||||
src/pipedal/svg/snapshot_4.svg
|
||||
src/pipedal/svg/fx_dial.svg
|
||||
src/pipedal/svg/fx_spatial.svg
|
||||
@@ -141,9 +184,11 @@ add_custom_command(
|
||||
src/pipedal/svg/ic_save_bank_as.svg
|
||||
src/pipedal/svg/fx_lr.svg
|
||||
src/pipedal/svg/fx_pitch.svg
|
||||
src/pipedal/svg/fx_nam.svg
|
||||
src/pipedal/svg/snapshot_3.svg
|
||||
src/pipedal/svg/ic_pluginpreset2.svg
|
||||
src/pipedal/svg/ic_bank.svg
|
||||
src/pipedal/svg/mod_ui.svg
|
||||
src/pipedal/svg/fx_utility.svg
|
||||
src/pipedal/svg/fx_compressor.svg
|
||||
src/pipedal/svg/fx_phaser.svg
|
||||
@@ -155,6 +200,7 @@ add_custom_command(
|
||||
src/pipedal/svg/fx_amplifier.svg
|
||||
src/pipedal/svg/ic_help_outline.svg
|
||||
src/pipedal/svg/fx_eq.svg
|
||||
src/pipedal/svg/pp_ui.svg
|
||||
src/pipedal/svg/fx_split_a.svg
|
||||
src/pipedal/svg/fx_spectral.svg
|
||||
src/pipedal/svg/fx_converter.svg
|
||||
@@ -191,6 +237,7 @@ add_custom_command(
|
||||
src/pipedal/svg/fx_empty.svg
|
||||
src/pipedal/svg/fx_flanger.svg
|
||||
src/pipedal/AboutDialog.tsx
|
||||
src/pipedal/ToobPlayerView.tsx
|
||||
src/pipedal/NumericInput.tsx
|
||||
src/pipedal/SelectHoverBackground.tsx
|
||||
src/pipedal/MidiBindingView.tsx
|
||||
@@ -199,6 +246,7 @@ add_custom_command(
|
||||
src/pipedal/AndroidHost.tsx
|
||||
src/pipedal/SplitControlView.tsx
|
||||
src/pipedal/ToobFrequencyResponseView.tsx
|
||||
src/pipedal/ButtonTooltip.tsx
|
||||
src/pipedal/ObservableProperty.tsx
|
||||
src/pipedal/UploadPresetDialog.tsx
|
||||
src/pipedal/Utility.tsx
|
||||
@@ -209,6 +257,8 @@ add_custom_command(
|
||||
src/pipedal/StringBuilder.tsx
|
||||
src/pipedal/ToobCabSimView.tsx
|
||||
src/pipedal/ToobSpectrumAnalyzerView.tsx
|
||||
src/pipedal/ScratchClass.tsx
|
||||
src/pipedal/ButtonEx.tsx
|
||||
src/pipedal/ControlTooltip.tsx
|
||||
src/pipedal/AlsaMidiDeviceInfo.tsx
|
||||
src/pipedal/ToobInputStageView.tsx
|
||||
@@ -224,6 +274,7 @@ add_custom_command(
|
||||
src/pipedal/DarkMode.tsx
|
||||
src/pipedal/Updater.tsx
|
||||
src/pipedal/Rect.tsx
|
||||
src/pipedal/ToolTipEx.tsx
|
||||
src/pipedal/PedalboardView.tsx
|
||||
src/pipedal/JackServerSettingsDialog.tsx
|
||||
src/pipedal/PluginOutputControl.tsx
|
||||
@@ -236,6 +287,7 @@ add_custom_command(
|
||||
src/pipedal/WifiChannel.tsx
|
||||
src/pipedal/App.tsx
|
||||
src/pipedal/IControlViewFactory.tsx
|
||||
src/pipedal/Tone3000HelpDialog.tsx
|
||||
src/pipedal/WifiDirectConfigSettings.tsx
|
||||
src/pipedal/ZoomedUiControl.tsx
|
||||
src/pipedal/MidiBindingsDialog.tsx
|
||||
@@ -244,6 +296,7 @@ add_custom_command(
|
||||
src/pipedal/FullScreenIME.tsx
|
||||
src/pipedal/FilePropertyDialog.tsx
|
||||
src/pipedal/RenameDialog.tsx
|
||||
src/pipedal/SelectScreenOrientationDialog.tsx
|
||||
src/pipedal/ControlViewFactory.tsx
|
||||
src/pipedal/MaterialColors.tsx
|
||||
src/pipedal/PluginPresetsDialog.tsx
|
||||
@@ -265,19 +318,25 @@ add_custom_command(
|
||||
src/pipedal/BankDialog.tsx
|
||||
src/pipedal/FilePropertyDirectorySelectDialog.tsx
|
||||
src/pipedal/GovernorSettings.tsx
|
||||
src/pipedal/ModGuiHost.tsx
|
||||
src/pipedal/XxxSnippet.tsx
|
||||
src/pipedal/Tone3000Dialog.tsx
|
||||
src/pipedal/PluginPresetSelector.tsx
|
||||
src/pipedal/OldDeleteIcon.tsx
|
||||
src/pipedal/SelectMidiChannelsDialog.tsx
|
||||
src/pipedal/SearchFilter.tsx
|
||||
src/pipedal/Jack.tsx
|
||||
src/pipedal/ToobPlayerControl.tsx
|
||||
src/pipedal/ModGuiErrorBoundary.tsx
|
||||
src/pipedal/LoadPluginDialog.tsx
|
||||
src/pipedal/Pedalboard.tsx
|
||||
src/pipedal/Draggable.tsx
|
||||
src/pipedal/SelectChannelsDialog.tsx
|
||||
src/pipedal/PluginClass.tsx
|
||||
src/pipedal/DraggableGrid.tsx
|
||||
src/pipedal/GraphicEqCtl.tsx
|
||||
src/pipedal/WifiConfigDialog.tsx
|
||||
src/pipedal/GuitarMlHelpDialog.tsx
|
||||
src/pipedal/SelectThemeDialog.tsx
|
||||
src/pipedal/WifiDirectConfigDialog.tsx
|
||||
src/pipedal/UploadFileDialog.tsx
|
||||
@@ -285,6 +344,7 @@ add_custom_command(
|
||||
src/pipedal/Rectangle.tsx
|
||||
src/pipedal/PluginPreset.tsx
|
||||
src/pipedal/SettingsDialog.tsx
|
||||
src/pipedal/AutoZoom.tsx
|
||||
src/pipedal/AppThemed.tsx
|
||||
src/pipedal/OptionsDialog.tsx
|
||||
src/pipedal/MidiChannelBinding.tsx
|
||||
@@ -299,12 +359,12 @@ add_custom_command(
|
||||
tsconfig.json
|
||||
tsconfig.node.json
|
||||
vite.config.ts
|
||||
|
||||
|
||||
)
|
||||
|
||||
message(STATUS "VITE TARGET: ${BUILD_DIRECTORY}/index.html")
|
||||
|
||||
add_custom_target (
|
||||
add_custom_target(
|
||||
ReactBuild ALL
|
||||
DEPENDS ${BUILD_DIRECTORY}/index.html
|
||||
)
|
||||
|
||||
@@ -31,6 +31,8 @@ import ToobPowerstage2ViewFactory from './ToobPowerStage2View';
|
||||
import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView';
|
||||
import ToobMLViewFactory from './ToobMLView';
|
||||
import ToobPlayerFactory from './ToobPlayerView';
|
||||
import ToobNamViewFactory from './ToobNamView';
|
||||
import ToobParametericEqViewFactory from './ToobParametricEqView';
|
||||
|
||||
|
||||
let pluginFactories: IControlViewFactory[] = [
|
||||
@@ -38,7 +40,9 @@ let pluginFactories: IControlViewFactory[] = [
|
||||
new ToobPowerstage2ViewFactory(),
|
||||
new ToobSpectrumAnalyzerViewFactory(),
|
||||
new ToobMLViewFactory(),
|
||||
new ToobPlayerFactory()
|
||||
new ToobPlayerFactory(),
|
||||
new ToobNamViewFactory(),
|
||||
new ToobParametericEqViewFactory()
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ interface DialogExProps extends DialogProps {
|
||||
tag: string;
|
||||
fullwidth?: boolean;
|
||||
onEnterKey: () => void;
|
||||
dlgRef?: (instance: HTMLDivElement | null) => void;
|
||||
}
|
||||
|
||||
class DialogEx extends React.Component<DialogExProps, DialogExState> implements IDialogStackable {
|
||||
@@ -99,12 +100,13 @@ class DialogEx extends React.Component<DialogExProps, DialogExState> implements
|
||||
evt.stopPropagation();
|
||||
}
|
||||
render() {
|
||||
let { tag, onClose, fullWidth, onEnterKey, ...extra } = this.props;
|
||||
let { dlgRef,tag, onClose, fullWidth, onEnterKey, ...extra } = this.props;
|
||||
return (
|
||||
<Dialog fullWidth={fullWidth ?? false}
|
||||
maxWidth={fullWidth ? false : undefined} {...extra}
|
||||
onClose={(event, reason) => { this.myOnClose(event, reason); }}
|
||||
onKeyDown={(evt) => { this.onKeyDown(evt); }}
|
||||
ref={dlgRef}
|
||||
>
|
||||
{this.props.children}
|
||||
</Dialog>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost,ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import GxTunerControl from './GxTunerControl';
|
||||
|
||||
const GXTUNER_URI = "http://guitarix.sourceforge.net/plugins/gxtuner#tuner";
|
||||
@@ -81,7 +81,7 @@ const GxTunerView =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
modifyControls(host: ICustomizationHost,controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
{
|
||||
let refFreqIndex = this.getControlIndex("REFFREQ");
|
||||
let thresholdIndex = this.getControlIndex("THRESHOLD");
|
||||
|
||||
@@ -647,6 +647,10 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new UiControl().deserialize(this);
|
||||
}
|
||||
applyProperties(properties: Partial<UiControl>): UiControl {
|
||||
return { ...this, ...properties };
|
||||
}
|
||||
@@ -879,6 +883,11 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
case Units.pc:
|
||||
text += "%";
|
||||
break;
|
||||
case Units.unknown:
|
||||
if (this.custom_units !== "") {
|
||||
text = this.custom_units.replace("%f",text);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
|
||||
@@ -486,6 +486,19 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
}
|
||||
throw new PiPedalArgumentError("Item not found.");
|
||||
}
|
||||
tryGetItem(instanceId: number): PedalboardItem | null {
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
if (v.value.instanceId === instanceId)
|
||||
{
|
||||
return v.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
deleteItem_(instanceId: number,items: PedalboardItem[]): number | null
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
|
||||
@@ -45,7 +45,7 @@ import FilePropertyDirectoryTree from './FilePropertyDirectoryTree';
|
||||
import AudioFileMetadata from './AudioFileMetadata';
|
||||
import { pathFileName } from './FileUtils';
|
||||
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
|
||||
import {getDefaultModGuiPreference} from './ModGuiHost';
|
||||
import { getDefaultModGuiPreference } from './ModGuiHost';
|
||||
|
||||
export enum State {
|
||||
Loading,
|
||||
@@ -96,11 +96,10 @@ export class HostVersion {
|
||||
} else {
|
||||
this.hostName = "Unknown Host";
|
||||
remainder = "v0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
const args = remainder.split(","); // make make provisions for more.
|
||||
if (args.length >= 1)
|
||||
{
|
||||
if (args.length >= 1) {
|
||||
let versionString = args[0].trim();
|
||||
if (versionString.startsWith('v')) {
|
||||
versionString = versionString.substring(1).trim();
|
||||
@@ -119,7 +118,7 @@ export class HostVersion {
|
||||
this.majorVersion = parseInt(parts[0]);
|
||||
this.minorVersion = parseInt(parts[1]);
|
||||
this.buildNumber = parseInt(parts[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lessThan(major: number, minor: number, build: number): boolean {
|
||||
@@ -133,7 +132,7 @@ export class HostVersion {
|
||||
hostName: string = "";
|
||||
releaseType: string = "";
|
||||
majorVersion: number = 0;
|
||||
minorVersion: number = 0;
|
||||
minorVersion: number = 0;
|
||||
buildNumber: number = 0;
|
||||
}
|
||||
export type PedalboardItemEnabledChangeCallback = (instanceId: number, isEnabled: boolean) => void;
|
||||
@@ -533,7 +532,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
favorites: ObservableProperty<FavoritesList> = new ObservableProperty<FavoritesList>({});
|
||||
|
||||
alsaSequencerConfiguration : ObservableProperty<AlsaSequencerConfiguration> = new ObservableProperty<AlsaSequencerConfiguration>(new AlsaSequencerConfiguration());
|
||||
alsaSequencerConfiguration: ObservableProperty<AlsaSequencerConfiguration> = new ObservableProperty<AlsaSequencerConfiguration>(new AlsaSequencerConfiguration());
|
||||
|
||||
presets: ObservableProperty<PresetIndex> = new ObservableProperty<PresetIndex>
|
||||
(
|
||||
@@ -652,11 +651,10 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return true;
|
||||
|
||||
}
|
||||
private updateEnabledItems(pedalboard: Pedalboard)
|
||||
{
|
||||
private updateEnabledItems(pedalboard: Pedalboard) {
|
||||
for (let item of pedalboard.itemsGenerator()) {
|
||||
this.updatePedalboardItemEnabled(item.instanceId, item.isEnabled);
|
||||
this.updatePedalboardItemUseModUi(item.instanceId, item.useModUi);
|
||||
this.updatePedalboardItemUseModUi(item.instanceId, item.useModUi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1358,9 +1356,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.state.set(State.Ready);
|
||||
if (this.androidHost) {
|
||||
this.hostVersion = new HostVersion(this.androidHost.getHostVersion());
|
||||
if (!this.hostVersion.lessThan(1,1,16))
|
||||
{
|
||||
this.androidHost.setServerVersion(this.serverVersion?.serverVersion??"");
|
||||
if (!this.hostVersion.lessThan(1, 1, 16)) {
|
||||
this.androidHost.setServerVersion(this.serverVersion?.serverVersion ?? "");
|
||||
this.canKeepScreenOn = true;
|
||||
this.keepScreenOn.set(this.androidHost.getKeepScreenOn())
|
||||
this.canSetScreenOrientation = true;
|
||||
@@ -1424,7 +1421,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
|
||||
let newPedalboard = pedalboard.clone();
|
||||
|
||||
let item = newPedalboard.getItem(instanceId);
|
||||
let item = newPedalboard.tryGetItem(instanceId);
|
||||
if (!item) return;
|
||||
|
||||
let changed = false;
|
||||
for (let i = 0; i < controlValues.length; ++i) {
|
||||
@@ -1780,7 +1778,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
// null -> we've never seen a value.
|
||||
item.pathProperties[fileProperty.patchProperty] = "null";
|
||||
}
|
||||
newPedalboard.selectedPlugin = item.instanceId;
|
||||
newPedalboard.selectedPlugin = item.instanceId;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard()
|
||||
return item.instanceId;
|
||||
@@ -2372,14 +2370,19 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let result = new Promise<Type>((resolve, reject) => {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
if (pedalboard) {
|
||||
let item = pedalboard.getItem(instanceId);
|
||||
if (item) {
|
||||
if (item.pathProperties.hasOwnProperty(uri)) {
|
||||
let value = item.pathProperties[uri];
|
||||
let jsonValue = JSON.parse(value);
|
||||
resolve(jsonValue as Type);
|
||||
}
|
||||
try {
|
||||
let item = pedalboard.getItem(instanceId);
|
||||
if (item) {
|
||||
if (item.pathProperties.hasOwnProperty(uri)) {
|
||||
let value = item.pathProperties[uri];
|
||||
let jsonValue = JSON.parse(value);
|
||||
resolve(jsonValue as Type);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!this.webSocket) {
|
||||
@@ -2618,9 +2621,13 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
let pedalboardItem = pedalboard.getItem(instanceId);
|
||||
if (pedalboardItem) {
|
||||
pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject);
|
||||
try {
|
||||
let pedalboardItem = pedalboard.tryGetItem(instanceId);
|
||||
if (pedalboardItem) {
|
||||
pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject);
|
||||
}
|
||||
} catch (ignored) {
|
||||
// e.g. notification for a pedalboard item that is no longer valid.
|
||||
}
|
||||
for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) {
|
||||
let listener = this.monitorPatchPropertyListeners[i];
|
||||
@@ -3390,9 +3397,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.webSocket?.send("setAlsaSequencerConfiguration", alsaSequencerConfiguration);
|
||||
}
|
||||
async getAlsaSequencerConfiguration(): Promise<AlsaSequencerConfiguration> {
|
||||
if (this.webSocket)
|
||||
{
|
||||
let result = await this.webSocket.request<any>("getAlsaSequencerConfiguration");
|
||||
if (this.webSocket) {
|
||||
let result = await this.webSocket.request<any>("getAlsaSequencerConfiguration");
|
||||
return new AlsaSequencerConfiguration().deserialize(result);
|
||||
}
|
||||
throw new Error("No connection.");
|
||||
@@ -3417,7 +3423,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
listener.onEnabledChanged(instanceId, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updatePedalboardItemUseModUi(instanceId: number, useModUi: boolean) {
|
||||
@@ -3434,18 +3440,18 @@ export class PiPedalModel //implements PiPedalModel
|
||||
addPedalboardItemEnabledChangeListener(
|
||||
instanceId: number,
|
||||
onEnabledChanged: PedalboardItemEnabledChangeCallback
|
||||
) : ListenHandle{
|
||||
): ListenHandle {
|
||||
let handle = ++this.nextListenHandle;
|
||||
let currentValue = this.getPedalboardItemEnabled(instanceId);
|
||||
|
||||
let entry: PedalboardItemEnabledChangeItem = {
|
||||
instanceId: instanceId,
|
||||
handle: handle,
|
||||
currentValue: currentValue,
|
||||
let entry: PedalboardItemEnabledChangeItem = {
|
||||
instanceId: instanceId,
|
||||
handle: handle,
|
||||
currentValue: currentValue,
|
||||
onEnabledChanged: onEnabledChanged
|
||||
};
|
||||
|
||||
this.pedalboardItemEnabledChangeListeners.push( entry);
|
||||
this.pedalboardItemEnabledChangeListeners.push(entry);
|
||||
|
||||
onEnabledChanged(instanceId, currentValue);
|
||||
|
||||
@@ -3453,20 +3459,20 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
addPedalboardItemUseModUiChangeListener(
|
||||
instanceId: number,
|
||||
onUseModUiChanged : PedalboardItemUseModUiChangeCallback
|
||||
) : ListenHandle{
|
||||
onUseModUiChanged: PedalboardItemUseModUiChangeCallback
|
||||
): ListenHandle {
|
||||
|
||||
let handle = ++this.nextListenHandle;
|
||||
let currentValue = this.getPedalboardItemUseModUi(instanceId);
|
||||
|
||||
let entry: PedalboardItemUseModUiChangeItem = {
|
||||
instanceId: instanceId,
|
||||
handle: handle,
|
||||
currentValue: currentValue,
|
||||
let entry: PedalboardItemUseModUiChangeItem = {
|
||||
instanceId: instanceId,
|
||||
handle: handle,
|
||||
currentValue: currentValue,
|
||||
onUseModUiChanged: onUseModUiChanged
|
||||
};
|
||||
|
||||
this.pedalboardItemUseModUiChangeListeners.push( entry);
|
||||
this.pedalboardItemUseModUiChangeListeners.push(entry);
|
||||
|
||||
onUseModUiChanged(instanceId, currentValue);
|
||||
|
||||
@@ -3493,7 +3499,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return false;
|
||||
}
|
||||
|
||||
setKeepScreenOn(keepScreenOn: boolean): void {
|
||||
setKeepScreenOn(keepScreenOn: boolean): void {
|
||||
if (this.canKeepScreenOn) {
|
||||
this.keepScreenOn.set(keepScreenOn);
|
||||
if (this.androidHost) {
|
||||
|
||||
@@ -143,7 +143,7 @@ export interface PluginControlProps extends WithStyles<typeof pluginControlStyle
|
||||
theme: Theme;
|
||||
requestIMEEdit: (uiControl: UiControl, value: number) => void;
|
||||
}
|
||||
type PluginControlState = {
|
||||
export type PluginControlState = {
|
||||
error: boolean;
|
||||
editFocused: boolean;
|
||||
previewValue?: string;
|
||||
|
||||
@@ -27,7 +27,7 @@ import AutoZoom from './AutoZoom';
|
||||
|
||||
import ModGuiHost from './ModGuiHost';
|
||||
|
||||
import {midiChannelBindingControlFeatureEnabled} from './MidiChannelBinding';
|
||||
import { midiChannelBindingControlFeatureEnabled } from './MidiChannelBinding';
|
||||
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
@@ -60,6 +60,12 @@ export const StandardItemSize = { width: 80, height: 110 };
|
||||
const LANDSCAPE_HEIGHT_BREAK = 500;
|
||||
|
||||
|
||||
export interface ICustomizationHost {
|
||||
makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode;
|
||||
renderControlGroup(controlGroup: ControlGroup, key: string): ReactNode;
|
||||
isLandscapeGrid(): boolean;
|
||||
}
|
||||
|
||||
function makeIoPluginInfo(name: string, uri: string): UiPlugin {
|
||||
let result = new UiPlugin();
|
||||
result.name = name;
|
||||
@@ -336,7 +342,7 @@ export type ControlNodes = (ReactNode | ControlGroup)[];
|
||||
|
||||
|
||||
export interface ControlViewCustomization {
|
||||
modifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[];
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[];
|
||||
fullScreen(): boolean;
|
||||
|
||||
}
|
||||
@@ -365,7 +371,7 @@ type PluginControlViewState = {
|
||||
|
||||
const PluginControlView =
|
||||
withTheme(withStyles(
|
||||
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> {
|
||||
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> implements ICustomizationHost {
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: PluginControlViewProps) {
|
||||
@@ -546,6 +552,47 @@ const PluginControlView =
|
||||
}
|
||||
}
|
||||
|
||||
isLandscapeGrid(): boolean {
|
||||
return this.state.landscapeGrid;
|
||||
}
|
||||
renderControlGroup(controlGroup: ControlGroup, key: string): ReactNode {
|
||||
let isLandscapeGrid = this.state.landscapeGrid;
|
||||
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
|
||||
let controls: ReactNode[] = [];
|
||||
for (let j = 0; j < controlGroup.controls.length; ++j) {
|
||||
let item = controlGroup.controls[j];
|
||||
controls.push(
|
||||
(
|
||||
<div key={"cgctlx" + j} className={classes.controlPadding}>
|
||||
{item}
|
||||
</div>
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={key} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}
|
||||
style={{ borderWidth: (controlGroup.name === "" ? 0: undefined) }}
|
||||
>
|
||||
{controlGroup.name !== "" && (
|
||||
<div className={classes.portGroupTitle}>
|
||||
<ToolTipEx title={controlGroup.name}
|
||||
>
|
||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||
</ToolTipEx>
|
||||
</div>
|
||||
)}
|
||||
<div className={
|
||||
this.state.landscapeGrid ? classes.portGroupControlsLandscape : classes.portGroupControls} >
|
||||
{
|
||||
controls
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes {
|
||||
let result: ControlNodes = [];
|
||||
let portGroupMap: { [id: string]: ControlGroup } = {};
|
||||
@@ -697,7 +744,6 @@ const PluginControlView =
|
||||
controlKeyIndex: number = 0;
|
||||
controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let isLandscapeGrid = this.state.landscapeGrid;
|
||||
let hasGroups = this.hasGroups(nodes);
|
||||
|
||||
let result: ReactNode[] = [];
|
||||
@@ -706,36 +752,8 @@ const PluginControlView =
|
||||
let node = nodes[i];
|
||||
if (node instanceof ControlGroup) {
|
||||
let controlGroup = node as ControlGroup;
|
||||
let controls: ReactNode[] = [];
|
||||
for (let j = 0; j < controlGroup.controls.length; ++j) {
|
||||
let item = controlGroup.controls[j];
|
||||
controls.push(
|
||||
(
|
||||
<div key={"ctlx" + (this.controlKeyIndex++)} className={classes.controlPadding}>
|
||||
{item}
|
||||
</div>
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
result.push((
|
||||
<div key={"ctlx" + (this.controlKeyIndex++)} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||
<div className={classes.portGroupTitle}>
|
||||
<ToolTipEx title={controlGroup.name}
|
||||
>
|
||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||
</ToolTipEx>
|
||||
</div>
|
||||
<div className={
|
||||
this.state.landscapeGrid ? classes.portGroupControlsLandscape : classes.portGroupControls} >
|
||||
{
|
||||
controls
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
let item = this.renderControlGroup(controlGroup, "cgix" + i)
|
||||
result.push(item);
|
||||
} else {
|
||||
if (this.fullScreen()) {
|
||||
result.push(
|
||||
@@ -825,7 +843,7 @@ const PluginControlView =
|
||||
if (this.props.onSetShowModGui) {
|
||||
this.props.onSetShowModGui(this.props.instanceId, false);
|
||||
}
|
||||
this.setState({showModGuiZoomed: false})
|
||||
this.setState({ showModGuiZoomed: false })
|
||||
}}
|
||||
handleFileSelect={(instanceId, fileProperty, selectedFile) => {
|
||||
this.handleModGuiFileProperty(
|
||||
@@ -883,7 +901,7 @@ const PluginControlView =
|
||||
|
||||
if (this.props.customization) {
|
||||
// allow wrapper class to insert/remove/rebuild controls.
|
||||
controlNodes = this.props.customization.modifyControls(controlNodes);
|
||||
controlNodes = this.props.customization.modifyControls(this, controlNodes);
|
||||
}
|
||||
|
||||
let nodes = this.controlNodesToNodes(controlNodes);
|
||||
|
||||
@@ -33,6 +33,30 @@ import ControlTooltip from './ControlTooltip';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
|
||||
export interface VuColor {
|
||||
minDb: number;
|
||||
maxDb: number;
|
||||
color: string;
|
||||
};
|
||||
|
||||
const defaultVuColors: VuColor[] = [
|
||||
{ minDb: 0, maxDb: 10000, color: "#F00" },
|
||||
{ minDb: -10, maxDb: 0, color: "#FF0" },
|
||||
{ minDb: -10000, maxDb: -10, color: "#0C0" },
|
||||
];
|
||||
|
||||
function getVuColor(vuColors: VuColor[], levelDb: number): string {
|
||||
let ix = 0;
|
||||
for (let i = 0; i < vuColors.length; ++i)
|
||||
{
|
||||
let c = vuColors[i];
|
||||
if (levelDb > c.minDb && levelDb <= c.maxDb) {
|
||||
ix = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return vuColors[ix].color;
|
||||
}
|
||||
|
||||
function makeLedGradient(color: string) {
|
||||
if (color === "green") {
|
||||
@@ -243,6 +267,7 @@ const PluginOutputControl =
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateDbVuTelltale() {
|
||||
let telltaleDone = true;
|
||||
if (this.dbVuHoldTime !== 0) {
|
||||
@@ -264,13 +289,8 @@ const PluginOutputControl =
|
||||
|
||||
if (this.dbVuTelltaleRef.current) {
|
||||
let telltaleStyle = this.dbVuTelltaleRef.current.style;
|
||||
let telltaleColor = getVuColor(defaultVuColors, this.dbVuTelltale);
|
||||
telltaleStyle.marginTop = y + "px";
|
||||
let telltaleColor = "#0C0";
|
||||
if (this.dbVuTelltale >= 0) {
|
||||
telltaleColor = "#F00";
|
||||
} else if (this.dbVuTelltale >= -10) {
|
||||
telltaleColor = "#FF0";
|
||||
}
|
||||
telltaleStyle.background = telltaleColor;
|
||||
}
|
||||
}
|
||||
@@ -366,6 +386,13 @@ const PluginOutputControl =
|
||||
|
||||
dbVuMap(value: number): number {
|
||||
let control = this.props.uiControl;
|
||||
if (value > control.max_value) {
|
||||
value = control.max_value;
|
||||
}
|
||||
if (value < control.min_value)
|
||||
{
|
||||
value = control.min_value;
|
||||
}
|
||||
let y = (control.max_value - value) * this.DB_VU_HEIGHT / (control.max_value - control.min_value);
|
||||
return y;
|
||||
}
|
||||
@@ -448,8 +475,8 @@ const PluginOutputControl =
|
||||
|
||||
} else if (control.isDbVu()) {
|
||||
item_width = undefined;
|
||||
let redLevel = this.dbVuMap(0);
|
||||
let yellowLevel = this.dbVuMap(-10);
|
||||
let vuColors = defaultVuColors;
|
||||
|
||||
return (
|
||||
<div className={classes.controlFrame}
|
||||
style={{ width: item_width }}>
|
||||
@@ -469,11 +496,24 @@ const PluginOutputControl =
|
||||
style={{ flex: "1 1 1", display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
|
||||
<div style={{ width: 8, height: this.DB_VU_HEIGHT + 4, background: "#000", }}>
|
||||
<div style={{ height: this.DB_VU_HEIGHT, width: 4, overflow: "hidden", position: "absolute", margin: 2 }}>
|
||||
<div style={{ width: 4, height: redLevel, position: "absolute", marginTop: 0, background: "#F00" }} />
|
||||
<div style={{ width: 4, height: (yellowLevel - redLevel), position: "absolute", marginTop: redLevel, background: "#CC0" }} />
|
||||
<div style={{ width: 4, height: (this.DB_VU_HEIGHT - yellowLevel), position: "absolute", marginTop: yellowLevel, background: "#0A0" }} />
|
||||
<div style={{ width: 4, height: this.DB_VU_HEIGHT, position: "absolute", left: 0, top: 0 }}>
|
||||
{
|
||||
vuColors.map((vuColor,ix)=>{
|
||||
let top = Math.floor(this.dbVuMap(vuColor.maxDb));
|
||||
let bottom = Math.ceil(this.dbVuMap(vuColor.minDb))+1;
|
||||
|
||||
<div ref={this.dbVuRef} style={{ width: 4, position: "absolute", marginTop: 0, height: this.VU_HEIGHT, background: "#000" }} />
|
||||
return (
|
||||
<div key={ix}
|
||||
style={{ position: "absolute", width: 4, height: (bottom-top),
|
||||
top: top, left:0, background: vuColor.color }} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div ref={this.dbVuRef} style={{
|
||||
width: 4, position: "absolute", marginTop: 0, height: this.VU_HEIGHT, background: "#000"
|
||||
}} />
|
||||
<div ref={this.dbVuTelltaleRef} style={{ width: 4, position: "absolute", marginTop: 100, height: 3, background: "#C00" }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ const ToobCabSimView =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
modifyControls(host: ICustomizationHost,controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
{
|
||||
controls.splice(0,0,
|
||||
( <ToobFrequencyResponseView instanceId={this.props.instanceId} />)
|
||||
|
||||
@@ -32,14 +32,14 @@ import SvgPathBuilder from './SvgPathBuilder';
|
||||
|
||||
const StandardItemSize = { width: 80, height: 110 };
|
||||
|
||||
const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector";
|
||||
export const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector";
|
||||
|
||||
const PLOT_WIDTH = StandardItemSize.width * 2 - 16;
|
||||
const DEFAULT_PLOT_WIDTH = StandardItemSize.width * 2 - 16;
|
||||
const PLOT_HEIGHT = StandardItemSize.height - 12;
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444",
|
||||
width: DEFAULT_PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444",
|
||||
borderRadius: 6,
|
||||
marginTop: 12, marginLeft: 8, marginRight: 8,
|
||||
boxShadow: "1px 4px 8px #000 inset"
|
||||
@@ -127,14 +127,16 @@ const ToobFrequencyResponseView =
|
||||
|
||||
// Size of the SVG element.
|
||||
xMin: number = 0;
|
||||
xMax: number = PLOT_WIDTH + 4;
|
||||
xMax() {
|
||||
return this.props.width ? this.props.width: DEFAULT_PLOT_WIDTH;
|
||||
}
|
||||
yMin: number = 0;
|
||||
yMax: number = PLOT_HEIGHT;
|
||||
dbTickSpacing: number = this.dbTickSpacingOpt;
|
||||
|
||||
toX(frequency: number): number {
|
||||
var logV = Math.log(frequency);
|
||||
return (this.xMax - this.xMin) * (logV - this.logMin) / (this.logMax - this.logMin) + this.xMin;
|
||||
return (this.xMax() - this.xMin) * (logV - this.logMin) / (this.logMax - this.logMin) + this.xMin;
|
||||
|
||||
}
|
||||
toY(value: number): number {
|
||||
@@ -156,11 +158,12 @@ const ToobFrequencyResponseView =
|
||||
|
||||
let dbMin = data[2];
|
||||
let dbMax = data[3];
|
||||
let xMax = this.xMax();
|
||||
|
||||
let n = data.length-4;
|
||||
|
||||
let toX_ = (bin: number): number => {
|
||||
return (this.xMax - this.xMin) * bin/n;
|
||||
return (xMax - this.xMin) * bin/n;
|
||||
|
||||
};
|
||||
let toY_ = (value: number): number => {
|
||||
@@ -240,16 +243,17 @@ const ToobFrequencyResponseView =
|
||||
grid(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
let xMax = this.xMax();
|
||||
for (var db = Math.ceil(this.dbMax / this.dbTickSpacing) * this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing) {
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin);
|
||||
if (db === 0) {
|
||||
result.push(
|
||||
this.majorGridLine(this.xMin, y, this.xMax, y)
|
||||
this.majorGridLine(this.xMin, y, xMax, y)
|
||||
);
|
||||
|
||||
} else {
|
||||
result.push(
|
||||
this.gridLine(this.xMin, y, this.xMax, y)
|
||||
this.gridLine(this.xMin, y, xMax, y)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -286,11 +290,12 @@ const ToobFrequencyResponseView =
|
||||
this.logMin = Math.log(this.fMin);
|
||||
this.logMax = Math.log(this.fMax);
|
||||
|
||||
let xMax = this.xMax();
|
||||
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
return (
|
||||
<div className={classes.frame} >
|
||||
<svg width={PLOT_WIDTH} height={PLOT_HEIGHT} viewBox={"0 0 " + PLOT_WIDTH + " " + PLOT_HEIGHT} stroke="#0F8" fill="none" strokeWidth="2.5" opacity="0.6">
|
||||
<div className={classes.frame} style={{width: xMax }} >
|
||||
<svg width={xMax} height={PLOT_HEIGHT} viewBox={"0 0 " + xMax + " " + PLOT_HEIGHT} stroke="#0F8" fill="none" strokeWidth="2.5" opacity="0.6">
|
||||
{this.grid()}
|
||||
<path d={this.state.path} ref={this.pathRef} />
|
||||
</svg>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
//import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ const ToobInputStageView =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
modifyControls(host: ICustomizationHost,controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
return controls;
|
||||
// let group = controls[1] as ControlGroup;
|
||||
// group.controls.splice(0,0,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, MonitorPortHandle } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
// import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
@@ -107,7 +107,7 @@ const ToobMLView =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
modifyControls(host: ICustomizationHost,controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
// Find EQ group
|
||||
// let group = controls.find((control) => typeof control !== 'string' && (control as ControlGroup).name === "EQ") as ControlGroup;
|
||||
// if (group) {
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
// Copyright (c) 2025 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 { Theme } from '@mui/material/styles';
|
||||
|
||||
import WithStyles from './WithStyles';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
import { withStyles } from "tss-react/mui";
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, ListenHandle, State } from "./PiPedalModel";
|
||||
import { PedalboardItem,ControlValue } from './Pedalboard';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import { UiPlugin,UiControl, ControlType } from './Lv2Plugin';
|
||||
|
||||
|
||||
const TOOB_NAM__MODEL_METADATA = "http://two-play.com/plugins/toob-nam#model_metadata";
|
||||
|
||||
// offset 0: an integer with the folloing bits set.
|
||||
class TOOB_NAM_METADATA_OFFSETS {
|
||||
static readonly flags = 0;
|
||||
static readonly preset_version = 1;
|
||||
static readonly loudness = 2;
|
||||
static readonly gain = 3;
|
||||
static readonly input_level_dbu = 4;
|
||||
static readonly output_level_dbu = 5;
|
||||
static readonly max_medatadata = 6;
|
||||
};
|
||||
|
||||
|
||||
class TOOB_NAM_METADATA_FLAGS {
|
||||
static readonly has_model = 1;
|
||||
static readonly has_loudness = 2;
|
||||
static readonly has_gain = 4;
|
||||
static readonly has_input_level_dbu = 8;
|
||||
static readonly has_output_level_dbu = 16;
|
||||
};
|
||||
|
||||
class ModelMetadata {
|
||||
constructor(metadataValues?: number[])
|
||||
{
|
||||
|
||||
|
||||
if (metadataValues) {
|
||||
let flags = metadataValues[TOOB_NAM_METADATA_OFFSETS.flags];
|
||||
this.hasModel = (flags & TOOB_NAM_METADATA_FLAGS.has_model) !== 0;
|
||||
this.hasLoudness = (flags & TOOB_NAM_METADATA_FLAGS.has_loudness) !== 0;
|
||||
this.hasGain = (flags & TOOB_NAM_METADATA_FLAGS.has_gain) !== 0;
|
||||
this.hasInputLevelDBU = (flags & TOOB_NAM_METADATA_FLAGS.has_input_level_dbu) !== 0;
|
||||
this.hasOutputLevelDBU = (flags & TOOB_NAM_METADATA_FLAGS.has_output_level_dbu) !== 0;
|
||||
|
||||
this.loudness = metadataValues[TOOB_NAM_METADATA_OFFSETS.loudness];
|
||||
this.gain = metadataValues[TOOB_NAM_METADATA_OFFSETS.gain];
|
||||
this.inputlevelDBU = metadataValues[TOOB_NAM_METADATA_OFFSETS.input_level_dbu];
|
||||
this.outputlevelDBU = metadataValues[TOOB_NAM_METADATA_OFFSETS.output_level_dbu];
|
||||
this.preset_version = metadataValues[TOOB_NAM_METADATA_OFFSETS.preset_version];
|
||||
} else {
|
||||
this.hasModel = false;
|
||||
this.hasLoudness = false;
|
||||
this.hasGain = false;
|
||||
this.hasInputLevelDBU = false;
|
||||
this.hasOutputLevelDBU = false;
|
||||
|
||||
this.loudness = 0;
|
||||
this.gain = 0;
|
||||
this.inputlevelDBU = 0;
|
||||
this.outputlevelDBU = 0;
|
||||
this.preset_version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
preset_version: number;
|
||||
|
||||
hasModel: boolean;
|
||||
hasLoudness: boolean;
|
||||
hasGain: boolean;
|
||||
hasInputLevelDBU: boolean;
|
||||
hasOutputLevelDBU: boolean;
|
||||
|
||||
loudness: number;
|
||||
gain: number;
|
||||
inputlevelDBU: number;
|
||||
outputlevelDBU: number;
|
||||
|
||||
}
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
});
|
||||
|
||||
interface ToobNamViewProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobNamViewState {
|
||||
showEqSection: boolean;
|
||||
enableCalibration: boolean;
|
||||
enableOutputNormalization: boolean;
|
||||
modelMetadata: ModelMetadata;
|
||||
}
|
||||
|
||||
const ToobNamView =
|
||||
withStyles(
|
||||
class extends React.Component<ToobNamViewProps, ToobNamViewState>
|
||||
implements ControlViewCustomization {
|
||||
model: PiPedalModel;
|
||||
|
||||
customizationId: number = 1;
|
||||
|
||||
constructor(props: ToobNamViewProps) {
|
||||
super(props);
|
||||
this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this);
|
||||
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
showEqSection: false,
|
||||
enableCalibration: false,
|
||||
enableOutputNormalization: false,
|
||||
modelMetadata: new ModelMetadata()
|
||||
}
|
||||
let pluginInfo: UiPlugin | null = this.model.getUiPlugin(this.props.item.uri);
|
||||
if (pluginInfo === null)
|
||||
{
|
||||
throw new Error("Plugin not fouund.");
|
||||
}
|
||||
let inputCalibrationControl = pluginInfo.getControl("inputCalibrationMode");
|
||||
if (!inputCalibrationControl)
|
||||
{
|
||||
throw new Error("Control not found.");
|
||||
}
|
||||
let patchedInputControl = new UiControl().deserialize(inputCalibrationControl);
|
||||
patchedInputControl.controlType = ControlType.Select;
|
||||
this.patchedInputControl = patchedInputControl
|
||||
|
||||
let outputCalibrationControl = pluginInfo.getControl("outputCalibration");
|
||||
if (!outputCalibrationControl)
|
||||
{
|
||||
throw new Error("Control not found.");
|
||||
}
|
||||
let patchedOutputControl = new UiControl().deserialize(outputCalibrationControl);
|
||||
patchedOutputControl.controlType = ControlType.Select;
|
||||
this.noCalibrationOutputControl = patchedOutputControl
|
||||
this.noCalibrationOutputControl.scale_points.splice(1,1);
|
||||
}
|
||||
|
||||
private patchedInputControl: UiControl;
|
||||
private noCalibrationOutputControl: UiControl;
|
||||
fullScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
disableControl(control: React.ReactNode, key: string) {
|
||||
return (
|
||||
<div style={{ opacity: 0.3, pointerEvents: "none" }}
|
||||
key={key}
|
||||
|
||||
onPointerDownCapture={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{control}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
let EqPos = 7;
|
||||
let CalibrationGroupPos = 8;
|
||||
|
||||
let calibrationGroup = controls[CalibrationGroupPos] as ControlGroup;
|
||||
|
||||
calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl,this.props.item.controlValues);
|
||||
|
||||
if (this.state.enableCalibration)
|
||||
{
|
||||
calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl,this.props.item.controlValues);
|
||||
|
||||
} else {
|
||||
calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl,
|
||||
[ new ControlValue("inputCalibrationMode",0.0)]
|
||||
);
|
||||
calibrationGroup.controls[0] = this.disableControl(calibrationGroup.controls[0], "cg_01d");
|
||||
calibrationGroup.controls[1] = this.disableControl(calibrationGroup.controls[1], "cg_02d");
|
||||
|
||||
if (this.state.enableOutputNormalization)
|
||||
{
|
||||
calibrationGroup.controls[2] = host.makeStandardControl(this.noCalibrationOutputControl, this.props.item.controlValues);
|
||||
} else {
|
||||
let tControl: UiControl = this.noCalibrationOutputControl.clone();
|
||||
tControl.symbol = "_disabled_output";
|
||||
calibrationGroup.controls[2] = host.makeStandardControl(
|
||||
tControl,
|
||||
[ new ControlValue("_disabled_output",2.0)]);
|
||||
calibrationGroup.controls[2] = this.disableControl(calibrationGroup.controls[2], "cg_03d");
|
||||
|
||||
}
|
||||
}
|
||||
if (!this.state.showEqSection) {
|
||||
controls.splice(EqPos, 1);
|
||||
}
|
||||
return controls;
|
||||
}
|
||||
|
||||
private handleConnectionStateChanged(state: State) {
|
||||
if (state === State.Ready) {
|
||||
this.unsubscribeFromMetadata();
|
||||
this.subscribeToMetdata();
|
||||
}
|
||||
}
|
||||
|
||||
handleModelMetadata(atomData: any) {
|
||||
if (atomData && atomData.otype_ === "Vector" && atomData.value) {
|
||||
let metadata = new ModelMetadata(atomData.value as number[]);
|
||||
this.setState({
|
||||
modelMetadata: metadata,
|
||||
showEqSection: metadata.preset_version === 0,
|
||||
enableCalibration: metadata.hasInputLevelDBU && metadata.hasModel,
|
||||
enableOutputNormalization: metadata.hasLoudness && metadata.hasModel
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
subscribeToMetdata() {
|
||||
this.subscribedId = this.props.instanceId;
|
||||
this.listenHandle = this.model.monitorPatchProperty(
|
||||
this.props.instanceId,
|
||||
TOOB_NAM__MODEL_METADATA,
|
||||
(instanceId, propertyUri, atomData) => {
|
||||
this.handleModelMetadata(atomData);
|
||||
});
|
||||
this.model.getPatchProperty(
|
||||
this.props.instanceId,
|
||||
TOOB_NAM__MODEL_METADATA
|
||||
).then((atomData) => {
|
||||
this.handleModelMetadata(atomData);
|
||||
}).catch((e) => {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
unsubscribeFromMetadata() {
|
||||
this.subscribedId = null;
|
||||
if (this.listenHandle) {
|
||||
this.model.cancelMonitorPatchProperty(this.listenHandle);
|
||||
this.listenHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private listenHandle: ListenHandle | null = null;
|
||||
componentDidMount() {
|
||||
if (super.componentDidMount) {
|
||||
super.componentDidMount();
|
||||
}
|
||||
this.subscribeToMetdata();
|
||||
this.model.state.addOnChangedHandler(this.handleConnectionStateChanged);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeFromMetadata();
|
||||
if (super.componentWillUnmount) {
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
this.model.state.removeOnChangedHandler(this.handleConnectionStateChanged);
|
||||
|
||||
}
|
||||
|
||||
private subscribedId: number | null = null;
|
||||
componentDidUpdate() {
|
||||
if (this.props.instanceId !== this.subscribedId) {
|
||||
this.unsubscribeFromMetadata();
|
||||
this.subscribeToMetdata();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (<PluginControlView
|
||||
instanceId={this.props.instanceId}
|
||||
item={this.props.item}
|
||||
customization={this}
|
||||
customizationId={this.customizationId}
|
||||
showModGui={false}
|
||||
onSetShowModGui={(instanceId: number, showModGui: boolean) => { }}
|
||||
|
||||
/>);
|
||||
}
|
||||
},
|
||||
styles
|
||||
);
|
||||
|
||||
|
||||
|
||||
class ToobNamViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-nam";
|
||||
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobNamView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
export default ToobNamViewFactory;
|
||||
@@ -0,0 +1,724 @@
|
||||
// Copyright (c) 2025 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 { Theme } from '@mui/material/styles';
|
||||
|
||||
import ToobFrequencyResponseView, { FREQUENCY_RESPONSE_VECTOR_URI } from './ToobFrequencyResponseView';
|
||||
import WithStyles from './WithStyles';
|
||||
import { createStyles } from './WithStyles';
|
||||
import SvgPathBuilder from './SvgPathBuilder';
|
||||
import CircleUpIcon from './svg/expand_circle_up_24dp.svg?react';
|
||||
import CircleDownIcon from './svg/expand_circle_down_24dp.svg?react';
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, State } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import { UiPlugin } from './Lv2Plugin';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import IconButton from '@mui/material/IconButton/IconButton';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
menuIcon: css({
|
||||
fill: (theme.palette.text.primary + "!important"), //theme.palette.text.primary,
|
||||
opacity: 0.6
|
||||
}),
|
||||
upIcon: css({
|
||||
fill: (theme.palette.text.primary + "!important"), //theme.palette.text.primary,
|
||||
opacity: 0.6,
|
||||
alignSelf: "flex-start"
|
||||
}),
|
||||
});
|
||||
|
||||
interface Point { x: number, y: number };
|
||||
|
||||
|
||||
function offsetFromEnd(pt0: Point, pt1: Point, distance: number) {
|
||||
let dx = pt1.x - pt0.x;
|
||||
let dy = pt1.y - pt0.y;
|
||||
let mag = Math.sqrt(dx * dx + dy * dy);
|
||||
let normX = dx / mag;
|
||||
let normY = dy / mag;
|
||||
return {
|
||||
x: pt1.x - normX * distance,
|
||||
y: pt1.y - normY * distance
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getWindowSize = () => {
|
||||
return {
|
||||
width: document.documentElement.clientWidth,
|
||||
height: document.documentElement.clientHeight,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
interface UnpackedControls {
|
||||
eq: React.ReactNode;
|
||||
filters_loCut: React.ReactNode;
|
||||
filters_hiCut: React.ReactNode;
|
||||
low_level: React.ReactNode;
|
||||
low_freq: React.ReactNode;
|
||||
lmf_level: React.ReactNode;
|
||||
lmf_freq: React.ReactNode;
|
||||
lmf_q: React.ReactNode;
|
||||
hmf_level: React.ReactNode;
|
||||
hmf_freq: React.ReactNode;
|
||||
hmf_q: React.ReactNode;
|
||||
hi_level: React.ReactNode;
|
||||
hi_freq: React.ReactNode;
|
||||
gain: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ToobParametricEqViewProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalboardItem;
|
||||
|
||||
}
|
||||
interface ToobParametricEqViewState {
|
||||
windowSize: { width: number, height: number };
|
||||
maximized: boolean;
|
||||
|
||||
}
|
||||
|
||||
const ToobParametricEqView =
|
||||
withStyles(
|
||||
class extends React.Component<ToobParametricEqViewProps, ToobParametricEqViewState>
|
||||
implements ControlViewCustomization {
|
||||
model: PiPedalModel;
|
||||
|
||||
customizationId: number = 943;
|
||||
|
||||
constructor(props: ToobParametricEqViewProps) {
|
||||
super(props);
|
||||
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this);
|
||||
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
windowSize: getWindowSize(),
|
||||
maximized: false
|
||||
}
|
||||
let pluginInfo: UiPlugin | null = this.model.getUiPlugin(this.props.item.uri);
|
||||
if (pluginInfo === null) {
|
||||
throw new Error("Plugin not fouund.");
|
||||
}
|
||||
}
|
||||
|
||||
fullScreen() {
|
||||
return true;
|
||||
}
|
||||
|
||||
horizontalScrollControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
// Implement horizontal scrolling behavior for tiny horizontal screens
|
||||
controls[0] = (
|
||||
<div id="fixedGraph_xxx" key="fixedGraph" style={{ position: "absolute", top: 0, left: 0, padding: 5, background: "#F88" }}>
|
||||
{controls[0] as React.ReactNode}
|
||||
</div>
|
||||
);
|
||||
return controls;
|
||||
}
|
||||
|
||||
unpackControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): UnpackedControls {
|
||||
return {
|
||||
eq: controls[0] as React.ReactNode,
|
||||
filters_loCut: (controls[1] as ControlGroup).controls[0],
|
||||
filters_hiCut: (controls[1] as ControlGroup).controls[1],
|
||||
low_level: (controls[2] as ControlGroup).controls[0],
|
||||
low_freq: (controls[2] as ControlGroup).controls[1],
|
||||
lmf_level: (controls[3] as ControlGroup).controls[0],
|
||||
lmf_freq: (controls[3] as ControlGroup).controls[1],
|
||||
lmf_q: (controls[3] as ControlGroup).controls[2],
|
||||
hmf_level: (controls[4] as ControlGroup).controls[0],
|
||||
hmf_freq: (controls[4] as ControlGroup).controls[1],
|
||||
hmf_q: (controls[4] as ControlGroup).controls[2],
|
||||
hi_level: (controls[5] as ControlGroup).controls[0],
|
||||
hi_freq: (controls[5] as ControlGroup).controls[1],
|
||||
gain: controls[6] as React.ReactNode
|
||||
};
|
||||
}
|
||||
absolutePosition(x: number, y: number, el: React.ReactNode) {
|
||||
return (<div style={{ position: "absolute", left: x, top: y }}>{el}</div>);
|
||||
}
|
||||
private ROW_HEIGHT = 140;
|
||||
private BORDER_HEIGHT = 350;
|
||||
private DIAL_WIDTH = 63;
|
||||
private GRAPH_WIDTH = this.DIAL_WIDTH * 2;
|
||||
|
||||
private smoothPath(pt0: Point, pt1: Point, pt2: Point, pt3: Point, curveRadius: number): string {
|
||||
let pathBuilder = new SvgPathBuilder();
|
||||
|
||||
|
||||
let ctl01 = offsetFromEnd(pt0, pt1, curveRadius);
|
||||
let ctl02 = offsetFromEnd(pt0, pt1, curveRadius / 3);
|
||||
let ctl03 = offsetFromEnd(pt2, pt1, curveRadius / 3);
|
||||
let ctl04 = offsetFromEnd(pt2, pt1, curveRadius);
|
||||
|
||||
let ctl11 = offsetFromEnd(pt1, pt2, curveRadius);
|
||||
let ctl12 = offsetFromEnd(pt1, pt2, curveRadius / 3);
|
||||
let ctl13 = offsetFromEnd(pt3, pt2, curveRadius / 3);
|
||||
let ctl14 = offsetFromEnd(pt3, pt2, curveRadius);
|
||||
|
||||
pathBuilder.moveTo(pt0.x, pt0.y);
|
||||
pathBuilder.lineTo(ctl01.x, ctl01.y);
|
||||
pathBuilder.bezierTo(
|
||||
ctl02.x, ctl02.y,
|
||||
ctl03.x, ctl03.y,
|
||||
ctl04.x, ctl04.y
|
||||
);
|
||||
pathBuilder.lineTo(ctl11.x, ctl11.y);
|
||||
pathBuilder.bezierTo(
|
||||
ctl12.x, ctl12.y,
|
||||
ctl13.x, ctl13.y,
|
||||
ctl14.x, ctl14.y
|
||||
);
|
||||
pathBuilder.lineTo(pt3.x, pt3.y);
|
||||
return pathBuilder.toString();
|
||||
}
|
||||
horizontalCompactControls(host: ICustomizationHost, controls: UnpackedControls) {
|
||||
let height = this.BORDER_HEIGHT;
|
||||
let row0 = height / 2 - this.ROW_HEIGHT;
|
||||
let row1 = row0 + this.ROW_HEIGHT;
|
||||
|
||||
let borderTop = 0;
|
||||
let borderBottom = height - 8;
|
||||
|
||||
let labelTop = row0 - 30;
|
||||
let labelBottom = row1 + this.ROW_HEIGHT - 5;
|
||||
|
||||
let colPos = (group: number, col: number) => this.DIAL_WIDTH * (0.3 * group + col);
|
||||
let cpos0 = (group: number, col: number, el: React.ReactNode) => this.absolutePosition(colPos(group, col), row0, el);
|
||||
let cpos1 = (group: number, col: number, el: React.ReactNode) => this.absolutePosition(colPos(group, col) + this.DIAL_WIDTH / 2, row1, el);
|
||||
|
||||
|
||||
let label1 = (y: number, group: number, col0: number, col1: number, text: string, align: "left" | "center" | "right") => {
|
||||
let left = colPos(group, col0);
|
||||
let right = colPos(group, col1);
|
||||
return (
|
||||
<Typography variant="body1" noWrap style={{
|
||||
fontSize: "0.85em", fontWeight: 700,
|
||||
top: y, paddingLeft: 0, paddingRight: 0,
|
||||
position: "absolute", left: left + 8, width: right - left,
|
||||
opacity: 0.4, textAlign: align
|
||||
}}>{text}</Typography>
|
||||
);
|
||||
};
|
||||
let divider = (group: number, col0: number, y0: number, col1: number, y1: number) => {
|
||||
let x0 = colPos(group, col0);
|
||||
let x1 = colPos(group, col1);
|
||||
return (
|
||||
<svg width={x1 - x0 + 4} height={y1 - y0 + 4} style={{ position: "absolute", left: x0 - 2, top: y0 - 2 }}>
|
||||
<line x1={2} y1={2} x2={2 + x1 - x0} y2={2 + y1 - y0} stroke="#888" strokeWidth={4} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
let leftDivider = (group: number, col0: number, y0: number, col1: number, y1: number) => {
|
||||
let x0 = colPos(group, col0);
|
||||
let x1 = colPos(group, col1);
|
||||
let xOrg = Math.min(x0, x1) - 2;
|
||||
let yOrg = Math.min(y0, y1) - 2;
|
||||
let width = Math.abs(x1 - x0) + 4;
|
||||
let height = Math.abs(y1 - y0) + 4;
|
||||
|
||||
let bezierLength = 10;
|
||||
let pt0 = { x: x0, y: y0 };
|
||||
let pt1 = { x: x0, y: row1 - 18 };
|
||||
let pt2 = { x: x1, y: row1 + 3 };
|
||||
let pt3 = { x: x1, y: y1 };
|
||||
|
||||
let path = this.smoothPath(pt0, pt1, pt2, pt3, bezierLength);
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} style={{ position: "absolute", left: xOrg - 1, top: yOrg }}
|
||||
viewBox={`${xOrg} ${yOrg} ${width} ${height}`}
|
||||
>
|
||||
<path d={path} fill="none" stroke="#888" strokeWidth="4px" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
let graph = (
|
||||
<ToobFrequencyResponseView width={this.GRAPH_WIDTH}
|
||||
propertyName={FREQUENCY_RESPONSE_VECTOR_URI}
|
||||
instanceId={this.props.instanceId} />
|
||||
);
|
||||
|
||||
return [(<div style={{
|
||||
width: "100%", height: "100%",
|
||||
position: "relative", display: "flex", flexFlow: "column nowrap",
|
||||
alignItems: "center", justifyContent: "center"
|
||||
}}>
|
||||
<div style={{ flex: "1 1 0px", }} />
|
||||
<div style={{
|
||||
flex: "0 0 auto", border: "4px #888 solid", borderRadius: "8px 8px 8px 8px", position: "relative",
|
||||
width: colPos(6, 8.5), height: height,
|
||||
}}>
|
||||
{label1(labelBottom, 1, 2, 3.0, "Lo Shelf", "center")}
|
||||
{label1(labelTop, 2, 3, 5, "Low Mid", "center")}
|
||||
{label1(labelBottom, 3, 4.5, 6.5, "High Mid", "center")}
|
||||
{label1(labelTop, 4, 6.5, 7.5, "Hi Shelf", "center")}
|
||||
{divider(1, 2.0, borderTop, 2.0, borderBottom)}
|
||||
{divider(5, 7.5, borderTop, 7.5, borderBottom)}
|
||||
{leftDivider(2, 3.0, borderTop, 3.0, borderBottom)}
|
||||
{leftDivider(3, 5.0, borderTop, 4.5, borderBottom)}
|
||||
{leftDivider(4, 6.5, borderTop, 6.5, borderBottom)}
|
||||
|
||||
{this.absolutePosition(0, row0, graph)}
|
||||
{this.absolutePosition(colPos(0, 0), row1, controls.filters_loCut)}
|
||||
{this.absolutePosition(colPos(0, 1), row1, controls.filters_hiCut)}
|
||||
|
||||
{cpos0(1, 2, controls.low_level)}
|
||||
{cpos1(1, 2 - 0.5, controls.low_freq)}
|
||||
|
||||
{cpos0(2, 3.0, controls.lmf_q)}
|
||||
{cpos1(2, 2.75, controls.lmf_freq)}
|
||||
{cpos0(2, 4, controls.lmf_level)}
|
||||
|
||||
{cpos1(3, 4, controls.hmf_freq)}
|
||||
{cpos0(3, 5.25, controls.hmf_level)}
|
||||
{cpos1(3, 5, controls.hmf_q)}
|
||||
|
||||
{cpos0(4, 6.5, controls.hi_level)}
|
||||
{cpos1(4, 6, controls.hi_freq)}
|
||||
|
||||
{cpos0(5, 7.5, controls.gain)}
|
||||
</div>
|
||||
<div style={{ flex: "5 5 0px", }} />
|
||||
|
||||
</div>)];
|
||||
}
|
||||
verticalCompactControls(host: ICustomizationHost, controls: UnpackedControls) {
|
||||
let width = 380;
|
||||
|
||||
let graph = (
|
||||
<ToobFrequencyResponseView width={344}
|
||||
propertyName={FREQUENCY_RESPONSE_VECTOR_URI}
|
||||
instanceId={this.props.instanceId} />
|
||||
);
|
||||
let divider = () => {
|
||||
return (
|
||||
<div style={{ width: "100%", height: 1, background: "#8888" }} />
|
||||
);
|
||||
}
|
||||
let vDivider = () => {
|
||||
return (
|
||||
<div style={{ width: 1, minWidth: 1, height: 164, background: "#888" }} />
|
||||
);
|
||||
}
|
||||
let panel = (label: string, controls: React.ReactNode[]) => {
|
||||
return (
|
||||
<div style={{ display: "flex", flexFlow: "column nowrap", alignItems: "center", marginRight: 8, marginLeft: 8 }}>
|
||||
<div style={{
|
||||
width: controls.length * this.DIAL_WIDTH, height: 130, position: "relative",
|
||||
overflow: "hidden", marginRight: 8
|
||||
}}>
|
||||
{controls.map((ctl, i) => {
|
||||
return (
|
||||
<div key={i} style={{ width: this.DIAL_WIDTH, position: "absolute", left: i * this.DIAL_WIDTH, top: 8 }}>
|
||||
{ctl}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Typography variant="body1" noWrap style={{
|
||||
fontSize: "0.85em", fontWeight: 700, width: label !== "" ? 100 : 5, paddingTop: 4, paddingBottom: 8,
|
||||
opacity: 0.4, textAlign: "center"
|
||||
}}>{label}</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return [(<div key="tpq_cv" style={{
|
||||
width: "100%", height: "100%",
|
||||
position: "relative", display: "flex", flexFlow: "column nowrap",
|
||||
alignItems: "center", justifyContent: "center"
|
||||
}}>
|
||||
<div style={{ flex: "1 1 0px" }} />
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row wrap", alignItems: "center", width: width, justifyContent: "space-between",
|
||||
flex: "0 0 auto", border: "4px #888 solid", borderRadius: "8px 8px 8px 8px", position: "relative",
|
||||
}}>
|
||||
|
||||
{graph}
|
||||
{(<div style={{ width: "100%", height: 16 }} />)}
|
||||
{panel("", [
|
||||
controls.filters_loCut,
|
||||
controls.filters_hiCut,
|
||||
])}
|
||||
{panel("", [
|
||||
controls.gain
|
||||
])}
|
||||
{divider()}
|
||||
{panel("Low",
|
||||
[controls.low_level,
|
||||
controls.low_freq
|
||||
])
|
||||
}
|
||||
{vDivider()}
|
||||
{panel("Low Mid",
|
||||
|
||||
[
|
||||
controls.lmf_level,
|
||||
controls.lmf_freq,
|
||||
controls.lmf_q,
|
||||
])
|
||||
}
|
||||
{divider()}
|
||||
{panel("High Mid",
|
||||
|
||||
[
|
||||
controls.hmf_level,
|
||||
controls.hmf_freq,
|
||||
controls.hmf_q,
|
||||
])
|
||||
}
|
||||
{vDivider()}
|
||||
{panel("High",
|
||||
[controls.hi_level,
|
||||
controls.hi_freq
|
||||
])
|
||||
}
|
||||
</div>
|
||||
<div style={{ flex: "5 5 0px", }} />
|
||||
|
||||
</div>)];
|
||||
}
|
||||
|
||||
tinyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let isLandscape = host.isLandscapeGrid();
|
||||
let panelControls: React.ReactNode[] = [];
|
||||
|
||||
let filterGroup = controls[1] as ControlGroup;
|
||||
let miscGroup: ControlGroup = {
|
||||
name: "",
|
||||
indexes: [],
|
||||
controls: [
|
||||
filterGroup.controls[0],
|
||||
filterGroup.controls[1],
|
||||
controls[controls.length - 1] as React.ReactNode
|
||||
]
|
||||
};
|
||||
panelControls.push(host.renderControlGroup(miscGroup, "vtc_miscGroup"));
|
||||
if (!isLandscape) {
|
||||
panelControls.push(
|
||||
(
|
||||
<div key="ctlx_div" style={{ width: "100%", height: 0 }} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 2; i < controls.length - 1; ++i) {
|
||||
let c = controls[i];
|
||||
if (c instanceof ControlGroup) {
|
||||
panelControls.push(
|
||||
host.
|
||||
renderControlGroup(c as ControlGroup, "vtc_cg" + i)
|
||||
);
|
||||
} else {
|
||||
panelControls.push(c as React.ReactNode);
|
||||
}
|
||||
}
|
||||
let panelStyle: React.CSSProperties;
|
||||
let scrollStyle: React.CSSProperties;
|
||||
let frameStyle: React.CSSProperties;
|
||||
|
||||
if (isLandscape) {
|
||||
panelStyle = { display: "flex", flexDirection: "row", alignItems: "stretch", width: "100%" };
|
||||
scrollStyle = { flex: "1 1 auto", overflowX: "auto", overflowY: "hidden", whiteSpace: "nowrap" };
|
||||
frameStyle = { display: "flex", flexFlow: "row nowrap", alignItems: "center",paddingTop: 8 };
|
||||
} else {
|
||||
panelStyle = { display: "flex", flexDirection: "column", alignItems: "stretch", height: "100%" };
|
||||
scrollStyle = { flex: "1 1 auto", overflowX: "hidden", overflowY: "auto", whiteSpace: "nowrap" };
|
||||
frameStyle = { display: "flex", flexFlow: "row wrap", alignItems: "left", justifyContent: "left", gap: 12, paddingTop: 24, paddingBottom: 48 };
|
||||
}
|
||||
|
||||
return [(
|
||||
<div key={"vtc_container"} style={panelStyle}>
|
||||
<div key={"vtc_panel"} style={{ flex: " 0 0 auto", paddingBottom: 8, display: "flex", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||
{controls[0] as React.ReactNode}
|
||||
<div style={{ alignSelf: "flex-start", marginTop: 8 }}>
|
||||
<IconButton onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ maximized: true });
|
||||
}}>
|
||||
<CircleUpIcon className={classes.upIcon} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div style={scrollStyle}>
|
||||
<div style={frameStyle}>
|
||||
{panelControls}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)];
|
||||
}
|
||||
private dialogRef: HTMLDivElement | null = null;
|
||||
private panelRef: HTMLDivElement | null = null;
|
||||
|
||||
dlgTickInAnimation(yOffset: number, startTime: number) {
|
||||
let elapsed = Date.now() - startTime;
|
||||
let pct = elapsed / 200;
|
||||
if (pct > 1) pct = 1;
|
||||
if (!this.dialogRef) {
|
||||
return;
|
||||
}
|
||||
this.dialogRef.style.opacity = `${pct}`;
|
||||
this.dialogRef.style.transform = `translateY(${(1 - pct) * yOffset}px)`;
|
||||
if (pct >= 1) {
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
this.dlgTickInAnimation(yOffset, startTime);
|
||||
});
|
||||
}
|
||||
dlgTickOutAnimation(yOffset: number, startTime: number) {
|
||||
if (!this.dialogRef || !this.panelRef) {
|
||||
this.setState({ maximized: false });
|
||||
return;
|
||||
}
|
||||
let elapsed = Date.now() - startTime;
|
||||
let pct = elapsed / 200;
|
||||
if (pct > 1) pct = 1;
|
||||
if (!this.dialogRef) {
|
||||
return;
|
||||
}
|
||||
this.dialogRef.style.transform = `translateY(${(pct) * yOffset}px)`;
|
||||
this.dialogRef.style.opacity = `${1 - pct}`;
|
||||
if (pct >= 1) {
|
||||
this.setState({ maximized: false });
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
this.dlgTickOutAnimation(yOffset, startTime);
|
||||
});
|
||||
}
|
||||
dlgSlideInAnimation(yOffset: number) {
|
||||
if (!this.animateDialog) {
|
||||
return;
|
||||
}
|
||||
this.animateDialog = false;
|
||||
window.requestAnimationFrame(() => {
|
||||
if (this.dialogRef) {
|
||||
let startTime = Date.now();
|
||||
this.dlgTickInAnimation(yOffset, startTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
dlgSlideOutAnimation(yOffset: number) {
|
||||
if (!this.animateDialog) {
|
||||
return;
|
||||
}
|
||||
this.animateDialog = false;
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
if (this.dialogRef) {
|
||||
let startTime = Date.now();
|
||||
this.dlgTickInAnimation(yOffset, startTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
startSlideOutAnimation() {
|
||||
if (this.panelRef && this.dialogRef) {
|
||||
let yOffset = this.panelRef.getBoundingClientRect().top;
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
if (this.dialogRef && this.dialogRef) {
|
||||
let startTime = Date.now();
|
||||
this.dlgTickOutAnimation(yOffset, startTime);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({ maximized: false });
|
||||
}
|
||||
}
|
||||
checkTransitionStart() {
|
||||
if (this.dialogRef && this.panelRef) {
|
||||
let panelRect = this.panelRef.getBoundingClientRect();
|
||||
this.dlgSlideInAnimation(panelRect.top);
|
||||
}
|
||||
}
|
||||
setDialogRef(ref: HTMLDivElement | null) {
|
||||
this.dialogRef = ref;
|
||||
this.checkTransitionStart();
|
||||
};
|
||||
setPanelRef(ref: HTMLDivElement | null) {
|
||||
this.panelRef = ref;
|
||||
this.checkTransitionStart();
|
||||
}
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let unpackedControls = this.unpackControls(host, controls);
|
||||
|
||||
if (this.state.maximized) {
|
||||
return [(
|
||||
<div ref={(instance: HTMLDivElement | null): void => { this.setPanelRef(instance); }}
|
||||
style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<DialogEx key="peq_dialog"
|
||||
open={this.state.maximized}
|
||||
onEnterKey={() => this.setState({ maximized: false })}
|
||||
onClose={() => this.setState({ maximized: false })}
|
||||
fullScreen={true}
|
||||
tag="peq_dialog"
|
||||
|
||||
dlgRef={(instance: HTMLDivElement | null): void => {
|
||||
this.setDialogRef(instance);
|
||||
}}
|
||||
>
|
||||
|
||||
<DialogContent>
|
||||
{this.state.windowSize.width > this.state.windowSize.height ? (
|
||||
<div style={{
|
||||
width: "100%", height: "100%",
|
||||
display: "flex", flexFlow: "row nowrap"
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<IconButton onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.startSlideOutAnimation();
|
||||
}} style={{ paddingTop: 48, paddingLeft: 8, paddingRight: 8 }}>
|
||||
<CircleDownIcon className={classes.menuIcon} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div style={{ flex: "1 1 auto", height: "100%", position: "relative" }}>
|
||||
{this.horizontalCompactControls(host, unpackedControls)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div style={{
|
||||
width: "100%", height: "100%",
|
||||
display: "flex", flexFlow: "column nowrap"
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<IconButton onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.startSlideOutAnimation();
|
||||
}} style={{ paddingLeft: 8, paddingTop: 8, paddingBottom: 8 }}>
|
||||
<CircleDownIcon className={classes.menuIcon} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div style={{ flex: "1 1 auto", width: "100%", position: "relative" }}>
|
||||
{this.verticalCompactControls(host, unpackedControls)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
</DialogContent>
|
||||
</DialogEx>
|
||||
</div>
|
||||
|
||||
)];
|
||||
}
|
||||
|
||||
|
||||
if (this.state.windowSize.width > 750 && this.state.windowSize.height > 600) {
|
||||
return this.horizontalCompactControls(host, unpackedControls);
|
||||
}
|
||||
return this.tinyControls(host, controls);
|
||||
}
|
||||
|
||||
private handleConnectionStateChanged(state: State) {
|
||||
if (state === State.Ready) {
|
||||
this.unsubscribe();
|
||||
this.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private handleResize() {
|
||||
this.setState({ windowSize: getWindowSize() });
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.subscribedId = this.props.instanceId;
|
||||
}
|
||||
unsubscribe() {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (super.componentDidMount) {
|
||||
super.componentDidMount();
|
||||
}
|
||||
this.subscribe();
|
||||
this.model.state.addOnChangedHandler(this.handleConnectionStateChanged);
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe();
|
||||
if (super.componentWillUnmount) {
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
this.model.state.removeOnChangedHandler(this.handleConnectionStateChanged);
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
|
||||
}
|
||||
|
||||
private subscribedId: number | null = null;
|
||||
private animateDialog: boolean = false;
|
||||
componentDidUpdate(oldProps: ToobParametricEqViewProps, oldState: ToobParametricEqViewState) {
|
||||
if (this.props.instanceId !== this.subscribedId) {
|
||||
this.unsubscribe();
|
||||
this.subscribe();
|
||||
}
|
||||
this.animateDialog = this.state.maximized != oldState.maximized;
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (<PluginControlView
|
||||
instanceId={this.props.instanceId}
|
||||
item={this.props.item}
|
||||
customization={this}
|
||||
customizationId={this.customizationId}
|
||||
showModGui={false}
|
||||
onSetShowModGui={(instanceId: number, showModGui: boolean) => { }}
|
||||
|
||||
/>);
|
||||
}
|
||||
},
|
||||
styles
|
||||
);
|
||||
|
||||
|
||||
|
||||
class ToobParametricEqViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-parametric-eq";
|
||||
|
||||
Create(model: PiPedalModel, pedalboardItem: PedalboardItem): React.ReactNode {
|
||||
return (<ToobParametricEqView instanceId={pedalboardItem.instanceId} item={pedalboardItem} />);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
export default ToobParametricEqViewFactory;
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, MonitorPortHandle } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
// import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
import ToobPlayerControl from './ToobPlayerControl';
|
||||
|
||||
@@ -103,7 +103,7 @@ const ToobPlayerView =
|
||||
fullScreen() {
|
||||
return true;
|
||||
}
|
||||
modifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
let extraControls: React.ReactElement[] = [];
|
||||
let mixPanel = controls[3] as ControlGroup;
|
||||
let iKey = 0;
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobWaveShapeView, {BidirectionalVuPeak} from './ToobWaveShapeView';
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ const ToobPowerstage2View =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
{
|
||||
let time = Date.now()*0.001;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import ToobSpectrumResponseView from './ToobSpectrumResponseView';
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ const ToobSpectrumAnalyzerView =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
controls.splice(0, 0,
|
||||
(<ToobSpectrumResponseView instanceId={this.props.instanceId} />)
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel, ControlValueChangedHandle } from "./PiPedalModel";
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import PluginControlView, { ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ const ToobToneStackView =
|
||||
return false;
|
||||
}
|
||||
|
||||
modifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
if (this.state.isBaxandall) {
|
||||
controls.splice(0, 0,
|
||||
(<ToobFrequencyResponseView instanceId={this.props.instanceId}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="m480-340 180-180-57-56-123 123-123-123-57 56 180 180Zm0 260q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 448 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px">
|
||||
<path d="m357-384 123-123 123 123 57-56-180-180-180 180 57 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 464 B |
Reference in New Issue
Block a user