+
this.onMeasureRef(element)}
+ style={{ flex: "1 1 100%", display: "flex", flexFlow: "row wrap",
+ justifyContent: "flex-start", alignContent: "flex-start", paddingLeft: 16, paddingTop: 16,paddingBottom: 16 }}>
{
+ (this.state.columns !== 0) && // don't render until we have number of columns derived from layout.
this.state.files.map(
(value: string, index: number) => {
+ let displayValue = value;
+ if (displayValue === "")
+ {
+ displayValue = "
";
+ } else {
+ displayValue = this.fileNameOnly(value);
+ }
let selected = value === this.state.selectedFile;
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
+ let scrollRef: ((element: HTMLDivElement | null) => void) | undefined = undefined;
+ if (selected)
+ {
+ scrollRef = (element)=> { this.onScrollRef(element)};
+ }
+
return (
this.onSelectValue(value)} onDoubleClick={()=> {this.onDoubleClickValue(value);}}
>
-
-
+
+
-
{this.fileNameOnly(value)}
+
{displayValue}
);
@@ -265,7 +347,11 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent
{ this.props.onCancel(); }} aria-label="cancel">
Cancel
-
diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx
index 7265a1b..af174da 100644
--- a/react/src/Lv2Plugin.tsx
+++ b/react/src/Lv2Plugin.tsx
@@ -107,23 +107,78 @@ export class PortGroup {
program_list_id: number = -1;
};
-export class PiPedalFileType {
- deserialize(input: any): PiPedalFileType {
+export class UiFileType {
+ deserialize(input: any): UiFileType {
this.name = input.name;
this.fileExtension = input.fileExtension;
+ this.mimeType = input.mimeType;
return this;
}
- static deserialize_array(input: any): PiPedalFileType[]
+ static deserialize_array(input: any): UiFileType[]
{
- let result: PiPedalFileType[] = [];
+ let result: UiFileType[] = [];
for (let i = 0; i < input.length; ++i)
{
- result[i] = new PiPedalFileType().deserialize(input[i]);
+ result[i] = new UiFileType().deserialize(input[i]);
+ }
+ return result;
+ }
+
+ private static IsAndroid() : boolean
+ {
+ return /Android/i.test(navigator.userAgent);
+ }
+ static MergeMimeTypes(fileTypes: UiFileType[]): string
+ {
+ if (fileTypes.length === 0) {
+ return "";
+ }
+ let result = fileTypes[0].mimeType;
+ for (let i = 1; i < fileTypes.length; ++i)
+ {
+ let fileType = fileTypes[i];
+ if (fileType.mimeType !== result)
+ {
+ if (result.startsWith("audio/") && fileType.mimeType.startsWith("audio/"))
+ {
+ result = "audio/*";
+ } else if (result.startsWith("video/") && fileType.mimeType.startsWith("video/"))
+ {
+ result = "video/*";
+ } else if (result.startsWith("text/") && fileType.mimeType.startsWith("text/"))
+ {
+ result = "text/*";
+ } else {
+ result = "application/octet-stream";
+ }
+ }
+ }
+ if (this.IsAndroid())
+ {
+ if (result.startsWith("audio/")) result = "audio/*";
+ if (result.startsWith("video/")) result = "video/*";
+ if (result.startsWith("text/")) result = "text/*";
+ } else {
+ // chrome desktop thinks "application/octet-stream" is .exe, .com, or .bat.
+ // Feed it file extensions isntead.
+ if (result = "application/octet-stream")
+ {
+ result = "";
+ for (let i = 0; i < fileTypes.length; ++i)
+ {
+ if (i !== 0)
+ {
+ result += ',';
+ }
+ result += fileTypes[i].fileExtension;
+ }
+ }
}
return result;
}
name: string = "";
fileExtension: string = "";
+ mimeType: string = "";
}
export class UiPropertyNotification {
@@ -150,23 +205,23 @@ export class UiPropertyNotification {
protocol: string = "";
};
-export class PiPedalFileProperty {
- deserialize(input: any): PiPedalFileProperty
+export class UiFileProperty {
+ deserialize(input: any): UiFileProperty
{
this.label = input.label;
- this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes);
+ this.fileTypes = UiFileType.deserialize_array(input.fileTypes);
this.patchProperty = input.patchProperty;
this.directory = input.directory;
this.index = input.index;
this.portGroup = input.portGroup;
return this;
}
- static deserialize_array(input: any): PiPedalFileProperty[]
+ static deserialize_array(input: any): UiFileProperty[]
{
- let result: PiPedalFileProperty[] = [];
+ let result: UiFileProperty[] = [];
for (let i = 0; i < input.length; ++i)
{
- result[i] = new PiPedalFileProperty().deserialize(input[i]);
+ result[i] = new UiFileProperty().deserialize(input[i]);
}
return result;
}
@@ -191,7 +246,7 @@ export class PiPedalFileProperty {
if (this.fileTypes.length === 0) {
return true;
}
- let extension = PiPedalFileProperty.getFileExtension(filename);
+ let extension = UiFileProperty.getFileExtension(filename);
for (let fileType of this.fileTypes)
{
if (fileType.fileExtension === extension )
@@ -203,7 +258,7 @@ export class PiPedalFileProperty {
}
label: string = "";
- fileTypes: PiPedalFileType[] = [];
+ fileTypes: UiFileType[] = [];
patchProperty: string = "";
directory: string = "";
index: number = -1;
@@ -226,7 +281,7 @@ export class Lv2Plugin implements Deserializable {
this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties)
{
- this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties)
+ this.fileProperties = UiFileProperty.deserialize_array(input.fileProperties)
} else {
this.fileProperties = [];
}
@@ -251,7 +306,7 @@ export class Lv2Plugin implements Deserializable {
comment: string = "";
ports: Port[] = Port.EmptyPorts;
port_groups: PortGroup[] = [];
- fileProperties: PiPedalFileProperty[] = [];
+ fileProperties: UiFileProperty[] = [];
uiPortNotifications: UiPropertyNotification[] = [];
}
@@ -508,7 +563,7 @@ export class UiControl implements Deserializable {
}
formatDisplayValue(value: number): string {
- if (this.integer_property || this.enumeration_property) {
+ if (this.integer_property) {
value = Math.round(value);
}
@@ -614,7 +669,7 @@ export class UiPlugin implements Deserializable {
this.port_groups = PortGroup.deserialize_array(input.port_groups);
if (input.fileProperties)
{
- this.fileProperties = PiPedalFileProperty.deserialize_array(input.fileProperties)
+ this.fileProperties = UiFileProperty.deserialize_array(input.fileProperties)
} else {
this.fileProperties = [];
}
@@ -682,7 +737,7 @@ export class UiPlugin implements Deserializable {
description: string = "";
controls: UiControl[] = [];
port_groups: PortGroup[] = [];
- fileProperties: PiPedalFileProperty[] = [];
+ fileProperties: UiFileProperty[] = [];
is_vst3 : boolean = false;
}
diff --git a/react/src/Pedalboard.tsx b/react/src/Pedalboard.tsx
index 093bf2e..44d78e7 100644
--- a/react/src/Pedalboard.tsx
+++ b/react/src/Pedalboard.tsx
@@ -67,6 +67,7 @@ export class PedalboardItem implements Deserializable {
this.controlValues = ControlValue.deserializeArray(input.controlValues);
this.vstState = input.vstState ?? "";
+ this.stateUpdateCount = input.stateUpdateCount;
this.lv2State = input.lv2State;
return this;
}
@@ -195,6 +196,7 @@ export class PedalboardItem implements Deserializable {
controlValues: ControlValue[] = ControlValue.EmptyArray;
midiBindings: MidiBinding[] = [];
vstState: string = "";
+ stateUpdateCount: number = 0;
lv2State: [boolean,any] = [false,{}]
};
diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx
index faae50a..a5c8e1e 100644
--- a/react/src/PiPedalModel.tsx
+++ b/react/src/PiPedalModel.tsx
@@ -17,7 +17,7 @@
// 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 { UiPlugin, UiControl, PluginType, PiPedalFileProperty } from './Lv2Plugin';
+import { UiPlugin, UiControl, PluginType, UiFileProperty } from './Lv2Plugin';
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
@@ -588,6 +588,10 @@ export class PiPedalModel //implements PiPedalModel
} else if (message === "onSystemMidiBindingsChanged") {
let bindings = MidiBinding.deserialize_array(body);
this.systemMidiBindings.set(bindings);
+ } else if (message === "onErrorMessage")
+ {
+ this.showAlert(body as string);
+
} else {
throw new PiPedalStateError("Unrecognized message received from server: " + message);
}
@@ -1054,6 +1058,8 @@ export class PiPedalModel //implements PiPedalModel
handleOnLoadPluginPreset(instanceId: number, controlValues: ControlValue[]) {
+ // note that plugins with state are dealt with server-side.
+ // if we made it here, we can just load the controls.
let pedalboard = this.pedalboard.get();
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalboard = pedalboard.clone();
@@ -1342,13 +1348,15 @@ export class PiPedalModel //implements PiPedalModel
while (true) {
let v = it.next();
if (v.done) break;
- let item = v.value;
+ let item = v.value;
if (item.instanceId === itemId) {
+ item.deserialize(new PedalboardItem()); // skeezy way to re-initialize.
item.instanceId = ++newPedalboard.nextInstanceId;
item.uri = selectedUri;
item.pluginName = plugin.name;
item.controlValues = this.getDefaultValues(item.uri);
item.isEnabled = true;
+ // lv2State: not valid. vstState : not valid.
this.pedalboard.set(newPedalboard);
this.updateServerPedalboard()
return item.instanceId;
@@ -1590,7 +1598,7 @@ export class PiPedalModel //implements PiPedalModel
});
}
- requestFileList(piPedalFileProperty: PiPedalFileProperty): Promise {
+ requestFileList(piPedalFileProperty: UiFileProperty): Promise {
return nullCast(this.webSocket)
.request('requestFileList', piPedalFileProperty);
}
diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx
index 6645c1b..62be402 100644
--- a/react/src/PluginControlView.tsx
+++ b/react/src/PluginControlView.tsx
@@ -23,7 +23,7 @@ import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
-import { UiPlugin, UiControl, PiPedalFileProperty, ScalePoint } from './Lv2Plugin';
+import { UiPlugin, UiControl, UiFileProperty, ScalePoint } from './Lv2Plugin';
import {
Pedalboard, PedalboardItem, ControlValue
} from './Pedalboard';
@@ -239,7 +239,7 @@ type PluginControlViewState = {
imeCaption: string;
imeInitialHeight: number;
showFileDialog: boolean,
- dialogFileProperty: PiPedalFileProperty,
+ dialogFileProperty: UiFileProperty,
dialogFileValue: string
};
@@ -260,7 +260,7 @@ const PluginControlView =
imeCaption: "",
imeInitialHeight: 0,
showFileDialog: false,
- dialogFileProperty: new PiPedalFileProperty(),
+ dialogFileProperty: new UiFileProperty(),
dialogFileValue: ""
}
@@ -323,10 +323,10 @@ const PluginControlView =
});
}
- makeFilePropertyUI(fileProperty: PiPedalFileProperty): ReactNode {
+ makeFilePropertyUI(fileProperty: UiFileProperty): ReactNode {
return ((
- {
this.setState({ showFileDialog: true, dialogFileProperty: fileProperty, dialogFileValue: selectedFile });
diff --git a/react/src/PluginPresetsDialog.tsx b/react/src/PluginPresetsDialog.tsx
index d7ff77e..acd54b8 100644
--- a/react/src/PluginPresetsDialog.tsx
+++ b/react/src/PluginPresetsDialog.tsx
@@ -259,7 +259,7 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
- noWrap
+
{presetEntry.label}
diff --git a/react/src/SelectHoverBackground.tsx b/react/src/SelectHoverBackground.tsx
index 09588d3..ea0fc20 100644
--- a/react/src/SelectHoverBackground.tsx
+++ b/react/src/SelectHoverBackground.tsx
@@ -80,13 +80,10 @@ export const SelectHoverBackground =
}
handleMouseOver(e: SyntheticEvent): void {
- console.log("onMouseOver")
if (this.props.showHover ?? true) {
if (this.hoverElementRef.current)
{
this.hoverElementRef.current.style.display = "block";
- } else {
- console.log("No hoverElementRef")
}
}
@@ -96,9 +93,7 @@ export const SelectHoverBackground =
if (this.hoverElementRef.current)
{
this.hoverElementRef.current.style.display = "none";
- } else {
- console.log("No hoverElementRef")
- }
+ }
}
}
diff --git a/react/src/UploadFileDialog.tsx b/react/src/UploadFileDialog.tsx
index 477e14f..cfb8444 100644
--- a/react/src/UploadFileDialog.tsx
+++ b/react/src/UploadFileDialog.tsx
@@ -27,7 +27,7 @@ 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 { UiFileProperty,UiFileType } from './Lv2Plugin';
import SvgIcon from '@mui/material/SvgIcon';
import ErrorIcon from '@mui/icons-material/Error';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
@@ -45,7 +45,7 @@ export interface UploadFileDialogProps {
onClose: () => void,
onUploaded: (fileName: string) => void,
uploadPage: string,
- fileProperty: PiPedalFileProperty
+ fileProperty: UiFileProperty
};
@@ -204,6 +204,10 @@ export default class UploadFileDialog extends ResizeResponsiveComponent
) {
+ private static IsAndroid() : boolean
+ {
+ return /Android/i.test(navigator.userAgent);
+ }
+
+ handleButtonSelect(e: any) {
if (e.currentTarget.files) {
this.uploadFiles(e.currentTarget.files);
}
@@ -290,20 +299,13 @@ export default class UploadFileDialog extends ResizeResponsiveComponent this.handleClose()}
fullScreen={this.state.fullScreen}
@@ -321,7 +323,7 @@ export default class UploadFileDialog extends ResizeResponsiveComponent
{!this.hasFileList() && (
-
Drop files here
+
+ { isAndroid? "Select files": "Drop files here" }
)}
{this.hasFileList() && (
@@ -381,15 +384,15 @@ export default class UploadFileDialog extends ResizeResponsiveComponent
- Select Files
+ Select Files
this.handleButtonSelect(e)}
+ onInput={(e) => this.handleButtonSelect(e)}
/>
diff --git a/react/tmp.txt b/react/tmp.txt
deleted file mode 100644
index 86dae80..0000000
--- a/react/tmp.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-src/SearchControl.tsx
-src/SplitControlView.tsx
-src/WifiConfigSettings.tsx
-src/FullScreenIME.tsx
-src/JackHostStatus.tsx
-src/SearchFilter.tsx
-src/MainPage.tsx
-src/PiPedalModel.tsx
-src/PluginPresetSelector.tsx
-src/Pedal.tsx
-src/ToobMLView.tsx
-src/SettingsDialog.tsx
-src/PiPedalSocket.tsx
-src/ToobFrequencyResponseView.tsx
-src/ControlViewFactory.tsx
-src/Utility.tsx
-src/ToobToneStackView.tsx
-src/SelectHoverBackground.tsx
-src/MidiBindingsDialog.tsx
-src/TemporaryDrawer.tsx
-src/GxTunerView.tsx
-src/ToobInputStageView.tsx
-src/DraggableGrid.tsx
-src/JackStatusView.tsx
-src/PluginControl.tsx
-src/XxxSnippet.tsx
-src/PluginPreset.tsx
-src/Jack.tsx
-src/AlsaDeviceInfo.tsx
-src/MidiBinding.tsx
-src/PiPedalError.tsx
-src/IControlViewFactory.tsx
-src/Lv2Plugin.tsx
-src/Pedalboard.tsx
-src/PedalboardView.tsx
-src/PresetDialog.tsx
-src/AppThemed.tsx
-src/ZoomedDial.tsx
-src/RenameDialog.tsx
-src/SplitUiControls.tsx
-src/GovernorSettings.tsx
-src/AboutDialog.tsx
-src/ToobSpectrumResponseView.tsx
-src/PluginControlView.tsx
-src/NoChangePassword.tsx
-src/ToobSpectrumAnalyzerView.tsx
-src/MidiBindingView.tsx
-src/GxTunerControl.tsx
-src/Banks.tsx
-src/PluginPresetsDialog.tsx
-src/BankDialog.tsx
-src/UploadPresetDialog.tsx
-src/ToobPowerStage2View.tsx
-src/index.tsx
-src/Units.tsx
-src/VuMeter.tsx
-src/App.test.tsx
-src/DialogEx.tsx
-src/StringBuilder.tsx
-src/SelectChannelsDialog.tsx
-src/Draggable.tsx
-src/ZoomedUiControl.tsx
-src/Rect.tsx
-src/PluginClass.tsx
-src/PluginIcon.tsx
-src/App.tsx
-src/ResizeResponsiveComponent.tsx
-src/SvgPathBuilder.tsx
-src/NumericInput.tsx
-src/JackServerSettings.tsx
-src/ListSelectDialog.tsx
-src/JackServerSettingsDialog.tsx
-src/ObservableProperty.tsx
-src/SelectMidiChannelsDialog.tsx
-src/WifiChannel.tsx
-src/LoadPluginDialog.tsx
-src/WifiConfigDialog.tsx
-src/PluginInfoDialog.tsx
-src/ToobWaveShapeView.tsx
-src/ToobCabSimView.tsx
-src/PresetSelector.tsx
diff --git a/reset_presets b/reset_presets
new file mode 100755
index 0000000..fbd6ea5
--- /dev/null
+++ b/reset_presets
@@ -0,0 +1,8 @@
+#!/usr/bin/bash
+# Set presets and plugin presets to default.
+rm -rf /var/pipedal/plugin_presets
+rm -rf /var/pipedal/presets
+mkdir /var/pipedal/plugin_presets
+mkdir /var/pipedal/presets
+cp -r default_presets/plugin_presets /var/pipedal/plugin_presets/
+cp -r default_presets/presets /var/pipedal/presets/
diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp
index 22a8459..cc51c83 100644
--- a/src/AudioHost.cpp
+++ b/src/AudioHost.cpp
@@ -349,7 +349,6 @@ private:
audioDriver->Close();
StopReaderThread();
- pHost->GetHostWorkerThread()->Close();
// release any pdealboards owned by the process thread.
@@ -1164,6 +1163,21 @@ public:
RealtimeNextMidiProgramRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request);
+ } else if (command == RingBufferCommand::Lv2ErrorMessage)
+ {
+ size_t size;
+ int64_t instanceId;
+ hostReader.read(&instanceId);
+ hostReader.read(&size);
+ if (this->atomBuffer.size() < size+1)
+ {
+ this->atomBuffer.resize(size+1);
+ }
+ hostReader.read(size, &(atomBuffer[0]));
+ char *p = (char*)&(atomBuffer[0]);
+ p[size] = 0;
+ std::string message(p);
+ pNotifyCallbacks->OnNotifyLv2RealtimeError(instanceId,message);
}
else
{
@@ -1243,6 +1257,10 @@ public:
this->inputRingBuffer.reset();
this->outputRingBuffer.reset();
+ this->hostReader.Reset();
+ this->hostWriter.Reset();
+ this->realtimeReader.Reset();
+ this->realtimeWriter.Reset();
this->channelSelection = channelSelection;
diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp
index 9c43b91..ad86f7c 100644
--- a/src/AudioHost.hpp
+++ b/src/AudioHost.hpp
@@ -154,6 +154,7 @@ public:
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
+ virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) = 0;
};
@@ -195,6 +196,7 @@ public:
virtual void SetListenForAtomOutput(bool listen) = 0;
virtual void UpdatePluginStates(Pedalboard& pedalboard) = 0;
+ virtual void UpdatePluginState(PedalboardItem& pedalboardItem) = 0;
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0bcb15f..d854595 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -141,6 +141,8 @@ else()
endif()
set (PIPEDAL_SOURCES
+ MimeTypes.cpp MimeTypes.hpp
+ inverting_mutex.hpp
DbDezipper.hpp DbDezipper.cpp
WebServerLog.hpp
@@ -277,6 +279,8 @@ target_link_libraries(pipedald PRIVATE
#################################
add_executable(pipedaltest testMain.cpp
+
+ InvertingMutexTest.cpp
jsonTest.cpp
json_variant.cpp
json_variant.hpp
diff --git a/src/IEffect.hpp b/src/IEffect.hpp
index fd82b68..b16ad96 100644
--- a/src/IEffect.hpp
+++ b/src/IEffect.hpp
@@ -59,5 +59,8 @@ namespace pipedal {
virtual bool IsVst3() const = 0;
virtual bool GetLv2State(Lv2PluginState*state) = 0;
+
+ virtual bool HasErrorMessage() const = 0;
+ virtual const char*TakeErrorMessage() = 0;
};
} //namespace
\ No newline at end of file
diff --git a/src/IHost.hpp b/src/IHost.hpp
index 39ef5d1..0a8444d 100644
--- a/src/IHost.hpp
+++ b/src/IHost.hpp
@@ -49,5 +49,6 @@ namespace pipedal {
virtual std::shared_ptr GetHostWorkerThread() = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
+
};
}
\ No newline at end of file
diff --git a/src/InvertingMutexTest.cpp b/src/InvertingMutexTest.cpp
new file mode 100644
index 0000000..626676b
--- /dev/null
+++ b/src/InvertingMutexTest.cpp
@@ -0,0 +1,71 @@
+// 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.
+
+#include "pch.h"
+#include "catch.hpp"
+#include
+#include
+#include
+
+#include "json.hpp"
+#include "json_variant.hpp"
+#include
+#include
+#include "inverting_mutex.hpp"
+#include
+#include
+#include
+#include "util.hpp"
+
+using namespace pipedal;
+using namespace std;
+using namespace std::chrono;
+
+TEST_CASE("inverting_mutext test", "[inverting_mutex_test]")
+{
+
+ inverting_mutex mutex;
+
+ {
+ std::thread thread(
+ [&mutex]() mutable
+ {
+ nice(5);
+ {
+ std::lock_guard lock(mutex);
+ SetThreadName("imTest");
+ cout << "Thread holds lock" << endl;
+ std::this_thread::sleep_for(3s);
+
+ std::this_thread::sleep_for(1s);
+ cout << "Thread releases lock" << endl;
+ }
+ });
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ {
+ std::lock_guard lock(mutex); // cause the thread to avoid priority-inversion.
+ cout << "Main resumed." << endl;
+ }
+ {
+ std::lock_guard lock(mutex);
+ }
+ thread.join();
+ }
+}
diff --git a/src/LogFeature.cpp b/src/LogFeature.cpp
index 776a3cc..aaafd3f 100644
--- a/src/LogFeature.cpp
+++ b/src/LogFeature.cpp
@@ -50,29 +50,38 @@ int LogFeature::vprintf(LV2_URID type,const char*fmt, va_list va)
{
std::lock_guard guard(logMutex);
- const char* prefix = "";
- char buffer[1024];
- int result = vsnprintf(buffer, sizeof(buffer), fmt, va);
+ int result = 0;
+ if (this->logMessageListener)
+ {
+ const char* prefix = "";
+ char buffer[1024];
+ strcpy(buffer,messagePrefix.c_str());
+ char *p = buffer+messagePrefix.length();
- if (type == uris.ridError)
- {
- Lv2Log::error(buffer);
+
+ result = vsnprintf(p, sizeof(buffer)-messagePrefix.length(), fmt, va);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ if (type == uris.ridError)
+ {
+ logMessageListener->OnLogError(buffer);
+ }
+ else if (type == uris.ridWarning)
+ {
+ logMessageListener->OnLogWarning(buffer);
+ }
+ else if (type == uris.ridNote)
+ {
+ logMessageListener->OnLogInfo(buffer);
+ }
+ else if (type == uris.ridTrace)
+ {
+ logMessageListener->OnLogDebug(buffer);
+ }
+ else {
+ logMessageListener->OnLogInfo(buffer);
+ }
}
- else if (type == uris.ridWarning)
- {
- Lv2Log::warning(buffer);
- }
- else if (type == uris.ridNote)
- {
- Lv2Log::info(buffer);
- }
- else if (type == uris.ridTrace)
- {
- Lv2Log::debug(buffer);
- }
- else {
- Lv2Log::info(buffer);
- }
return result;
}
@@ -85,9 +94,11 @@ LogFeature::LogFeature()
log.printf = printfFn;
log.vprintf = vprintfFn;
}
-void LogFeature::Prepare(MapFeature*map)
+void LogFeature::Prepare(MapFeature*map, const std::string &messagePrefix, LogMessageListener*listener)
{
uris.Map(map);
+ this->messagePrefix = messagePrefix;
+ this->logMessageListener = listener;
}
diff --git a/src/LogFeature.hpp b/src/LogFeature.hpp
index 2db4f18..20ba951 100644
--- a/src/LogFeature.hpp
+++ b/src/LogFeature.hpp
@@ -35,8 +35,17 @@
namespace pipedal {
class LogFeature {
-
+ public:
+ class LogMessageListener {
+ public:
+ virtual void OnLogError(const char*message) = 0;
+ virtual void OnLogWarning(const char*message) = 0;
+ virtual void OnLogInfo(const char*message) = 0;
+ virtual void OnLogDebug(const char*message) = 0;
+ };
private:
+ LogMessageListener *logMessageListener = nullptr;
+ std::string messagePrefix;
LV2_URID nextAtom = 0;
LV2_Feature feature;
LV2_Log_Log log;
@@ -59,7 +68,8 @@ namespace pipedal {
public:
LogFeature();
- void Prepare(MapFeature* map);
+ void Prepare(MapFeature* map, const std::string &messagePrefix, LogMessageListener*listener);
+
void LogError(const char*fmt,...);
void LogWarning(const char*fmt,...);
@@ -84,5 +94,6 @@ namespace pipedal {
int vprintf(LV2_URID type, const char* fmt, va_list va);
+
};
}
\ No newline at end of file
diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp
index e0d4768..06fd457 100644
--- a/src/Lv2Effect.cpp
+++ b/src/Lv2Effect.cpp
@@ -54,6 +54,7 @@ Lv2Effect::Lv2Effect(
{
auto pWorld = pHost_->getWorld();
+ logFeature.Prepare(&(pHost_->GetMapFeature()),info_->name() + ": ",this);
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
this->bypass = pedalboardItem.isEnabled();
@@ -71,6 +72,8 @@ Lv2Effect::Lv2Effect(
LV2_Feature *const *features = pHost_->GetLv2Features();
+ this->features.push_back(logFeature.GetFeature());
+
for (auto p = features; *p != nullptr; ++p)
{
this->features.push_back(*p);
@@ -92,7 +95,7 @@ Lv2Effect::Lv2Effect(
}
this->features.push_back(nullptr);
- LV2_Feature **myFeatures = &this->features[0];
+ const LV2_Feature **myFeatures = &this->features[0];
LilvInstance *pInstance = nullptr;
try {
@@ -676,4 +679,28 @@ bool Lv2Effect::GetLv2State(Lv2PluginState*state)
}
+void Lv2Effect::OnLogError(const char*message)
+{
+ // only errors get transmitted to the client.
+ strncpy(this->errorMessage,message,sizeof(errorMessage));
+ errorMessage[sizeof(errorMessage)-1] = '\0';
+ this->hasErrorMessage = true;
+}
+
+void Lv2Effect::OnLogWarning(const char*message)
+{
+ Lv2Log::warning(message);
+
+}
+void Lv2Effect::OnLogInfo(const char*message)
+{
+ Lv2Log::info(message);
+
+}
+void Lv2Effect::OnLogDebug(const char*message)
+{
+ Lv2Log::debug(message);
+
+}
+
diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp
index e423a1f..194643b 100644
--- a/src/Lv2Effect.hpp
+++ b/src/Lv2Effect.hpp
@@ -34,6 +34,7 @@
#include "lv2/atom.lv2/forge.h"
#include "AtomBuffer.hpp"
#include "StateInterface.hpp"
+#include "LogFeature.hpp"
namespace pipedal
@@ -41,12 +42,19 @@ namespace pipedal
class RealtimeRingBufferWriter;
- class Lv2Effect : public IEffect
+ class Lv2Effect : public IEffect, private LogFeature::LogMessageListener
{
private:
+ virtual void OnLogError(const char*message);
+ virtual void OnLogWarning(const char*message);
+ virtual void OnLogInfo(const char*message);
+ virtual void OnLogDebug(const char*message);
+
+ private:
+
std::unique_ptr stateInterface;
void RestoreState(const PedalboardItem&pedalboardItem);
-
+ LogFeature logFeature;
std::map patchPropertyPrototypes;
IHost *pHost = nullptr;
@@ -74,7 +82,7 @@ namespace pipedal
std::vector inputAtomBuffers;
std::vector outputAtomBuffers;
- std::vector features;
+ std::vector features;
LV2_Feature *work_schedule_feature = nullptr;
virtual std::string GetUri() const { return info->uri(); }
@@ -169,6 +177,7 @@ namespace pipedal
void BypassTo(float value);
public:
+
virtual bool GetLv2State(Lv2PluginState*state);
virtual void RequestPatchProperty(LV2_URID uridUri);
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value);
@@ -187,7 +196,8 @@ namespace pipedal
}
-
+ bool hasErrorMessage = false;
+ char errorMessage[1024];
public:
Lv2Effect(
IHost *pHost,
@@ -195,7 +205,8 @@ namespace pipedal
const PedalboardItem &pedalboardItem);
~Lv2Effect();
-
+ bool HasErrorMessage() const { return this->hasErrorMessage; }
+ const char*TakeErrorMessage() { this->hasErrorMessage = false; return this->errorMessage; }
virtual void ResetAtomBuffers();
virtual uint64_t GetInstanceId() const { return instanceId; }
diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp
index 717af99..4cf6a35 100644
--- a/src/Lv2Pedalboard.cpp
+++ b/src/Lv2Pedalboard.cpp
@@ -46,6 +46,8 @@ std::vector Lv2Pedalboard::AllocateAudioBuffers(int nChannels)
return result;
}
+
+
int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbol)
{
for (int i = 0; i < realtimeEffects.size(); ++i)
@@ -60,7 +62,9 @@ int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbo
}
std::vector Lv2Pedalboard::PrepareItems(
std::vector &items,
- std::vector inputBuffers)
+ std::vector inputBuffers,
+ Lv2PedalboardErrorList&errorList
+ )
{
for (int i = 0; i < items.size(); ++i)
{
@@ -84,8 +88,8 @@ std::vector Lv2Pedalboard::PrepareItems(
this->processActions.push_back(preMixAction);
- std::vector topResult = PrepareItems(item.topChain(), topInputs);
- std::vector bottomResult = PrepareItems(item.bottomChain(), bottomInputs);
+ std::vector topResult = PrepareItems(item.topChain(), topInputs,errorList);
+ std::vector bottomResult = PrepareItems(item.bottomChain(), bottomInputs,errorList);
this->processActions.push_back(
[pSplit](uint32_t frames)
@@ -113,6 +117,12 @@ std::vector Lv2Pedalboard::PrepareItems(
{
Lv2Log::warning(SS(e.what()));
}
+ if (pLv2Effect->HasErrorMessage())
+ {
+ std::string error = pLv2Effect->TakeErrorMessage();
+ Lv2Log::error(error);
+ errorList.push_back({item.instanceId(), error});
+ }
if (pLv2Effect)
{
@@ -206,7 +216,7 @@ std::vector Lv2Pedalboard::PrepareItems(
return inputBuffers;
}
-void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
+void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList)
{
this->pHost = pHost;
@@ -224,7 +234,7 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize()));
}
- auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers);
+ auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers,errorList);
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
if (nOutputs == 1)
{
@@ -396,6 +406,14 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa
{
processActions[i](samples);
}
+ for (size_t i = 0; i < this->effects.size(); ++i)
+ {
+ IEffect* effect = effects[i].get();
+ if (effect->HasErrorMessage())
+ {
+ ringBufferWriter->WriteLv2ErrorMessage(effect->GetInstanceId(),effect->TakeErrorMessage());
+ }
+ }
for (size_t i = 0; i < samples; ++i)
{
float volume = outputVolume.Tick();
diff --git a/src/Lv2Pedalboard.hpp b/src/Lv2Pedalboard.hpp
index fda1555..d79e657 100644
--- a/src/Lv2Pedalboard.hpp
+++ b/src/Lv2Pedalboard.hpp
@@ -35,6 +35,16 @@ class RealtimeVuBuffers;
class RealtimePatchPropertyRequest;
class RealtimeRingBufferWriter;
+struct Lv2PedalboardError {
+ int64_t intanceId;
+ std::string message;
+};
+
+class Lv2PedalboardErrorList: public std::vector // (forward declaration issues with a using statement)
+{
+
+};
+
class Lv2Pedalboard {
IHost *pHost = nullptr;
@@ -86,7 +96,8 @@ class Lv2Pedalboard {
std::vector PrepareItems(
std::vector & items,
- std::vector inputBuffers
+ std::vector inputBuffers,
+ Lv2PedalboardErrorList &errorList
);
void PrepareMidiMap(const Pedalboard&pedalboard);
@@ -99,7 +110,7 @@ public:
Lv2Pedalboard() { }
~Lv2Pedalboard() { }
- void Prepare(IHost *pHost,Pedalboard&pedalboard);
+ void Prepare(IHost *pHost,Pedalboard&pedalboard, Lv2PedalboardErrorList &errorList);
std::vector GetEffects() { return realtimeEffects; }
diff --git a/src/MapPathFeature.cpp b/src/MapPathFeature.cpp
index b47b081..85040cd 100644
--- a/src/MapPathFeature.cpp
+++ b/src/MapPathFeature.cpp
@@ -36,10 +36,15 @@ MapPathFeature::MapPathFeature(const std::filesystem::path &storagePath)
lv2_state_make_path.handle = (LV2_State_Make_Path_Handle*)this;
lv2_state_make_path.path = FnAbsolutePath;
+ lv2_state_free_path.handle = (LV2_State_Free_Path_Handle*)this;
+ lv2_state_free_path.free_path = FnFreePath;
+
mapPathFeature.URI = LV2_STATE__mapPath;
mapPathFeature.data = (void*)&lv2_state_map_path;
makePathFeature.URI = LV2_STATE__makePath;
makePathFeature.data = (void*)&lv2_state_make_path;
+ freePathFeature.URI = LV2_STATE__freePath;
+ freePathFeature.data = (void*)&lv2_state_free_path;
}
void MapPathFeature::Prepare(MapFeature* map)
@@ -47,6 +52,16 @@ void MapPathFeature::Prepare(MapFeature* map)
}
+void MapPathFeature::FreePath(char *path)
+{
+ free(path);
+}
+
+void MapPathFeature::FnFreePath(LV2_State_Free_Path_Handle handle, char* path)
+{
+ ((MapPathFeature*)handle)->FreePath(path);
+}
+
/*static*/ char *MapPathFeature::FnAbsolutePath(
LV2_State_Map_Path_Handle handle,
@@ -57,6 +72,10 @@ void MapPathFeature::Prepare(MapFeature* map)
char *MapPathFeature::AbsolutePath(const char *abstract_path)
{
+ if (strlen(abstract_path) == 0)
+ {
+ return strdup("");
+ }
std::filesystem::path t (abstract_path);
if (t.is_absolute()) {
return strdup(abstract_path);
@@ -75,6 +94,10 @@ char *MapPathFeature::FnAbstractPath(
char *MapPathFeature::AbstractPath(const char *absolute_path)
{
+ if (strlen(absolute_path) == 0)
+ {
+ return strdup("");
+ }
if (strncmp(storagePath.c_str(),absolute_path,storagePath.length()) == 0)
{
const char*result = absolute_path + storagePath.length();
diff --git a/src/MapPathFeature.hpp b/src/MapPathFeature.hpp
index 10df165..bf6f8bd 100644
--- a/src/MapPathFeature.hpp
+++ b/src/MapPathFeature.hpp
@@ -32,16 +32,25 @@ namespace pipedal
void SetPluginStoragePath(const std::filesystem::path&path) { storagePath = path;}
const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;}
const LV2_Feature*GetMakePathFeature() { return &makePathFeature;}
+ const LV2_Feature*GetFreePathFeature() { return &freePathFeature;}
private:
char *AbsolutePath(const char *abstract_path);
static char *FnAbsolutePath(LV2_State_Map_Path_Handle handle,
const char *abstract_path);
+
+
LV2_State_Map_Path lv2_state_map_path;
LV2_State_Make_Path lv2_state_make_path;
+ LV2_State_Free_Path lv2_state_free_path;
+
+ void FreePath(char *path);
+ static void FnFreePath(LV2_State_Free_Path_Handle handle, char* path);
+
LV2_Feature mapPathFeature;
LV2_Feature makePathFeature;
+ LV2_Feature freePathFeature;
char *AbstractPath(const char *abstract_path);
static char * FnAbstractPath(
diff --git a/src/MimeTypes.cpp b/src/MimeTypes.cpp
new file mode 100644
index 0000000..91538cb
--- /dev/null
+++ b/src/MimeTypes.cpp
@@ -0,0 +1,160 @@
+// 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.
+
+
+#include "MimeTypes.hpp"
+
+using namespace pipedal;
+
+void MimeTypes::MaybeInitialize()
+{
+ if (!initialized)
+ {
+ initialized = true;
+ AddMimeType("MP3", "audio/mpeg");
+ AddMimeType("MPGA", "audio/mpeg");
+ AddMimeType("M4A", "audio/mp4");
+ AddMimeType("WAV", "audio/x-wav");
+ AddMimeType("WAV", "audio/wav");
+ AddMimeType("AMR", "audio/amr");
+ AddMimeType("AWB", "audio/amr-wb");
+ AddMimeType("WMA", "audio/x-ms-wma");
+ AddMimeType("OGG", "audio/ogg");
+ AddMimeType("OGG", "application/ogg");
+ AddMimeType("OGA", "application/ogg");
+ AddMimeType("AAC", "audio/aac");
+ AddMimeType("AAC", "audio/aac-adts");
+ AddMimeType("MKA", "audio/x-matroska");
+ AddMimeType("MID", "audio/midi");
+ AddMimeType("MIDI", "audio/midi");
+ AddMimeType("XMF", "audio/midi");
+ AddMimeType("RTTTL", "audio/midi");
+ AddMimeType("SMF", "audio/sp-midi");
+ AddMimeType("IMY", "audio/imelody");
+ AddMimeType("RTX", "audio/midi");
+ AddMimeType("OTA", "audio/midi");
+ AddMimeType("MXMF", "audio/midi");
+
+ AddMimeType("MPEG", "video/mpeg");
+ AddMimeType("MPG", "video/mpeg");
+ AddMimeType("MP4", "video/mp4");
+ AddMimeType("M4V", "video/mp4");
+ AddMimeType("3GP", "video/3gpp");
+ AddMimeType("3GPP", "video/3gpp");
+ AddMimeType("3G2", "video/3gpp2");
+ AddMimeType("3GPP2", "video/3gpp2");
+ AddMimeType("MKV", "video/x-matroska");
+ AddMimeType("WEBM", "video/webm");
+ AddMimeType("TS", "video/mp2ts");
+ AddMimeType("AVI", "video/avi");
+ AddMimeType("WMV", "video/x-ms-wmv");
+ AddMimeType("ASF", "video/x-ms-asf");
+ AddMimeType("JPG", "image/jpeg");
+ AddMimeType("JPEG", "image/jpeg");
+ AddMimeType("GIF", "image/gif");
+ AddMimeType("PNG", "image/png");
+ AddMimeType("BMP", "image/x-ms-bmp");
+ AddMimeType("WBMP", "image/vnd.wap.wbmp");
+ AddMimeType("WEBP", "image/webp");
+
+ AddMimeType("M3U", "audio/x-mpegurl");
+ AddMimeType("M3U", "application/x-mpegurl");
+ AddMimeType("PLS", "audio/x-scpls");
+ AddMimeType("WPL", "application/vnd.ms-wpl");
+ AddMimeType("M3U8", "application/vnd.apple.mpegurl");
+ AddMimeType("M3U8", "audio/mpegurl");
+ AddMimeType("M3U8", "audio/x-mpegurl");
+ AddMimeType("FL", "application/x-android-drm-fl");
+ AddMimeType("TXT", "text/plain");
+ AddMimeType("HTM", "text/html");
+ AddMimeType("HTML", "text/html");
+ AddMimeType("PDF", "application/pdf");
+ AddMimeType("DOC", "application/msword");
+ AddMimeType("XLS", "application/vnd.ms-excel");
+ AddMimeType("PPT", "application/mspowerpoint");
+ AddMimeType("FLAC", "audio/x-flac");
+ AddMimeType("FLAC", "audio/flac");
+ AddMimeType("ZIP", "application/zip");
+ AddMimeType("MPG", "video/mp2p");
+ AddMimeType("MPEG", "video/mp2p");
+
+ }
+}
+
+static MimeTypes staticContruct;
+
+static std::string empty;
+
+const std::string& MimeTypes::MimeTypeFromExtension(const std::string &extension)
+{
+ MaybeInitialize();
+ auto iter = extensionToMimeType.find(extension);
+ if (iter == extensionToMimeType.end()) return empty;
+ return iter->second;
+}
+
+const std::string& MimeTypes::ExtensionFromMimeType(const std::string &mimeType)
+{
+ MaybeInitialize();
+ auto iter = mimeTypeToExtension.find(mimeType);
+ if (iter == mimeTypeToExtension.end()) return empty;
+ return iter->second;
+}
+
+
+static std::string toLower(const std::string&value)
+{
+ std::string result;
+ result.resize(value.length());
+ for (size_t i = 0; i < value.length(); ++i)
+ {
+ char c = value[i];
+ if (c >= 'A' && c <= 'Z')
+ {
+ c += 'a'-'A';
+ }
+ result[i] = c;
+ }
+ return result;
+}
+
+void MimeTypes::AddMimeType(const std::string&extension_, const std::string&mimeType)
+{
+ std::string extension = "." + toLower(extension_);
+ mimeTypeToExtension[mimeType] = extension;
+ extensionToMimeType[extension] = mimeType;
+ if (mimeType.starts_with("audio/"))
+ {
+ audioExtensions.insert(extension);
+ }
+ if (mimeType.starts_with("video/"))
+ {
+ videoExtensions.insert(extension);
+ }
+
+}
+
+std::map MimeTypes::mimeTypeToExtension;
+std::map MimeTypes::extensionToMimeType;
+std::set MimeTypes::audioExtensions;
+std::set MimeTypes::videoExtensions;
+bool MimeTypes::initialized;
+
+
+
diff --git a/src/MimeTypes.hpp b/src/MimeTypes.hpp
new file mode 100644
index 0000000..d7fd8c9
--- /dev/null
+++ b/src/MimeTypes.hpp
@@ -0,0 +1,41 @@
+// 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.
+
+#pragma once
+
+#include
+#include