Snapshots
@@ -5,3 +5,4 @@
|
|||||||
path = modules/websocketpp
|
path = modules/websocketpp
|
||||||
url = https://github.com/rerdavies/websocketpp.git
|
url = https://github.com/rerdavies/websocketpp.git
|
||||||
branch = develop
|
branch = develop
|
||||||
|
update = merge
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ConfigSerializer.hpp"
|
#include "ConfigSerializer.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
|
||||||
using namespace config_serializer;
|
using namespace config_serializer;
|
||||||
@@ -38,29 +39,6 @@ std::string config_serializer::detail::trim(const std::string &v)
|
|||||||
return v.substr(start, end+1 - start);
|
return v.substr(start, end+1 - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> config_serializer::detail::split(const std::string &value, char delimiter)
|
|
||||||
{
|
|
||||||
size_t start = 0;
|
|
||||||
std::vector<std::string> result;
|
|
||||||
while (start < value.length())
|
|
||||||
{
|
|
||||||
size_t pos = value.find_first_of(delimiter, start);
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
{
|
|
||||||
result.push_back(value.substr(start));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result.push_back(value.substr(start, pos - start));
|
|
||||||
start = pos + 1;
|
|
||||||
if (start == value.length())
|
|
||||||
{
|
|
||||||
// ends with delimieter? Then there's an empty-length value at the end.
|
|
||||||
result.push_back("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::string config_serializer::detail::EncodeString(const std::string &s)
|
std::string config_serializer::detail::EncodeString(const std::string &s)
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include "ofstream_synced.hpp"
|
#include "ofstream_synced.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
namespace config_serializer
|
namespace config_serializer
|
||||||
{
|
{
|
||||||
@@ -38,8 +41,6 @@ namespace config_serializer
|
|||||||
{
|
{
|
||||||
std::string trim(const std::string &v);
|
std::string trim(const std::string &v);
|
||||||
|
|
||||||
std::vector<std::string> split(const std::string &v, char seperator);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert string to config file format.
|
* @brief Convert string to config file format.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -586,8 +586,12 @@ namespace pipedal
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
void write(const std::shared_ptr<T> &obj)
|
void write(const std::shared_ptr<T> &obj)
|
||||||
{
|
{
|
||||||
|
if (!obj) {
|
||||||
|
write_raw("null");
|
||||||
|
} else {
|
||||||
write(obj.get());
|
write(obj.get());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void write(const std::weak_ptr<T> &obj)
|
void write(const std::weak_ptr<T> &obj)
|
||||||
{
|
{
|
||||||
@@ -864,12 +868,20 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void read(std::shared_ptr<T> *pUniquePtr)
|
void read(std::shared_ptr<T> *pUniquePtr)
|
||||||
|
{
|
||||||
|
if (peek() == 'n')
|
||||||
|
{
|
||||||
|
consumeToken("null", "Expecting '{' or 'null'.");
|
||||||
|
*pUniquePtr = nullptr;
|
||||||
|
|
||||||
|
} else
|
||||||
{
|
{
|
||||||
std::shared_ptr<T> p = std::make_shared<T>();
|
std::shared_ptr<T> p = std::make_shared<T>();
|
||||||
|
|
||||||
read(p.get());
|
read(p.get());
|
||||||
(*pUniquePtr) = std::move(p);
|
(*pUniquePtr) = std::move(p);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void read(std::weak_ptr<T> *pUniquePtr)
|
void read(std::weak_ptr<T> *pUniquePtr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
#include <barrier>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
@@ -38,4 +41,30 @@ namespace pipedal {
|
|||||||
|
|
||||||
std::string GetHostName();
|
std::string GetHostName();
|
||||||
|
|
||||||
|
std::vector<std::string> split(const std::string &value, char delimiter);
|
||||||
|
|
||||||
|
inline void ReadBarrier() {
|
||||||
|
std::atomic_thread_fence(std::memory_order::acquire);
|
||||||
|
}
|
||||||
|
inline void WriteBarrier() {
|
||||||
|
std::atomic_thread_fence(std::memory_order::release);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoCopy {
|
||||||
|
public:
|
||||||
|
NoCopy() { }
|
||||||
|
NoCopy(const NoCopy&) = delete;
|
||||||
|
NoCopy&operator=(const NoCopy&) = delete;
|
||||||
|
~NoCopy() {}
|
||||||
|
};
|
||||||
|
class NoMove {
|
||||||
|
public:
|
||||||
|
NoMove() { }
|
||||||
|
NoMove(NoMove&&) = delete;
|
||||||
|
NoMove&operator=(NoMove&&) = delete;
|
||||||
|
~NoMove() { }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,3 +112,27 @@ std::string pipedal::GetHostName()
|
|||||||
buffer[1023] = '\0';
|
buffer[1023] = '\0';
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string> pipedal::split(const std::string &value, char delimiter)
|
||||||
|
{
|
||||||
|
size_t start = 0;
|
||||||
|
std::vector<std::string> result;
|
||||||
|
while (start < value.length())
|
||||||
|
{
|
||||||
|
size_t pos = value.find_first_of(delimiter, start);
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
{
|
||||||
|
result.push_back(value.substr(start));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.push_back(value.substr(start, pos - start));
|
||||||
|
start = pos + 1;
|
||||||
|
if (start == value.length())
|
||||||
|
{
|
||||||
|
// ends with delimieter? Then there's an empty-length value at the end.
|
||||||
|
result.push_back("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<style id="bgStyle">
|
<style id="bgStyle">
|
||||||
BODY {
|
BODY {
|
||||||
background: #333;
|
background: #333; overscroll-behavior: "none"
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
@@ -97,9 +97,9 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body style="overscroll-behavior: none; overflow: hidden">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root" style="height: 100%;position: absolute;left: 0px;right: 0px;top:0px;bottom: 0px"></div>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
If you open it directly in the browser, you will see an empty page.
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const theme = createTheme(
|
|||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#FF6060"
|
main: "#FF6060"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
mainBackground: "#222",
|
mainBackground: "#222",
|
||||||
toolbarColor: '#222'
|
toolbarColor: '#222'
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { SyntheticEvent } from 'react';
|
|||||||
import { WithStyles } from '@mui/styles';
|
import { WithStyles } from '@mui/styles';
|
||||||
|
|
||||||
import './AppThemed.css';
|
import './AppThemed.css';
|
||||||
|
|
||||||
import AppBar from '@mui/material/AppBar';
|
import AppBar from '@mui/material/AppBar';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
@@ -48,7 +49,7 @@ import SettingsDialog from './SettingsDialog';
|
|||||||
import AboutDialog from './AboutDialog';
|
import AboutDialog from './AboutDialog';
|
||||||
import BankDialog from './BankDialog';
|
import BankDialog from './BankDialog';
|
||||||
|
|
||||||
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo, wantsLoadingScreen } from './PiPedalModel';
|
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo, wantsReloadingScreen } from './PiPedalModel';
|
||||||
import ZoomedUiControl from './ZoomedUiControl'
|
import ZoomedUiControl from './ZoomedUiControl'
|
||||||
import MainPage from './MainPage';
|
import MainPage from './MainPage';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
@@ -67,6 +68,9 @@ import { ReactComponent as SaveBankAsIcon } from './svg/ic_save_bank_as.svg';
|
|||||||
import { ReactComponent as EditBanksIcon } from './svg/ic_edit_banks.svg';
|
import { ReactComponent as EditBanksIcon } from './svg/ic_edit_banks.svg';
|
||||||
import { ReactComponent as SettingsIcon } from './svg/ic_settings.svg';
|
import { ReactComponent as SettingsIcon } from './svg/ic_settings.svg';
|
||||||
import { ReactComponent as HelpOutlineIcon } from './svg/ic_help_outline.svg';
|
import { ReactComponent as HelpOutlineIcon } from './svg/ic_help_outline.svg';
|
||||||
|
import { ReactComponent as FxAmplifierIcon } from './svg/fx_amplifier.svg';
|
||||||
|
import { PerformanceView } from './PerformanceView';
|
||||||
|
|
||||||
import DialogEx, { DialogStackState } from './DialogEx';
|
import DialogEx, { DialogStackState } from './DialogEx';
|
||||||
|
|
||||||
|
|
||||||
@@ -264,6 +268,8 @@ type AppState = {
|
|||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
displayState: State;
|
displayState: State;
|
||||||
|
|
||||||
|
performanceView: boolean;
|
||||||
|
|
||||||
canFullScreen: boolean;
|
canFullScreen: boolean;
|
||||||
isFullScreen: boolean;
|
isFullScreen: boolean;
|
||||||
tinyToolBar: boolean;
|
tinyToolBar: boolean;
|
||||||
@@ -313,6 +319,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
performanceView: false,
|
||||||
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
||||||
isDrawerOpen: false,
|
isDrawerOpen: false,
|
||||||
errorMessage: this.model_.errorMessage.get(),
|
errorMessage: this.model_.errorMessage.get(),
|
||||||
@@ -729,8 +736,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
<div style={{
|
<div style={{
|
||||||
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
||||||
minHeight: 345, minWidth: 390,
|
minHeight: 345, minWidth: 390,
|
||||||
position: "absolute", width: "100%", height: "100%", background: "#F88", userSelect: "none",
|
position: "absolute", left:0, top: 0, right:0,bottom:0,
|
||||||
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
|
||||||
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||||
}}
|
}}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
@@ -740,7 +746,19 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{(!this.state.tinyToolBar) ?
|
{this.state.performanceView ? (
|
||||||
|
<PerformanceView
|
||||||
|
onClose={() => { this.setState({ performanceView: false }); }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
position: "absolute", width: "100%", height: "100%", userSelect: "none",
|
||||||
|
display: "flex", flexDirection: "column", flexWrap: "nowrap"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
{(!this.state.tinyToolBar) && !this.state.performanceView ?
|
||||||
(
|
(
|
||||||
<AppBar position="absolute" >
|
<AppBar position="absolute" >
|
||||||
<Toolbar variant="dense" className={classes.toolBar} >
|
<Toolbar variant="dense" className={classes.toolBar} >
|
||||||
@@ -803,8 +821,25 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
)}
|
)}
|
||||||
<TemporaryDrawer position='left' title="PiPedal"
|
<TemporaryDrawer position='left' title="PiPedal"
|
||||||
is_open={this.state.isDrawerOpen} onClose={() => { this.hideDrawer(); }} >
|
is_open={this.state.isDrawerOpen} onClose={() => { this.hideDrawer(); }} >
|
||||||
<ListSubheader className="listSubheader" component="div" id="xnested-list-subheader" style={{ background: "rgba(12,12,12,0.0)" }}>
|
|
||||||
<Typography variant="caption" style={{ position: "relative", top: 15 }}>Banks</Typography></ListSubheader>
|
<List>
|
||||||
|
<ListItem button key='PerformanceView'
|
||||||
|
onClick={(ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hideDrawer(true);
|
||||||
|
this.setState({ performanceView: true });
|
||||||
|
}}>
|
||||||
|
<ListItemIcon >
|
||||||
|
<FxAmplifierIcon color='inherit' className={classes.menuIcon} style={{ width: 24, height: 24 }} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary='Performance View' />
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
<Divider />
|
||||||
|
<ListSubheader className="listSubheader" component="div" id="xnested-list-subheader" style={{ lineHeight: "24px", height: 24, background: "rgba(12,12,12,0.0)" }}
|
||||||
|
disableSticky={true}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" style={{}}>Banks</Typography></ListSubheader>
|
||||||
|
|
||||||
<List >
|
<List >
|
||||||
{
|
{
|
||||||
@@ -918,9 +953,11 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
<main className={classes.mainFrame} >
|
<main className={classes.mainFrame} >
|
||||||
<div className={classes.mainSizingPosition}>
|
<div className={classes.mainSizingPosition}>
|
||||||
<div className={classes.heroContent}>
|
<div className={classes.heroContent}>
|
||||||
{(this.state.displayState !== State.Loading) && (
|
{(this.state.displayState !== State.Loading) &&
|
||||||
<MainPage hasTinyToolBar={this.state.tinyToolBar} />
|
(
|
||||||
)}
|
<MainPage hasTinyToolBar={this.state.tinyToolBar} enableStructureEditing={true} />
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -963,11 +1000,13 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
/>
|
/>
|
||||||
<UpdateDialog open={this.state.updateDialogOpen} />
|
<UpdateDialog open={this.state.updateDialogOpen} />
|
||||||
{this.state.showStatusMonitor && (<JackStatusView />)}
|
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<DialogEx
|
<DialogEx tag="Alert"
|
||||||
tag="Alert"
|
|
||||||
open={this.state.alertDialogOpen}
|
open={this.state.alertDialogOpen}
|
||||||
onClose={this.handleCloseAlert}
|
onClose={this.handleCloseAlert}
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
@@ -989,24 +1028,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
</DialogEx>
|
</DialogEx>
|
||||||
|
|
||||||
|
|
||||||
<div className={classes.errorContent} style={{
|
|
||||||
display: (
|
|
||||||
wantsLoadingScreen(this.state.displayState)
|
|
||||||
? "block" : "none"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={classes.errorContentMask} />
|
|
||||||
|
|
||||||
<div className={classes.loadingBox}>
|
|
||||||
<div className={classes.loadingBoxItem}>
|
|
||||||
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
|
|
||||||
</div>
|
|
||||||
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
|
|
||||||
{this.getReloadingMessage()}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Error ? "flex" : "none" }}
|
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Error ? "flex" : "none" }}
|
||||||
onMouseDown={preventDefault} onKeyDown={preventDefault}
|
onMouseDown={preventDefault} onKeyDown={preventDefault}
|
||||||
>
|
>
|
||||||
@@ -1034,6 +1055,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
<div style={{ flex: "5 5 auto", height: 20 }} > </div>
|
<div style={{ flex: "5 5 auto", height: 20 }} > </div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* initial load mask*/}
|
||||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Loading ? "block" : "none" }}>
|
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Loading ? "block" : "none" }}>
|
||||||
<div className={classes.errorContentMask} />
|
<div className={classes.errorContentMask} />
|
||||||
<div className={classes.loadingBox}>
|
<div className={classes.loadingBox}>
|
||||||
@@ -1045,11 +1067,30 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Reloading mask */}
|
||||||
|
<div className={classes.errorContent} style={{
|
||||||
|
zIndex: 501,
|
||||||
|
display: (
|
||||||
|
wantsReloadingScreen(this.state.displayState)
|
||||||
|
? "block" : "none"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={classes.errorContentMask} />
|
||||||
|
|
||||||
|
<div className={classes.loadingBox}>
|
||||||
|
<div className={classes.loadingBoxItem}>
|
||||||
|
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
|
||||||
|
</div>
|
||||||
|
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
|
||||||
|
{this.getReloadingMessage()}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default AppThemed;
|
export default AppThemed;
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import ButtonBase from '@mui/material/ButtonBase';
|
||||||
|
import Menu from '@mui/material/Menu';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import { colorKeys, getBackgroundColor } from './MaterialColors';
|
||||||
|
import { useTheme } from '@mui/styles';
|
||||||
|
|
||||||
|
const colors: string[] = colorKeys;
|
||||||
|
|
||||||
|
interface ColorDropdownButtonProps {
|
||||||
|
currentColor?: string;
|
||||||
|
onColorChange: (color: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColorDropdownButton: React.FC<ColorDropdownButtonProps> = ({
|
||||||
|
currentColor = colors[0],
|
||||||
|
onColorChange,
|
||||||
|
}) => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const [selectedColor, setSelectedColor] = useState<string>(currentColor);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedColor(currentColor);
|
||||||
|
}, [currentColor]);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleColorSelect = (color: string) => {
|
||||||
|
handleClose();
|
||||||
|
setSelectedColor(color);
|
||||||
|
onColorChange(color);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ButtonBase
|
||||||
|
onClick={handleClick}
|
||||||
|
style={{
|
||||||
|
width: 48, height: 48, padding: 12, borderRadius: 18
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: 24, height: 24,
|
||||||
|
background: getBackgroundColor(selectedColor),
|
||||||
|
borderRadius: 6,
|
||||||
|
borderColor: theme.palette.text.secondary,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid"
|
||||||
|
}} />
|
||||||
|
|
||||||
|
</ButtonBase>
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={anchorEl !== null}
|
||||||
|
onClose={handleClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexFlow: "row wrap", width: 56 * 4 + 2 }}>
|
||||||
|
{colors.map((color) => {
|
||||||
|
let selected = color === selectedColor;
|
||||||
|
return (
|
||||||
|
<MenuItem key={color} onClick={() => handleColorSelect(color)}
|
||||||
|
style={{
|
||||||
|
width: 48, height: 48, borderRadius: 6, margin: 4,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: selected ? 2 : 0.25,
|
||||||
|
borderColor: color === selectedColor ?
|
||||||
|
theme.palette.text.primary
|
||||||
|
: theme.palette.text.secondary,
|
||||||
|
backgroundColor: getBackgroundColor(color)
|
||||||
|
}}
|
||||||
|
></MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorDropdownButton;
|
||||||
@@ -24,6 +24,7 @@ import Dialog, {DialogProps} from '@mui/material/Dialog';
|
|||||||
|
|
||||||
interface DialogExProps extends DialogProps {
|
interface DialogExProps extends DialogProps {
|
||||||
tag: string;
|
tag: string;
|
||||||
|
fullwidth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DialogExState {
|
interface DialogExState {
|
||||||
@@ -171,7 +172,7 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
|||||||
render() {
|
render() {
|
||||||
let { tag,onClose, ...extra} = this.props;
|
let { tag,onClose, ...extra} = this.props;
|
||||||
return (
|
return (
|
||||||
<Dialog {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
|
<Dialog fullWidth={this.props.fullWidth??false} maxWidth={this.props.fullWidth ? false: undefined} {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ const GxTunerControl =
|
|||||||
return this.makeTick(pitchInfo,cents,r0,r1,"#666", width);
|
return this.makeTick(pitchInfo,cents,r0,r1,"#666", width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextKey() { return "key" + (this.keyCounter++)}
|
||||||
makeTick(pitchInfo: PitchInfo, cents: number, r0: number, r1: number, stroke: string,width: number): React.ReactNode
|
makeTick(pitchInfo: PitchInfo, cents: number, r0: number, r1: number, stroke: string,width: number): React.ReactNode
|
||||||
{
|
{
|
||||||
let range = pitchInfo.semitoneCents;
|
let range = pitchInfo.semitoneCents;
|
||||||
@@ -335,7 +336,7 @@ const GxTunerControl =
|
|||||||
let cy = NEEDLE_CY;
|
let cy = NEEDLE_CY;
|
||||||
let path = new SvgPathBuilder().moveTo(r0*sin_+cx,r0*cos_+cy).lineTo(r1*sin_+cx,r1*cos_+cy).toString();
|
let path = new SvgPathBuilder().moveTo(r0*sin_+cx,r0*cos_+cy).lineTo(r1*sin_+cx,r1*cos_+cy).toString();
|
||||||
|
|
||||||
return (<path d={path} stroke={stroke} strokeWidth={width+""} />);
|
return (<path key={this.nextKey()} d={path} stroke={stroke} strokeWidth={width+""} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastValidTime: number = 0;
|
lastValidTime: number = 0;
|
||||||
@@ -406,7 +407,10 @@ const GxTunerControl =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyCounter: number = 0;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
this.keyCounter = 0;
|
||||||
let textColor = isDarkMode() ? "#999": "#444";
|
let textColor = isDarkMode() ? "#999": "#444";
|
||||||
return (<div ref={this.refRoot} style={{width: DIAL_WIDTH, height: DIAL_HEIGHT, fontSize: "2em", fontWeight: 700, position: "relative",
|
return (<div ref={this.refRoot} style={{width: DIAL_WIDTH, height: DIAL_HEIGHT, fontSize: "2em", fontWeight: 700, position: "relative",
|
||||||
boxShadow: isDarkMode() ?
|
boxShadow: isDarkMode() ?
|
||||||
|
|||||||
@@ -292,8 +292,11 @@ export const LoadPluginDialog =
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
mounted: boolean = false;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
super.componentDidMount();
|
super.componentDidMount();
|
||||||
|
this.mounted = true;
|
||||||
this.updateWindowSize();
|
this.updateWindowSize();
|
||||||
window.addEventListener('resize', this.updateWindowSize);
|
window.addEventListener('resize', this.updateWindowSize);
|
||||||
|
|
||||||
@@ -307,10 +310,12 @@ export const LoadPluginDialog =
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.cancelSearchTimeout();
|
||||||
this.model.ui_plugins.removeOnChangedHandler(this.handlePluginsChanged);
|
this.model.ui_plugins.removeOnChangedHandler(this.handlePluginsChanged);
|
||||||
this.model.favorites.removeOnChangedHandler(this.handleFavoritesChanged);
|
this.model.favorites.removeOnChangedHandler(this.handleFavoritesChanged);
|
||||||
super.componentWillUnmount();
|
|
||||||
window.removeEventListener('resize', this.updateWindowSize);
|
window.removeEventListener('resize', this.updateWindowSize);
|
||||||
|
this.mounted = false;
|
||||||
|
super.componentWillUnmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(oldProps: PluginGridProps) {
|
componentDidUpdate(oldProps: PluginGridProps) {
|
||||||
@@ -574,6 +579,10 @@ export const LoadPluginDialog =
|
|||||||
|
|
||||||
|
|
||||||
handleSearchStringReady() {
|
handleSearchStringReady() {
|
||||||
|
if (!this.mounted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.changedSearchString !== undefined) {
|
if (this.changedSearchString !== undefined) {
|
||||||
this.requestScrollTo();
|
this.requestScrollTo();
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -584,12 +593,17 @@ export const LoadPluginDialog =
|
|||||||
this.hSearchTimeout = undefined;
|
this.hSearchTimeout = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelSearchTimeout()
|
||||||
handleSearchStringChanged(text: string): void {
|
{
|
||||||
if (this.hSearchTimeout) {
|
if (this.hSearchTimeout) {
|
||||||
clearTimeout(this.hSearchTimeout);
|
clearTimeout(this.hSearchTimeout);
|
||||||
this.hSearchTimeout = undefined;
|
this.hSearchTimeout = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchStringChanged(text: string): void {
|
||||||
|
this.cancelSearchTimeout();
|
||||||
this.changedSearchString = text;
|
this.changedSearchString = text;
|
||||||
this.hSearchTimeout = setTimeout(
|
this.hSearchTimeout = setTimeout(
|
||||||
this.handleSearchStringReady,
|
this.handleSearchStringReady,
|
||||||
|
|||||||
@@ -45,13 +45,25 @@ import PluginInfoDialog from './PluginInfoDialog';
|
|||||||
import { GetControlView } from './ControlViewFactory';
|
import { GetControlView } from './ControlViewFactory';
|
||||||
import MidiBindingsDialog from './MidiBindingsDialog';
|
import MidiBindingsDialog from './MidiBindingsDialog';
|
||||||
import PluginPresetSelector from './PluginPresetSelector';
|
import PluginPresetSelector from './PluginPresetSelector';
|
||||||
import {ReactComponent as OldDeleteIcon} from "./svg/old_delete_outline_24dp.svg";
|
import { ReactComponent as OldDeleteIcon } from "./svg/old_delete_outline_24dp.svg";
|
||||||
import {ReactComponent as MidiIcon} from "./svg/ic_midi.svg";
|
import { ReactComponent as MidiIcon } from "./svg/ic_midi.svg";
|
||||||
import {isDarkMode} from './DarkMode';
|
import { isDarkMode } from './DarkMode';
|
||||||
|
import { ReactComponent as Snapshot0Icon } from "./svg/snapshot_0.svg";
|
||||||
|
import { ReactComponent as Snapshot1Icon } from "./svg/snapshot_1.svg";
|
||||||
|
import { ReactComponent as Snapshot2Icon } from "./svg/snapshot_2.svg";
|
||||||
|
import { ReactComponent as Snapshot3Icon } from "./svg/snapshot_3.svg";
|
||||||
|
import { ReactComponent as Snapshot4Icon } from "./svg/snapshot_4.svg";
|
||||||
|
import { ReactComponent as Snapshot5Icon } from "./svg/snapshot_5.svg";
|
||||||
|
import { ReactComponent as Snapshot6Icon } from "./svg/snapshot_6.svg";
|
||||||
|
|
||||||
const SPLIT_CONTROLBAR_THRESHHOLD = 650;
|
import SnapshotDialog from './SnapshotDialog';
|
||||||
|
|
||||||
|
|
||||||
|
const SPLIT_CONTROLBAR_THRESHHOLD = 750;
|
||||||
|
const DISPLAY_AUTHOR_THRESHHOLD = 750;
|
||||||
|
const DISPLAY_AUTHOR_SPLIT_THRESHOLD = 500;
|
||||||
const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500;
|
const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const HORIZONTAL_LAYOUT_MQ = "@media (max-height: " + HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK + "px)";
|
const HORIZONTAL_LAYOUT_MQ = "@media (max-height: " + HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK + "px)";
|
||||||
|
|
||||||
@@ -86,7 +98,7 @@ const styles = ({ palette }: Theme) => createStyles({
|
|||||||
flex: "0 0 162px", width: "100%", height: 162, overflowY: "hidden",
|
flex: "0 0 162px", width: "100%", height: 162, overflowY: "hidden",
|
||||||
},
|
},
|
||||||
title: { fontSize: "1.1em", fontWeight: 700, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 },
|
title: { fontSize: "1.1em", fontWeight: 700, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 },
|
||||||
author: { fontWeight: 500, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap",opacity: 0.75 }
|
author: { fontWeight: 500, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -94,14 +106,18 @@ const styles = ({ palette }: Theme) => createStyles({
|
|||||||
interface MainProps extends WithStyles<typeof styles> {
|
interface MainProps extends WithStyles<typeof styles> {
|
||||||
hasTinyToolBar: boolean;
|
hasTinyToolBar: boolean;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
enableStructureEditing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainState {
|
interface MainState {
|
||||||
selectedPedal: number;
|
selectedPedal: number;
|
||||||
|
selectedSnapshot: number;
|
||||||
loadDialogOpen: boolean;
|
loadDialogOpen: boolean;
|
||||||
|
snapshotDialogOpen: boolean;
|
||||||
pedalboard: Pedalboard;
|
pedalboard: Pedalboard;
|
||||||
addMenuAnchorEl: HTMLElement | null;
|
addMenuAnchorEl: HTMLElement | null;
|
||||||
splitControlBar: boolean;
|
splitControlBar: boolean;
|
||||||
|
displayAuthor: boolean;
|
||||||
horizontalScrollLayout: boolean;
|
horizontalScrollLayout: boolean;
|
||||||
showMidiBindingsDialog: boolean;
|
showMidiBindingsDialog: boolean;
|
||||||
screenHeight: number;
|
screenHeight: number;
|
||||||
@@ -111,10 +127,21 @@ interface MainState {
|
|||||||
|
|
||||||
export const MainPage =
|
export const MainPage =
|
||||||
withStyles(styles, { withTheme: true })(
|
withStyles(styles, { withTheme: true })(
|
||||||
class extends ResizeResponsiveComponent<MainProps, MainState>
|
class extends ResizeResponsiveComponent<MainProps, MainState> {
|
||||||
{
|
|
||||||
model: PiPedalModel;
|
model: PiPedalModel;
|
||||||
|
|
||||||
|
getSplitToolbar() {
|
||||||
|
return this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD;
|
||||||
|
}
|
||||||
|
getDisplayAuthor()
|
||||||
|
{
|
||||||
|
if (this.getSplitToolbar())
|
||||||
|
{
|
||||||
|
return this.windowSize.width >= DISPLAY_AUTHOR_SPLIT_THRESHOLD;
|
||||||
|
} else {
|
||||||
|
return this.windowSize.width >= DISPLAY_AUTHOR_THRESHHOLD;
|
||||||
|
}
|
||||||
|
}
|
||||||
constructor(props: MainProps) {
|
constructor(props: MainProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.model = PiPedalModelFactory.getInstance();
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
@@ -123,14 +150,18 @@ export const MainPage =
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedPedal: selectedPedal,
|
selectedPedal: selectedPedal,
|
||||||
|
selectedSnapshot: -1,
|
||||||
loadDialogOpen: false,
|
loadDialogOpen: false,
|
||||||
|
snapshotDialogOpen: false,
|
||||||
pedalboard: pedalboard,
|
pedalboard: pedalboard,
|
||||||
addMenuAnchorEl: null,
|
addMenuAnchorEl: null,
|
||||||
splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD,
|
splitControlBar: this.getSplitToolbar(),
|
||||||
|
displayAuthor: this.getDisplayAuthor(),
|
||||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||||
showMidiBindingsDialog: false,
|
showMidiBindingsDialog: false,
|
||||||
screenHeight: this.windowSize.height
|
screenHeight: this.windowSize.height
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
||||||
this.onPedalDoubleClick = this.onPedalDoubleClick.bind(this);
|
this.onPedalDoubleClick = this.onPedalDoubleClick.bind(this);
|
||||||
@@ -138,6 +169,7 @@ export const MainPage =
|
|||||||
this.onLoadOk = this.onLoadOk.bind(this);
|
this.onLoadOk = this.onLoadOk.bind(this);
|
||||||
this.onLoadCancel = this.onLoadCancel.bind(this);
|
this.onLoadCancel = this.onLoadCancel.bind(this);
|
||||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||||
|
this.onSelectedSnapshotChanged = this.onSelectedSnapshotChanged.bind(this);
|
||||||
this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this);
|
this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,9 +236,16 @@ export const MainPage =
|
|||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
pedalboard: value,
|
pedalboard: value,
|
||||||
selectedPedal: selectedItem
|
selectedPedal: selectedItem,
|
||||||
|
selectedSnapshot: value.selectedSnapshot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
onSelectedSnapshotChanged(selectedSnapshot: number) {
|
||||||
|
this.setState({
|
||||||
|
selectedSnapshot: selectedSnapshot
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
onDeletePedal(instanceId: number): void {
|
onDeletePedal(instanceId: number): void {
|
||||||
let result = this.model.deletePedalboardPedal(instanceId);
|
let result = this.model.deletePedalboardPedal(instanceId);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -217,14 +256,17 @@ export const MainPage =
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
super.componentDidMount();
|
super.componentDidMount();
|
||||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||||
|
this.model.selectedSnapshot.addOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.model.selectedSnapshot.removeOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||||
super.componentWillUnmount();
|
super.componentWillUnmount();
|
||||||
}
|
}
|
||||||
updateResponsive() {
|
updateResponsive() {
|
||||||
this.setState({
|
this.setState({
|
||||||
splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD,
|
splitControlBar: this.getSplitToolbar(),
|
||||||
|
displayAuthor: this.getDisplayAuthor(),
|
||||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||||
screenHeight: this.windowSize.height
|
screenHeight: this.windowSize.height
|
||||||
});
|
});
|
||||||
@@ -267,10 +309,16 @@ export const MainPage =
|
|||||||
let newSelectedItem = this.model.loadPedalboardPlugin(itemId, selectedUri);
|
let newSelectedItem = this.model.loadPedalboardPlugin(itemId, selectedUri);
|
||||||
this.setState({ selectedPedal: newSelectedItem });
|
this.setState({ selectedPedal: newSelectedItem });
|
||||||
}
|
}
|
||||||
|
onSnapshotDialogOk(): void {
|
||||||
|
this.setState({ snapshotDialogOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
onLoadClick(e: SyntheticEvent) {
|
onLoadClick(e: SyntheticEvent) {
|
||||||
this.setState({ loadDialogOpen: true });
|
this.setState({ loadDialogOpen: true });
|
||||||
}
|
}
|
||||||
|
onSnapshotClick() {
|
||||||
|
this.setState({ snapshotDialogOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
getPedalboardItem(selectedId?: number): PedalboardItem | null {
|
getPedalboardItem(selectedId?: number): PedalboardItem | null {
|
||||||
if (selectedId === undefined) return null;
|
if (selectedId === undefined) return null;
|
||||||
@@ -352,8 +400,8 @@ export const MainPage =
|
|||||||
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flex: "0 1 auto",minWidth: 0 }}>
|
<div style={{ flex: "0 1 auto", minWidth: 0 }}>
|
||||||
<span style={{ color: isDarkMode()? "#F02020": "#800000" }}>
|
<span style={{ color: isDarkMode() ? "#F02020" : "#800000" }}>
|
||||||
<span className={classes.title}>{title}</span>
|
<span className={classes.title}>{title}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -363,13 +411,15 @@ export const MainPage =
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
flex: "1 1 auto", minWidth: 0,overflow: "hidden", marginRight: 8,
|
flex: "1 1 auto", minWidth: 0, overflow: "hidden", marginRight: 8,
|
||||||
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flex: "0 1 auto", minWidth: 0,overflow: "hidden",textOverflow: "ellipsis" }}>
|
<div style={{ flex: "0 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
<span className={classes.title}>{title}</span>
|
<span className={classes.title}>{title}</span>
|
||||||
|
{this.state.displayAuthor&&(
|
||||||
<span className={classes.author}>{author}</span>
|
<span className={classes.author}>{author}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
|
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
|
||||||
<PluginInfoDialog plugin_uri={pluginUri} />
|
<PluginInfoDialog plugin_uri={pluginUri} />
|
||||||
@@ -383,6 +433,25 @@ export const MainPage =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
snapshotIcon(theme: Theme, snapshotNumber: number) {
|
||||||
|
switch (snapshotNumber + 1) {
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
return (<Snapshot0Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
case 1:
|
||||||
|
return (<Snapshot1Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
case 2:
|
||||||
|
return (<Snapshot2Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
case 3:
|
||||||
|
return (<Snapshot3Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
case 4:
|
||||||
|
return (<Snapshot4Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
case 5:
|
||||||
|
return (<Snapshot5Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
case 6:
|
||||||
|
return (<Snapshot6Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
@@ -438,6 +507,7 @@ export const MainPage =
|
|||||||
<div id="pedalboardScroll" className={horizontalScrollLayout ? classes.pedalboardScrollSmall : classes.pedalboardScroll}
|
<div id="pedalboardScroll" className={horizontalScrollLayout ? classes.pedalboardScrollSmall : classes.pedalboardScroll}
|
||||||
style={{ maxHeight: horizontalScrollLayout ? undefined : this.state.screenHeight / 2 }}>
|
style={{ maxHeight: horizontalScrollLayout ? undefined : this.state.screenHeight / 2 }}>
|
||||||
<PedalboardView key={pluginUri} selectedId={this.state.selectedPedal}
|
<PedalboardView key={pluginUri} selectedId={this.state.selectedPedal}
|
||||||
|
enableStructureEditing={this.props.enableStructureEditing}
|
||||||
onSelectionChanged={this.onSelectionChanged}
|
onSelectionChanged={this.onSelectionChanged}
|
||||||
onDoubleClick={this.onPedalDoubleClick}
|
onDoubleClick={this.onPedalDoubleClick}
|
||||||
hasTinyToolBar={this.props.hasTinyToolBar}
|
hasTinyToolBar={this.props.hasTinyToolBar}
|
||||||
@@ -455,14 +525,16 @@ export const MainPage =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
(!this.state.splitControlBar) && this.titleBar(pedalboardItem)
|
(!this.state.splitControlBar || !this.props.enableStructureEditing) && this.titleBar(pedalboardItem)
|
||||||
}
|
}
|
||||||
<div style={{ flex: "1 1 1px" }}>
|
<div style={{ flex: "1 1 1px" }}>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{this.props.enableStructureEditing && (
|
||||||
|
<div style={{ flex: "0 0 auto", display: "flex", flexFlow: "row nowrap",alignItems: "center" }}>
|
||||||
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}>
|
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}>
|
||||||
<IconButton onClick={(e) => { this.onAddClick(e) }} size="large">
|
<IconButton onClick={(e) => { this.onAddClick(e) }} size="large">
|
||||||
<AddIcon style={{height: 24, width: 24,fill: this.props.theme.palette.text.primary, opacity: 0.6}} />
|
<AddIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Menu
|
<Menu
|
||||||
id="add-menu"
|
id="add-menu"
|
||||||
@@ -483,7 +555,7 @@ export const MainPage =
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
|
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
|
||||||
size="large">
|
size="large">
|
||||||
<OldDeleteIcon style={{height: 24, width: 24,fill: this.props.theme.palette.text.primary, opacity: 0.6}} />
|
<OldDeleteIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: "0 0 auto" }}>
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
@@ -494,8 +566,10 @@ export const MainPage =
|
|||||||
onClick={this.onLoadClick}
|
onClick={this.onLoadClick}
|
||||||
disabled={this.state.selectedPedal === -1 || (!canLoad) || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
disabled={this.state.selectedPedal === -1 || (!canLoad) || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
||||||
startIcon={<InputIcon />}
|
startIcon={<InputIcon />}
|
||||||
style={{ textTransform: "none",
|
style={{
|
||||||
background: (isDarkMode() ? "#6750A4": undefined) }}
|
textTransform: "none",
|
||||||
|
background: (isDarkMode() ? "#6750A4" : undefined)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Load
|
Load
|
||||||
</Button>
|
</Button>
|
||||||
@@ -504,14 +578,22 @@ export const MainPage =
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={(e) => { this.handleMidiConfiguration(instanceId); }}
|
onClick={(e) => { this.handleMidiConfiguration(instanceId); }}
|
||||||
size="large">
|
size="large">
|
||||||
<MidiIcon style={{height: 24, width: 24,fill: this.props.theme.palette.text.primary, opacity: 0.6}} />
|
<MidiIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ flex: "0 0 auto" }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => { this.setState({ snapshotDialogOpen: true }); }}
|
||||||
|
size="large">
|
||||||
|
{this.snapshotIcon(this.props.theme, this.state.selectedSnapshot)}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
this.state.splitControlBar && (
|
this.state.splitControlBar && this.props.enableStructureEditing && (
|
||||||
<div className={classes.splitControlBar}>
|
<div className={classes.splitControlBar}>
|
||||||
{
|
{
|
||||||
this.titleBar(pedalboardItem)
|
this.titleBar(pedalboardItem)
|
||||||
@@ -544,6 +626,9 @@ export const MainPage =
|
|||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{(this.state.snapshotDialogOpen) && (
|
||||||
|
<SnapshotDialog open={this.state.snapshotDialogOpen} onOk={() => this.onSnapshotDialogOk()} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,356 @@
|
|||||||
|
import { isDarkMode } from "./DarkMode";
|
||||||
|
|
||||||
|
export const materialColors: { [Name: string]: { [Name: number]: string } } = {
|
||||||
|
"grey": {
|
||||||
|
50: '#FAFAFA',
|
||||||
|
100: '#F5F5F5',
|
||||||
|
200: '#EEEEEE',
|
||||||
|
300: '#E0E0E0',
|
||||||
|
400: '#BDBDBD',
|
||||||
|
500: '#9E9E9E',
|
||||||
|
600: '#757575',
|
||||||
|
700: '#616161',
|
||||||
|
800: '#424242',
|
||||||
|
900: '#212121'
|
||||||
|
},
|
||||||
|
"blueGrey": {
|
||||||
|
50: '#ECEFF1',
|
||||||
|
100: '#CFD8DC',
|
||||||
|
200: '#B0BEC5',
|
||||||
|
300: '#90A4AE',
|
||||||
|
400: '#78909C',
|
||||||
|
500: '#607D8B',
|
||||||
|
600: '#546E7A',
|
||||||
|
700: '#455A64',
|
||||||
|
800: '#37474F',
|
||||||
|
900: '#263238'
|
||||||
|
},
|
||||||
|
|
||||||
|
"red": {
|
||||||
|
50: '#FFEBEE',
|
||||||
|
100: '#FFCDD2',
|
||||||
|
200: '#EF9A9A',
|
||||||
|
300: '#E57373',
|
||||||
|
400: '#EF5350',
|
||||||
|
500: '#F44336',
|
||||||
|
600: '#E53935',
|
||||||
|
700: '#D32F2F',
|
||||||
|
800: '#C62828',
|
||||||
|
900: '#B71C1C',
|
||||||
|
// A100: '#FF8A80',
|
||||||
|
// A200: '#FF5252',
|
||||||
|
// A400: '#FF1744',
|
||||||
|
// A700: '#D50000'
|
||||||
|
},
|
||||||
|
"pink": {
|
||||||
|
50: '#FCE4EC',
|
||||||
|
100: '#F8BBD0',
|
||||||
|
200: '#F48FB1',
|
||||||
|
300: '#F06292',
|
||||||
|
400: '#EC407A',
|
||||||
|
500: '#E91E63',
|
||||||
|
600: '#D81B60',
|
||||||
|
700: '#C2185B',
|
||||||
|
800: '#AD1457',
|
||||||
|
900: '#880E4F',
|
||||||
|
// A100: '#FF80AB',
|
||||||
|
// A200: '#FF4081',
|
||||||
|
// A400: '#F50057',
|
||||||
|
// A700: '#C51162'
|
||||||
|
},
|
||||||
|
"purple": {
|
||||||
|
50: '#F3E5F5',
|
||||||
|
100: '#E1BEE7',
|
||||||
|
200: '#CE93D8',
|
||||||
|
300: '#BA68C8',
|
||||||
|
400: '#AB47BC',
|
||||||
|
500: '#9C27B0',
|
||||||
|
600: '#8E24AA',
|
||||||
|
700: '#7B1FA2',
|
||||||
|
800: '#6A1B9A',
|
||||||
|
900: '#4A148C',
|
||||||
|
// A100: '#EA80FC',
|
||||||
|
// A200: '#E040FB',
|
||||||
|
// A400: '#D500F9',
|
||||||
|
// A700: '#AA00FF'
|
||||||
|
},
|
||||||
|
"deepPurple": {
|
||||||
|
50: '#EDE7F6',
|
||||||
|
100: '#D1C4E9',
|
||||||
|
200: '#B39DDB',
|
||||||
|
300: '#9575CD',
|
||||||
|
400: '#7E57C2',
|
||||||
|
500: '#673AB7',
|
||||||
|
600: '#5E35B1',
|
||||||
|
700: '#512DA8',
|
||||||
|
800: '#4527A0',
|
||||||
|
900: '#311B92',
|
||||||
|
// A100: '#B388FF',
|
||||||
|
// A200: '#7C4DFF',
|
||||||
|
// A400: '#651FFF',
|
||||||
|
// A700: '#6200EA'
|
||||||
|
},
|
||||||
|
"indigo": {
|
||||||
|
50: '#E8EAF6',
|
||||||
|
100: '#C5CAE9',
|
||||||
|
200: '#9FA8DA',
|
||||||
|
300: '#7986CB',
|
||||||
|
400: '#5C6BC0',
|
||||||
|
500: '#3F51B5',
|
||||||
|
600: '#3949AB',
|
||||||
|
700: '#303F9F',
|
||||||
|
800: '#283593',
|
||||||
|
900: '#1A237E',
|
||||||
|
// A100: '#8C9EFF',
|
||||||
|
// A200: '#536DFE',
|
||||||
|
// A400: '#3D5AFE',
|
||||||
|
// A700: '#304FFE'
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
50: '#E3F2FD',
|
||||||
|
100: '#BBDEFB',
|
||||||
|
200: '#90CAF9',
|
||||||
|
300: '#64B5F6',
|
||||||
|
400: '#42A5F5',
|
||||||
|
500: '#2196F3',
|
||||||
|
600: '#1E88E5',
|
||||||
|
700: '#1976D2',
|
||||||
|
800: '#1565C0',
|
||||||
|
900: '#0D47A1',
|
||||||
|
// A100: '#82B1FF',
|
||||||
|
// A200: '#448AFF',
|
||||||
|
// A400: '#2979FF',
|
||||||
|
// A700: '#2962FF'
|
||||||
|
},
|
||||||
|
"lightBlue": {
|
||||||
|
50: '#E1F5FE',
|
||||||
|
100: '#B3E5FC',
|
||||||
|
200: '#81D4FA',
|
||||||
|
300: '#4FC3F7',
|
||||||
|
400: '#29B6F6',
|
||||||
|
500: '#03A9F4',
|
||||||
|
600: '#039BE5',
|
||||||
|
700: '#0288D1',
|
||||||
|
800: '#0277BD',
|
||||||
|
900: '#01579B',
|
||||||
|
// A100: '#80D8FF',
|
||||||
|
// A200: '#40C4FF',
|
||||||
|
// A400: '#00B0FF',
|
||||||
|
// A700: '#0091EA'
|
||||||
|
},
|
||||||
|
"cyan": {
|
||||||
|
50: '#E0F7FA',
|
||||||
|
100: '#B2EBF2',
|
||||||
|
200: '#80DEEA',
|
||||||
|
300: '#4DD0E1',
|
||||||
|
400: '#26C6DA',
|
||||||
|
500: '#00BCD4',
|
||||||
|
600: '#00ACC1',
|
||||||
|
700: '#0097A7',
|
||||||
|
800: '#00838F',
|
||||||
|
900: '#006064',
|
||||||
|
// A100: '#84FFFF',
|
||||||
|
// A200: '#18FFFF',
|
||||||
|
// A400: '#00E5FF',
|
||||||
|
// A700: '#00B8D4'
|
||||||
|
},
|
||||||
|
"teal": {
|
||||||
|
50: '#E0F2F1',
|
||||||
|
100: '#B2DFDB',
|
||||||
|
200: '#80CBC4',
|
||||||
|
300: '#4DB6AC',
|
||||||
|
400: '#26A69A',
|
||||||
|
500: '#009688',
|
||||||
|
600: '#00897B',
|
||||||
|
700: '#00796B',
|
||||||
|
800: '#00695C',
|
||||||
|
900: '#004D40',
|
||||||
|
// A100: '#A7FFEB',
|
||||||
|
// A200: '#64FFDA',
|
||||||
|
// A400: '#1DE9B6',
|
||||||
|
// A700: '#00BFA5'
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
50: '#E8F5E9',
|
||||||
|
100: '#C8E6C9',
|
||||||
|
200: '#A5D6A7',
|
||||||
|
300: '#81C784',
|
||||||
|
400: '#66BB6A',
|
||||||
|
500: '#4CAF50',
|
||||||
|
600: '#43A047',
|
||||||
|
700: '#388E3C',
|
||||||
|
800: '#2E7D32',
|
||||||
|
900: '#1B5E20',
|
||||||
|
// A100: '#B9F6CA',
|
||||||
|
// A200: '#69F0AE',
|
||||||
|
// A400: '#00E676',
|
||||||
|
// A700: '#00C853'
|
||||||
|
},
|
||||||
|
"lightGreen": {
|
||||||
|
50: '#F1F8E9',
|
||||||
|
100: '#DCEDC8',
|
||||||
|
200: '#C5E1A5',
|
||||||
|
300: '#AED581',
|
||||||
|
400: '#9CCC65',
|
||||||
|
500: '#8BC34A',
|
||||||
|
600: '#7CB342',
|
||||||
|
700: '#689F38',
|
||||||
|
800: '#558B2F',
|
||||||
|
900: '#33691E',
|
||||||
|
// A100: '#CCFF90',
|
||||||
|
// A200: '#B2FF59',
|
||||||
|
// A400: '#76FF03',
|
||||||
|
// A700: '#64DD17'
|
||||||
|
},
|
||||||
|
"lime": {
|
||||||
|
50: '#F9FBE7',
|
||||||
|
100: '#F0F4C3',
|
||||||
|
200: '#E6EE9C',
|
||||||
|
300: '#DCE775',
|
||||||
|
400: '#D4E157',
|
||||||
|
500: '#CDDC39',
|
||||||
|
600: '#C0CA33',
|
||||||
|
700: '#AFB42B',
|
||||||
|
800: '#9E9D24',
|
||||||
|
900: '#827717',
|
||||||
|
// A100: '#F4FF81',
|
||||||
|
// A200: '#EEFF41',
|
||||||
|
// A400: '#C6FF00',
|
||||||
|
// A700: '#AEEA00'
|
||||||
|
},
|
||||||
|
"yellow": {
|
||||||
|
50: '#FFFDE7',
|
||||||
|
100: '#FFF9C4',
|
||||||
|
200: '#FFF59D',
|
||||||
|
300: '#FFF176',
|
||||||
|
400: '#FFEE58',
|
||||||
|
500: '#FFEB3B',
|
||||||
|
600: '#FDD835',
|
||||||
|
700: '#FBC02D',
|
||||||
|
800: '#F9A825',
|
||||||
|
900: '#F57F17',
|
||||||
|
// A100: '#FFFF8D',
|
||||||
|
// A200: '#FFFF00',
|
||||||
|
// A400: '#FFEA00',
|
||||||
|
// A700: '#FFD600'
|
||||||
|
},
|
||||||
|
"amber": {
|
||||||
|
50: '#FFF8E1',
|
||||||
|
100: '#FFECB3',
|
||||||
|
200: '#FFE082',
|
||||||
|
300: '#FFD54F',
|
||||||
|
400: '#FFCA28',
|
||||||
|
500: '#FFC107',
|
||||||
|
600: '#FFB300',
|
||||||
|
700: '#FFA000',
|
||||||
|
800: '#FF8F00',
|
||||||
|
900: '#FF6F00',
|
||||||
|
// A100: '#FFE57F',
|
||||||
|
// A200: '#FFD740',
|
||||||
|
// A400: '#FFC400',
|
||||||
|
// A700: '#FFAB00'
|
||||||
|
},
|
||||||
|
"orange": {
|
||||||
|
50: '#FFF3E0',
|
||||||
|
100: '#FFE0B2',
|
||||||
|
200: '#FFCC80',
|
||||||
|
300: '#FFB74D',
|
||||||
|
400: '#FFA726',
|
||||||
|
500: '#FF9800',
|
||||||
|
600: '#FB8C00',
|
||||||
|
700: '#F57C00',
|
||||||
|
800: '#EF6C00',
|
||||||
|
900: '#E65100',
|
||||||
|
// A100: '#FFD180',
|
||||||
|
// A200: '#FFAB40',
|
||||||
|
// A400: '#FF9100',
|
||||||
|
// A700: '#FF6D00'
|
||||||
|
},
|
||||||
|
"deepOrange": {
|
||||||
|
50: '#FBE9E7',
|
||||||
|
100: '#FFCCBC',
|
||||||
|
200: '#FFAB91',
|
||||||
|
300: '#FF8A65',
|
||||||
|
400: '#FF7043',
|
||||||
|
500: '#FF5722',
|
||||||
|
600: '#F4511E',
|
||||||
|
700: '#E64A19',
|
||||||
|
800: '#D84315',
|
||||||
|
900: '#BF360C',
|
||||||
|
// A100: '#FF9E80',
|
||||||
|
// A200: '#FF6E40',
|
||||||
|
// A400: '#FF3D00',
|
||||||
|
// A700: '#DD2C00'
|
||||||
|
},
|
||||||
|
"brown": {
|
||||||
|
50: '#EFEBE9',
|
||||||
|
100: '#D7CCC8',
|
||||||
|
200: '#BCAAA4',
|
||||||
|
300: '#A1887F',
|
||||||
|
400: '#8D6E63',
|
||||||
|
500: '#795548',
|
||||||
|
600: '#6D4C41',
|
||||||
|
700: '#5D4037',
|
||||||
|
800: '#4E342E',
|
||||||
|
900: '#3E2723'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function makeLabels() {
|
||||||
|
let result: string[] = [];
|
||||||
|
for (let [key] of Object.entries(materialColors)) {
|
||||||
|
result.push(key.toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorKeys: string[] = makeLabels();
|
||||||
|
|
||||||
|
const darkMode: boolean = isDarkMode();
|
||||||
|
|
||||||
|
export function getBackgroundColor(colorKey: string): string {
|
||||||
|
let list = materialColors[colorKey];
|
||||||
|
if (!list) {
|
||||||
|
list = materialColors["grey"];
|
||||||
|
}
|
||||||
|
if (darkMode) {
|
||||||
|
if (colorKey === "grey" || colorKey === "yellow")
|
||||||
|
{
|
||||||
|
return list[900];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return list[800];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (colorKey === "grey")
|
||||||
|
{
|
||||||
|
return list[200];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return list[100];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBorderColor(colorKey: string): string {
|
||||||
|
let list = materialColors[colorKey];
|
||||||
|
if (!list)
|
||||||
|
{
|
||||||
|
list = materialColors["grey"];
|
||||||
|
}
|
||||||
|
if (darkMode) {
|
||||||
|
if (colorKey === "grey" || colorKey === "yellow")
|
||||||
|
{
|
||||||
|
return list[700];
|
||||||
|
}
|
||||||
|
return list[500];
|
||||||
|
} else {
|
||||||
|
if (colorKey === "grey")
|
||||||
|
{
|
||||||
|
return list[300];
|
||||||
|
}
|
||||||
|
return list[200];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -70,6 +70,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
|||||||
this.stateUpdateCount = input.stateUpdateCount;
|
this.stateUpdateCount = input.stateUpdateCount;
|
||||||
this.lv2State = input.lv2State;
|
this.lv2State = input.lv2State;
|
||||||
this.lilvPresetUri = input.lilvPresetUri;
|
this.lilvPresetUri = input.lilvPresetUri;
|
||||||
|
this.pathProperties = input.pathProperties;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
deserialize(input: any): PedalboardItem {
|
deserialize(input: any): PedalboardItem {
|
||||||
@@ -200,8 +201,81 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
|||||||
stateUpdateCount: number = 0;
|
stateUpdateCount: number = 0;
|
||||||
lv2State: [boolean,any] = [false,{}];
|
lv2State: [boolean,any] = [false,{}];
|
||||||
lilvPresetUri: string = "";
|
lilvPresetUri: string = "";
|
||||||
|
pathProperties: {[Name: string]: string} = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class SnapshotValue {
|
||||||
|
deserialize(input: any): SnapshotValue {
|
||||||
|
this.instanceId = input.instanceId;
|
||||||
|
this.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||||
|
this.lv2State = input.lv2state;
|
||||||
|
this.pathProperties = input.pathProperties;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
static deserializeArray(input: any): SnapshotValue[] {
|
||||||
|
let result: SnapshotValue[] = [];
|
||||||
|
for (let i = 0; i < input.length; ++i) {
|
||||||
|
let inputItem: any = input[i];
|
||||||
|
let outputItem = new SnapshotValue().deserialize(inputItem);
|
||||||
|
result[i] = outputItem;
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static createFromPedalboardItem(item: PedalboardItem)
|
||||||
|
{
|
||||||
|
let result = new SnapshotValue();
|
||||||
|
result.instanceId = item.instanceId;
|
||||||
|
result.controlValues = ControlValue.deserializeArray(item.controlValues);
|
||||||
|
result.lv2State = item.lv2State; // we can do this, because lv2State is immutable.
|
||||||
|
result.pathProperties = {... item.pathProperties}; // clone the dictionary.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
instanceId: number = -1;
|
||||||
|
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||||
|
lv2State: [boolean,any] = [false,{}];
|
||||||
|
pathProperties: {[Name: string]: string} = {};
|
||||||
|
}
|
||||||
|
export class Snapshot {
|
||||||
|
deserialize(input: any): Snapshot {
|
||||||
|
this.values = SnapshotValue.deserializeArray(input.values);
|
||||||
|
this.name = input.name;
|
||||||
|
this.color = input.color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
static deserializeArray(input: any): (Snapshot| null)[] {
|
||||||
|
let result: (Snapshot|null)[] = [];
|
||||||
|
for (let i = 0; i < input.length; ++i) {
|
||||||
|
let inputItem: any = input[i];
|
||||||
|
let outputItem: (Snapshot|null) = null;
|
||||||
|
if (inputItem !== null)
|
||||||
|
{
|
||||||
|
outputItem = new Snapshot().deserialize(inputItem);
|
||||||
|
}
|
||||||
|
result[i] = outputItem;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static readonly MAX_SNAPSHOTS: number = 6;
|
||||||
|
|
||||||
|
static cloneSnapshots(snapshots: (Snapshot|null)[]): (Snapshot|null)[]
|
||||||
|
{
|
||||||
|
let result: (Snapshot|null)[] = [];
|
||||||
|
for (let i = 0; i < Snapshot.MAX_SNAPSHOTS; ++i)
|
||||||
|
{
|
||||||
|
if (i >= snapshots.length)
|
||||||
|
{
|
||||||
|
result.push(null);
|
||||||
|
} else {
|
||||||
|
result.push(snapshots[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
name: string = "";
|
||||||
|
color: string = "";
|
||||||
|
values: SnapshotValue[] = [];
|
||||||
|
};
|
||||||
|
|
||||||
export enum SplitType {
|
export enum SplitType {
|
||||||
Ab = 0,
|
Ab = 0,
|
||||||
@@ -277,6 +351,9 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
this.output_volume_db = input.output_volume_db;
|
this.output_volume_db = input.output_volume_db;
|
||||||
this.items = PedalboardItem.deserializeArray(input.items);
|
this.items = PedalboardItem.deserializeArray(input.items);
|
||||||
this.nextInstanceId = input.nextInstanceId ?? -1;
|
this.nextInstanceId = input.nextInstanceId ?? -1;
|
||||||
|
this.snapshots = input.snapshots ? Snapshot.deserializeArray(input.snapshots): [];
|
||||||
|
this.selectedSnapshot = input.selectedSnapshot;
|
||||||
|
this.pathProperties = input.pathProperties;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,6 +366,10 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
items: PedalboardItem[] = [];
|
items: PedalboardItem[] = [];
|
||||||
nextInstanceId: number = -1;
|
nextInstanceId: number = -1;
|
||||||
|
|
||||||
|
snapshots: (Snapshot | null)[] = [];
|
||||||
|
selectedSnapshot: number = -1;
|
||||||
|
pathProperties: {[Name: string]: string} = {};
|
||||||
|
|
||||||
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
|
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
|
||||||
let it = itemGenerator_(this.items);
|
let it = itemGenerator_(this.items);
|
||||||
while (true)
|
while (true)
|
||||||
@@ -299,6 +380,19 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makeSnapshot(): Snapshot {
|
||||||
|
let result = new Snapshot();
|
||||||
|
let it = this.itemsGenerator();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
let v = it.next();
|
||||||
|
if (v.done) break;
|
||||||
|
let pedalboardItem = v.value;
|
||||||
|
let snapshotValue = SnapshotValue.createFromPedalboardItem(pedalboardItem);
|
||||||
|
result.values.push(snapshotValue);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
hasItem(instanceId: number): boolean
|
hasItem(instanceId: number): boolean
|
||||||
{
|
{
|
||||||
let it = this.itemsGenerator();
|
let it = this.itemsGenerator();
|
||||||
@@ -570,6 +664,8 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
|||||||
item.pluginName = "";
|
item.pluginName = "";
|
||||||
item.isEnabled = true;
|
item.isEnabled = true;
|
||||||
item.controlValues = [];
|
item.controlValues = [];
|
||||||
|
item.vstState = "";
|
||||||
|
item.lv2State = [false,{}];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ interface PedalboardProps extends WithStyles<typeof pedalboardStyles> {
|
|||||||
onSelectionChanged?: OnSelectHandler;
|
onSelectionChanged?: OnSelectHandler;
|
||||||
onDoubleClick?: OnSelectHandler;
|
onDoubleClick?: OnSelectHandler;
|
||||||
hasTinyToolBar: boolean;
|
hasTinyToolBar: boolean;
|
||||||
|
enableStructureEditing: boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
interface LayoutSize {
|
interface LayoutSize {
|
||||||
@@ -392,6 +393,10 @@ const PedalboardView =
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDragEnd(instanceId: number, clientX: number, clientY: number) {
|
onDragEnd(instanceId: number, clientX: number, clientY: number) {
|
||||||
|
if (!this.props.enableStructureEditing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.currentLayout) return;
|
if (!this.currentLayout) return;
|
||||||
|
|
||||||
if (!this.frameRef.current) return;
|
if (!this.frameRef.current) return;
|
||||||
@@ -670,18 +675,27 @@ const PedalboardView =
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (this.props.onDoubleClick && instanceId) {
|
if (this.props.onDoubleClick && instanceId && this.props.enableStructureEditing) {
|
||||||
this.props.onDoubleClick(instanceId);
|
this.props.onDoubleClick(instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onItemLongClick(event: SyntheticEvent, instanceId?: number): void {
|
onItemLongClick(event: SyntheticEvent, instanceId?: number): void {
|
||||||
|
if (!instanceId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (!this.props.enableStructureEditing)
|
||||||
|
{
|
||||||
|
this.setSelection(instanceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!Utility.needsZoomedControls()) {
|
if (!Utility.needsZoomedControls()) {
|
||||||
if (this.props.onDoubleClick && instanceId) {
|
if (this.props.onDoubleClick && this.props.enableStructureEditing && instanceId) {
|
||||||
this.props.onDoubleClick(instanceId);
|
this.props.onDoubleClick(instanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -927,7 +941,7 @@ const PedalboardView =
|
|||||||
onContextMenu={(e: SyntheticEvent) => { this.onItemLongClick(e, instanceId); }}
|
onContextMenu={(e: SyntheticEvent) => { this.onItemLongClick(e, instanceId); }}
|
||||||
>
|
>
|
||||||
<SelectHoverBackground selected={instanceId === this.props.selectedId} showHover={true} borderRadius={6} >
|
<SelectHoverBackground selected={instanceId === this.props.selectedId} showHover={true} borderRadius={6} >
|
||||||
<Draggable draggable={draggable} getScrollContainer={() => this.getScrollContainer()}
|
<Draggable draggable={draggable && (this.props.enableStructureEditing)} getScrollContainer={() => this.getScrollContainer()}
|
||||||
onDragEnd={(x, y) => { this.onDragEnd(instanceId, x, y) }}
|
onDragEnd={(x, y) => { this.onDragEnd(instanceId, x, y) }}
|
||||||
>
|
>
|
||||||
<PluginIcon pluginType={iconType} size={24} pluginMissing={pluginNotFound} opacity={enabled? 0.99:0.6} />
|
<PluginIcon pluginType={iconType} size={24} pluginMissing={pluginNotFound} opacity={enabled? 0.99:0.6} />
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
// Copyright (c) 2024 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 { Theme } from '@mui/material/styles';
|
||||||
|
import { WithStyles } from '@mui/styles';
|
||||||
|
import createStyles from '@mui/styles/createStyles';
|
||||||
|
import withStyles from '@mui/styles/withStyles';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import AppBar from '@mui/material/AppBar';
|
||||||
|
import SnapshotPanel from './SnapshotPanel';
|
||||||
|
import ArrowLeftOutlined from '@mui/icons-material/ArrowLeft';
|
||||||
|
import ArrowRightOutlined from '@mui/icons-material/ArrowRight';
|
||||||
|
import Select from '@mui/material/Select';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import { isDarkMode } from './DarkMode';
|
||||||
|
import { BankIndex } from './Banks';
|
||||||
|
import SnapshotEditor from './SnapshotEditor';
|
||||||
|
import { Snapshot } from './Pedalboard';
|
||||||
|
|
||||||
|
|
||||||
|
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => createStyles({
|
||||||
|
frame: {
|
||||||
|
position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||||
|
justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden"
|
||||||
|
},
|
||||||
|
select: { // fu fu fu.Overrides for white selector on dark background.
|
||||||
|
'&:before': {
|
||||||
|
borderColor: selectColor,
|
||||||
|
},
|
||||||
|
'&:after': {
|
||||||
|
borderColor: selectColor,
|
||||||
|
},
|
||||||
|
'&:hover:not(.Mui-disabled):before': {
|
||||||
|
borderColor: selectColor,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select_icon: {
|
||||||
|
fill: selectColor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
interface PerformanceViewProps extends WithStyles<typeof styles> {
|
||||||
|
onClose: () => void,
|
||||||
|
theme: Theme
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PerformanceViewState {
|
||||||
|
wrapSelects: boolean
|
||||||
|
presets: PresetIndex;
|
||||||
|
banks: BankIndex;
|
||||||
|
showSnapshotEditor: boolean;
|
||||||
|
snapshotEditorIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const PerformanceView =
|
||||||
|
withStyles(styles, { withTheme: true })(
|
||||||
|
class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> {
|
||||||
|
model: PiPedalModel;
|
||||||
|
|
||||||
|
constructor(props: PerformanceViewProps) {
|
||||||
|
super(props);
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
// let pedalboard = this.model.pedalboard.get();
|
||||||
|
// let selectedPedal = pedalboard.getFirstSelectableItem();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
presets: this.model.presets.get(),
|
||||||
|
banks: this.model.banks.get(),
|
||||||
|
wrapSelects: false,
|
||||||
|
showSnapshotEditor: false,
|
||||||
|
snapshotEditorIndex: 0
|
||||||
|
};
|
||||||
|
this.onPresetsChanged = this.onPresetsChanged.bind(this);
|
||||||
|
this.onBanksChanged = this.onBanksChanged.bind(this);
|
||||||
|
}
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
this.setState({ wrapSelects: width < 700 });
|
||||||
|
}
|
||||||
|
|
||||||
|
onPresetsChanged(newValue: PresetIndex) {
|
||||||
|
this.setState({ presets: this.model.presets.get() })
|
||||||
|
}
|
||||||
|
onBanksChanged(newValue: BankIndex) {
|
||||||
|
this.setState({ banks: this.model.banks.get() })
|
||||||
|
}
|
||||||
|
componentDidMount(): void {
|
||||||
|
super.componentDidMount();
|
||||||
|
this.model.presets.addOnChangedHandler(this.onPresetsChanged);
|
||||||
|
this.model.banks.addOnChangedHandler(this.onBanksChanged);
|
||||||
|
this.setState({
|
||||||
|
presets: this.model.presets.get(),
|
||||||
|
banks: this.model.banks.get()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.model.presets.removeOnChangedHandler(this.onPresetsChanged);
|
||||||
|
this.model.banks.removeOnChangedHandler(this.onBanksChanged);
|
||||||
|
super.componentWillUnmount();
|
||||||
|
}
|
||||||
|
handlePresetSelectClose(event: any): void {
|
||||||
|
let value = event.currentTarget.getAttribute("data-value");
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
this.model.loadPreset(parseInt(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBankSelectClose(event: any): void {
|
||||||
|
let value = event.currentTarget.getAttribute("data-value");
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
this.model.openBank(parseInt(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleOnEdit(index: number): boolean {
|
||||||
|
// load it so we can edit it.
|
||||||
|
this.model.selectSnapshot(index);
|
||||||
|
this.setState({
|
||||||
|
showSnapshotEditor: true,
|
||||||
|
snapshotEditorIndex: index
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleSnapshotEditOk(index: number, name: string, color: string,newSnapshots: (Snapshot|null)[])
|
||||||
|
{
|
||||||
|
// results have been sent to the server. we would perform a short-cut commit to our local state, to avoid laggy display updates.
|
||||||
|
// but we don't actually have that in our state.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePreviousBank()
|
||||||
|
{
|
||||||
|
this.model.previousBank();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNextBank()
|
||||||
|
{
|
||||||
|
this.model.nextBank();
|
||||||
|
}
|
||||||
|
handlePreviousPreset()
|
||||||
|
{
|
||||||
|
this.model.previousPreset();
|
||||||
|
}
|
||||||
|
handleNextPreset()
|
||||||
|
{
|
||||||
|
this.model.nextPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let classes = this.props.classes;
|
||||||
|
let wrapSelects = this.state.wrapSelects;
|
||||||
|
let presets = this.state.presets;
|
||||||
|
let banks = this.state.banks;
|
||||||
|
return (
|
||||||
|
<div className={this.props.classes.frame} style={{ overflow: "clip", height: "100%" }} >
|
||||||
|
<div className={this.props.classes.frame} style={{ overflow: "clip", height: "100%" }} >
|
||||||
|
<AppBar id="select-plugin-dialog-title"
|
||||||
|
style={{
|
||||||
|
position: "static", flex: "0 0 auto", height: wrapSelects ? 104 : 54,
|
||||||
|
display: this.state.showSnapshotEditor ? "none" : undefined
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
display: "flex", flexFlow: "row nowrap", alignContent: "center",
|
||||||
|
paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
|
||||||
|
alignItems: "start"
|
||||||
|
}}>
|
||||||
|
<IconButton aria-label="menu" color="inherit"
|
||||||
|
onClick={() => { this.props.onClose(); }} style={{ flex: "0 0 auto" }} >
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
flex: "1 1 1px", display: "flex",
|
||||||
|
flexFlow: wrapSelects ? "column nowrap" : "row nowrap",
|
||||||
|
alignItems: wrapSelects ? "stretch" : undefined,
|
||||||
|
justifyContent: wrapSelects ? undefined : "space-evenly",
|
||||||
|
marginLeft: 8,
|
||||||
|
gap: 8
|
||||||
|
|
||||||
|
}}>
|
||||||
|
{/********* BANKS *******************/}
|
||||||
|
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="previous-bank"
|
||||||
|
onClick={() => { this.handlePreviousBank(); }}
|
||||||
|
color="inherit"
|
||||||
|
style={{
|
||||||
|
borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeftOutlined style={{ opacity: 0.75 }} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Select variant="standard"
|
||||||
|
className={classes.select}
|
||||||
|
style={{ flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF" }} disabled={false}
|
||||||
|
displayEmpty
|
||||||
|
onClose={(e) => this.handleBankSelectClose(e)}
|
||||||
|
value={banks.selectedBank === 0 ? undefined : banks.selectedBank}
|
||||||
|
inputProps={{
|
||||||
|
classes: { icon: classes.select_icon },
|
||||||
|
'aria-label': "Select preset"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
banks.entries.map((entry) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={entry.instanceId} value={entry.instanceId} >
|
||||||
|
{entry.name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
aria-label="next-bank"
|
||||||
|
onClick={() => { this.handleNextBank(); }}
|
||||||
|
color="inherit"
|
||||||
|
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||||
|
>
|
||||||
|
<ArrowRightOutlined style={{ opacity: 0.75 }} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/********* PRESETS *******************/}
|
||||||
|
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="previous-presest"
|
||||||
|
onClick={() => { this.handlePreviousPreset(); }}
|
||||||
|
color="inherit"
|
||||||
|
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
|
||||||
|
>
|
||||||
|
<ArrowLeftOutlined style={{ opacity: 0.75 }} />
|
||||||
|
</IconButton>
|
||||||
|
<Select variant="standard"
|
||||||
|
className={classes.select}
|
||||||
|
style={{
|
||||||
|
flex: "1 1 1px", width: "100%", color: "#FFFFFF"
|
||||||
|
}} disabled={false}
|
||||||
|
displayEmpty
|
||||||
|
onClose={(e) => this.handlePresetSelectClose(e)}
|
||||||
|
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
|
||||||
|
|
||||||
|
inputProps={{
|
||||||
|
classes: { icon: classes.select_icon },
|
||||||
|
'aria-label': "Select preset"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
presets.presets.map((preset) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={preset.instanceId} value={preset.instanceId} >
|
||||||
|
{preset.name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
aria-label="next-preset"
|
||||||
|
onClick={() => { this.handleNextPreset(); }}
|
||||||
|
color="inherit"
|
||||||
|
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||||
|
>
|
||||||
|
<ArrowRightOutlined style={{ opacity: 0.75 }} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppBar >
|
||||||
|
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16 }}>
|
||||||
|
<SnapshotPanel onEdit={(index) => { return this.handleOnEdit(index); }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.state.showSnapshotEditor && (
|
||||||
|
<SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex}
|
||||||
|
onClose={() => {
|
||||||
|
this.setState({ showSnapshotEditor: false });
|
||||||
|
}}
|
||||||
|
onOk={(index,name,color,newSnapshots)=>{
|
||||||
|
this.setState({ showSnapshotEditor: false});
|
||||||
|
this.handleSnapshotEditOk(index,name,color,newSnapshots);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ import { UiPlugin, UiControl, PluginType, UiFileProperty } from './Lv2Plugin';
|
|||||||
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
|
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
|
||||||
import { UpdateStatus, UpdatePolicyT } from './Updater';
|
import { UpdateStatus, UpdatePolicyT } from './Updater';
|
||||||
import { ObservableProperty } from './ObservableProperty';
|
import { ObservableProperty } from './ObservableProperty';
|
||||||
import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'
|
import { Pedalboard, PedalboardItem, ControlValue, Snapshot } from './Pedalboard'
|
||||||
import PluginClass from './PluginClass';
|
import PluginClass from './PluginClass';
|
||||||
import PiPedalSocket, { PiPedalMessageHeader } from './PiPedalSocket';
|
import PiPedalSocket, { PiPedalMessageHeader } from './PiPedalSocket';
|
||||||
import { nullCast } from './Utility'
|
import { nullCast } from './Utility'
|
||||||
@@ -57,7 +57,7 @@ export enum State {
|
|||||||
|
|
||||||
class UpdatedError extends Error {
|
class UpdatedError extends Error {
|
||||||
};
|
};
|
||||||
export function wantsLoadingScreen(state: State) {
|
export function wantsReloadingScreen(state: State) {
|
||||||
return state >= State.Reconnecting;
|
return state >= State.Reconnecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
clientId: number = -1;
|
clientId: number = -1;
|
||||||
|
|
||||||
serverVersion?: PiPedalVersion;
|
serverVersion?: PiPedalVersion;
|
||||||
countryCodes: {[Name: string]: string} = {};
|
countryCodes: { [Name: string]: string } = {};
|
||||||
|
|
||||||
socketServerUrl: string = "";
|
socketServerUrl: string = "";
|
||||||
varServerUrl: string = "";
|
varServerUrl: string = "";
|
||||||
@@ -390,6 +390,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
showStatusMonitor: ObservableProperty<boolean> = new ObservableProperty<boolean>(true);
|
showStatusMonitor: ObservableProperty<boolean> = new ObservableProperty<boolean>(true);
|
||||||
|
|
||||||
pedalboard: ObservableProperty<Pedalboard> = new ObservableProperty<Pedalboard>(new Pedalboard());
|
pedalboard: ObservableProperty<Pedalboard> = new ObservableProperty<Pedalboard>(new Pedalboard());
|
||||||
|
presetChanged: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
|
||||||
|
selectedSnapshot: ObservableProperty<number> = new ObservableProperty<number>(-1);
|
||||||
plugin_classes: ObservableProperty<PluginClass> = new ObservableProperty<PluginClass>(new PluginClass());
|
plugin_classes: ObservableProperty<PluginClass> = new ObservableProperty<PluginClass>(new PluginClass());
|
||||||
jackConfiguration: ObservableProperty<JackConfiguration> = new ObservableProperty<JackConfiguration>(new JackConfiguration());
|
jackConfiguration: ObservableProperty<JackConfiguration> = new ObservableProperty<JackConfiguration>(new JackConfiguration());
|
||||||
jackSettings: ObservableProperty<JackChannelSelection> = new ObservableProperty<JackChannelSelection>(new JackChannelSelection());
|
jackSettings: ObservableProperty<JackChannelSelection> = new ObservableProperty<JackChannelSelection>(new JackChannelSelection());
|
||||||
@@ -498,6 +500,10 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private setModelPedalboard(pedalboard: Pedalboard) {
|
||||||
|
this.pedalboard.set(pedalboard);
|
||||||
|
this.selectedSnapshot.set(pedalboard.selectedSnapshot);
|
||||||
|
}
|
||||||
onSocketMessage(header: PiPedalMessageHeader, body?: any) {
|
onSocketMessage(header: PiPedalMessageHeader, body?: any) {
|
||||||
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||||
|
|
||||||
@@ -558,6 +564,19 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let channelSelectionBody = body as ChannelSelectionChangedBody;
|
let channelSelectionBody = body as ChannelSelectionChangedBody;
|
||||||
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection);
|
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection);
|
||||||
this.jackSettings.set(channelSelection);
|
this.jackSettings.set(channelSelection);
|
||||||
|
} else if (message === "onSelectedSnapshotChanged") {
|
||||||
|
let selectedSnapshot = body as number;
|
||||||
|
this.pedalboard.get().selectedSnapshot = selectedSnapshot;
|
||||||
|
this.selectedSnapshot.set(selectedSnapshot);
|
||||||
|
} else if (message === "onPresetChanged") {
|
||||||
|
let changed = body as boolean;
|
||||||
|
|
||||||
|
if (this.presets.get().presetChanged !== changed) {
|
||||||
|
let newPresets = this.presets.get().clone(); // deep clone.
|
||||||
|
newPresets.presetChanged = changed;
|
||||||
|
this.presets.set(newPresets);
|
||||||
|
}
|
||||||
|
this.presetChanged.set(changed);
|
||||||
} else if (message === "onPresetsChanged") {
|
} else if (message === "onPresetsChanged") {
|
||||||
let presetsChangedBody = body as PresetsChangedBody;
|
let presetsChangedBody = body as PresetsChangedBody;
|
||||||
let presets = new PresetIndex().deserialize(presetsChangedBody.presets);
|
let presets = new PresetIndex().deserialize(presetsChangedBody.presets);
|
||||||
@@ -580,7 +599,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
);
|
);
|
||||||
} else if (message === "onPedalboardChanged") {
|
} else if (message === "onPedalboardChanged") {
|
||||||
let pedalChangedBody = body as PedalboardChangedBody;
|
let pedalChangedBody = body as PedalboardChangedBody;
|
||||||
this.pedalboard.set(new Pedalboard().deserialize(pedalChangedBody.pedalboard));
|
this.setModelPedalboard(new Pedalboard().deserialize(pedalChangedBody.pedalboard));
|
||||||
|
|
||||||
} else if (message === "onMidiValueChanged") {
|
} else if (message === "onMidiValueChanged") {
|
||||||
let controlChangedBody = body as ControlChangedBody;
|
let controlChangedBody = body as ControlChangedBody;
|
||||||
@@ -599,6 +618,14 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let isNote = body.isNote as boolean;
|
let isNote = body.isNote as boolean;
|
||||||
let noteOrControl = body.noteOrControl as number;
|
let noteOrControl = body.noteOrControl as number;
|
||||||
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
||||||
|
} else if (message === "onNotifyPathPatchPropertyChanged") {
|
||||||
|
let instanceId = body.instanceId as number;
|
||||||
|
let propertyUri = body.propertyUri as string;
|
||||||
|
let atomJson = body.atomJson as any;
|
||||||
|
this.handleNotifyPathPatchPropertyChanged(instanceId, propertyUri, atomJson);
|
||||||
|
if (header.replyTo) {
|
||||||
|
this.webSocket?.reply(header.replyTo, "onNotifyPatchProperty", true);
|
||||||
|
}
|
||||||
} else if (message === "onNotifyPatchProperty") {
|
} else if (message === "onNotifyPatchProperty") {
|
||||||
let clientHandle = body.clientHandle as number;
|
let clientHandle = body.clientHandle as number;
|
||||||
let instanceId = body.instanceId as number;
|
let instanceId = body.instanceId as number;
|
||||||
@@ -649,8 +676,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
} else if (message === "onUpdateStatusChanged") {
|
} else if (message === "onUpdateStatusChanged") {
|
||||||
let updateStatus = new UpdateStatus().deserialize(body);
|
let updateStatus = new UpdateStatus().deserialize(body);
|
||||||
this.onUpdateStatusChanged(updateStatus);
|
this.onUpdateStatusChanged(updateStatus);
|
||||||
} else if (message === "onNetworkChanging")
|
} else if (message === "onNetworkChanging") {
|
||||||
{
|
|
||||||
this.onNetworkChanging(body as boolean);
|
this.onNetworkChanging(body as boolean);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -687,8 +713,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
private lastCanUpdateNow: boolean = false;
|
private lastCanUpdateNow: boolean = false;
|
||||||
private updatePromptForUpdate() {
|
private updatePromptForUpdate() {
|
||||||
this.clearPromptForUpdateTimer();
|
this.clearPromptForUpdateTimer();
|
||||||
if (!this.enableAutoUpdate)
|
if (!this.enableAutoUpdate) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +727,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let nDate = Date.now();
|
let nDate = Date.now();
|
||||||
|
|
||||||
let now: Date = new Date(nDate);
|
let now: Date = new Date(nDate);
|
||||||
let maxDate: Date = new Date(nDate + 86400000*2); // sanity check for systems with unstable system clock
|
let maxDate: Date = new Date(nDate + 86400000 * 2); // sanity check for systems with unstable system clock
|
||||||
|
|
||||||
timeEnabled = (updateLaterTime < now || updateLaterTime >= maxDate)
|
timeEnabled = (updateLaterTime < now || updateLaterTime >= maxDate)
|
||||||
}
|
}
|
||||||
@@ -710,13 +735,11 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let statusEnabled = updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable;
|
let statusEnabled = updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable;
|
||||||
|
|
||||||
let canUpdateNow: boolean = (stateEnabled && timeEnabled && statusEnabled);
|
let canUpdateNow: boolean = (stateEnabled && timeEnabled && statusEnabled);
|
||||||
if (updateStatus.updatePolicy === UpdatePolicyT.Disable)
|
if (updateStatus.updatePolicy === UpdatePolicyT.Disable) {
|
||||||
{
|
|
||||||
canUpdateNow = false;
|
canUpdateNow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canUpdateNow && canUpdateNow !== this.lastCanUpdateNow)
|
if (canUpdateNow && canUpdateNow !== this.lastCanUpdateNow) {
|
||||||
{
|
|
||||||
this.showUpdateDialogValue = true; // make the dialog sticky so it can show OK button
|
this.showUpdateDialogValue = true; // make the dialog sticky so it can show OK button
|
||||||
}
|
}
|
||||||
this.lastCanUpdateNow = canUpdateNow;
|
this.lastCanUpdateNow = canUpdateNow;
|
||||||
@@ -795,26 +818,23 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
androidReconnectTimeout?: NodeJS.Timeout = undefined;
|
androidReconnectTimeout?: NodeJS.Timeout = undefined;
|
||||||
|
|
||||||
cancelAndroidReconnectTimer()
|
cancelAndroidReconnectTimer() {
|
||||||
{
|
|
||||||
if (this.androidReconnectTimeout) {
|
if (this.androidReconnectTimeout) {
|
||||||
clearTimeout(this.androidReconnectTimeout);
|
clearTimeout(this.androidReconnectTimeout);
|
||||||
this.androidReconnectTimeout = undefined;
|
this.androidReconnectTimeout = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startAndroidReconnectTimer()
|
startAndroidReconnectTimer() {
|
||||||
{
|
|
||||||
this.cancelAndroidReconnectTimer();
|
this.cancelAndroidReconnectTimer();
|
||||||
this.androidReconnectTimeout = setTimeout(()=>{
|
this.androidReconnectTimeout = setTimeout(() => {
|
||||||
this.androidReconnectTimeout = undefined;
|
this.androidReconnectTimeout = undefined;
|
||||||
this.androidHost?.setDisconnected(true);
|
this.androidHost?.setDisconnected(true);
|
||||||
},20*1000);
|
}, 20 * 1000);
|
||||||
}
|
}
|
||||||
onSocketConnectionLost() {
|
onSocketConnectionLost() {
|
||||||
// remove all the events and subscriptions we have.
|
// remove all the events and subscriptions we have.
|
||||||
if (this.isClosed)
|
if (this.isClosed) {
|
||||||
{
|
|
||||||
return; // page unloading. do NOT change the UI.
|
return; // page unloading. do NOT change the UI.
|
||||||
}
|
}
|
||||||
this.vuSubscriptions = [];
|
this.vuSubscriptions = [];
|
||||||
@@ -822,8 +842,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
if (this.isAndroidHosted()) {
|
if (this.isAndroidHosted()) {
|
||||||
// if unexpected, go back to the device browser immediately.
|
// if unexpected, go back to the device browser immediately.
|
||||||
if (this.reconnectReason === ReconnectReason.Disconnected)
|
if (this.reconnectReason === ReconnectReason.Disconnected) {
|
||||||
{
|
|
||||||
this.androidHost?.setDisconnected(true);
|
this.androidHost?.setDisconnected(true);
|
||||||
} else {
|
} else {
|
||||||
this.startAndroidReconnectTimer();
|
this.startAndroidReconnectTimer();
|
||||||
@@ -856,7 +875,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.pedalboard.set(new Pedalboard().deserialize(data));
|
this.setModelPedalboard(new Pedalboard().deserialize(data));
|
||||||
|
|
||||||
return this.getWebSocket().request<boolean>("getShowStatusMonitor");
|
return this.getWebSocket().request<boolean>("getShowStatusMonitor");
|
||||||
})
|
})
|
||||||
@@ -923,8 +942,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
this.setState(State.Ready);
|
this.setState(State.Ready);
|
||||||
})
|
})
|
||||||
.catch((what) => {
|
.catch((what) => {
|
||||||
if (what instanceof UpdatedError)
|
if (what instanceof UpdatedError) {
|
||||||
{
|
|
||||||
// do nothing. a page reload is imminent and unavoidable as soon as we return to the dispatcher.
|
// do nothing. a page reload is imminent and unavoidable as soon as we return to the dispatcher.
|
||||||
} else {
|
} else {
|
||||||
this.onError(what.toString());
|
this.onError(what.toString());
|
||||||
@@ -1002,7 +1020,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((countryCodes) => {
|
.then((countryCodes) => {
|
||||||
this.countryCodes = countryCodes as {[Name: string]: string};
|
this.countryCodes = countryCodes as { [Name: string]: string };
|
||||||
|
|
||||||
return this.getWebSocket().request<number>("hello");
|
return this.getWebSocket().request<number>("hello");
|
||||||
})
|
})
|
||||||
@@ -1031,7 +1049,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.pedalboard.set(new Pedalboard().deserialize(data));
|
this.setModelPedalboard(new Pedalboard().deserialize(data));
|
||||||
|
|
||||||
return this.getWebSocket().request<any>("pluginClasses");
|
return this.getWebSocket().request<any>("pluginClasses");
|
||||||
})
|
})
|
||||||
@@ -1120,17 +1138,16 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onError(message: string| Error): void {
|
onError(message: string | Error): void {
|
||||||
let m = message;
|
let m = message;
|
||||||
if (message instanceof Error)
|
if (message instanceof Error) {
|
||||||
{
|
|
||||||
let e = message as Error;
|
let e = message as Error;
|
||||||
if (e.message) {
|
if (e.message) {
|
||||||
m = e.message as string;
|
m = e.message as string;
|
||||||
} else {
|
} else {
|
||||||
m = e.toString();
|
m = e.toString();
|
||||||
}
|
}
|
||||||
} else{
|
} else {
|
||||||
m = message.toString();
|
m = message.toString();
|
||||||
}
|
}
|
||||||
this.errorMessage.set(m);
|
this.errorMessage.set(m);
|
||||||
@@ -1269,7 +1286,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
changed = item.setControlValue(controlValue.key, controlValue.value) || changed;
|
changed = item.setControlValue(controlValue.key, controlValue.value) || changed;
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1354,6 +1372,33 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextBank() {
|
||||||
|
this.webSocket?.send("nextBank");
|
||||||
|
}
|
||||||
|
previousBank() {
|
||||||
|
this.webSocket?.send("previousBank");
|
||||||
|
}
|
||||||
|
nextPreset() {
|
||||||
|
this.webSocket?.send("nextPreset");
|
||||||
|
}
|
||||||
|
previousPreset() {
|
||||||
|
this.webSocket?.send("previousPreset");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
selectSnapshot(index: number) {
|
||||||
|
this.webSocket?.send("setSnapshot", index);
|
||||||
|
}
|
||||||
|
setSnapshots(snapshots: (Snapshot | null)[], selectedSnapshot: number) {
|
||||||
|
let pedalboard = this.pedalboard.get().clone();
|
||||||
|
pedalboard.snapshots = snapshots;
|
||||||
|
if (selectedSnapshot !== -1) {
|
||||||
|
pedalboard.selectedSnapshot = selectedSnapshot;
|
||||||
|
}
|
||||||
|
this.setModelPedalboard(pedalboard);
|
||||||
|
|
||||||
|
this.webSocket?.send("setSnapshots", { snapshots: snapshots, selectedSnapshot: selectedSnapshot });
|
||||||
|
}
|
||||||
private _setPedalboardPropertyValue(instanceId: number, propertyUri: string, value: any): void {
|
private _setPedalboardPropertyValue(instanceId: number, propertyUri: string, value: any): void {
|
||||||
let body: PatchPropertyChangedBody = {
|
let body: PatchPropertyChangedBody = {
|
||||||
clientId: this.clientId,
|
clientId: this.clientId,
|
||||||
@@ -1383,7 +1428,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
if (pedalboard.input_volume_db !== volume_db) {
|
if (pedalboard.input_volume_db !== volume_db) {
|
||||||
let newPedalboard = pedalboard.clone();
|
let newPedalboard = pedalboard.clone();
|
||||||
newPedalboard.input_volume_db = volume_db;
|
newPedalboard.input_volume_db = volume_db;
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
@@ -1411,7 +1457,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
if (pedalboard.output_volume_db !== volume_db) {
|
if (pedalboard.output_volume_db !== volume_db) {
|
||||||
let newPedalboard = pedalboard.clone();
|
let newPedalboard = pedalboard.clone();
|
||||||
newPedalboard.output_volume_db = volume_db;
|
newPedalboard.output_volume_db = volume_db;
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
@@ -1447,7 +1494,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
if (notifyServer) {
|
if (notifyServer) {
|
||||||
this._setServerControl("setControl", instanceId, key, value);
|
this._setServerControl("setControl", instanceId, key, value);
|
||||||
}
|
}
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
||||||
let item = this._controlValueChangeItems[i];
|
let item = this._controlValueChangeItems[i];
|
||||||
if (instanceId === item.instanceId) {
|
if (instanceId === item.instanceId) {
|
||||||
@@ -1467,7 +1515,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
item.vstState = state;
|
item.vstState = state;
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.pedalboard.set(newPedalboard);
|
this.setModelPedalboard(newPedalboard);
|
||||||
if (notifyServer) {
|
if (notifyServer) {
|
||||||
this._setServerControl("setControl", instanceId, key, value);
|
this._setServerControl("setControl", instanceId, key, value);
|
||||||
}
|
}
|
||||||
@@ -1499,7 +1547,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let changed = value !== item.isEnabled;
|
let changed = value !== item.isEnabled;
|
||||||
if (changed) {
|
if (changed) {
|
||||||
item.isEnabled = value;
|
item.isEnabled = value;
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
if (notifyServer) {
|
if (notifyServer) {
|
||||||
let body: PedalboardItemEnableBody = {
|
let body: PedalboardItemEnableBody = {
|
||||||
clientId: this.clientId,
|
clientId: this.clientId,
|
||||||
@@ -1548,8 +1597,16 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
item.pluginName = plugin.name;
|
item.pluginName = plugin.name;
|
||||||
item.controlValues = this.getDefaultValues(item.uri);
|
item.controlValues = this.getDefaultValues(item.uri);
|
||||||
item.isEnabled = true;
|
item.isEnabled = true;
|
||||||
// lv2State: not valid. vstState : not valid.
|
item.lv2State = [false, {}];
|
||||||
this.pedalboard.set(newPedalboard);
|
item.vstState = "";
|
||||||
|
item.pathProperties = {};
|
||||||
|
for (let fileProperty of plugin.fileProperties) {
|
||||||
|
// stringized json for an atom. see AtomConverter.hpp.
|
||||||
|
// null -> we've never seen a value.
|
||||||
|
item.pathProperties[fileProperty.patchProperty] = "null";
|
||||||
|
}
|
||||||
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard()
|
this.updateServerPedalboard()
|
||||||
return item.instanceId;
|
return item.instanceId;
|
||||||
}
|
}
|
||||||
@@ -1589,7 +1646,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
let result = newPedalboard.deleteItem(instanceId);
|
let result = newPedalboard.deleteItem(instanceId);
|
||||||
if (result !== null) {
|
if (result !== null) {
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -1611,7 +1669,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
newPedalboard.deleteItem(fromInstanceId);
|
newPedalboard.deleteItem(fromInstanceId);
|
||||||
|
|
||||||
newPedalboard.addBefore(fromItem, toInstanceId);
|
newPedalboard.addBefore(fromItem, toInstanceId);
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1631,7 +1690,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
newPedalboard.addAfter(fromItem, toInstanceId);
|
newPedalboard.addAfter(fromItem, toInstanceId);
|
||||||
|
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,7 +1712,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
newPedalboard.addToStart(fromItem);
|
newPedalboard.addToStart(fromItem);
|
||||||
|
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
}
|
}
|
||||||
movePedalboardItemToEnd(instanceId: number): void {
|
movePedalboardItemToEnd(instanceId: number): void {
|
||||||
@@ -1671,7 +1732,9 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
newPedalboard.addToEnd(fromItem);
|
newPedalboard.addToEnd(fromItem);
|
||||||
|
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
|
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
|
|
||||||
|
|
||||||
@@ -1697,7 +1760,9 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let emptyItem = newPedalboard.createEmptyItem();
|
let emptyItem = newPedalboard.createEmptyItem();
|
||||||
newPedalboard.replaceItem(fromInstanceId, emptyItem);
|
newPedalboard.replaceItem(fromInstanceId, emptyItem);
|
||||||
newPedalboard.replaceItem(toInstanceId, fromItem);
|
newPedalboard.replaceItem(toInstanceId, fromItem);
|
||||||
this.pedalboard.set(newPedalboard);
|
|
||||||
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1720,7 +1785,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
let newItem = newPedalboard.createEmptyItem();
|
let newItem = newPedalboard.createEmptyItem();
|
||||||
newPedalboard.addItem(newItem, instanceId, append);
|
newPedalboard.addItem(newItem, instanceId, append);
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
return newItem.instanceId;
|
return newItem.instanceId;
|
||||||
}
|
}
|
||||||
@@ -1745,7 +1811,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
let newItem = newPedalboard.createEmptySplit();
|
let newItem = newPedalboard.createEmptySplit();
|
||||||
newPedalboard.addItem(newItem, instanceId, append);
|
newPedalboard.addItem(newItem, instanceId, append);
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
return newItem.instanceId;
|
return newItem.instanceId;
|
||||||
|
|
||||||
@@ -1762,7 +1829,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
newPedalboard.setItemEmpty(item);
|
newPedalboard.setItemEmpty(item);
|
||||||
|
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
this.updateServerPedalboard();
|
this.updateServerPedalboard();
|
||||||
return item.instanceId;
|
return item.instanceId;
|
||||||
|
|
||||||
@@ -1956,17 +2024,16 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return this.copyPreset(instanceId, -1);
|
return this.copyPreset(instanceId, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
showAlert(message: string| Error): void {
|
showAlert(message: string | Error): void {
|
||||||
let m = message;
|
let m = message;
|
||||||
if (message instanceof Error)
|
if (message instanceof Error) {
|
||||||
{
|
|
||||||
let e = message as Error;
|
let e = message as Error;
|
||||||
if (e.message) {
|
if (e.message) {
|
||||||
m = e.message as string;
|
m = e.message as string;
|
||||||
} else {
|
} else {
|
||||||
m = e.toString();
|
m = e.toString();
|
||||||
}
|
}
|
||||||
} else{
|
} else {
|
||||||
m = message.toString();
|
m = message.toString();
|
||||||
}
|
}
|
||||||
this.alertMessage.set(m);
|
this.alertMessage.set(m);
|
||||||
@@ -2265,7 +2332,8 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let newPedalboard = pedalboard.clone();
|
let newPedalboard = pedalboard.clone();
|
||||||
this.updateVst3State(newPedalboard);
|
this.updateVst3State(newPedalboard);
|
||||||
if (newPedalboard.setMidiBinding(instanceId, midiBinding)) {
|
if (newPedalboard.setMidiBinding(instanceId, midiBinding)) {
|
||||||
this.pedalboard.set(newPedalboard);
|
newPedalboard.selectedSnapshot = -1;
|
||||||
|
this.setModelPedalboard(newPedalboard);
|
||||||
|
|
||||||
|
|
||||||
// notify the server.
|
// notify the server.
|
||||||
@@ -2329,8 +2397,31 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleNotifyPathPatchPropertyChanged(
|
||||||
|
instanceId: number, propertyUri: string, jsonObject: any
|
||||||
|
) {
|
||||||
|
let pedalboard = this.pedalboard.get();
|
||||||
|
let pedalboardItem = pedalboard.getItem(instanceId);
|
||||||
|
if (pedalboardItem) {
|
||||||
|
pedalboardItem.pathProperties[propertyUri] = jsonObject;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) {
|
||||||
|
let listener = this.monitorPatchPropertyListeners[i];
|
||||||
|
if (listener.instanceId === instanceId) {
|
||||||
|
listener.callback(instanceId, propertyUri, jsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) {
|
private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) {
|
||||||
|
// yyy this whole path is obsolete. We now get property change notifications
|
||||||
|
// always, since the entire pedalboard must track excactly.
|
||||||
|
// Review carefully.
|
||||||
|
let pedalboard = this.pedalboard.get();
|
||||||
|
let pedalboardItem = pedalboard.getItem(instanceId);
|
||||||
|
if (pedalboardItem) {
|
||||||
|
pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject);
|
||||||
|
}
|
||||||
for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) {
|
for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) {
|
||||||
let listener = this.monitorPatchPropertyListeners[i];
|
let listener = this.monitorPatchPropertyListeners[i];
|
||||||
if (listener.handle === clientHandle && listener.instanceId === instanceId) {
|
if (listener.handle === clientHandle && listener.instanceId === instanceId) {
|
||||||
@@ -2367,7 +2458,13 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
download(targetType: string, instanceId: number | string): void {
|
download(targetType: string, instanceId: number | string): void {
|
||||||
if (instanceId === -1) return;
|
if (instanceId === -1) return;
|
||||||
let url = this.varServerUrl + targetType + "?id=" + instanceId;
|
let url = this.varServerUrl + targetType + "?id=" + instanceId;
|
||||||
window.open(url, "_blank");
|
|
||||||
|
// window.open(url, "_blank");
|
||||||
|
// download with no flashing temporary tab.
|
||||||
|
let link = window.document.createElement("A") as HTMLLinkElement;
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute("download", "");
|
||||||
|
link.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
uploadFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
||||||
@@ -2611,8 +2708,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
wifiConfigSettings.hasPassword = false;
|
wifiConfigSettings.hasPassword = false;
|
||||||
wifiConfigSettings.password = "";
|
wifiConfigSettings.password = "";
|
||||||
} else {
|
} else {
|
||||||
if (wifiConfigSettings.hasPassword)
|
if (wifiConfigSettings.hasPassword) {
|
||||||
{
|
|
||||||
wifiConfigSettings.hasSavedPassword = true;
|
wifiConfigSettings.hasSavedPassword = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2714,7 +2810,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getKnownWifiNetworks() : Promise<string[]> {
|
getKnownWifiNetworks(): Promise<string[]> {
|
||||||
let result = new Promise<string[]>((resolve, reject) => {
|
let result = new Promise<string[]>((resolve, reject) => {
|
||||||
if (!this.webSocket) {
|
if (!this.webSocket) {
|
||||||
reject("Connection closed.");
|
reject("Connection closed.");
|
||||||
@@ -2945,81 +3041,67 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
private expectNetworkChangeTimeout?: NodeJS.Timeout = undefined;
|
private expectNetworkChangeTimeout?: NodeJS.Timeout = undefined;
|
||||||
private hotspotReconnectTimer?: NodeJS.Timeout = undefined;
|
private hotspotReconnectTimer?: NodeJS.Timeout = undefined;
|
||||||
|
|
||||||
async detectServer(address: string)
|
async detectServer(address: string) {
|
||||||
{
|
|
||||||
let port = window.location.port;
|
let port = window.location.port;
|
||||||
let newUrl = new URL("http://" + address + ":" + port + "/manifest.json");
|
let newUrl = new URL("http://" + address + ":" + port + "/manifest.json");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response = await fetch(newUrl);
|
let response = await fetch(newUrl);
|
||||||
if (response.ok)
|
if (response.ok) {
|
||||||
{
|
return "http://" + address + ":" + port;
|
||||||
return "http://" + address +":"+ port;
|
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
} catch (error: any)
|
} catch (error: any) {
|
||||||
{
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async pollForLiveServer() {
|
async pollForLiveServer() {
|
||||||
let wifiConfigSettings = this.wifiConfigSettings.get();
|
let wifiConfigSettings = this.wifiConfigSettings.get();
|
||||||
if (wifiConfigSettings.mdnsName.length !== 0)
|
if (wifiConfigSettings.mdnsName.length !== 0) {
|
||||||
{
|
|
||||||
let newUrl = await this.detectServer(wifiConfigSettings.mdnsName);
|
let newUrl = await this.detectServer(wifiConfigSettings.mdnsName);
|
||||||
if (newUrl.length !== 0)
|
if (newUrl.length !== 0) {
|
||||||
{
|
|
||||||
return newUrl;
|
return newUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.networkChanging_expectHotspot)
|
if (this.networkChanging_expectHotspot) {
|
||||||
{
|
|
||||||
let newUrl = await this.detectServer("10.40.0.1");
|
let newUrl = await this.detectServer("10.40.0.1");
|
||||||
if (newUrl.length !== 0)
|
if (newUrl.length !== 0) {
|
||||||
{
|
|
||||||
return newUrl;
|
return newUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
cancelHotspotReconnectTimer()
|
cancelHotspotReconnectTimer() {
|
||||||
{
|
if (this.hotspotReconnectTimer) {
|
||||||
if (this.hotspotReconnectTimer)
|
|
||||||
{
|
|
||||||
clearTimeout(this.hotspotReconnectTimer);
|
clearTimeout(this.hotspotReconnectTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
startHotspotReconnectTimer()
|
startHotspotReconnectTimer() {
|
||||||
{
|
|
||||||
// poll for access to a running pipedal server
|
// poll for access to a running pipedal server
|
||||||
this.hotspotReconnectTimer = setTimeout(
|
this.hotspotReconnectTimer = setTimeout(
|
||||||
async () => {
|
async () => {
|
||||||
let newUrl = await this.pollForLiveServer();
|
let newUrl = await this.pollForLiveServer();
|
||||||
if (newUrl.length === 0)
|
if (newUrl.length === 0) {
|
||||||
{
|
|
||||||
this.startHotspotReconnectTimer();
|
this.startHotspotReconnectTimer();
|
||||||
} else {
|
} else {
|
||||||
this.cancelOnNetworkChanging();
|
this.cancelOnNetworkChanging();
|
||||||
window.location.replace(newUrl);
|
window.location.replace(newUrl);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
5*1000);
|
5 * 1000);
|
||||||
|
|
||||||
}
|
}
|
||||||
cancelOnNetworkChanging()
|
cancelOnNetworkChanging() {
|
||||||
{
|
|
||||||
this.cancelHotspotReconnectTimer();
|
this.cancelHotspotReconnectTimer();
|
||||||
if (this.expectNetworkChangeTimeout)
|
if (this.expectNetworkChangeTimeout) {
|
||||||
{
|
|
||||||
clearTimeout(this.expectNetworkChangeTimeout);
|
clearTimeout(this.expectNetworkChangeTimeout);
|
||||||
this.expectNetworkChangeTimeout = undefined;
|
this.expectNetworkChangeTimeout = undefined;
|
||||||
}
|
}
|
||||||
this.networkChanging = false;
|
this.networkChanging = false;
|
||||||
this.expectDisconnect(ReconnectReason.Disconnected);
|
this.expectDisconnect(ReconnectReason.Disconnected);
|
||||||
}
|
}
|
||||||
onNetworkChanging(hotspotConnected: boolean)
|
onNetworkChanging(hotspotConnected: boolean) {
|
||||||
{
|
|
||||||
this.cancelOnNetworkChanging();
|
this.cancelOnNetworkChanging();
|
||||||
|
|
||||||
this.networkChanging = true;
|
this.networkChanging = true;
|
||||||
@@ -3030,7 +3112,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
() => {
|
() => {
|
||||||
this.cancelOnNetworkChanging();
|
this.cancelOnNetworkChanging();
|
||||||
},
|
},
|
||||||
30*1000);
|
30 * 1000);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ const PluginControlView =
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controlKeyIndex : number = 0;
|
||||||
controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] {
|
controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] {
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let isLandscapeGrid = this.state.landscapeGrid;
|
let isLandscapeGrid = this.state.landscapeGrid;
|
||||||
@@ -542,7 +543,7 @@ const PluginControlView =
|
|||||||
let item = controlGroup.controls[j];
|
let item = controlGroup.controls[j];
|
||||||
controls.push(
|
controls.push(
|
||||||
(
|
(
|
||||||
<div className={classes.controlPadding}>
|
<div key={"ctl"+(this.controlKeyIndex++)} className={classes.controlPadding}>
|
||||||
{item}
|
{item}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -551,7 +552,7 @@ const PluginControlView =
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.push((
|
result.push((
|
||||||
<div className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
<div key={"ctl"+(this.controlKeyIndex++)} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||||
<div className={classes.portGroupTitle}>
|
<div className={classes.portGroupTitle}>
|
||||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||||
</div>
|
</div>
|
||||||
@@ -565,7 +566,7 @@ const PluginControlView =
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
result.push((
|
result.push((
|
||||||
<div className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
|
<div key={"ctl"+(this.controlKeyIndex++)} className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
|
||||||
{node as ReactNode}
|
{node as ReactNode}
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
@@ -583,6 +584,7 @@ const PluginControlView =
|
|||||||
|
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
|
this.controlKeyIndex = 0;
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let pedalboardItem: PedalboardItem;
|
let pedalboardItem: PedalboardItem;
|
||||||
let pedalboard = this.model.pedalboard.get();
|
let pedalboard = this.model.pedalboard.get();
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Robin E. R. 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const darkcolors: string[] =
|
||||||
|
[
|
||||||
|
"#b71C1C","#880E4F","#4A148c","#311B92","#1A237E","#0D47A1","#01579B","#006064","#004D40"
|
||||||
|
];
|
||||||
|
|
||||||
|
const lighcolors: string[] = [
|
||||||
|
"#FFcDD2","#F8BBd0","#E1BEE7","#D1c4E9","#C5CAE9","#BBDEFB","#B3E5FC","#B2EbF2","B2DFDB"
|
||||||
|
];
|
||||||
|
export function getLightSnapshotcolors() {
|
||||||
|
let result:string[] = [];
|
||||||
|
for (let h = 0; h < 360; h += 30)
|
||||||
|
{
|
||||||
|
result.push("hsl(" + h + ",100%,79%)");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDarkSnapshotColors() {
|
||||||
|
let result:string[] = [];
|
||||||
|
for (let h = 0; h < 360; h += 30)
|
||||||
|
{
|
||||||
|
result.push("hsl(" + h + ",90%,20%)");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSnapshotColors() {
|
||||||
|
return getDarkSnapshotColors();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Robin E. R. 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 Typography from '@mui/material/Typography';
|
||||||
|
import DialogEx from './DialogEx';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
|
||||||
|
import Fade from '@mui/material/Fade';
|
||||||
|
import SnapshotPanel from './SnapshotPanel';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
dialogPaper: {
|
||||||
|
minHeight: '80vh',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const Transition = React.forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: React.ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: React.Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Fade in={true} ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface SnapshotDialogProps {
|
||||||
|
open: boolean,
|
||||||
|
onOk: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SnapshotDialogState {
|
||||||
|
fullScreen: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SnapshotDialog extends ResizeResponsiveComponent<SnapshotDialogProps, SnapshotDialogState> {
|
||||||
|
private model: PiPedalModel;
|
||||||
|
|
||||||
|
constructor(props: SnapshotDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
this.state = {
|
||||||
|
fullScreen: this.getFullScreen(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
super.componentDidMount();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullScreen() {
|
||||||
|
return window.innerHeight < 450;
|
||||||
|
}
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
fullScreen: this.getFullScreen(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<DialogEx fullWidth={true} tag="shapshot" open={this.props.open} onClose={() => this.props.onOk()} fullScreen={this.state.fullScreen}
|
||||||
|
TransitionComponent={Transition} keepMounted
|
||||||
|
style={{ userSelect: "none", }}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
maxWidth: '1000px'
|
||||||
|
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
<div>
|
||||||
|
<IconButton edge="start" color="inherit" onClick={() => { this.props.onOk(); }} aria-label="back"
|
||||||
|
>
|
||||||
|
<ArrowBackIcon fontSize="small" style={{ opacity: "0.6" }} />
|
||||||
|
</IconButton>
|
||||||
|
<Typography display="inline" variant="body1" color="textSecondary" >Snapshots</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent style={{
|
||||||
|
overflow: "hidden", margin: 0, height: "80vh",
|
||||||
|
paddingLeft: 16, paddingRight: 16, paddingTop: 0, paddingBottom: 24,
|
||||||
|
}}>
|
||||||
|
<SnapshotPanel panelHeight="80vh" />
|
||||||
|
</DialogContent>
|
||||||
|
</DialogEx>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
// Copyright (c) 2024 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 { WithStyles } from '@mui/styles';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import AppBar from '@mui/material/AppBar';
|
||||||
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import createStyles from '@mui/styles/createStyles';
|
||||||
|
import withStyles from '@mui/styles/withStyles';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
||||||
|
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo } from './PiPedalModel';
|
||||||
|
import ZoomedUiControl from './ZoomedUiControl'
|
||||||
|
import MainPage from './MainPage';
|
||||||
|
import JackStatusView from './JackStatusView';
|
||||||
|
import { Theme } from '@mui/material/styles';
|
||||||
|
import { isDarkMode } from './DarkMode';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import { Snapshot } from './Pedalboard';
|
||||||
|
import ColorDropdownButton from './ColorDropdownButton';
|
||||||
|
|
||||||
|
|
||||||
|
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||||
|
|
||||||
|
|
||||||
|
const appStyles = (theme: Theme) => createStyles({
|
||||||
|
"&": { // :root
|
||||||
|
colorScheme: (isDarkMode() ? "dark" : "light")
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
mainFrame: {
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
marginTop: 8,
|
||||||
|
flex: "1 1 100%"
|
||||||
|
},
|
||||||
|
heroContent: {
|
||||||
|
backgroundColor: theme.mainBackground,
|
||||||
|
position: "relative",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
shadowCatcher: {
|
||||||
|
backgroundColor: theme.mainBackground
|
||||||
|
},
|
||||||
|
|
||||||
|
select: { // fu fu fu.Overrides for white selector on dark background.
|
||||||
|
'&:before': {
|
||||||
|
borderColor: selectColor,
|
||||||
|
},
|
||||||
|
'&:after': {
|
||||||
|
borderColor: selectColor,
|
||||||
|
},
|
||||||
|
'&:hover:not(.Mui-disabled):before': {
|
||||||
|
borderColor: selectColor,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select_icon: {
|
||||||
|
fill: selectColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function supportsFullScreen(): boolean {
|
||||||
|
let doc: any = window.document;
|
||||||
|
let docEl: any = doc.documentElement;
|
||||||
|
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||||
|
return (!!requestFullScreen);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFullScreen(value: boolean) {
|
||||||
|
let doc: any = window.document;
|
||||||
|
let docEl: any = doc.documentElement;
|
||||||
|
|
||||||
|
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||||
|
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
|
||||||
|
|
||||||
|
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
|
||||||
|
requestFullScreen.call(docEl);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cancelFullScreen.call(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnapshotEditorState = {
|
||||||
|
zoomedControlInfo: ZoomedControlInfo | undefined;
|
||||||
|
zoomedControlOpen: boolean;
|
||||||
|
|
||||||
|
canFullScreen: boolean;
|
||||||
|
isFullScreen: boolean;
|
||||||
|
isDebug: boolean;
|
||||||
|
collapseLabel: boolean;
|
||||||
|
|
||||||
|
showStatusMonitor: boolean;
|
||||||
|
name: string,
|
||||||
|
color: string
|
||||||
|
|
||||||
|
};
|
||||||
|
interface SnapshotEditorProps extends WithStyles<typeof appStyles> {
|
||||||
|
onClose: () => void;
|
||||||
|
onOk: (snapshotIndex: number, name: string, color: string, newSnapshots: (Snapshot | null)[]) => void;
|
||||||
|
snapshotIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveComponent<SnapshotEditorProps, SnapshotEditorState> {
|
||||||
|
// Before the component mounts, we initialise our state
|
||||||
|
|
||||||
|
model_: PiPedalModel;
|
||||||
|
|
||||||
|
getCollapseLabel() {
|
||||||
|
return this.windowSize.width < 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props: SnapshotEditorProps) {
|
||||||
|
super(props);
|
||||||
|
this.model_ = PiPedalModelFactory.getInstance();
|
||||||
|
|
||||||
|
this.model_.zoomedUiControl.addOnChangedHandler(
|
||||||
|
() => {
|
||||||
|
this.setState({
|
||||||
|
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
|
||||||
|
zoomedControlInfo: this.model_.zoomedUiControl.get()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let snapshot = this.model_.pedalboard.get().snapshots[this.props.snapshotIndex] ?? new Snapshot();
|
||||||
|
this.state = {
|
||||||
|
zoomedControlOpen: false,
|
||||||
|
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
||||||
|
canFullScreen: supportsFullScreen() && !this.model_.isAndroidHosted(),
|
||||||
|
isFullScreen: !!document.fullscreenElement,
|
||||||
|
showStatusMonitor: this.model_.showStatusMonitor.get(),
|
||||||
|
isDebug: true,
|
||||||
|
name: snapshot.name,
|
||||||
|
color: snapshot.color,
|
||||||
|
collapseLabel: this.getCollapseLabel()
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatusMonitorHandler() {
|
||||||
|
this.setState({
|
||||||
|
showStatusMonitor: this.model_.showStatusMonitor.get()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toggleFullScreen(): void {
|
||||||
|
setFullScreen(this.state.isFullScreen);
|
||||||
|
this.setState({ isFullScreen: !this.state.isFullScreen });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
|
||||||
|
super.componentDidMount();
|
||||||
|
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateOverscroll(): void {
|
||||||
|
if (this.model_.serverVersion) {
|
||||||
|
// no pull-down refresh on android devices once we're ready (unless we're debug)
|
||||||
|
let preventOverscroll =
|
||||||
|
this.model_.state.get() === State.Ready
|
||||||
|
&& !this.model_.debug;
|
||||||
|
|
||||||
|
let overscrollBehavior = preventOverscroll ? "none" : "auto";
|
||||||
|
document.body.style.overscrollBehavior = overscrollBehavior;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
super.onWindowSizeChanged(width, height);
|
||||||
|
this.setState({
|
||||||
|
collapseLabel: this.getCollapseLabel()
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOk() {
|
||||||
|
let selectedSnapshot = this.props.snapshotIndex;
|
||||||
|
let currentPedalboard = this.model_.pedalboard.get();
|
||||||
|
let newSnapshots = Snapshot.cloneSnapshots(currentPedalboard.snapshots);
|
||||||
|
let newSnapshot = this.model_.pedalboard.get().makeSnapshot();
|
||||||
|
newSnapshot.name = this.state.name;
|
||||||
|
newSnapshot.color = this.state.color;
|
||||||
|
newSnapshots[selectedSnapshot] = newSnapshot;
|
||||||
|
this.model_.setSnapshots(newSnapshots, selectedSnapshot);
|
||||||
|
|
||||||
|
this.props.onOk(selectedSnapshot, this.state.name, this.state.color, newSnapshots);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const { classes } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classes.shadowCatcher}
|
||||||
|
style={{
|
||||||
|
position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
|
||||||
|
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
||||||
|
minHeight: 345, minWidth: 390,
|
||||||
|
userSelect: "none",
|
||||||
|
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||||
|
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||||
|
}}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
if (!this.model_.debug) {
|
||||||
|
e.preventDefault(); e.stopPropagation();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CssBaseline />
|
||||||
|
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? "#100020" : "#200040" }} >
|
||||||
|
<Toolbar variant="dense" >
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
aria-label="menu"
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => { this.handleOk(); }}
|
||||||
|
size="large">
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
{!this.state.collapseLabel && (
|
||||||
|
<Typography style={{ flex: "0 1 auto", opacity: 0.66 }}
|
||||||
|
variant="body1" noWrap
|
||||||
|
>
|
||||||
|
{'Snapshot ' + (this.props.snapshotIndex + 1)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ flex: "1 1 auto", display: "flex", flexFlow: "column nowrap", marginRight: 8 }}>
|
||||||
|
<TextField
|
||||||
|
sx={{
|
||||||
|
input: { color: 'white' },
|
||||||
|
'& .MuiInput-underline:before': { borderBottomColor: '#FFFFFFC0' },
|
||||||
|
'& .MuiInput-underline:hover:before': { borderBottomColor: '#FFFFFFE0' },
|
||||||
|
'& .MuiInput-underline:after': { borderBottomColor: '#FFFFFF' }
|
||||||
|
}}
|
||||||
|
variant="standard"
|
||||||
|
style={{ flex: "1 1 auto", marginLeft: 16, maxWidth: 500, color: "white" }}
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={(ev) => {
|
||||||
|
this.setState({ name: ev.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ColorDropdownButton
|
||||||
|
currentColor={this.state.color}
|
||||||
|
onColorChange={(newColor) => {
|
||||||
|
this.setState({ color: newColor });
|
||||||
|
}} />
|
||||||
|
{this.state.canFullScreen &&
|
||||||
|
<IconButton
|
||||||
|
aria-label="full-screen"
|
||||||
|
onClick={() => { this.toggleFullScreen(); }}
|
||||||
|
color="inherit"
|
||||||
|
size="medium">
|
||||||
|
{this.state.isFullScreen ? (
|
||||||
|
<FullscreenExitIcon style={{ opacity: 0.75 }} />
|
||||||
|
) : (
|
||||||
|
<FullscreenIcon style={{ opacity: 0.75 }} />
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
<IconButton
|
||||||
|
aria-label="cancel"
|
||||||
|
onClick={() => { this.props.onClose(); }}
|
||||||
|
color="inherit"
|
||||||
|
size="medium">
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
<main className={classes.mainFrame} >
|
||||||
|
<div style={{
|
||||||
|
overflow: "hidden",
|
||||||
|
flex: "1 1 auto"
|
||||||
|
}} >
|
||||||
|
<div className={classes.heroContent}>
|
||||||
|
<MainPage hasTinyToolBar={false} enableStructureEditing={false} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<ZoomedUiControl
|
||||||
|
dialogOpen={this.state.zoomedControlOpen}
|
||||||
|
controlInfo={this.state.zoomedControlInfo}
|
||||||
|
onDialogClose={() => { this.setState({ zoomedControlOpen: false }); }}
|
||||||
|
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SnapshotEditor;
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Robin E. R. 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 Typography from '@mui/material/Typography';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import ButtonBase from '@mui/material/ButtonBase';
|
||||||
|
import { Pedalboard, Snapshot } from './Pedalboard';
|
||||||
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
import SaveIconOutline from '@mui/icons-material/Save';
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import EditIconOutline from '@mui/icons-material/Edit';
|
||||||
|
|
||||||
|
import SnapshotPropertiesDialog from './SnapshotPropertiesDialog';
|
||||||
|
import { colorKeys, getBackgroundColor, getBorderColor } from './MaterialColors';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface SnapshotPanelProps {
|
||||||
|
panelHeight?: string,
|
||||||
|
onEdit?: (snapshotIndex: number) => boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SnapshotPanelState {
|
||||||
|
snapshots: (Snapshot | null)[],
|
||||||
|
portraitOrientation: boolean,
|
||||||
|
collapseButtons: boolean,
|
||||||
|
largeText: boolean,
|
||||||
|
|
||||||
|
snapshotPropertiesDialogOpen: boolean,
|
||||||
|
editTitle: string,
|
||||||
|
editColor: string,
|
||||||
|
editing: boolean,
|
||||||
|
editId: number,
|
||||||
|
selectedSnapshot: number
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SnapshotPanel extends ResizeResponsiveComponent<SnapshotPanelProps, SnapshotPanelState> {
|
||||||
|
private model: PiPedalModel;
|
||||||
|
|
||||||
|
constructor(props: SnapshotPanelProps) {
|
||||||
|
super(props);
|
||||||
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
this.state = {
|
||||||
|
snapshots: this.getCurrentSnapshots(),
|
||||||
|
selectedSnapshot: this.getSelectedSnapshot(),
|
||||||
|
portraitOrientation: this.getPortraitOrientation(),
|
||||||
|
collapseButtons: this.getCollapseButtons(),
|
||||||
|
largeText: this.getLargeText(),
|
||||||
|
|
||||||
|
snapshotPropertiesDialogOpen: false,
|
||||||
|
editTitle: "",
|
||||||
|
editColor: "",
|
||||||
|
editing: false,
|
||||||
|
editId: 0,
|
||||||
|
|
||||||
|
};
|
||||||
|
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||||
|
this.onSelectedSnapshotChanged = this.onSelectedSnapshotChanged.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSnapshots() {
|
||||||
|
let result: (Snapshot | null)[] = [];
|
||||||
|
let currentSnapshots = this.model.pedalboard.get().snapshots;
|
||||||
|
for (let i = 0; i < 6; ++i) {
|
||||||
|
let mySnapshot = null;
|
||||||
|
if (i < currentSnapshots.length) {
|
||||||
|
if (currentSnapshots[i] !== null) {
|
||||||
|
mySnapshot = new Snapshot().deserialize(currentSnapshots[i]); // a deep clone.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(mySnapshot);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
getSelectedSnapshot() {
|
||||||
|
return this.model.selectedSnapshot.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectedSnapshotChanged(value: number) {
|
||||||
|
this.setState({ selectedSnapshot: this.getSelectedSnapshot() })
|
||||||
|
}
|
||||||
|
onPedalboardChanged(value: Pedalboard) {
|
||||||
|
// boofs our current edit, oh well. we can't track property across a structural change.
|
||||||
|
this.setState({
|
||||||
|
snapshots: this.getCurrentSnapshots(),
|
||||||
|
selectedSnapshot: this.getSelectedSnapshot()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
componentDidMount(): void {
|
||||||
|
super.componentDidMount();
|
||||||
|
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||||
|
this.model.selectedSnapshot.addOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||||
|
this.setState({
|
||||||
|
snapshots: this.getCurrentSnapshots(),
|
||||||
|
selectedSnapshot: this.getSelectedSnapshot()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
super.componentWillUnmount();
|
||||||
|
this.model.selectedSnapshot.removeOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||||
|
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getCollapseButtons() {
|
||||||
|
if (this.getPortraitOrientation())
|
||||||
|
{
|
||||||
|
return window.innerWidth < 550;
|
||||||
|
} else {
|
||||||
|
return window.innerWidth < 800;
|
||||||
|
}
|
||||||
|
return window.innerWidth < 500;
|
||||||
|
}
|
||||||
|
getPortraitOrientation() {
|
||||||
|
return window.innerWidth * 2 < window.innerHeight * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLargeText() {
|
||||||
|
return window.innerHeight > 700 && window.innerWidth > 1000;
|
||||||
|
}
|
||||||
|
onSaveSnapshot(index: number) {
|
||||||
|
let snapshot = this.state.snapshots[index];
|
||||||
|
if (snapshot) {
|
||||||
|
// no dialog required. just save it.
|
||||||
|
this.handleSnapshotPropertyOk(
|
||||||
|
index,
|
||||||
|
snapshot.name,
|
||||||
|
snapshot.color,
|
||||||
|
false);
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
editTitle: "",
|
||||||
|
editColor: "",
|
||||||
|
editId: index,
|
||||||
|
snapshotPropertiesDialogOpen: true,
|
||||||
|
editing: false
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
onEditSnapshot(index: number) {
|
||||||
|
if (this.props.onEdit) {
|
||||||
|
if (this.props.onEdit(index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let snapshot = this.state.snapshots[index];
|
||||||
|
if (snapshot) {
|
||||||
|
this.setState({
|
||||||
|
editTitle: snapshot.name,
|
||||||
|
editColor: snapshot.color,
|
||||||
|
editId: index,
|
||||||
|
snapshotPropertiesDialogOpen: true,
|
||||||
|
editing: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
portraitOrientation: this.getPortraitOrientation(),
|
||||||
|
collapseButtons: this.getCollapseButtons(),
|
||||||
|
largeText: this.getLargeText()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSnapshot(index: number) {
|
||||||
|
this.model.selectSnapshot(index);
|
||||||
|
}
|
||||||
|
renderSnapshot(snapshot: Snapshot | null, index: number) {
|
||||||
|
//let state = this.state;
|
||||||
|
let color: string;
|
||||||
|
let bordercolor: string;
|
||||||
|
if (snapshot) {
|
||||||
|
if (colorKeys.indexOf(snapshot.color) === -1) {
|
||||||
|
bordercolor = color = snapshot.color;
|
||||||
|
} else {
|
||||||
|
color = getBackgroundColor(snapshot.color);
|
||||||
|
bordercolor = getBorderColor(snapshot.color);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bordercolor = color = getBackgroundColor("grey");
|
||||||
|
}
|
||||||
|
let title = snapshot ? snapshot.name : "<unassigned>";
|
||||||
|
let disabled = snapshot === null;
|
||||||
|
let selected = this.state.selectedSnapshot === index;
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", position: "relative",flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16 }}>
|
||||||
|
|
||||||
|
<ButtonBase style={{
|
||||||
|
display: "flex", flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16,
|
||||||
|
background: color,
|
||||||
|
boxShadow: "1px 3px 6px #00000090"
|
||||||
|
}}
|
||||||
|
onMouseDown={(ev) => {
|
||||||
|
if (!selected) ev.stopPropagation();
|
||||||
|
}}
|
||||||
|
|
||||||
|
onClick={() => { if (snapshot) this.selectSnapshot(index); }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch" }}
|
||||||
|
onMouseDown={(ev) => {
|
||||||
|
if (disabled) ev.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch", justifyContent: "center", margin: 6,
|
||||||
|
borderColor: selected ? bordercolor : "transparent", borderStyle: "solid", borderWidth: 3, borderRadius: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ flexGrow: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||||
|
|
||||||
|
<Typography display="block" color="textPrimary" align="center" variant="body1"
|
||||||
|
style={{ fontSize: this.state.largeText ? "30px" : undefined }}
|
||||||
|
>{title}</Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{ height: 54,flexGrow: 0,flexShrink: 0 }}>
|
||||||
|
{/* placeholder where we will float the buttons, which can't be witin another button (tons of DOM validation warning messages in chrome) */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ButtonBase >
|
||||||
|
<div style={{ flexGrow: 0, position: "absolute", bottom:0,left:0,right:0,paddingBottom:10,paddingLeft: 16, paddingRight:12 }} >
|
||||||
|
<Typography variant="h3" style={{opacity: 0.15,fontSize: "44px",fontWeight:900,fontFamily: "Arial Black",fontStyle: "italic"}} >{(index+1).toString()}</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flexGrow: 0, position: "absolute", bottom:0,left:0,right:0,paddingBottom:12,paddingLeft: 12, paddingRight:12 }} >
|
||||||
|
{!this.state.collapseButtons ? (
|
||||||
|
<div style={{ marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
|
||||||
|
<div style={{ flexGrow: 1, flexBasis: 1 }} > </div>
|
||||||
|
<Button variant="dialogSecondary" startIcon={<SaveIconOutline />} color="inherit" style={{ textTransform: "none" }}
|
||||||
|
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||||
|
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||||
|
disabled={false}
|
||||||
|
onClick={(ev) => { ev.stopPropagation(); this.onSaveSnapshot(index); }}
|
||||||
|
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button variant="dialogSecondary" color="inherit" startIcon={<EditIconOutline />} style={{ textTransform: "none" }}
|
||||||
|
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||||
|
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={(ev) => { ev.stopPropagation(); this.onEditSnapshot(index); }}
|
||||||
|
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<div style={{ marginLeft: "auto", marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
|
||||||
|
<div style={{ flexGrow: 1, flexBasis: 1 }} > </div>
|
||||||
|
<IconButton color="inherit" style={{ opacity: 0.66 }} disabled={false}
|
||||||
|
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||||
|
onClick={() => { this.onSaveSnapshot(index); }}
|
||||||
|
>
|
||||||
|
<SaveIconOutline />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="inherit" disabled={disabled} style={{ opacity: 0.66 }}
|
||||||
|
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||||
|
onClick={() => { this.onEditSnapshot(index); }}
|
||||||
|
|
||||||
|
>
|
||||||
|
<EditIconOutline />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
handleSnapshotPropertyOk(index: number, name: string, color: string, editing: boolean) {
|
||||||
|
if (editing) {
|
||||||
|
let snapshots = Snapshot.cloneSnapshots(this.state.snapshots);
|
||||||
|
let snapshot = snapshots[index];
|
||||||
|
if (snapshot) {
|
||||||
|
snapshot.name = name;
|
||||||
|
snapshot.color = color;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
snapshots: snapshots,
|
||||||
|
snapshotPropertiesDialogOpen: false
|
||||||
|
});
|
||||||
|
this.model.setSnapshots(snapshots, -1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let pedalboard = this.model.pedalboard.get();
|
||||||
|
let newSnapshot = pedalboard.makeSnapshot();
|
||||||
|
newSnapshot.name = name;
|
||||||
|
newSnapshot.color = color;
|
||||||
|
let snapshots = Snapshot.cloneSnapshots(this.state.snapshots);
|
||||||
|
snapshots[index] = newSnapshot;
|
||||||
|
this.setState({
|
||||||
|
snapshots: snapshots,
|
||||||
|
snapshotPropertiesDialogOpen: false
|
||||||
|
});
|
||||||
|
this.model.setSnapshots(snapshots, index);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bg: undefined;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let snapshots = this.state.snapshots;
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
flex: "1 1 auto",
|
||||||
|
height: this.props.panelHeight,
|
||||||
|
overflow: "hidden", margin: 0,
|
||||||
|
paddingLeft: 16, paddingRight: 16, paddingTop: 0, paddingBottom: 24,
|
||||||
|
background: this.bg,
|
||||||
|
display: "flex", flexFlow: "column nowrap", alignContent: "stretch", justifyContent: "stretch"
|
||||||
|
}}>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
flexGrow: 1, flexShrink: 1, flexBasis: 1,
|
||||||
|
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: this.state.portraitOrientation ?
|
||||||
|
"row nowrap" : "column nowrap",
|
||||||
|
alignItems: "stretch", gap: 8
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
flexGrow: 1, flexShrink: 1, flexBasis: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: this.state.portraitOrientation ?
|
||||||
|
"column nowrap"
|
||||||
|
: "row nowrap",
|
||||||
|
alignItems: "stretch", gap: 8
|
||||||
|
}}>
|
||||||
|
{this.renderSnapshot(snapshots[0], 0)}
|
||||||
|
{this.renderSnapshot(snapshots[1], 1)}
|
||||||
|
{this.renderSnapshot(snapshots[2], 2)}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
flexGrow: 1, flexShrink: 1, flexBasis: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: this.state.portraitOrientation ? "column nowrap" : "row nowrap", alignItems: "stretch", gap: 8
|
||||||
|
}}>
|
||||||
|
{this.renderSnapshot(snapshots[3], 3)}
|
||||||
|
{this.renderSnapshot(snapshots[4], 4)}
|
||||||
|
{this.renderSnapshot(snapshots[5], 5)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.state.snapshotPropertiesDialogOpen && (
|
||||||
|
<SnapshotPropertiesDialog
|
||||||
|
open={this.state.snapshotPropertiesDialogOpen}
|
||||||
|
name={this.state.editTitle}
|
||||||
|
color={this.state.editColor}
|
||||||
|
editing={this.state.editing}
|
||||||
|
snapshotIndex={this.state.editId}
|
||||||
|
onClose={() => { this.setState({ snapshotPropertiesDialogOpen: false }); }}
|
||||||
|
onOk={(snapshotId, name, color, editing) => {
|
||||||
|
this.handleSnapshotPropertyOk(snapshotId, name, color, editing);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Robin E. R. Davies
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import ButtonBase from "@mui/material/ButtonBase"
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import DialogEx from './DialogEx';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import { colorKeys, getBackgroundColor } from "./MaterialColors";
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
|
||||||
|
const snapshotColors = colorKeys.slice(0,100);
|
||||||
|
const NBSP = "\u00A0";
|
||||||
|
|
||||||
|
export interface SnapshotPropertiesDialogProps {
|
||||||
|
open: boolean,
|
||||||
|
name: string,
|
||||||
|
editing: boolean,
|
||||||
|
snapshotIndex: number,
|
||||||
|
color: string,
|
||||||
|
onOk: (snapshotId: number, name: string, color: string, editing: boolean) => void,
|
||||||
|
onClose: () => void
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SnapshotPropertiesDialogState {
|
||||||
|
name: string,
|
||||||
|
color: string,
|
||||||
|
nameErrorMessage: string,
|
||||||
|
compactVertical: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<SnapshotPropertiesDialogProps, SnapshotPropertiesDialogState> {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(props: SnapshotPropertiesDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = this.stateFromProps();
|
||||||
|
}
|
||||||
|
getCompactVertical() {
|
||||||
|
return window.innerHeight < 450;
|
||||||
|
}
|
||||||
|
stateFromProps() {
|
||||||
|
let color = this.props.color;
|
||||||
|
if (color.length === 0) {
|
||||||
|
color = snapshotColors[0];
|
||||||
|
}
|
||||||
|
let name = this.props.name;
|
||||||
|
if (name.length === 0 && !this.props.editing)
|
||||||
|
{
|
||||||
|
name = "Snapshot " + (this.props.snapshotIndex+1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
color: color,
|
||||||
|
nameErrorMessage: "",
|
||||||
|
compactVertical: this.getCompactVertical()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
super.onWindowSizeChanged(width,height);
|
||||||
|
this.setState({compactVertical: this.getCompactVertical() })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOk() {
|
||||||
|
if (this.state.name === "") {
|
||||||
|
this.setState({ nameErrorMessage: "* Required" });
|
||||||
|
} else {
|
||||||
|
this.props.onOk(
|
||||||
|
this.props.snapshotIndex,
|
||||||
|
this.state.name,
|
||||||
|
this.state.color,
|
||||||
|
this.props.editing
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let props = this.props;
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
props.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
let okButtonText = this.props.editing ? "OK" : "Save";
|
||||||
|
return (
|
||||||
|
<DialogEx maxWidth={false} tag="snapshotProps" open={this.props.open} onClose={handleClose}
|
||||||
|
style={{ userSelect: "none" }} fullScreen={this.state.compactVertical}
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
<div>
|
||||||
|
<IconButton edge="start" color="inherit" onClick={() => { this.props.onClose(); }} aria-label="back"
|
||||||
|
>
|
||||||
|
<ArrowBackIcon fontSize="small" style={{ opacity: "0.6" }} />
|
||||||
|
</IconButton>
|
||||||
|
<Typography display="inline" variant="body1" color="textSecondary" >
|
||||||
|
{this.props.editing ? "Edit snapshot" : "Save snapshot"}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
|
||||||
|
<TextField variant="standard" style={{ flexGrow: 1, flexBasis: 1 }}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck="false"
|
||||||
|
error={this.state.nameErrorMessage.length !== 0}
|
||||||
|
id="name"
|
||||||
|
label="Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
helperText={this.state.nameErrorMessage.length === 0 ? NBSP : this.state.nameErrorMessage}
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={(e) => this.setState({ name: e.target.value, nameErrorMessage: "" })}
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption" color="textSecondary">Color</Typography>
|
||||||
|
<div style={{ margin: 4,display: "flex", flexFlow: "row wrap", gap: 8 }}>
|
||||||
|
{
|
||||||
|
snapshotColors.map((colorKey, index) => {
|
||||||
|
let selected = this.state.color === colorKey;
|
||||||
|
let color = getBackgroundColor(colorKey);
|
||||||
|
return (
|
||||||
|
<ButtonBase
|
||||||
|
key={"colorTag" + index}
|
||||||
|
style={{
|
||||||
|
width: 48, height: 48,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: selected ? color : "transparent",
|
||||||
|
borderWidth: 2,
|
||||||
|
}}
|
||||||
|
onClick={()=> {
|
||||||
|
this.setState({color: colorKey});
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: 40, height: 40,
|
||||||
|
borderRadius: 4,
|
||||||
|
margin: 2,
|
||||||
|
background: color
|
||||||
|
}}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ButtonBase>
|
||||||
|
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="dialogSecondary" onClick={handleClose} >
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="dialogPrimary" onClick={()=> {this.handleOk();}} >
|
||||||
|
{okButtonText}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogEx>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<defs
|
||||||
|
id="defs2995" />
|
||||||
|
<path
|
||||||
|
d="m 480,-260 q 75,0 127.5,-52.5 Q 660,-365 660,-440 660,-515 607.5,-567.5 555,-620 480,-620 405,-620 352.5,-567.5 300,-515 300,-440 q 0,75 52.5,127.5 Q 405,-260 480,-260 z m 0,-80 q -42,0 -71,-29 -29,-29 -29,-71 0,-42 29,-71 29,-29 71,-29 42,0 71,29 29,29 29,71 0,42 -29,71 -29,29 -71,29 z m -320,220 q -33,0 -56.5,-23.5 Q 80,-167 80,-200 v -480 q 0,-33 23.5,-56.5 Q 127,-760 160,-760 h 126 l 74,-80 h 240 l 74,80 h 126 q 33,0 56.5,23.5 23.5,23.5 23.5,56.5 v 480 q 0,33 -23.5,56.5 Q 833,-120 800,-120 H 160 z m 0,-80 H 800 V -680 H 638 l -73,-80 H 395 l -73,80 H 160 v 480 z m 320,-240 z"
|
||||||
|
id="path2991" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 914 B |
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||||
|
id="path2991" />
|
||||||
|
<path
|
||||||
|
d="m 466.24994,-624.73151 74.61914,0 0,335.91797 -89.82422,0 0,-238.53515 -56.95312,41.36718 -42.31934,-62.44629 z"
|
||||||
|
id="path3772"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 886 B |
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||||
|
id="path2991" />
|
||||||
|
<path
|
||||||
|
d="m 464.58002,-359.31647 130.06347,0 0,70.50293 -243.77929,0 0,-69.71191 c 52.40226,-42.73426 89.24793,-76.47202 110.53711,-101.21338 21.2889,-24.74102 31.93342,-45.08523 31.93359,-61.03272 -1.7e-4,-11.65991 -3.7697,-20.95677 -11.30859,-27.89062 -7.53922,-6.93333 -17.67593,-10.40012 -30.41016,-10.40039 -11.68957,2.7e-4 -24.34337,1.91189 -37.96143,5.73486 -13.61824,3.82351 -30.86676,9.8025 -51.7456,17.93701 l -2.54883,-75.04394 c 20.30268,-7.66569 39.73137,-13.22965 58.28613,-16.6919 18.55458,-3.46157 37.07507,-5.19252 55.56153,-5.19287 34.45294,3.5e-4 62.57791,9.49252 84.375,28.47657 21.79661,18.98467 32.69504,43.37917 32.69531,73.18359 -2.7e-4,25.61545 -10.25905,52.81513 -30.77637,81.59912 -20.51779,28.78431 -52.15839,58.69883 -94.92187,89.74365 z"
|
||||||
|
id="path3775"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||||
|
id="path2991" />
|
||||||
|
<path
|
||||||
|
d="m 525.94232,-463.5401 c 20.01931,2.99822 35.90064,11.34538 47.64404,25.0415 11.74291,13.69643 17.61448,30.62268 17.61475,50.77881 -2.7e-4,31.46491 -13.95533,56.27446 -41.86523,74.42871 -27.91036,18.15431 -65.69352,27.23145 -113.34961,27.23145 -14.88291,0 -29.52157,-0.92285 -43.91602,-2.76856 -14.39458,-1.8457 -30.20023,-5.083 -47.41699,-9.71191 l 5.33203,-75.9375 c 16.123,6.02547 30.38813,10.44197 42.79541,13.24951 12.40714,2.80769 25.42715,4.2115 39.06006,4.21143 20.41003,7e-5 36.10581,-3.4179 47.0874,-10.25391 10.98128,-6.83585 16.472,-15.95205 16.47217,-27.34863 -1.7e-4,-10.37098 -4.06023,-18.31042 -12.18018,-23.81836 -8.12027,-5.50768 -20.41762,-8.26158 -36.89209,-8.26172 l -45.60058,0 0,-66.81152 42.37793,0 c 15.26353,2e-4 27.3729,-2.98564 36.32812,-8.95752 8.95492,-5.97146 13.43245,-13.52761 13.43262,-22.66846 -1.7e-4,-9.69702 -4.57048,-17.58275 -13.71094,-23.65723 -9.14077,-6.07395 -21.76771,-9.11106 -37.88086,-9.11132 -12.22667,2.6e-4 -24.54843,1.18435 -36.96533,3.55224 -12.41706,2.36843 -27.25835,6.00856 -44.52392,10.92041 l -4.33594,-68.67187 c 18.74018,-5.57585 36.48919,-9.57242 53.24707,-11.98975 16.75771,-2.41665 33.81824,-3.62515 51.18164,-3.62549 39.17951,3.4e-4 69.86551,8.23031 92.0581,24.68994 22.19214,16.46026 33.28832,39.28495 33.28858,68.47412 -2.6e-4,18.86742 -5.34694,34.70725 -16.04004,47.51954 -10.6936,12.81268 -25.10764,20.6447 -43.24219,23.49609 z"
|
||||||
|
id="path3778"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||||
|
id="path2991" />
|
||||||
|
<path
|
||||||
|
d="m 485.98138,-553.77448 -81.15234,133.79883 76.93359,0 z m -19.13086,-70.95703 104.58985,0 0,204.75586 44.34082,0 0,68.20313 -44.34082,0 0,62.95898 -89.82422,0 0,-62.95898 -146.38184,0 0,-78.22266 z"
|
||||||
|
id="path3781" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 966 B |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||||
|
id="path2991" />
|
||||||
|
<path
|
||||||
|
d="m 363.08099,-624.73151 221.22071,0 0,67.74903 -135.30762,0 -1.18652,45.54199 c 2.29479,-0.68337 6.31822,-1.23757 12.07031,-1.6626 5.75181,-0.42458 10.65415,-0.63698 14.70703,-0.63721 37.49004,2.3e-4 67.33132,9.93186 89.52393,29.79493 22.19211,19.86345 33.2883,46.49917 33.28857,79.90722 -2.7e-4,35.85946 -13.09108,64.48736 -39.27246,85.88379 -26.18185,21.39649 -61.22576,32.09473 -105.13184,32.09473 -17.79308,0 -34.15048,-1.04248 -49.07226,-3.12744 -14.92194,-2.08496 -30.3858,-5.71045 -46.3916,-10.87647 l 0,-77.63672 c 17.0019,6.62118 31.97747,11.49422 44.92675,14.61914 12.94913,3.12508 26.03994,4.68757 39.27246,4.6875 18.59362,7e-5 33.33725,-3.98918 44.23096,-11.96777 10.89338,-7.97843 16.34015,-18.28115 16.34033,-30.9082 -1.8e-4,-14.66784 -5.84001,-26.09606 -17.51953,-34.28467 -11.67984,-8.18832 -28.5597,-12.28256 -50.63965,-12.28272 -9.88291,1.6e-4 -20.32235,0.67887 -31.31836,2.03614 -10.99616,1.35758 -24.86821,3.51578 -41.61621,6.47461 z"
|
||||||
|
id="path3784"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
id="svg2989"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||||
|
id="path2991" />
|
||||||
|
<path
|
||||||
|
d="m 478.26166,-438.41803 c -9.97085,1.5e-4 -18.49623,4.18716 -25.57617,12.56104 -7.08021,8.37415 -10.62024,19.2506 -10.62012,32.62939 -1.2e-4,13.74032 3.31531,24.70467 9.94629,32.89307 6.63072,8.18854 15.07798,12.28277 25.34179,12.28271 11.14242,6e-5 19.88508,-3.84759 26.22803,-11.54297 6.34259,-7.69523 9.51397,-18.75479 9.51416,-33.17871 -1.9e-4,-13.94519 -3.26923,-25.03893 -9.80713,-33.28125 -6.53826,-8.24204 -14.88053,-12.36313 -25.02685,-12.36328 z m 110.18554,-176.64551 -7.52929,72.09961 c -14.57056,-6.2595 -27.30492,-10.88841 -38.20313,-13.88672 -10.89864,-2.99777 -21.96309,-4.49679 -33.19336,-4.49707 -23.86735,2.8e-4 -41.80434,7.36355 -53.81103,22.08985 -12.00696,14.72679 -18.0885,35.71798 -18.24463,62.97363 2.90027,-10.0584 10.48816,-17.93437 22.76367,-23.62793 12.27524,-5.69314 26.02523,-8.53982 41.25,-8.54004 30.15604,2.2e-4 54.94606,10.35666 74.37012,31.06934 19.42355,20.71305 29.13546,47.00209 29.13574,78.86718 -2.8e-4,34.4532 -11.29666,62.6148 -33.88916,84.48487 -22.593,21.87012 -51.7751,32.80517 -87.54639,32.80517 -43.32043,0 -76.57235,-14.64843 -99.75586,-43.94531 -23.18363,-29.29681 -34.77541,-70.67372 -34.77539,-124.13086 -2e-5,-58.50564 13.35201,-103.32737 40.05616,-134.46533 26.704,-31.13737 65.54918,-46.7062 116.53564,-46.70654 12.91973,3.4e-4 26.01786,1.19174 39.29443,3.57421 13.27614,2.38315 27.79028,6.32846 43.54248,11.83594 z"
|
||||||
|
id="path3886"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -953,9 +953,12 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
OpenMidi(jackServerSettings, channelSelection);
|
OpenMidi(jackServerSettings, channelSelection);
|
||||||
OpenAudio(jackServerSettings, channelSelection);
|
OpenAudio(jackServerSettings, channelSelection);
|
||||||
|
std::atomic_thread_fence(std::memory_order::release);
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
|
std::atomic_thread_fence(std::memory_order::release);
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -1359,7 +1362,7 @@ namespace pipedal
|
|||||||
long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
|
long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
|
||||||
{
|
{
|
||||||
long framesRead;
|
long framesRead;
|
||||||
auto frame_bytes = this->captureFrameSize;
|
auto frame_bytes = this->playbackFrameSize;
|
||||||
|
|
||||||
while (frames > 0)
|
while (frames > 0)
|
||||||
{
|
{
|
||||||
@@ -1969,6 +1972,8 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
virtual void Close()
|
virtual void Close()
|
||||||
{
|
{
|
||||||
|
std::atomic_thread_fence(std::memory_order::acquire);
|
||||||
|
|
||||||
if (!open)
|
if (!open)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -1977,6 +1982,7 @@ namespace pipedal
|
|||||||
Deactivate();
|
Deactivate();
|
||||||
AlsaCleanup();
|
AlsaCleanup();
|
||||||
DeleteBuffers();
|
DeleteBuffers();
|
||||||
|
std::atomic_thread_fence(std::memory_order::release);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual float CpuUse()
|
virtual float CpuUse()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2023 Robin E. R. Davies
|
* Copyright (c) 2023-2024 Robin E. R. Davies
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
#include "ss.hpp"
|
#include "ss.hpp"
|
||||||
#include "lv2/atom/util.h"
|
#include "lv2/atom/util.h"
|
||||||
|
#include "ss.hpp"
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
@@ -69,6 +70,76 @@ json_variant AtomConverter::ToJson(const LV2_Atom *atom)
|
|||||||
json_variant variant = ToVariant(const_cast<LV2_Atom*>(atom));
|
json_variant variant = ToVariant(const_cast<LV2_Atom*>(atom));
|
||||||
return std::move(variant);
|
return std::move(variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LV2_Atom*AtomConverter::ToAtom(const std::string&jsonString)
|
||||||
|
{
|
||||||
|
json_variant jvValue;
|
||||||
|
std::istringstream ss(jsonString);
|
||||||
|
json_reader reader(ss);
|
||||||
|
reader.read(&jvValue);
|
||||||
|
return ToAtom(jvValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AtomConverter::ToString(const LV2_Atom*atom)
|
||||||
|
{
|
||||||
|
json_variant v = ToJson(atom);
|
||||||
|
std::ostringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(v);
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
json_variant AtomConverter::MapPath(const json_variant&json, const std::string &pluginStoragePath)
|
||||||
|
{
|
||||||
|
std::string oType = json[OTYPE_TAG].as_string();
|
||||||
|
if (oType == SHORT_ATOM__Path)
|
||||||
|
{
|
||||||
|
std::string path = json["value"].as_string().c_str();
|
||||||
|
if (path.length() == 0 || path[0] == '/')
|
||||||
|
{
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
std::string newPath = SS(pluginStoragePath << "/" << path);
|
||||||
|
json_variant result = json_variant::make_object();
|
||||||
|
result[OTYPE_TAG] = json_variant(SHORT_ATOM__Path);
|
||||||
|
result["value"] = newPath;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json_variant AtomConverter::AbstractPath(const json_variant&json, const std::string &pluginStoragePath)
|
||||||
|
{
|
||||||
|
std::string oType = json[OTYPE_TAG].as_string();
|
||||||
|
if (oType == SHORT_ATOM__Path)
|
||||||
|
{
|
||||||
|
std::string path = json["value"].as_string().c_str();
|
||||||
|
if (path.length() == 0)
|
||||||
|
{
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
if (path.starts_with(pluginStoragePath))
|
||||||
|
{
|
||||||
|
auto pos = pluginStoragePath.length();
|
||||||
|
if (pos < path.length() && path[pos] == '/')
|
||||||
|
{
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
path = path.substr(pos);
|
||||||
|
}
|
||||||
|
json_variant result = json_variant::make_object();
|
||||||
|
result[OTYPE_TAG] = json_variant(SHORT_ATOM__Path);
|
||||||
|
result["value"] = path;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
LV2_Atom*AtomConverter::ToAtom(const json_variant&json)
|
LV2_Atom*AtomConverter::ToAtom(const json_variant&json)
|
||||||
{
|
{
|
||||||
if (outputBuffer.size() == 0)
|
if (outputBuffer.size() == 0)
|
||||||
@@ -517,6 +588,29 @@ std::string AtomConverter::TypeUridToString(LV2_URID urid)
|
|||||||
return map.UridToString(urid);
|
return map.UridToString(urid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool gEmptyPathInitialized = false;
|
||||||
|
static std::mutex gInitMutex;
|
||||||
|
std::string gEmptyPathstring;
|
||||||
|
|
||||||
|
std::string AtomConverter::EmptyPathstring()
|
||||||
|
{
|
||||||
|
std::lock_guard lock{gInitMutex};
|
||||||
|
|
||||||
|
if (!gEmptyPathInitialized)
|
||||||
|
{
|
||||||
|
json_variant v = TypedProperty(SHORT_ATOM__Path,std::string("",0));
|
||||||
|
std::ostringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(v);
|
||||||
|
|
||||||
|
gEmptyPathstring = ss.str();
|
||||||
|
|
||||||
|
}
|
||||||
|
return gEmptyPathstring;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AtomConverter::InitUrids()
|
void AtomConverter::InitUrids()
|
||||||
{
|
{
|
||||||
#define ATOM_INIT(name,shortName) urids.name = InitUrid(LV2_##name,#shortName)
|
#define ATOM_INIT(name,shortName) urids.name = InitUrid(LV2_##name,#shortName)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "lv2/atom/forge.h"
|
#include "lv2/atom/forge.h"
|
||||||
#include "json_variant.hpp"
|
#include "json_variant.hpp"
|
||||||
#include "atom_object.hpp"
|
#include "atom_object.hpp"
|
||||||
|
#include "json_variant.hpp"
|
||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
@@ -139,11 +140,25 @@ namespace pipedal
|
|||||||
|
|
||||||
/// @brief Convert a json variant to an atom.
|
/// @brief Convert a json variant to an atom.
|
||||||
/// @param json Json variant that matches the structure of the prototype.
|
/// @param json Json variant that matches the structure of the prototype.
|
||||||
/// @return An atom.
|
/// @return An atom. Not valid beyond the lifetime of the AtomConverter. Memory is owned by the AtomConverter.
|
||||||
/// @remarks
|
|
||||||
/// The json variant must match the structure of the LV2_Atom prototype supplied to
|
|
||||||
/// the constructor.
|
|
||||||
LV2_Atom*ToAtom(const json_variant& json);
|
LV2_Atom*ToAtom(const json_variant& json);
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief Convert a json string to an atom.
|
||||||
|
/// @param json Json string that matches the structure of the prototype.
|
||||||
|
/// @return An atom. Not valid beyond the lifetime of the AtomConverter. Memory is owned by the AtomConverter.
|
||||||
|
/// @remarks
|
||||||
|
|
||||||
|
LV2_Atom*ToAtom(const std::string &jsonString);
|
||||||
|
|
||||||
|
std::string ToString(const LV2_Atom*atom);
|
||||||
|
|
||||||
|
json_variant MapPath(const json_variant&json, const std::string &pluginStoragePath);
|
||||||
|
json_variant AbstractPath(const json_variant&json, const std::string &pluginStoragePath);
|
||||||
|
|
||||||
|
|
||||||
|
static std::string EmptyPathstring();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const std::string OTYPE_TAG;
|
static const std::string OTYPE_TAG;
|
||||||
static const std::string VTYPE_TAG;
|
static const std::string VTYPE_TAG;
|
||||||
@@ -160,14 +175,14 @@ namespace pipedal
|
|||||||
LV2_URID InitUrid(const char*uri, const char*shortUri);
|
LV2_URID InitUrid(const char*uri, const char*shortUri);
|
||||||
|
|
||||||
template <typename U>
|
template <typename U>
|
||||||
json_variant TypedProperty(const std::string type,U value)
|
static json_variant TypedProperty(const std::string type,U value)
|
||||||
{
|
{
|
||||||
json_variant result = json_variant::make_object();
|
json_variant result = json_variant::make_object();
|
||||||
result[OTYPE_TAG] = type;
|
result[OTYPE_TAG] = type;
|
||||||
result["value"] = json_variant(value);
|
result["value"] = json_variant(value);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
json_variant TypedProperty(const std::string type,const json_variant&& value)
|
static json_variant TypedProperty(const std::string type,const json_variant&& value)
|
||||||
{
|
{
|
||||||
json_variant result = json_variant::make_object();
|
json_variant result = json_variant::make_object();
|
||||||
result[OTYPE_TAG] = type;
|
result[OTYPE_TAG] = type;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "AudioHost.hpp"
|
#include "AudioHost.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
#include <lv2/atom/atom.h>
|
||||||
|
|
||||||
#include "Lv2Log.hpp"
|
#include "Lv2Log.hpp"
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@
|
|||||||
#include "AlsaDriver.hpp"
|
#include "AlsaDriver.hpp"
|
||||||
#include "DummyAudioDriver.hpp"
|
#include "DummyAudioDriver.hpp"
|
||||||
#include "AtomConverter.hpp"
|
#include "AtomConverter.hpp"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "PluginHost.hpp"
|
||||||
|
#include "PatchPropertyWriter.hpp"
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
@@ -107,6 +111,167 @@ static std::string GetGovernor()
|
|||||||
return pipedal::GetCpuGovernor();
|
return pipedal::GetCpuGovernor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace pipedal
|
||||||
|
{
|
||||||
|
|
||||||
|
struct PathPatchProperty
|
||||||
|
{
|
||||||
|
LV2_URID propertyUrid = 0;
|
||||||
|
std::vector<uint8_t> atomBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IndexedSnapshotValue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct InputControlEntry
|
||||||
|
{
|
||||||
|
bool isInputControl = false;
|
||||||
|
float value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
IndexedSnapshotValue(IEffect *effect, SnapshotValue *snapshotValue, PluginHost &pluginHost)
|
||||||
|
: pEffect(effect)
|
||||||
|
{
|
||||||
|
auto maxInputControl = effect->GetMaxInputControl();
|
||||||
|
inputControlValues.resize(maxInputControl);
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < maxInputControl; ++i)
|
||||||
|
{
|
||||||
|
bool isInputControl = effect->IsInputControl(i);
|
||||||
|
if (isInputControl)
|
||||||
|
{
|
||||||
|
inputControlValues[i] = InputControlEntry{isInputControl : true, value : effect->GetDefaultInputControlValue(i)};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputControlValues[i] = InputControlEntry{isInputControl : false, value : 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (snapshotValue)
|
||||||
|
{
|
||||||
|
for (auto &controlValue : snapshotValue->controlValues_)
|
||||||
|
{
|
||||||
|
auto index = effect->GetControlIndex(controlValue.key());
|
||||||
|
if (index >= 0 && index < inputControlValues.size())
|
||||||
|
{
|
||||||
|
inputControlValues[index].value = controlValue.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->lv2State = snapshotValue->lv2State_;
|
||||||
|
|
||||||
|
if (effect->IsLv2Effect())
|
||||||
|
{
|
||||||
|
Lv2Effect *lv2Effect = (Lv2Effect *)effect;
|
||||||
|
for (auto &pathProperty : snapshotValue->pathProperties_)
|
||||||
|
{
|
||||||
|
// only transmit changed path patch properties.
|
||||||
|
if (lv2Effect->GetPathPatchProperty(pathProperty.first) != pathProperty.second)
|
||||||
|
{
|
||||||
|
lv2Effect->SetPathPatchProperty(pathProperty.first, pathProperty.second);
|
||||||
|
|
||||||
|
PathPatchProperty pathPatchProperty;
|
||||||
|
pathPatchProperty.propertyUrid = pluginHost.GetLv2Urid(pathProperty.first.c_str());
|
||||||
|
// convert to json variant so we do a mappath operation.
|
||||||
|
json_variant vProperty;
|
||||||
|
std::istringstream ss(pathProperty.second);
|
||||||
|
json_reader reader(ss);
|
||||||
|
reader.read(&vProperty);
|
||||||
|
vProperty = pluginHost.MapPath(vProperty);
|
||||||
|
|
||||||
|
// now to atom format (what we want on the rt thread0)
|
||||||
|
AtomConverter atomConverter(pluginHost.GetMapFeature());
|
||||||
|
LV2_Atom *atomValue = atomConverter.ToAtom(vProperty);
|
||||||
|
|
||||||
|
size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4;
|
||||||
|
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
|
||||||
|
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
|
||||||
|
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ApplyValues(IEffect *effect)
|
||||||
|
{
|
||||||
|
if (effect != pEffect)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Wrong effect");
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < inputControlValues.size(); ++i)
|
||||||
|
{
|
||||||
|
InputControlEntry &e = inputControlValues[i];
|
||||||
|
if (e.isInputControl)
|
||||||
|
{
|
||||||
|
effect->SetControl((int)i, e.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &patchProperty : pathPatchProperties)
|
||||||
|
{
|
||||||
|
effect->SetPatchProperty(
|
||||||
|
patchProperty.propertyUrid, patchProperty.atomBuffer.size(), (LV2_Atom *)patchProperty.atomBuffer.data());
|
||||||
|
}
|
||||||
|
// effect->SetLv2State(lv2State);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
IEffect *pEffect;
|
||||||
|
Lv2PluginState lv2State;
|
||||||
|
std::vector<InputControlEntry> inputControlValues;
|
||||||
|
std::vector<PathPatchProperty> pathPatchProperties;
|
||||||
|
};
|
||||||
|
class IndexedSnapshot
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IndexedSnapshot(Snapshot *snapshot, std::shared_ptr<Lv2Pedalboard> currentPedalboard, PluginHost &pluginHost)
|
||||||
|
{
|
||||||
|
std::unordered_map<uint64_t, SnapshotValue *> index;
|
||||||
|
for (auto &value : snapshot->values_)
|
||||||
|
{
|
||||||
|
index[value.instanceId_] = &value;
|
||||||
|
}
|
||||||
|
std::vector<IEffect *> &effects = currentPedalboard->GetEffects();
|
||||||
|
snapshotValues.reserve(effects.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < effects.size(); ++i)
|
||||||
|
{
|
||||||
|
auto &effect = effects[i];
|
||||||
|
|
||||||
|
SnapshotValue *snapshotValue = getSnapshotValue(index, effect->GetInstanceId());
|
||||||
|
snapshotValues.push_back(IndexedSnapshotValue(effect, snapshotValue, pluginHost));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static SnapshotValue *getSnapshotValue(std::unordered_map<uint64_t, SnapshotValue *> &index, uint64_t instanceId)
|
||||||
|
{
|
||||||
|
auto iter = index.find(instanceId);
|
||||||
|
if (iter == index.end())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apply(std::vector<IEffect *> &effects)
|
||||||
|
{
|
||||||
|
if (effects.size() != snapshotValues.size())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Effects and values don't match");
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < snapshotValues.size(); ++i)
|
||||||
|
{
|
||||||
|
snapshotValues[i].ApplyValues(effects[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~IndexedSnapshot()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<IndexedSnapshotValue> snapshotValues;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class SystemMidiBinding
|
class SystemMidiBinding
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@@ -192,9 +357,12 @@ bool SystemMidiBinding::IsMatch(const MidiEvent &event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioHostImpl : public AudioHost, private AudioDriverHost
|
class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchWriterCallback
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
void OnWritePatchPropertyBuffer(
|
||||||
|
PatchPropertyWriter::Buffer *);
|
||||||
|
|
||||||
IHost *pHost = nullptr;
|
IHost *pHost = nullptr;
|
||||||
LV2_Atom_Forge inputWriterForge;
|
LV2_Atom_Forge inputWriterForge;
|
||||||
|
|
||||||
@@ -368,6 +536,8 @@ private:
|
|||||||
|
|
||||||
StopReaderThread();
|
StopReaderThread();
|
||||||
|
|
||||||
|
// delete any leaked snapshots.
|
||||||
|
CleanUpSnapshots();
|
||||||
|
|
||||||
// release any pdealboards owned by the process thread.
|
// release any pdealboards owned by the process thread.
|
||||||
this->activePedalboards.resize(0);
|
this->activePedalboards.resize(0);
|
||||||
@@ -489,8 +659,26 @@ private:
|
|||||||
|
|
||||||
RealtimePatchPropertyRequest *pParameterRequests = nullptr;
|
RealtimePatchPropertyRequest *pParameterRequests = nullptr;
|
||||||
|
|
||||||
|
void cancelParameterRequests()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
|
||||||
|
// auto p = this->pParameterRequests;
|
||||||
|
// while (pParameterRequests != nullptr)
|
||||||
|
// {
|
||||||
|
// auto nextParameterRequest = p->pNext;
|
||||||
|
// if (p->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet) {
|
||||||
|
// p->errorMessage = "Effect unloaded.";
|
||||||
|
// }
|
||||||
|
// p = nextParameterRequest;
|
||||||
|
// }
|
||||||
|
// this->realtimeWriter.ParameterRequestComplete(pParameterRequests);
|
||||||
|
// this->pParameterRequests = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool reEntered = false;
|
bool reEntered = false;
|
||||||
void ProcessInputCommands()
|
// returns false if the current effect has been replaced.
|
||||||
|
bool ProcessInputCommands()
|
||||||
{
|
{
|
||||||
if (reEntered)
|
if (reEntered)
|
||||||
{
|
{
|
||||||
@@ -586,6 +774,14 @@ private:
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case RingBufferCommand::LoadSnapshot:
|
||||||
|
{
|
||||||
|
IndexedSnapshot *snapshot;
|
||||||
|
realtimeReader.readComplete(&snapshot);
|
||||||
|
ApplySnapshot(snapshot);
|
||||||
|
realtimeWriter.FreeSnapshot(snapshot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case RingBufferCommand::SetVuSubscriptions:
|
case RingBufferCommand::SetVuSubscriptions:
|
||||||
{
|
{
|
||||||
RealtimeVuBuffers *configuration;
|
RealtimeVuBuffers *configuration;
|
||||||
@@ -622,16 +818,35 @@ private:
|
|||||||
// invalidate the possibly no-good subscriptions. Model will update them shortly.
|
// invalidate the possibly no-good subscriptions. Model will update them shortly.
|
||||||
freeRealtimeVuConfiguration();
|
freeRealtimeVuConfiguration();
|
||||||
freeRealtimeMonitorPortSubscriptions();
|
freeRealtimeMonitorPortSubscriptions();
|
||||||
|
cancelParameterRequests();
|
||||||
|
|
||||||
|
if (realtimeActivePedalboard)
|
||||||
|
{
|
||||||
|
realtimeActivePedalboard->ResetAtomBuffers();
|
||||||
|
// issue patch gets for all writable path properties.
|
||||||
|
for (auto pEffect : realtimeActivePedalboard->GetEffects())
|
||||||
|
{
|
||||||
|
pEffect->RequestAllPathPatchProperties();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
reEntered = false;
|
||||||
|
|
||||||
|
return false; // signal to caller that the effect has been replaced, and processing needs to start again.
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw PiPedalStateException("Unknown Ringbuffer command.");
|
throw PiPedalStateException("Unknown Ringbuffer command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reEntered = false;
|
reEntered = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplySnapshot(IndexedSnapshot *snapshot)
|
||||||
|
{
|
||||||
|
auto &effects = this->realtimeActivePedalboard->GetEffects();
|
||||||
|
snapshot->Apply(effects);
|
||||||
|
}
|
||||||
virtual void AckMidiProgramRequest(uint64_t requestId)
|
virtual void AckMidiProgramRequest(uint64_t requestId)
|
||||||
{
|
{
|
||||||
hostWriter.AckMidiProgramRequest(requestId);
|
hostWriter.AckMidiProgramRequest(requestId);
|
||||||
@@ -799,8 +1014,6 @@ private:
|
|||||||
|
|
||||||
std::mutex audioStoppedMutex;
|
std::mutex audioStoppedMutex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool IsAudioRunning()
|
bool IsAudioRunning()
|
||||||
{
|
{
|
||||||
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
|
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
|
||||||
@@ -816,20 +1029,31 @@ private:
|
|||||||
Lv2Log::info("Audio thread terminated.");
|
Lv2Log::info("Audio thread terminated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
virtual void OnProcess(size_t nframes)
|
virtual void OnProcess(size_t nframes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
float *in, *out;
|
float *in, *out;
|
||||||
|
|
||||||
pParameterRequests = nullptr;
|
Lv2Pedalboard *pedalboard = nullptr;
|
||||||
|
pedalboard = this->realtimeActivePedalboard;
|
||||||
|
if (pedalboard)
|
||||||
|
{
|
||||||
|
pedalboard->ResetAtomBuffers();
|
||||||
|
}
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
|
||||||
ProcessInputCommands();
|
if (ProcessInputCommands())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// else a new pedalboard was installed. start again.
|
||||||
|
pedalboard = this->realtimeActivePedalboard;
|
||||||
|
}
|
||||||
|
|
||||||
bool processed = false;
|
bool processed = false;
|
||||||
|
|
||||||
Lv2Pedalboard *pedalboard = this->realtimeActivePedalboard;
|
|
||||||
if (pedalboard != nullptr)
|
if (pedalboard != nullptr)
|
||||||
{
|
{
|
||||||
ProcessMidiInput();
|
ProcessMidiInput();
|
||||||
@@ -862,7 +1086,6 @@ private:
|
|||||||
|
|
||||||
if (buffersValid)
|
if (buffersValid)
|
||||||
{
|
{
|
||||||
pedalboard->ResetAtomBuffers();
|
|
||||||
pedalboard->ProcessParameterRequests(pParameterRequests);
|
pedalboard->ProcessParameterRequests(pParameterRequests);
|
||||||
|
|
||||||
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
|
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
|
||||||
@@ -885,6 +1108,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pedalboard->GatherPatchProperties(pParameterRequests);
|
pedalboard->GatherPatchProperties(pParameterRequests);
|
||||||
|
pedalboard->GatherPathPatchProperties(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,6 +1120,7 @@ private:
|
|||||||
if (pParameterRequests != nullptr)
|
if (pParameterRequests != nullptr)
|
||||||
{
|
{
|
||||||
this->realtimeWriter.ParameterRequestComplete(pParameterRequests);
|
this->realtimeWriter.ParameterRequestComplete(pParameterRequests);
|
||||||
|
pParameterRequests = nullptr;
|
||||||
}
|
}
|
||||||
// provide a grace period for undderruns, while spinning up. (15 second-ish)
|
// provide a grace period for undderruns, while spinning up. (15 second-ish)
|
||||||
if (currentSample <= this->overrunGracePeriodSamples && currentSample + nframes > this->overrunGracePeriodSamples)
|
if (currentSample <= this->overrunGracePeriodSamples && currentSample + nframes > this->overrunGracePeriodSamples)
|
||||||
@@ -926,7 +1151,6 @@ public:
|
|||||||
{
|
{
|
||||||
realtimeAtomBuffer.resize(32 * 1024);
|
realtimeAtomBuffer.resize(32 * 1024);
|
||||||
lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap());
|
lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap());
|
||||||
|
|
||||||
}
|
}
|
||||||
virtual ~AudioHostImpl()
|
virtual ~AudioHostImpl()
|
||||||
{
|
{
|
||||||
@@ -1193,6 +1417,18 @@ public:
|
|||||||
hostReader.read(&body);
|
hostReader.read(&body);
|
||||||
OnActivePedalboardReleased(body.oldEffect);
|
OnActivePedalboardReleased(body.oldEffect);
|
||||||
}
|
}
|
||||||
|
else if (command == RingBufferCommand::FreeSnapshot)
|
||||||
|
{
|
||||||
|
IndexedSnapshot *snapshot;
|
||||||
|
hostReader.read(&snapshot);
|
||||||
|
OnFreeSnapshot(snapshot);
|
||||||
|
}
|
||||||
|
else if (command == RingBufferCommand::SendPathPropertyBuffer)
|
||||||
|
{
|
||||||
|
PatchPropertyWriter::Buffer *buffer = nullptr;
|
||||||
|
hostReader.read(&buffer);
|
||||||
|
OnPathPropertyReceived(buffer);
|
||||||
|
}
|
||||||
else if (command == RingBufferCommand::AudioStopped)
|
else if (command == RingBufferCommand::AudioStopped)
|
||||||
{
|
{
|
||||||
AudioStoppedBody body;
|
AudioStoppedBody body;
|
||||||
@@ -1200,7 +1436,8 @@ public:
|
|||||||
OnAudioComplete();
|
OnAudioComplete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (command == RingBufferCommand::MidiProgramChange)
|
else if (command == RingBufferCommand::
|
||||||
|
MidiProgramChange)
|
||||||
{
|
{
|
||||||
RealtimeMidiProgramRequest programRequest;
|
RealtimeMidiProgramRequest programRequest;
|
||||||
hostReader.read(&programRequest);
|
hostReader.read(&programRequest);
|
||||||
@@ -1307,7 +1544,9 @@ public:
|
|||||||
{
|
{
|
||||||
this->isDummyAudioDriver = true;
|
this->isDummyAudioDriver = true;
|
||||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this));
|
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this));
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this->isDummyAudioDriver = false;
|
this->isDummyAudioDriver = false;
|
||||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this));
|
this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this));
|
||||||
}
|
}
|
||||||
@@ -1320,7 +1559,6 @@ public:
|
|||||||
this->currentSample = 0;
|
this->currentSample = 0;
|
||||||
this->underruns = 0;
|
this->underruns = 0;
|
||||||
|
|
||||||
|
|
||||||
this->inputRingBuffer.reset();
|
this->inputRingBuffer.reset();
|
||||||
this->outputRingBuffer.reset();
|
this->outputRingBuffer.reset();
|
||||||
this->hostReader.Reset();
|
this->hostReader.Reset();
|
||||||
@@ -1364,6 +1602,18 @@ public:
|
|||||||
{
|
{
|
||||||
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
|
pNotifyCallbacks->OnNotifyMidiProgramChange(programRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnPathPropertyReceived(PatchPropertyWriter::Buffer *buffer)
|
||||||
|
{
|
||||||
|
if (pNotifyCallbacks)
|
||||||
|
{
|
||||||
|
pNotifyCallbacks->OnNotifyPathPatchPropertyReceived(
|
||||||
|
buffer->instanceId,
|
||||||
|
buffer->patchPropertyUrid,
|
||||||
|
(LV2_Atom *)(buffer->memory.data()));
|
||||||
|
}
|
||||||
|
buffer->OnBufferReadComplete();
|
||||||
|
}
|
||||||
void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard)
|
void OnActivePedalboardReleased(Lv2Pedalboard *pPedalboard)
|
||||||
{
|
{
|
||||||
if (pPedalboard)
|
if (pPedalboard)
|
||||||
@@ -1465,6 +1715,49 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<IndexedSnapshot *> pendingSnapshots;
|
||||||
|
|
||||||
|
virtual void LoadSnapshot(Snapshot &snapshot, PluginHost &pluginHost) override
|
||||||
|
{
|
||||||
|
std::lock_guard guard(mutex);
|
||||||
|
if (active && this->currentPedalboard)
|
||||||
|
{
|
||||||
|
IndexedSnapshot *indexedSnapshot = new IndexedSnapshot(&snapshot, this->currentPedalboard, pluginHost);
|
||||||
|
pendingSnapshots.push_back(indexedSnapshot);
|
||||||
|
this->hostWriter.LoadSnapshot(indexedSnapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnNotifyPathPatchPropertyReceived(
|
||||||
|
int64_t instanceId,
|
||||||
|
const std::string &pathPatchPropertyUri,
|
||||||
|
const std::string &jsonAtom) override;
|
||||||
|
|
||||||
|
void OnFreeSnapshot(IndexedSnapshot *snapshot)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard guard(mutex);
|
||||||
|
for (auto i = pendingSnapshots.begin(); i != pendingSnapshots.end(); ++i)
|
||||||
|
{
|
||||||
|
if (*i == snapshot)
|
||||||
|
{
|
||||||
|
pendingSnapshots.erase(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete snapshot;
|
||||||
|
}
|
||||||
|
void CleanUpSnapshots()
|
||||||
|
{
|
||||||
|
for (auto i = pendingSnapshots.begin(); i != pendingSnapshots.end(); ++i)
|
||||||
|
{
|
||||||
|
IndexedSnapshot *snapshot = *i;
|
||||||
|
delete snapshot;
|
||||||
|
}
|
||||||
|
pendingSnapshots.clear();
|
||||||
|
}
|
||||||
|
|
||||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
|
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
|
||||||
{
|
{
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
@@ -1754,19 +2047,25 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
|
|||||||
else if (i->symbol() == "prevProgram")
|
else if (i->symbol() == "prevProgram")
|
||||||
{
|
{
|
||||||
this->prevMidiBinding.SetBinding(*i);
|
this->prevMidiBinding.SetBinding(*i);
|
||||||
} else if (i->symbol() == "startHotspot")
|
}
|
||||||
|
else if (i->symbol() == "startHotspot")
|
||||||
{
|
{
|
||||||
this->startHotspotMidiBinding.SetBinding(*i);
|
this->startHotspotMidiBinding.SetBinding(*i);
|
||||||
} else if (i->symbol() == "stopHotspot")
|
}
|
||||||
|
else if (i->symbol() == "stopHotspot")
|
||||||
{
|
{
|
||||||
this->stopHotspotMidiBinding.SetBinding(*i);
|
this->stopHotspotMidiBinding.SetBinding(*i);
|
||||||
} else if (i->symbol() == "reboot")
|
}
|
||||||
|
else if (i->symbol() == "reboot")
|
||||||
{
|
{
|
||||||
this->rebootMidiBinding.SetBinding(*i);
|
this->rebootMidiBinding.SetBinding(*i);
|
||||||
} else if (i->symbol() == "shutdown")
|
}
|
||||||
|
else if (i->symbol() == "shutdown")
|
||||||
{
|
{
|
||||||
this->shutdownMidiBinding.SetBinding(*i);
|
this->shutdownMidiBinding.SetBinding(*i);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
|
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1793,6 +2092,27 @@ bool AudioHostImpl::UpdatePluginStates(Pedalboard &pedalboard)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioHostImpl::OnNotifyPathPatchPropertyReceived(
|
||||||
|
int64_t instanceId,
|
||||||
|
const std::string &pathPatchPropertyUri,
|
||||||
|
const std::string &jsonAtom)
|
||||||
|
{
|
||||||
|
if (this->currentPedalboard)
|
||||||
|
{
|
||||||
|
IEffect*effect = this->currentPedalboard->GetEffect(instanceId);
|
||||||
|
if (!effect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (effect->IsLv2Effect())
|
||||||
|
{
|
||||||
|
Lv2Effect*lv2Effect = (Lv2Effect*)effect;
|
||||||
|
lv2Effect->SetPathPatchProperty(pathPatchPropertyUri,jsonAtom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
||||||
{
|
{
|
||||||
IEffect *effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
|
IEffect *effect = currentPedalboard->GetEffect(pedalboardItem.instanceId());
|
||||||
@@ -1821,6 +2141,12 @@ bool AudioHostImpl::UpdatePluginState(PedalboardItem &pedalboardItem)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioHostImpl::OnWritePatchPropertyBuffer(
|
||||||
|
PatchPropertyWriter::Buffer *buffer)
|
||||||
|
{
|
||||||
|
this->realtimeWriter.SendPathPropertyBuffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
JSON_MAP_BEGIN(JackHostStatus)
|
JSON_MAP_BEGIN(JackHostStatus)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, active)
|
JSON_MAP_REFERENCE(JackHostStatus, active)
|
||||||
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
JSON_MAP_REFERENCE(JackHostStatus, errorMessage)
|
||||||
|
|||||||
@@ -32,39 +32,42 @@
|
|||||||
#include "json_variant.hpp"
|
#include "json_variant.hpp"
|
||||||
#include "RealtimeMidiEventType.hpp"
|
#include "RealtimeMidiEventType.hpp"
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal
|
||||||
|
|
||||||
|
|
||||||
struct RealtimeMidiProgramRequest;
|
|
||||||
struct RealtimeNextMidiProgramRequest;
|
|
||||||
|
|
||||||
using PortMonitorCallback = std::function<void (int64_t handle, float value)>;
|
|
||||||
|
|
||||||
|
|
||||||
class MonitorPortUpdate
|
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
|
struct RealtimeMidiProgramRequest;
|
||||||
|
struct RealtimeNextMidiProgramRequest;
|
||||||
|
class PluginHost;
|
||||||
|
class Pedalboard;
|
||||||
|
|
||||||
|
using PortMonitorCallback = std::function<void(int64_t handle, float value)>;
|
||||||
|
|
||||||
|
class MonitorPortUpdate
|
||||||
|
{
|
||||||
|
public:
|
||||||
PortMonitorCallback *callbackPtr; // pointer because function<>'s probably aren't POD.
|
PortMonitorCallback *callbackPtr; // pointer because function<>'s probably aren't POD.
|
||||||
int64_t subscriptionHandle;
|
int64_t subscriptionHandle;
|
||||||
float value;
|
float value;
|
||||||
};
|
};
|
||||||
class RealtimePatchPropertyRequest {
|
class RealtimePatchPropertyRequest
|
||||||
public:
|
{
|
||||||
|
public:
|
||||||
int64_t clientId;
|
int64_t clientId;
|
||||||
int64_t instanceId;
|
int64_t instanceId;
|
||||||
LV2_URID uridUri;
|
LV2_URID uridUri;
|
||||||
|
|
||||||
enum class RequestType {
|
enum class RequestType
|
||||||
|
{
|
||||||
PatchGet,
|
PatchGet,
|
||||||
PatchSet
|
PatchSet
|
||||||
};
|
};
|
||||||
RequestType requestType;
|
RequestType requestType;
|
||||||
|
|
||||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestComplete;
|
std::function<void(RealtimePatchPropertyRequest *)> onPatchRequestComplete;
|
||||||
std::function<void(const std::string &jsonResjult)> onSuccess;
|
std::function<void(const std::string &jsonResjult)> onSuccess;
|
||||||
std::function<void(const std::string &error)> onError;
|
std::function<void(const std::string &error)> onError;
|
||||||
|
|
||||||
const char*errorMessage = nullptr;
|
const char *errorMessage = nullptr;
|
||||||
std::string jsonResponse;
|
std::string jsonResponse;
|
||||||
|
|
||||||
RealtimePatchPropertyRequest *pNext = nullptr;
|
RealtimePatchPropertyRequest *pNext = nullptr;
|
||||||
@@ -78,27 +81,28 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
size_t GetSize() const { return responseLength; }
|
size_t GetSize() const { return responseLength; }
|
||||||
uint8_t *GetBuffer() {
|
uint8_t *GetBuffer()
|
||||||
|
{
|
||||||
if (responseLength > sizeof(atomBuffer))
|
if (responseLength > sizeof(atomBuffer))
|
||||||
{
|
{
|
||||||
return &(longAtomBuffer[0]);
|
return &(longAtomBuffer[0]);
|
||||||
}
|
}
|
||||||
return atomBuffer;
|
return atomBuffer;
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
|
private:
|
||||||
int responseLength = 0;
|
int responseLength = 0;
|
||||||
uint8_t atomBuffer[2048];
|
uint8_t atomBuffer[2048];
|
||||||
std::vector<uint8_t> longAtomBuffer;
|
std::vector<uint8_t> longAtomBuffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RealtimePatchPropertyRequest(
|
RealtimePatchPropertyRequest(
|
||||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
|
std::function<void(RealtimePatchPropertyRequest *)> onPatchRequestcomplete_,
|
||||||
int64_t clientId_,
|
int64_t clientId_,
|
||||||
int64_t instanceId_,
|
int64_t instanceId_,
|
||||||
LV2_URID uridUri_,
|
LV2_URID uridUri_,
|
||||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||||
std::function<void(const std::string &error)> onError_
|
std::function<void(const std::string &error)> onError_)
|
||||||
)
|
|
||||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||||
clientId(clientId_),
|
clientId(clientId_),
|
||||||
instanceId(instanceId_),
|
instanceId(instanceId_),
|
||||||
@@ -109,14 +113,13 @@ public:
|
|||||||
requestType = RequestType::PatchGet;
|
requestType = RequestType::PatchGet;
|
||||||
}
|
}
|
||||||
RealtimePatchPropertyRequest(
|
RealtimePatchPropertyRequest(
|
||||||
std::function<void (RealtimePatchPropertyRequest*)> onPatchRequestcomplete_,
|
std::function<void(RealtimePatchPropertyRequest *)> onPatchRequestcomplete_,
|
||||||
int64_t clientId_,
|
int64_t clientId_,
|
||||||
int64_t instanceId_,
|
int64_t instanceId_,
|
||||||
LV2_URID uridUri_,
|
LV2_URID uridUri_,
|
||||||
LV2_Atom*atomValue,
|
LV2_Atom *atomValue,
|
||||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||||
std::function<void(const std::string &error)> onError_
|
std::function<void(const std::string &error)> onError_)
|
||||||
)
|
|
||||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||||
clientId(clientId_),
|
clientId(clientId_),
|
||||||
instanceId(instanceId_),
|
instanceId(instanceId_),
|
||||||
@@ -125,49 +128,51 @@ public:
|
|||||||
onError(onError_)
|
onError(onError_)
|
||||||
{
|
{
|
||||||
requestType = RequestType::PatchSet;
|
requestType = RequestType::PatchSet;
|
||||||
size_t size = atomValue->size+ sizeof(LV2_Atom);
|
size_t size = atomValue->size + sizeof(LV2_Atom);
|
||||||
SetSize(size);
|
SetSize(size);
|
||||||
memcpy(GetBuffer(),atomValue,size);
|
memcpy(GetBuffer(), atomValue, size);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
};
|
class MonitorPortSubscription
|
||||||
|
{
|
||||||
|
public:
|
||||||
class MonitorPortSubscription {
|
|
||||||
public:
|
|
||||||
int64_t subscriptionHandle;
|
int64_t subscriptionHandle;
|
||||||
int64_t instanceid;
|
int64_t instanceid;
|
||||||
std::string key;
|
std::string key;
|
||||||
float updateInterval;
|
float updateInterval;
|
||||||
PortMonitorCallback onUpdate;
|
PortMonitorCallback onUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
class IAudioHostCallbacks
|
||||||
|
{
|
||||||
class IAudioHostCallbacks {
|
public:
|
||||||
public:
|
|
||||||
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
|
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0;
|
||||||
virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0;
|
virtual void OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0;
|
||||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
|
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates) = 0;
|
||||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
||||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0;
|
||||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
||||||
|
|
||||||
|
virtual void OnNotifyPathPatchPropertyReceived(
|
||||||
|
int64_t instanceId,
|
||||||
|
LV2_URID pathPatchProperty,
|
||||||
|
LV2_Atom *pathProperty) = 0;
|
||||||
|
|
||||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) = 0;
|
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) = 0;
|
||||||
|
|
||||||
//virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0;
|
// virtual bool WantsAtomOutput(uint64_t instanceId,LV2_URID patchSetProperty) = 0;
|
||||||
//virtual void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0;
|
// virtual void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID patchSetProperty, const std::string&atomJson) = 0;
|
||||||
|
|
||||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
|
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) = 0;
|
||||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
|
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) = 0;
|
||||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) = 0;
|
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
|
||||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
class JackHostStatus
|
||||||
|
{
|
||||||
|
public:
|
||||||
class JackHostStatus {
|
|
||||||
public:
|
|
||||||
bool active_ = false;
|
bool active_ = false;
|
||||||
std::string errorMessage_;
|
std::string errorMessage_;
|
||||||
bool restarting_;
|
bool restarting_;
|
||||||
@@ -180,34 +185,33 @@ public:
|
|||||||
std::string governor_;
|
std::string governor_;
|
||||||
|
|
||||||
DECLARE_JSON_MAP(JackHostStatus);
|
DECLARE_JSON_MAP(JackHostStatus);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IHost;
|
||||||
|
|
||||||
};
|
class AudioHost
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
AudioHost() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static AudioHost *CreateInstance(IHost *pHost);
|
||||||
|
virtual ~AudioHost() {};
|
||||||
|
|
||||||
class IHost;
|
virtual void UpdateServerConfiguration(const JackServerSettings &jackServerSettings,
|
||||||
|
std::function<void(bool success, const std::string &errorMessage)> onComplete) = 0;
|
||||||
class AudioHost {
|
|
||||||
protected:
|
|
||||||
AudioHost() { }
|
|
||||||
public:
|
|
||||||
static AudioHost*CreateInstance(IHost *pHost);
|
|
||||||
virtual ~AudioHost() { };
|
|
||||||
|
|
||||||
virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings,
|
|
||||||
std::function<void(bool success, const std::string&errorMessage)> onComplete) = 0;
|
|
||||||
|
|
||||||
virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) = 0;
|
virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) = 0;
|
||||||
|
|
||||||
virtual void SetListenForMidiEvent(bool listen) = 0;
|
virtual void SetListenForMidiEvent(bool listen) = 0;
|
||||||
virtual void SetListenForAtomOutput(bool listen) = 0;
|
virtual void SetListenForAtomOutput(bool listen) = 0;
|
||||||
|
|
||||||
virtual bool UpdatePluginStates(Pedalboard& pedalboard) = 0;
|
virtual bool UpdatePluginStates(Pedalboard &pedalboard) = 0;
|
||||||
virtual bool UpdatePluginState(PedalboardItem& pedalboardItem) = 0;
|
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) = 0;
|
||||||
|
|
||||||
virtual std::string AtomToJson(const LV2_Atom*atom) = 0;
|
virtual std::string AtomToJson(const LV2_Atom *atom) = 0;
|
||||||
|
|
||||||
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
|
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) = 0;
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
||||||
virtual uint32_t GetSampleRate() = 0;
|
virtual uint32_t GetSampleRate() = 0;
|
||||||
@@ -216,7 +220,7 @@ public:
|
|||||||
|
|
||||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
||||||
|
|
||||||
virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0;
|
virtual void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) = 0;
|
||||||
virtual void SetInputVolume(float value) = 0;
|
virtual void SetInputVolume(float value) = 0;
|
||||||
virtual void SetOutputVolume(float value) = 0;
|
virtual void SetOutputVolume(float value) = 0;
|
||||||
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) = 0;
|
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) = 0;
|
||||||
@@ -227,16 +231,19 @@ public:
|
|||||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
|
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
|
||||||
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
|
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
|
||||||
|
|
||||||
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings) = 0;
|
virtual void SetSystemMidiBindings(const std::vector<MidiBinding> &bindings) = 0;
|
||||||
|
|
||||||
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest*pParameterRequest) = 0;
|
virtual void sendRealtimeParameterRequest(RealtimePatchPropertyRequest *pParameterRequest) = 0;
|
||||||
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
|
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
|
||||||
|
|
||||||
virtual JackHostStatus getJackStatus() = 0;
|
virtual JackHostStatus getJackStatus() = 0;
|
||||||
|
|
||||||
};
|
virtual void LoadSnapshot(Snapshot &snapshot, PluginHost &pluginHost) = 0;
|
||||||
|
|
||||||
|
virtual void OnNotifyPathPatchPropertyReceived(
|
||||||
|
int64_t instanceId,
|
||||||
|
const std::string &pathPatchPropertyUri,
|
||||||
|
const std::string &jsonAtom) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pipedal.
|
||||||
|
|
||||||
} //namespace pipedal.
|
|
||||||
@@ -100,6 +100,7 @@ public:
|
|||||||
presets_.clear();
|
presets_.clear();
|
||||||
selectedPreset_ = -1;
|
selectedPreset_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void move(size_t from, size_t to)
|
void move(size_t from, size_t to)
|
||||||
{
|
{
|
||||||
if (from >= this->presets_.size()) {
|
if (from >= this->presets_.size()) {
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set (PIPEDAL_SOURCES
|
set (PIPEDAL_SOURCES
|
||||||
|
PatchPropertyWriter.hpp
|
||||||
|
PresetBundle.cpp PresetBundle.hpp
|
||||||
RealtimeMidiEventType.hpp
|
RealtimeMidiEventType.hpp
|
||||||
DBusToLv2Log.cpp DBusToLv2Log.hpp
|
DBusToLv2Log.cpp DBusToLv2Log.hpp
|
||||||
HotspotManager.cpp HotspotManager.hpp
|
HotspotManager.cpp HotspotManager.hpp
|
||||||
|
|||||||
@@ -691,6 +691,10 @@ void SetVarPermissions(
|
|||||||
{
|
{
|
||||||
if (fs::exists(path))
|
if (fs::exists(path))
|
||||||
{
|
{
|
||||||
|
if (fs::is_symlink(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
std::ignore = chown(path.c_str(), uid, gid);
|
std::ignore = chown(path.c_str(), uid, gid);
|
||||||
if (fs::is_directory(path))
|
if (fs::is_directory(path))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,8 +32,19 @@ namespace pipedal {
|
|||||||
public:
|
public:
|
||||||
virtual ~IEffect() {}
|
virtual ~IEffect() {}
|
||||||
virtual uint64_t GetInstanceId() const = 0;
|
virtual uint64_t GetInstanceId() const = 0;
|
||||||
|
virtual bool IsLv2Effect() const = 0;
|
||||||
|
virtual uint64_t GetMaxInputControl() const = 0;
|
||||||
|
virtual bool IsInputControl(uint64_t index) const = 0;
|
||||||
|
virtual float GetDefaultInputControlValue(uint64_t index) const = 0;
|
||||||
|
|
||||||
virtual int GetControlIndex(const std::string&symbol) const = 0;
|
virtual int GetControlIndex(const std::string&symbol) const = 0;
|
||||||
virtual void SetControl(int index, float value) = 0;
|
virtual void SetControl(int index, float value) = 0;
|
||||||
|
|
||||||
|
virtual void SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value) = 0;
|
||||||
|
virtual void RequestPatchProperty(LV2_URID uridUri) = 0;
|
||||||
|
virtual void RequestAllPathPatchProperties() = 0;
|
||||||
|
|
||||||
|
|
||||||
virtual float GetControlValue(int index) const = 0;
|
virtual float GetControlValue(int index) const = 0;
|
||||||
virtual void SetBypass(bool enable) = 0;
|
virtual void SetBypass(bool enable) = 0;
|
||||||
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
virtual float GetOutputControlValue(int controlIndex) const = 0;
|
||||||
@@ -47,9 +58,6 @@ namespace pipedal {
|
|||||||
virtual bool GetRequestStateChangedNotification() const = 0;
|
virtual bool GetRequestStateChangedNotification() const = 0;
|
||||||
virtual void SetRequestStateChangedNotification(bool value) = 0;
|
virtual void SetRequestStateChangedNotification(bool value) = 0;
|
||||||
|
|
||||||
//virtual std::string AtomToJson(uint8_t*pAtom) = 0;
|
|
||||||
//virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
|
|
||||||
|
|
||||||
|
|
||||||
virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
|
virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
|
||||||
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
|
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
|
||||||
@@ -62,6 +70,7 @@ namespace pipedal {
|
|||||||
|
|
||||||
virtual bool IsVst3() const = 0;
|
virtual bool IsVst3() const = 0;
|
||||||
virtual bool GetLv2State(Lv2PluginState*state) = 0;
|
virtual bool GetLv2State(Lv2PluginState*state) = 0;
|
||||||
|
virtual void SetLv2State(Lv2PluginState&state) = 0;
|
||||||
|
|
||||||
virtual bool HasErrorMessage() const = 0;
|
virtual bool HasErrorMessage() const = 0;
|
||||||
virtual const char*TakeErrorMessage() = 0;
|
virtual const char*TakeErrorMessage() = 0;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Lv2Effect::Lv2Effect(
|
|||||||
IHost *pHost_,
|
IHost *pHost_,
|
||||||
const std::shared_ptr<Lv2PluginInfo> &info_,
|
const std::shared_ptr<Lv2PluginInfo> &info_,
|
||||||
PedalboardItem &pedalboardItem)
|
PedalboardItem &pedalboardItem)
|
||||||
: pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost)
|
: pHost(pHost_), pInstance(nullptr), info(info_), urids(pHost), instanceId(pedalboardItem.instanceId())
|
||||||
{
|
{
|
||||||
auto pWorld = pHost_->getWorld();
|
auto pWorld = pHost_->getWorld();
|
||||||
|
|
||||||
@@ -60,6 +60,23 @@ Lv2Effect::Lv2Effect(
|
|||||||
|
|
||||||
this->bypass = pedalboardItem.isEnabled();
|
this->bypass = pedalboardItem.isEnabled();
|
||||||
|
|
||||||
|
// stash a list of known file properties that we want to keep synced.
|
||||||
|
if (info->piPedalUI())
|
||||||
|
{
|
||||||
|
for (auto fileProperty : info->piPedalUI()->fileProperties())
|
||||||
|
{
|
||||||
|
LV2_URID filePropertyUrid = pHost->GetLv2Urid(fileProperty->patchProperty().c_str());
|
||||||
|
this->pathProperties.push_back(filePropertyUrid);
|
||||||
|
|
||||||
|
this->pathPropertyWriters.push_back(PatchPropertyWriter(instanceId, filePropertyUrid));
|
||||||
|
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &pathProperty: pedalboardItem.pathProperties_)
|
||||||
|
{
|
||||||
|
SetPathPatchProperty(pathProperty.first,pathProperty.second);
|
||||||
|
}
|
||||||
|
|
||||||
// initialize the atom forge used on the realtime thread.
|
// initialize the atom forge used on the realtime thread.
|
||||||
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
||||||
lv2_atom_forge_init(&inputForgeRt, map);
|
lv2_atom_forge_init(&inputForgeRt, map);
|
||||||
@@ -146,9 +163,28 @@ Lv2Effect::Lv2Effect(
|
|||||||
|
|
||||||
this->instanceId = pedalboardItem.instanceId();
|
this->instanceId = pedalboardItem.instanceId();
|
||||||
|
|
||||||
this->controlValues.resize(info->ports().size());
|
PreparePortIndices();
|
||||||
|
|
||||||
// Copy default pedalboard settings.
|
// Copy default pedalboard settings.
|
||||||
|
size_t maxPortIndex = 0;
|
||||||
|
std::vector<std::shared_ptr<Lv2PortInfo>> &t = info->ports();
|
||||||
|
for (std::shared_ptr<Lv2PortInfo> &port : info->ports())
|
||||||
|
{
|
||||||
|
if (port->is_control_port())
|
||||||
|
{
|
||||||
|
auto index = port->index();
|
||||||
|
if (index + 1 > maxPortIndex)
|
||||||
|
{
|
||||||
|
maxPortIndex = index + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maxPortIndex > info->ports().size())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Ports are not consecutive");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->controlValues.resize(info->ports().size());
|
||||||
for (auto i = pedalboardItem.controlValues().begin(); i != pedalboardItem.controlValues().end(); ++i)
|
for (auto i = pedalboardItem.controlValues().begin(); i != pedalboardItem.controlValues().end(); ++i)
|
||||||
{
|
{
|
||||||
auto &v = (*i);
|
auto &v = (*i);
|
||||||
@@ -158,7 +194,7 @@ Lv2Effect::Lv2Effect(
|
|||||||
this->controlValues[index] = v.value();
|
this->controlValues[index] = v.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PreparePortIndices();
|
|
||||||
ConnectControlPorts();
|
ConnectControlPorts();
|
||||||
|
|
||||||
if (!pedalboardItem.lilvPresetUri().empty())
|
if (!pedalboardItem.lilvPresetUri().empty())
|
||||||
@@ -194,7 +230,9 @@ void Lv2Effect::RestoreState(PedalboardItem &pedalboardItem)
|
|||||||
if (pedalboardItem.lv2State().isValid_)
|
if (pedalboardItem.lv2State().isValid_)
|
||||||
{
|
{
|
||||||
this->stateInterface->Restore(pedalboardItem.lv2State());
|
this->stateInterface->Restore(pedalboardItem.lv2State());
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// set the state to default state.
|
// set the state to default state.
|
||||||
auto savedState = this->stateInterface->Save();
|
auto savedState = this->stateInterface->Save();
|
||||||
pedalboardItem.lv2State(savedState);
|
pedalboardItem.lv2State(savedState);
|
||||||
@@ -234,6 +272,9 @@ void Lv2Effect::ConnectControlPorts()
|
|||||||
}
|
}
|
||||||
void Lv2Effect::PreparePortIndices()
|
void Lv2Effect::PreparePortIndices()
|
||||||
{
|
{
|
||||||
|
size_t nPorts = info->ports().size();
|
||||||
|
isInputControlPort.resize(nPorts);
|
||||||
|
this->defaultInputControlValues.resize(nPorts);
|
||||||
|
|
||||||
for (int i = 0; i < info->ports().size(); ++i)
|
for (int i = 0; i < info->ports().size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -270,7 +311,23 @@ void Lv2Effect::PreparePortIndices()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (port->is_control_port())
|
||||||
|
{
|
||||||
|
controlIndex[port->symbol()] = port->index();
|
||||||
|
if (port->is_input())
|
||||||
|
{
|
||||||
|
this->isInputControlPort[i] = true;
|
||||||
|
this->defaultInputControlValues[i] = port->default_value();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t maxInputControlPort = isInputControlPort.size();
|
||||||
|
while (maxInputControlPort != 0 && !isInputControlPort[maxInputControlPort - 1])
|
||||||
|
{
|
||||||
|
--maxInputControlPort;
|
||||||
|
}
|
||||||
|
this->maxInputControlPort = maxInputControlPort;
|
||||||
|
|
||||||
inputAudioBuffers.resize(inputAudioPortIndices.size());
|
inputAudioBuffers.resize(inputAudioPortIndices.size());
|
||||||
outputAudioBuffers.resize(outputAudioPortIndices.size());
|
outputAudioBuffers.resize(outputAudioPortIndices.size());
|
||||||
inputAtomBuffers.resize(inputAtomPortIndices.size());
|
inputAtomBuffers.resize(inputAtomPortIndices.size());
|
||||||
@@ -323,13 +380,12 @@ void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
|||||||
|
|
||||||
int Lv2Effect::GetControlIndex(const std::string &key) const
|
int Lv2Effect::GetControlIndex(const std::string &key) const
|
||||||
{
|
{
|
||||||
for (int i = 0; i < info->ports().size(); ++i)
|
auto i = controlIndex.find(key);
|
||||||
|
if (i == controlIndex.end())
|
||||||
{
|
{
|
||||||
auto &port = info->ports()[i];
|
|
||||||
if (port->symbol() == key)
|
|
||||||
return port->index();
|
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
return i->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lv2Effect::~Lv2Effect()
|
Lv2Effect::~Lv2Effect()
|
||||||
@@ -438,7 +494,6 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
worker->EmitResponses();
|
worker->EmitResponses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// do soft bypass.
|
// do soft bypass.
|
||||||
if (this->bypassSamplesRemaining == 0)
|
if (this->bypassSamplesRemaining == 0)
|
||||||
{
|
{
|
||||||
@@ -610,6 +665,14 @@ void Lv2Effect::RequestPatchProperty(LV2_URID uridUri)
|
|||||||
lv2_atom_forge_urid(&inputForgeRt, uridUri);
|
lv2_atom_forge_urid(&inputForgeRt, uridUri);
|
||||||
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
|
lv2_atom_forge_pop(&inputForgeRt, &objectFrame);
|
||||||
}
|
}
|
||||||
|
void Lv2Effect::RequestAllPathPatchProperties()
|
||||||
|
{
|
||||||
|
for (LV2_URID urid : this->pathProperties)
|
||||||
|
{
|
||||||
|
RequestPatchProperty(urid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Lv2Effect::SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value)
|
void Lv2Effect::SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value)
|
||||||
{
|
{
|
||||||
lv2_atom_forge_frame_time(&inputForgeRt, 0);
|
lv2_atom_forge_frame_time(&inputForgeRt, 0);
|
||||||
@@ -660,12 +723,68 @@ void Lv2Effect::RelayPatchSetMessages(uint64_t instanceId, RealtimeRingBufferWri
|
|||||||
{
|
{
|
||||||
requestStateChangedNotification = false;
|
requestStateChangedNotification = false;
|
||||||
realtimeRingBufferWriter->Lv2StateChanged(instanceId);
|
realtimeRingBufferWriter->Lv2StateChanged(instanceId);
|
||||||
} else if (maybeStateChanged)
|
}
|
||||||
|
else if (maybeStateChanged)
|
||||||
{
|
{
|
||||||
realtimeRingBufferWriter->MaybeLv2StateChanged(instanceId);
|
realtimeRingBufferWriter->MaybeLv2StateChanged(instanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Lv2Effect::GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter)
|
||||||
|
{
|
||||||
|
if (pathPropertyWriters.size() != 0)
|
||||||
|
{
|
||||||
|
LV2_Atom_Sequence *controlInput = (LV2_Atom_Sequence *)GetAtomOutputBuffer();
|
||||||
|
if (controlInput == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||||
|
{
|
||||||
|
|
||||||
|
auto frame_offset = ev->time.frames; // not really interested.
|
||||||
|
|
||||||
|
if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type))
|
||||||
|
{
|
||||||
|
const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
|
||||||
|
if (obj->body.otype == urids.patch__Set)
|
||||||
|
{
|
||||||
|
// Get the property and value of the set message
|
||||||
|
const LV2_Atom *property = NULL;
|
||||||
|
const LV2_Atom *value = NULL;
|
||||||
|
|
||||||
|
lv2_atom_object_get(
|
||||||
|
obj,
|
||||||
|
urids.patch__property, &property,
|
||||||
|
urids.patch__value, &value,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (property && property->type == urids.atom__URID && value)
|
||||||
|
{
|
||||||
|
LV2_URID key = ((const LV2_Atom_URID *)property)->body;
|
||||||
|
|
||||||
|
for (PatchPropertyWriter &pathPropertyWriter : pathPropertyWriters)
|
||||||
|
{
|
||||||
|
if (key == pathPropertyWriter.patchPropertyUrid)
|
||||||
|
{
|
||||||
|
auto buffer = pathPropertyWriter.AquireWriteBuffer();
|
||||||
|
size_t atom_size = value->size + sizeof(LV2_Atom);
|
||||||
|
buffer->memory.resize(atom_size);
|
||||||
|
memcpy(buffer->memory.data(), value, atom_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &writer : this->pathPropertyWriters)
|
||||||
|
{
|
||||||
|
writer.FlushWrites(cbPatchWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
||||||
{
|
{
|
||||||
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
if (pRequest->requestType == RealtimePatchPropertyRequest::RequestType::PatchGet)
|
||||||
@@ -711,6 +830,27 @@ void Lv2Effect::GatherPatchProperties(RealtimePatchPropertyRequest *pRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Lv2Effect::SetLv2State(Lv2PluginState &state)
|
||||||
|
{
|
||||||
|
if (state.isValid_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->stateInterface)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
this->stateInterface->Restore(state);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Failed to restore LV2 state.");
|
||||||
|
}
|
||||||
|
}
|
||||||
bool Lv2Effect::GetLv2State(Lv2PluginState *state)
|
bool Lv2Effect::GetLv2State(Lv2PluginState *state)
|
||||||
{
|
{
|
||||||
if (!this->stateInterface)
|
if (!this->stateInterface)
|
||||||
@@ -755,5 +895,31 @@ void Lv2Effect::OnLogDebug(const char *message)
|
|||||||
Lv2Log::debug(message);
|
Lv2Log::debug(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lv2Effect::GetRequestStateChangedNotification() const{ return requestStateChangedNotification; }
|
bool Lv2Effect::GetRequestStateChangedNotification() const { return requestStateChangedNotification; }
|
||||||
void Lv2Effect::SetRequestStateChangedNotification(bool value) { requestStateChangedNotification = value; }
|
void Lv2Effect::SetRequestStateChangedNotification(bool value) { requestStateChangedNotification = value; }
|
||||||
|
|
||||||
|
uint64_t Lv2Effect::GetMaxInputControl() const { return maxInputControlPort; }
|
||||||
|
|
||||||
|
bool Lv2Effect::IsInputControl(uint64_t index) const
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= isInputControlPort.size())
|
||||||
|
return false;
|
||||||
|
return isInputControlPort[index];
|
||||||
|
}
|
||||||
|
float Lv2Effect::GetDefaultInputControlValue(uint64_t index) const
|
||||||
|
{
|
||||||
|
return defaultInputControlValues[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Lv2Effect::GetPathPatchProperty(const std::string &propertyUri)
|
||||||
|
{
|
||||||
|
if (!this->mainThreadPathProperties.contains(propertyUri))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this->mainThreadPathProperties[propertyUri];
|
||||||
|
}
|
||||||
|
void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::string &jsonAtom)
|
||||||
|
{
|
||||||
|
mainThreadPathProperties[propertyUri] = jsonAtom;
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include <lilv/lilv.h>
|
#include <lilv/lilv.h>
|
||||||
#include "BufferPool.hpp"
|
#include "BufferPool.hpp"
|
||||||
#include "FileBrowserFilesFeature.hpp"
|
#include "FileBrowserFilesFeature.hpp"
|
||||||
|
#include "PatchPropertyWriter.hpp"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "IEffect.hpp"
|
#include "IEffect.hpp"
|
||||||
#include "Worker.hpp"
|
#include "Worker.hpp"
|
||||||
@@ -42,6 +44,7 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
|
|
||||||
class RealtimeRingBufferWriter;
|
class RealtimeRingBufferWriter;
|
||||||
|
class IPatchWriterCallback;
|
||||||
|
|
||||||
class Lv2Effect : public IEffect, private LogFeature::LogMessageListener
|
class Lv2Effect : public IEffect, private LogFeature::LogMessageListener
|
||||||
{
|
{
|
||||||
@@ -52,6 +55,8 @@ namespace pipedal
|
|||||||
virtual void OnLogDebug(const char*message);
|
virtual void OnLogDebug(const char*message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::unordered_map<std::string,int> controlIndex;
|
||||||
|
|
||||||
FileBrowserFilesFeature fileBrowserFilesFeature;
|
FileBrowserFilesFeature fileBrowserFilesFeature;
|
||||||
std::unique_ptr<StateInterface> stateInterface;
|
std::unique_ptr<StateInterface> stateInterface;
|
||||||
void RestoreState(PedalboardItem&pedalboardItem);
|
void RestoreState(PedalboardItem&pedalboardItem);
|
||||||
@@ -86,6 +91,11 @@ namespace pipedal
|
|||||||
std::vector<const LV2_Feature *> features;
|
std::vector<const LV2_Feature *> features;
|
||||||
LV2_Feature *work_schedule_feature = nullptr;
|
LV2_Feature *work_schedule_feature = nullptr;
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t maxInputControlPort = 0;
|
||||||
|
std::vector<bool> isInputControlPort;
|
||||||
|
std::vector<float> defaultInputControlValues;
|
||||||
|
|
||||||
virtual std::string GetUri() const { return info->uri(); }
|
virtual std::string GetUri() const { return info->uri(); }
|
||||||
|
|
||||||
std::vector<const Lv2PortInfo *> realtimePortInfo;
|
std::vector<const Lv2PortInfo *> realtimePortInfo;
|
||||||
@@ -99,6 +109,10 @@ namespace pipedal
|
|||||||
|
|
||||||
LV2_Atom_Forge outputForgeRt;
|
LV2_Atom_Forge outputForgeRt;
|
||||||
|
|
||||||
|
std::vector<LV2_URID> pathProperties;
|
||||||
|
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||||
|
|
||||||
|
std::unordered_map<std::string,std::string> mainThreadPathProperties;
|
||||||
|
|
||||||
class Urids
|
class Urids
|
||||||
{
|
{
|
||||||
@@ -164,6 +178,7 @@ namespace pipedal
|
|||||||
uint32_t size,
|
uint32_t size,
|
||||||
const void *data);
|
const void *data);
|
||||||
|
|
||||||
|
|
||||||
void ResetInputAtomBuffer(char*data);
|
void ResetInputAtomBuffer(char*data);
|
||||||
void ResetOutputAtomBuffer(char*data);
|
void ResetOutputAtomBuffer(char*data);
|
||||||
|
|
||||||
@@ -179,17 +194,29 @@ namespace pipedal
|
|||||||
|
|
||||||
void BypassTo(float value);
|
void BypassTo(float value);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual bool GetLv2State(Lv2PluginState*state);
|
// non RT-thread use only.
|
||||||
virtual void RequestPatchProperty(LV2_URID uridUri);
|
std::string GetPathPatchProperty(const std::string&propertyUri);
|
||||||
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value);
|
// non RT-thread use only.
|
||||||
virtual bool GetRequestStateChangedNotification() const;
|
void SetPathPatchProperty(const std::string &propertyUri, const std::string&jsonAtom);
|
||||||
virtual void SetRequestStateChangedNotification(bool value);
|
|
||||||
|
virtual bool IsLv2Effect() const { return true; }
|
||||||
|
virtual bool GetLv2State(Lv2PluginState*state) override;
|
||||||
|
virtual void SetLv2State(Lv2PluginState&state) override;
|
||||||
|
|
||||||
|
virtual void RequestPatchProperty(LV2_URID uridUri) ;
|
||||||
|
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value) override;
|
||||||
|
virtual void RequestAllPathPatchProperties();
|
||||||
|
|
||||||
|
virtual bool GetRequestStateChangedNotification() const override;
|
||||||
|
virtual void SetRequestStateChangedNotification(bool value) override;
|
||||||
|
|
||||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest);
|
virtual void GatherPatchProperties(RealtimePatchPropertyRequest*pRequest);
|
||||||
|
void GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter);
|
||||||
virtual bool IsVst3() const { return false; }
|
virtual bool IsVst3() const { return false; }
|
||||||
virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
virtual void RelayPatchSetMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) ;
|
||||||
|
|
||||||
virtual uint8_t*GetAtomInputBuffer() {
|
virtual uint8_t*GetAtomInputBuffer() {
|
||||||
if (this->inputAtomBuffers.size() == 0) return nullptr;
|
if (this->inputAtomBuffers.size() == 0) return nullptr;
|
||||||
@@ -240,7 +267,7 @@ namespace pipedal
|
|||||||
|
|
||||||
virtual int GetControlIndex(const std::string &symbol) const;
|
virtual int GetControlIndex(const std::string &symbol) const;
|
||||||
|
|
||||||
virtual void SetControl(int index, float value)
|
virtual void SetControl(int index, float value) override
|
||||||
{
|
{
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
@@ -250,6 +277,11 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual uint64_t GetMaxInputControl() const override;
|
||||||
|
virtual bool IsInputControl(uint64_t index) const override;
|
||||||
|
virtual float GetDefaultInputControlValue(uint64_t index) const override;
|
||||||
|
|
||||||
|
|
||||||
virtual float GetControlValue(int index) const {
|
virtual float GetControlValue(int index) const {
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -507,6 +507,19 @@ void Lv2Pedalboard::ResetAtomBuffers()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Lv2Pedalboard::GatherPathPatchProperties(IPatchWriterCallback*cbPatchWriter)
|
||||||
|
{
|
||||||
|
for (auto& pEffect : this->effects) {
|
||||||
|
if (pEffect->IsLv2Effect())
|
||||||
|
{
|
||||||
|
Lv2Effect *pLv2Effect = (Lv2Effect*)pEffect.get();
|
||||||
|
pLv2Effect->GatherPathPatchProperties(cbPatchWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests)
|
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests)
|
||||||
{
|
{
|
||||||
while (pParameterRequests != nullptr)
|
while (pParameterRequests != nullptr)
|
||||||
@@ -521,6 +534,8 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
|||||||
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (pEffect->IsLv2Effect())
|
||||||
{
|
{
|
||||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(pEffect);
|
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(pEffect);
|
||||||
|
|
||||||
@@ -537,10 +552,12 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
|||||||
pLv2Effect->SetRequestStateChangedNotification(true);
|
pLv2Effect->SetRequestStateChangedNotification(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pParameterRequests = pParameterRequests->pNext;
|
pParameterRequests = pParameterRequests->pNext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests)
|
void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests)
|
||||||
{
|
{
|
||||||
while (pParameterRequests != nullptr)
|
while (pParameterRequests != nullptr)
|
||||||
@@ -557,11 +574,14 @@ void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParamet
|
|||||||
pParameterRequests->errorMessage = "Not supported for VST3";
|
pParameterRequests->errorMessage = "Not supported for VST3";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (effect->IsLv2Effect())
|
||||||
{
|
{
|
||||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(effect);
|
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(effect);
|
||||||
pLv2Effect->GatherPatchProperties(pParameterRequests);
|
pLv2Effect->GatherPatchProperties(pParameterRequests);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pParameterRequests = pParameterRequests->pNext;
|
pParameterRequests = pParameterRequests->pNext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,36 +27,37 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include "DbDezipper.hpp"
|
#include "DbDezipper.hpp"
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RealtimeVuBuffers;
|
|
||||||
class RealtimePatchPropertyRequest;
|
|
||||||
class RealtimeRingBufferWriter;
|
|
||||||
|
|
||||||
struct Lv2PedalboardError {
|
|
||||||
int64_t intanceId;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Lv2PedalboardErrorList: public std::vector<Lv2PedalboardError> // (forward declaration issues with a using statement)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
};
|
class IPatchWriterCallback;
|
||||||
|
class RealtimeVuBuffers;
|
||||||
|
class RealtimePatchPropertyRequest;
|
||||||
|
class RealtimeRingBufferWriter;
|
||||||
|
|
||||||
class Lv2Pedalboard {
|
struct Lv2PedalboardError
|
||||||
|
{
|
||||||
|
int64_t intanceId;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lv2PedalboardErrorList : public std::vector<Lv2PedalboardError> // (forward declaration issues with a using statement)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lv2Pedalboard
|
||||||
|
{
|
||||||
IHost *pHost = nullptr;
|
IHost *pHost = nullptr;
|
||||||
|
|
||||||
DbDezipper inputVolume;
|
DbDezipper inputVolume;
|
||||||
DbDezipper outputVolume;
|
DbDezipper outputVolume;
|
||||||
|
|
||||||
BufferPool bufferPool;
|
BufferPool bufferPool;
|
||||||
std::vector<float*> pedalboardInputBuffers;
|
std::vector<float *> pedalboardInputBuffers;
|
||||||
std::vector<float*> pedalboardOutputBuffers;
|
std::vector<float *> pedalboardOutputBuffers;
|
||||||
|
|
||||||
std::vector<std::shared_ptr<IEffect> > effects;
|
std::vector<std::shared_ptr<IEffect>> effects;
|
||||||
std::vector<IEffect* > realtimeEffects; // std::shared_ptr is not thread-safe!!
|
std::vector<IEffect *> realtimeEffects;
|
||||||
|
|
||||||
using Action = std::function<void()>;
|
using Action = std::function<void()>;
|
||||||
using ProcessAction = std::function<void(uint32_t frames)>;
|
using ProcessAction = std::function<void(uint32_t frames)>;
|
||||||
@@ -71,13 +72,15 @@ class Lv2Pedalboard {
|
|||||||
|
|
||||||
RealtimeRingBufferWriter *ringBufferWriter;
|
RealtimeRingBufferWriter *ringBufferWriter;
|
||||||
|
|
||||||
enum class MappingType {
|
enum class MappingType
|
||||||
|
{
|
||||||
Linear,
|
Linear,
|
||||||
Circular,
|
Circular,
|
||||||
Momentary,
|
Momentary,
|
||||||
Latched,
|
Latched,
|
||||||
};
|
};
|
||||||
class MidiMapping {
|
class MidiMapping
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<Lv2PluginInfo> pluginInfo; // lifecycle
|
std::shared_ptr<Lv2PluginInfo> pluginInfo; // lifecycle
|
||||||
const Lv2PortInfo *pPortInfo = nullptr; // owned by port.
|
const Lv2PortInfo *pPortInfo = nullptr; // owned by port.
|
||||||
@@ -93,36 +96,39 @@ class Lv2Pedalboard {
|
|||||||
|
|
||||||
std::vector<MidiMapping> midiMappings;
|
std::vector<MidiMapping> midiMappings;
|
||||||
|
|
||||||
|
std::vector<float *> PrepareItems(
|
||||||
|
std::vector<PedalboardItem> &items,
|
||||||
|
std::vector<float *> inputBuffers,
|
||||||
|
Lv2PedalboardErrorList &errorList);
|
||||||
|
|
||||||
std::vector<float*> PrepareItems(
|
void PrepareMidiMap(const Pedalboard &pedalboard);
|
||||||
std::vector<PedalboardItem> & items,
|
void PrepareMidiMap(const PedalboardItem &pedalboardItem);
|
||||||
std::vector<float*> inputBuffers,
|
|
||||||
Lv2PedalboardErrorList &errorList
|
|
||||||
);
|
|
||||||
|
|
||||||
void PrepareMidiMap(const Pedalboard&pedalboard);
|
std::vector<float *> AllocateAudioBuffers(int nChannels);
|
||||||
void PrepareMidiMap(const PedalboardItem&pedalboardItem);
|
|
||||||
|
|
||||||
std::vector<float*> AllocateAudioBuffers(int nChannels);
|
|
||||||
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &items);
|
int CalculateChainInputs(const std::vector<float *> &inputBuffers, const std::vector<PedalboardItem> &items);
|
||||||
void AppendParameterRequest(uint8_t*atomBuffer, LV2_URID uridParameter);
|
void AppendParameterRequest(uint8_t *atomBuffer, LV2_URID uridParameter);
|
||||||
public:
|
|
||||||
Lv2Pedalboard() { }
|
|
||||||
~Lv2Pedalboard() { }
|
|
||||||
|
|
||||||
void Prepare(IHost *pHost,Pedalboard&pedalboard, Lv2PedalboardErrorList &errorList);
|
public:
|
||||||
|
Lv2Pedalboard() {}
|
||||||
|
~Lv2Pedalboard() {}
|
||||||
|
|
||||||
std::vector<IEffect* > GetEffects() { return realtimeEffects; }
|
void Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList);
|
||||||
|
|
||||||
int GetIndexOfInstanceId(uint64_t instanceId) {
|
std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
|
||||||
|
|
||||||
|
int GetIndexOfInstanceId(uint64_t instanceId)
|
||||||
|
{
|
||||||
for (int i = 0; i < this->realtimeEffects.size(); ++i)
|
for (int i = 0; i < this->realtimeEffects.size(); ++i)
|
||||||
{
|
{
|
||||||
if (this->realtimeEffects[i]->GetInstanceId() == instanceId) return i;
|
if (this->realtimeEffects[i]->GetInstanceId() == instanceId)
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
IEffect*GetEffect(uint64_t instanceId) {
|
IEffect *GetEffect(uint64_t instanceId)
|
||||||
for (int i = 0; i < realtimeEffects.size(); ++i) {
|
{
|
||||||
|
for (int i = 0; i < realtimeEffects.size(); ++i)
|
||||||
|
{
|
||||||
if (realtimeEffects[i]->GetInstanceId() == instanceId)
|
if (realtimeEffects[i]->GetInstanceId() == instanceId)
|
||||||
{
|
{
|
||||||
return realtimeEffects[i];
|
return realtimeEffects[i];
|
||||||
@@ -132,35 +138,31 @@ public:
|
|||||||
}
|
}
|
||||||
void Activate();
|
void Activate();
|
||||||
void Deactivate();
|
void Deactivate();
|
||||||
bool Run(float**inputBuffers, float**outputBuffers,uint32_t samples, RealtimeRingBufferWriter*realtimeWriter);
|
bool Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter *realtimeWriter);
|
||||||
|
|
||||||
void ResetAtomBuffers();
|
void ResetAtomBuffers();
|
||||||
|
|
||||||
|
|
||||||
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
|
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
|
||||||
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
|
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
|
||||||
|
void GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter);
|
||||||
|
|
||||||
|
std::vector<float *> &GetInputBuffers() { return this->pedalboardInputBuffers; }
|
||||||
|
std::vector<float *> &GetoutputBuffers() { return this->pedalboardOutputBuffers; }
|
||||||
|
|
||||||
std::vector<float*> &GetInputBuffers() { return this->pedalboardInputBuffers;}
|
int GetControlIndex(uint64_t instanceId, const std::string &symbol);
|
||||||
std::vector<float*> &GetoutputBuffers() { return this->pedalboardOutputBuffers;}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int GetControlIndex(uint64_t instanceId,const std::string&symbol);
|
|
||||||
void SetControlValue(int effectIndex, int portIndex, float value);
|
void SetControlValue(int effectIndex, int portIndex, float value);
|
||||||
void SetInputVolume(float value) { this->inputVolume.SetTarget(value);}
|
void SetInputVolume(float value) { this->inputVolume.SetTarget(value); }
|
||||||
void SetOutputVolume(float value) { this->outputVolume.SetTarget(value);}
|
void SetOutputVolume(float value) { this->outputVolume.SetTarget(value); }
|
||||||
void SetBypass(int effectIndex,bool enabled);
|
void SetBypass(int effectIndex, bool enabled);
|
||||||
|
|
||||||
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**inputBuffers, float**outputBuffers);
|
void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float **inputBuffers, float **outputBuffers);
|
||||||
|
|
||||||
float GetControlOutputValue(int effectIndex, int portIndex);
|
float GetControlOutputValue(int effectIndex, int portIndex);
|
||||||
|
|
||||||
typedef void (MidiCallbackFn) (void* data,uint64_t intanceId, int controlIndex, float value);
|
typedef void(MidiCallbackFn)(void *data, uint64_t intanceId, int controlIndex, float value);
|
||||||
void OnMidiMessage(size_t size, uint8_t*data,
|
void OnMidiMessage(size_t size, uint8_t *data,
|
||||||
void *callbackHandle,
|
void *callbackHandle,
|
||||||
MidiCallbackFn*pfnCallback);
|
MidiCallbackFn *pfnCallback);
|
||||||
|
};
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
// Copyright (c) 2024 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 <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <atomic>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "util.hpp"
|
||||||
|
#include <lv2/atom/atom.h>
|
||||||
|
namespace pipedal
|
||||||
|
{
|
||||||
|
|
||||||
|
// A mechanism for writing (path) patch properties back to the non-realtime threads.
|
||||||
|
// Has the following characteristics:
|
||||||
|
// 1. Only one property in flight to the non-realtime thread at any givien moment.
|
||||||
|
// 2. No blocking, no spinning on the realtime thread. RT operations require nothing other than atomic operations.
|
||||||
|
// 3. Multiple responses in the same RT frame are coaslesced into one response (the latest).
|
||||||
|
// 4. Responses that are found while a property notification is in flight are coaslesced.
|
||||||
|
|
||||||
|
class IPatchWriterCallback;
|
||||||
|
|
||||||
|
class PatchPropertyWriter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
enum class StateT : char
|
||||||
|
{
|
||||||
|
Empty,
|
||||||
|
WriteStarted,
|
||||||
|
WriteLocked, // written data is available.
|
||||||
|
ReadLocked, // data is in flight between the realtime thread and non-realtime service thread.
|
||||||
|
Deleted // detect use after free.
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
PatchPropertyWriter(int64_t instanceId, LV2_URID patchPropertyUrid)
|
||||||
|
: instanceId(instanceId), patchPropertyUrid(patchPropertyUrid),
|
||||||
|
buffer0(new Buffer(instanceId, patchPropertyUrid)),
|
||||||
|
buffer1(new Buffer(instanceId, patchPropertyUrid))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
// no copy.
|
||||||
|
PatchPropertyWriter(const PatchPropertyWriter&) = delete;
|
||||||
|
// move
|
||||||
|
PatchPropertyWriter(PatchPropertyWriter&&other) {
|
||||||
|
std::swap(this->buffer0,other.buffer0);
|
||||||
|
std::swap(this->buffer1,other.buffer1);
|
||||||
|
this->currentWriteBuffer = nullptr;
|
||||||
|
this->instanceId = other.instanceId;
|
||||||
|
this->patchPropertyUrid = other.patchPropertyUrid;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
~PatchPropertyWriter()
|
||||||
|
{
|
||||||
|
delete buffer0;
|
||||||
|
delete buffer1;
|
||||||
|
}
|
||||||
|
class Buffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::atomic<StateT> state{StateT::Empty};
|
||||||
|
|
||||||
|
Buffer(int64_t instanceId, LV2_URID propertyUrid)
|
||||||
|
: instanceId(instanceId), patchPropertyUrid(propertyUrid)
|
||||||
|
{
|
||||||
|
memory.reserve(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBufferWriteStarted()
|
||||||
|
{
|
||||||
|
if (state != StateT::Empty && state != StateT::WriteLocked)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Bad state.");
|
||||||
|
}
|
||||||
|
state = StateT::WriteStarted;
|
||||||
|
}
|
||||||
|
void OnBufferWritten()
|
||||||
|
{
|
||||||
|
if (state != StateT::WriteStarted)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Bad state.");
|
||||||
|
}
|
||||||
|
state = StateT::ReadLocked;
|
||||||
|
WriteBarrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBufferReadStarted()
|
||||||
|
{
|
||||||
|
if (state != StateT::ReadLocked)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Bad state.");
|
||||||
|
}
|
||||||
|
ReadBarrier();
|
||||||
|
}
|
||||||
|
void OnBufferReadComplete()
|
||||||
|
{
|
||||||
|
if (state != StateT::ReadLocked)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Bad state.");
|
||||||
|
}
|
||||||
|
state = StateT::Empty;
|
||||||
|
}
|
||||||
|
int64_t instanceId;
|
||||||
|
LV2_URID patchPropertyUrid;
|
||||||
|
|
||||||
|
std::vector<uint8_t> memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
// RT thread only.
|
||||||
|
Buffer *AquireWriteBuffer()
|
||||||
|
{
|
||||||
|
// RT only.
|
||||||
|
if (currentWriteBuffer)
|
||||||
|
{
|
||||||
|
return currentWriteBuffer;
|
||||||
|
}
|
||||||
|
if (buffer0->state == StateT::Empty)
|
||||||
|
{
|
||||||
|
buffer0->OnBufferWriteStarted();
|
||||||
|
currentWriteBuffer = buffer0;
|
||||||
|
return currentWriteBuffer;
|
||||||
|
}
|
||||||
|
if (buffer1->state == StateT::Empty)
|
||||||
|
{
|
||||||
|
buffer1->OnBufferWriteStarted();
|
||||||
|
currentWriteBuffer = buffer1;
|
||||||
|
return currentWriteBuffer;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Bad state. Unable to aquire a write buffer.");
|
||||||
|
}
|
||||||
|
// RT thread ony.
|
||||||
|
void OnBufferWritten()
|
||||||
|
{
|
||||||
|
if (currentWriteBuffer)
|
||||||
|
{
|
||||||
|
currentWriteBuffer->OnBufferWritten();
|
||||||
|
currentWriteBuffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Buffer *GetCurrentWriteBuffer()
|
||||||
|
{
|
||||||
|
return currentWriteBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushWrites(IPatchWriterCallback*cbWrite);
|
||||||
|
|
||||||
|
int64_t instanceId;
|
||||||
|
LV2_URID patchPropertyUrid;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Buffer *buffer0 = 0;
|
||||||
|
Buffer *buffer1 = 0;
|
||||||
|
Buffer *currentWriteBuffer = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IPatchWriterCallback {
|
||||||
|
public:
|
||||||
|
virtual void OnWritePatchPropertyBuffer(
|
||||||
|
PatchPropertyWriter::Buffer *
|
||||||
|
) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void PatchPropertyWriter::FlushWrites(IPatchWriterCallback*cbWrite)
|
||||||
|
{
|
||||||
|
auto buffer = GetCurrentWriteBuffer();
|
||||||
|
if (buffer)
|
||||||
|
{
|
||||||
|
// is the OTHER buffer empty?
|
||||||
|
if (buffer0->state == StateT::Empty || buffer1->state == StateT::Empty)
|
||||||
|
{
|
||||||
|
OnBufferWritten();
|
||||||
|
cbWrite->OnWritePatchPropertyBuffer(buffer);
|
||||||
|
} else {
|
||||||
|
// we have an update in flight, so just keep accumulating, and maybe write next time.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "Pedalboard.hpp"
|
#include "Pedalboard.hpp"
|
||||||
|
#include "AtomConverter.hpp"
|
||||||
|
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
@@ -177,9 +178,200 @@ bool IsPedalboardSplitItem(const PedalboardItem*self, const std::vector<Pedalboa
|
|||||||
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
|
return self->uri() == SPLIT_PEDALBOARD_ITEM_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Pedalboard::ApplySnapshot(int64_t snapshotIndex)
|
||||||
|
{
|
||||||
|
if (snapshotIndex < 0 ||
|
||||||
|
snapshotIndex >= this->snapshots_.size() ||
|
||||||
|
this->snapshots_[snapshotIndex] == nullptr ||
|
||||||
|
this->selectedSnapshot() == snapshotIndex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::map<int64_t, SnapshotValue*> indexedValues;
|
||||||
|
Snapshot *snapshot = this->snapshots_[snapshotIndex].get();
|
||||||
|
|
||||||
|
for (auto &value: snapshot->values_)
|
||||||
|
{
|
||||||
|
indexedValues[value.instanceId_] = &value;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto plugins = this->GetAllPlugins();
|
||||||
|
for (PedalboardItem *pedalboardItem: plugins)
|
||||||
|
{
|
||||||
|
SnapshotValue*snapshotValue = indexedValues[pedalboardItem->instanceId()];
|
||||||
|
if (snapshotValue)
|
||||||
|
{
|
||||||
|
pedalboardItem->ApplySnapshotValue(snapshotValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PedalboardItem::ApplySnapshotValue(SnapshotValue*snapshotValue)
|
||||||
|
{
|
||||||
|
std::map<std::string,float> cumulativeValues;
|
||||||
|
for (auto &controlValue: this->controlValues())
|
||||||
|
{
|
||||||
|
cumulativeValues[controlValue.key()] = controlValue.value();
|
||||||
|
}
|
||||||
|
for (auto&controlValue : snapshotValue->controlValues_)
|
||||||
|
{
|
||||||
|
cumulativeValues[controlValue.key()] = controlValue.value();
|
||||||
|
}
|
||||||
|
this->controlValues().clear();
|
||||||
|
for (auto&pair: cumulativeValues)
|
||||||
|
{
|
||||||
|
this->controlValues_.push_back(ControlValue(pair.first.c_str(),pair.second));
|
||||||
|
}
|
||||||
|
if (this->lv2State() != snapshotValue->lv2State_)
|
||||||
|
{
|
||||||
|
this->lv2State(snapshotValue->lv2State_);
|
||||||
|
this->stateUpdateCount(this->stateUpdateCount()+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// can we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
|
||||||
|
bool Pedalboard::IsStructureIdentical(const Pedalboard &other) const
|
||||||
|
{
|
||||||
|
if (this->nextInstanceId_ != other.nextInstanceId_) // quick check that catches 95% of structural changes.
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->items_.size() != other.items_.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < this->items_.size();++i)
|
||||||
|
{
|
||||||
|
if (!this->items_[i].IsStructurallyIdentical(other.items_[i]))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PedalboardItem::IsStructurallyIdentical(const PedalboardItem&other) const
|
||||||
|
{
|
||||||
|
if (this->instanceId() != other.instanceId()) // must match in order to ensure that realtime message passing works.
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->uri() != other.uri())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->isSplit()) // so is the other by virtue of idential uris.
|
||||||
|
{
|
||||||
|
if (topChain().size() != other.topChain().size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < topChain().size(); ++i)
|
||||||
|
{
|
||||||
|
if (!topChain()[i].IsStructurallyIdentical(other.topChain()[i] ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bottomChain().size() != other.bottomChain().size())
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < bottomChain().size(); ++i)
|
||||||
|
{
|
||||||
|
if (!bottomChain()[i].IsStructurallyIdentical(other.bottomChain()[i]))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PedalboardItem::AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const
|
||||||
|
{
|
||||||
|
SnapshotValue snapshotValue;
|
||||||
|
snapshotValue.instanceId_ = this->instanceId_;
|
||||||
|
|
||||||
|
for (const ControlValue &value: this->controlValues_)
|
||||||
|
{
|
||||||
|
snapshotValue.controlValues_.push_back(value);
|
||||||
|
}
|
||||||
|
for (const auto&pathProperty: this->pathProperties_)
|
||||||
|
{
|
||||||
|
snapshotValue.pathProperties_[pathProperty.first] = pathProperty.second;
|
||||||
|
}
|
||||||
|
snapshotValue.lv2State_ = this->lv2State_;
|
||||||
|
snapshot.values_.push_back(std::move(snapshotValue));
|
||||||
|
|
||||||
|
if (this->isSplit())
|
||||||
|
{
|
||||||
|
for (auto&item: this->topChain_)
|
||||||
|
{
|
||||||
|
item.AddToSnapshotFromCurrentSettings(snapshot);;
|
||||||
|
}
|
||||||
|
for (auto&item: this->bottomChain_)
|
||||||
|
{
|
||||||
|
item.AddToSnapshotFromCurrentSettings(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PedalboardItem::AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const
|
||||||
|
{
|
||||||
|
// structure must be identical
|
||||||
|
// items must be enumerated in the same order as AddToSnapshotFromCurrentSettings
|
||||||
|
SnapshotValue&snapshotValue = snapshot.values_[*index];
|
||||||
|
|
||||||
|
if (snapshotValue.instanceId_ != this->instanceId())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Pedalboard structure does not match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&property: this->pathProperties_)
|
||||||
|
{
|
||||||
|
auto f = snapshotValue.pathProperties_.find(property.first);
|
||||||
|
if (f == snapshotValue.pathProperties_.end())
|
||||||
|
{
|
||||||
|
snapshotValue.pathProperties_[property.first] = AtomConverter::EmptyPathstring();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++(*index);
|
||||||
|
|
||||||
|
if (this->isSplit())
|
||||||
|
{
|
||||||
|
for (auto&item: this->topChain())
|
||||||
|
{
|
||||||
|
item.AddResetsForMissingProperties(snapshot,index);
|
||||||
|
}
|
||||||
|
for (auto&item: this->bottomChain())
|
||||||
|
{
|
||||||
|
item.AddResetsForMissingProperties(snapshot,index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard)
|
||||||
|
{
|
||||||
|
Snapshot snapshot;
|
||||||
|
// name and color don't matter. this is strictly for loading purposes.
|
||||||
|
for (auto &item : this->items())
|
||||||
|
{
|
||||||
|
item.AddToSnapshotFromCurrentSettings(snapshot);
|
||||||
|
}
|
||||||
|
// a neccesary precondition: the previous pedalboard must have identical structure,
|
||||||
|
// so we can just
|
||||||
|
size_t index = 0;
|
||||||
|
for (auto&item: previousPedalboard.items_)
|
||||||
|
{
|
||||||
|
item.AddResetsForMissingProperties(snapshot,&index);
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -202,6 +394,7 @@ JSON_MAP_BEGIN(PedalboardItem)
|
|||||||
JSON_MAP_REFERENCE(PedalboardItem,stateUpdateCount)
|
JSON_MAP_REFERENCE(PedalboardItem,stateUpdateCount)
|
||||||
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
||||||
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
||||||
|
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|
||||||
@@ -211,5 +404,21 @@ JSON_MAP_BEGIN(Pedalboard)
|
|||||||
JSON_MAP_REFERENCE(Pedalboard,output_volume_db)
|
JSON_MAP_REFERENCE(Pedalboard,output_volume_db)
|
||||||
JSON_MAP_REFERENCE(Pedalboard,items)
|
JSON_MAP_REFERENCE(Pedalboard,items)
|
||||||
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
||||||
|
JSON_MAP_REFERENCE(Pedalboard,snapshots)
|
||||||
|
JSON_MAP_REFERENCE(Pedalboard,selectedSnapshot)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(SnapshotValue)
|
||||||
|
JSON_MAP_REFERENCE(SnapshotValue,instanceId)
|
||||||
|
JSON_MAP_REFERENCE(SnapshotValue,controlValues)
|
||||||
|
JSON_MAP_REFERENCE(SnapshotValue,lv2State)
|
||||||
|
JSON_MAP_REFERENCE(SnapshotValue,pathProperties)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(Snapshot)
|
||||||
|
JSON_MAP_REFERENCE(Snapshot,name)
|
||||||
|
JSON_MAP_REFERENCE(Snapshot,color)
|
||||||
|
JSON_MAP_REFERENCE(Snapshot,values)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
#include "atom_object.hpp"
|
#include "atom_object.hpp"
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
class SnapshotValue;
|
||||||
|
class Snapshot;
|
||||||
|
|
||||||
#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split"
|
#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split"
|
||||||
#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty"
|
#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty"
|
||||||
@@ -45,7 +47,9 @@ namespace pipedal {
|
|||||||
|
|
||||||
#define GETTER_SETTER_VEC(name) \
|
#define GETTER_SETTER_VEC(name) \
|
||||||
decltype(name##_)& name() { return name##_;} \
|
decltype(name##_)& name() { return name##_;} \
|
||||||
const decltype(name##_)& name() const { return name##_;}
|
const decltype(name##_)& name() const { return name##_;} \
|
||||||
|
void name(decltype(name##_)&&value) { this->name##_ = std::move(value); }\
|
||||||
|
void name(const decltype(name##_)&value) { this->name##_ = value; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -91,12 +95,16 @@ public:
|
|||||||
uint32_t stateUpdateCount_ = 0;
|
uint32_t stateUpdateCount_ = 0;
|
||||||
Lv2PluginState lv2State_;
|
Lv2PluginState lv2State_;
|
||||||
std::string lilvPresetUri_;
|
std::string lilvPresetUri_;
|
||||||
|
std::map<std::string,std::string> pathProperties_;
|
||||||
|
|
||||||
// non persistent state.
|
// non persistent state.
|
||||||
PropertyMap patchProperties;
|
PropertyMap patchProperties;
|
||||||
public:
|
public:
|
||||||
ControlValue*GetControlValue(const std::string&symbol);
|
ControlValue*GetControlValue(const std::string&symbol);
|
||||||
|
|
||||||
|
bool IsStructurallyIdentical(const PedalboardItem&other) const;
|
||||||
|
|
||||||
|
void ApplySnapshotValue(SnapshotValue*snapshotValue);
|
||||||
bool hasLv2State() const {
|
bool hasLv2State() const {
|
||||||
return lv2State_.isValid_ != 0;
|
return lv2State_.isValid_ != 0;
|
||||||
}
|
}
|
||||||
@@ -124,6 +132,10 @@ public:
|
|||||||
return uri_ == EMPTY_PEDALBOARD_ITEM_URI;
|
return uri_ == EMPTY_PEDALBOARD_ITEM_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const;
|
||||||
|
void AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const;
|
||||||
|
|
||||||
|
|
||||||
virtual void write_members(json_writer&writer) const {
|
virtual void write_members(json_writer&writer) const {
|
||||||
writer.write_member("instanceId",instanceId_);
|
writer.write_member("instanceId",instanceId_);
|
||||||
writer.write_member("uri",uri_);
|
writer.write_member("uri",uri_);
|
||||||
@@ -139,6 +151,25 @@ public:
|
|||||||
DECLARE_JSON_MAP(PedalboardItem);
|
DECLARE_JSON_MAP(PedalboardItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SnapshotValue {
|
||||||
|
public:
|
||||||
|
uint64_t instanceId_;
|
||||||
|
std::vector<ControlValue> controlValues_;
|
||||||
|
Lv2PluginState lv2State_;
|
||||||
|
std::map<std::string,std::string> pathProperties_;
|
||||||
|
DECLARE_JSON_MAP(SnapshotValue);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Snapshot {
|
||||||
|
public:
|
||||||
|
std::string name_;
|
||||||
|
std::string color_;
|
||||||
|
std::vector<SnapshotValue> values_;
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(Snapshot);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
class Pedalboard {
|
class Pedalboard {
|
||||||
std::string name_;
|
std::string name_;
|
||||||
@@ -149,22 +180,31 @@ class Pedalboard {
|
|||||||
uint64_t nextInstanceId_ = 0;
|
uint64_t nextInstanceId_ = 0;
|
||||||
uint64_t NextInstanceId() { return ++nextInstanceId_; }
|
uint64_t NextInstanceId() { return ++nextInstanceId_; }
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Snapshot>> snapshots_;
|
||||||
|
int64_t selectedSnapshot_ = -1;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
||||||
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
||||||
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
||||||
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
||||||
|
|
||||||
|
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
|
||||||
|
Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard);
|
||||||
|
|
||||||
PedalboardItem*GetItem(int64_t pedalItemId);
|
PedalboardItem*GetItem(int64_t pedalItemId);
|
||||||
const PedalboardItem*GetItem(int64_t pedalItemId) const;
|
const PedalboardItem*GetItem(int64_t pedalItemId) const;
|
||||||
std::vector<PedalboardItem*>GetAllPlugins();
|
std::vector<PedalboardItem*>GetAllPlugins();
|
||||||
|
|
||||||
bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
|
bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; }
|
||||||
|
bool ApplySnapshot(int64_t snapshotIndex);
|
||||||
|
|
||||||
GETTER_SETTER_REF(name)
|
GETTER_SETTER_REF(name)
|
||||||
GETTER_SETTER_VEC(items)
|
GETTER_SETTER_VEC(items)
|
||||||
GETTER_SETTER(input_volume_db)
|
GETTER_SETTER(input_volume_db)
|
||||||
GETTER_SETTER(output_volume_db)
|
GETTER_SETTER(output_volume_db)
|
||||||
|
GETTER_SETTER_VEC(snapshots)
|
||||||
|
GETTER_SETTER(selectedSnapshot)
|
||||||
|
|
||||||
|
|
||||||
DECLARE_JSON_MAP(Pedalboard);
|
DECLARE_JSON_MAP(Pedalboard);
|
||||||
|
|||||||
@@ -89,16 +89,15 @@ PiPedalModel::PiPedalModel()
|
|||||||
this->OnUpdateStatusChanged(updateStatus);
|
this->OnUpdateStatusChanged(updateStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
DbusLogToLv2Log();
|
DbusLogToLv2Log();
|
||||||
SetDBusLogLevel(DBusLogLevel::Info);
|
SetDBusLogLevel(DBusLogLevel::Info);
|
||||||
|
|
||||||
hotspotManager = HotspotManager::Create();
|
hotspotManager = HotspotManager::Create();
|
||||||
hotspotManager->SetNetworkChangingListener(
|
hotspotManager->SetNetworkChangingListener(
|
||||||
[this](bool ethernetConnected, bool hotspotEnabling) {
|
[this](bool ethernetConnected, bool hotspotEnabling)
|
||||||
OnNetworkChanging(ethernetConnected,hotspotEnabling);
|
{
|
||||||
}
|
OnNetworkChanging(ethernetConnected, hotspotEnabling);
|
||||||
);
|
});
|
||||||
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
|
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +112,8 @@ void PiPedalModel::Close()
|
|||||||
}
|
}
|
||||||
closed = true;
|
closed = true;
|
||||||
|
|
||||||
|
if (avahiService)
|
||||||
if (avahiService) {
|
{
|
||||||
this->avahiService = nullptr; // and close.
|
this->avahiService = nullptr; // and close.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +360,7 @@ void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
|
|||||||
{
|
{
|
||||||
// a sent PATCH_Set, or an explicit state changed notification.
|
// a sent PATCH_Set, or an explicit state changed notification.
|
||||||
OnNotifyMaybeLv2StateChanged(instanceId);
|
OnNotifyMaybeLv2StateChanged(instanceId);
|
||||||
this->SetPresetChanged(-1, true);
|
this->SetPresetChanged(-1, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
|
void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
|
||||||
@@ -566,6 +565,69 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
|||||||
this->SetPresetChanged(clientId, true);
|
this->SetPresetChanged(clientId, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
if (this->pedalboard.selectedSnapshot() == selectedSnapshot)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->pedalboard.ApplySnapshot(selectedSnapshot))
|
||||||
|
{
|
||||||
|
if (this->audioHost)
|
||||||
|
{
|
||||||
|
this->audioHost->LoadSnapshot(*(this->pedalboard.snapshots()[selectedSnapshot]), this->pluginHost); // no longer own it.
|
||||||
|
}
|
||||||
|
SetPresetChanged(-1, true, false);
|
||||||
|
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||||
|
FirePedalboardChanged(-1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshots, int64_t selectedSnapshot)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
// notionally, snapshots are saved whenever they are modified, but pedalboards are not.
|
||||||
|
// so we must add the current snapshots BOTH to the current pedalbaoard, AND to the
|
||||||
|
// current saved instance of the pedalboard to create the illusion that the are stored separately.
|
||||||
|
// In practice, snapshots have to track structure changes (delete pedalboard items that have been deleted,
|
||||||
|
// and set default settings for pedalboard items that have ben added since the snapshots was last saved.
|
||||||
|
//
|
||||||
|
// This is acheived by:
|
||||||
|
// 1. updating BOTH the current pedalboard, and its saved instance.
|
||||||
|
// 2. discarding references to dangling instances as plugins are loaded (see UpdateDefaults).
|
||||||
|
// 3. Setting missing snapshot controls to default values (NOT the current pedalboard values)
|
||||||
|
// 4. (We currently don't do the right thing with patch properties if the patch property has never been set).
|
||||||
|
|
||||||
|
UpdateVst3Settings(pedalboard);
|
||||||
|
|
||||||
|
{
|
||||||
|
// stealth update of the saved snapshots.
|
||||||
|
auto savedPedalboard = storage.GetCurrentPreset();
|
||||||
|
savedPedalboard.snapshots(snapshots); // makes a shallow copy
|
||||||
|
|
||||||
|
if (selectedSnapshot != -1)
|
||||||
|
{
|
||||||
|
// implies that this is a snapshot of the currently running pedalboard. so we can mark it as the selected pedalboard.
|
||||||
|
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||||
|
}
|
||||||
|
// UpdateDefaults(&savedPedalboard); // The update is awfully fresh. wait until it gets loaded again before pruning.
|
||||||
|
storage.SaveCurrentPreset(savedPedalboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->pedalboard.snapshots(std::move(snapshots));
|
||||||
|
if (selectedSnapshot != -1)
|
||||||
|
{
|
||||||
|
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->FirePedalboardChanged(-1, false); // notify clients (but don't change the running pedalboard, because it's still the same)
|
||||||
|
// this means that all clients get an up-to-date copy of the snapshots AND the currently selected snapshot if that applies
|
||||||
|
// (and a fresh copy of the pedalboard settings as well, which is harmless, since they have not changed)
|
||||||
|
}
|
||||||
|
|
||||||
void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -628,12 +690,60 @@ void PiPedalModel::GetBank(int64_t instanceId, BankFile *pResult)
|
|||||||
this->storage.GetBankFile(instanceId, pResult);
|
this->storage.GetBankFile(instanceId, pResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::SetPresetChanged(int64_t clientId, bool value)
|
void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect)
|
||||||
{
|
{
|
||||||
|
if (changeSnapshotSelect && value && this->pedalboard.selectedSnapshot() != -1)
|
||||||
|
{
|
||||||
|
this->pedalboard.selectedSnapshot(-1);
|
||||||
|
FireSelectedSnapshotChanged(-1);
|
||||||
|
}
|
||||||
if (value != this->hasPresetChanged)
|
if (value != this->hasPresetChanged)
|
||||||
{
|
{
|
||||||
hasPresetChanged = value;
|
hasPresetChanged = value;
|
||||||
FirePresetsChanged(clientId);
|
FirePresetChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::FireSelectedSnapshotChanged(int64_t selectedSnapshot)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||||
|
{
|
||||||
|
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||||
|
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||||
|
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||||
|
{
|
||||||
|
t[i] = this->subscribers[i];
|
||||||
|
}
|
||||||
|
size_t n = this->subscribers.size();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
t[i]->OnSelectedSnapshotChanged(selectedSnapshot);
|
||||||
|
}
|
||||||
|
delete[] t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::FirePresetChanged(bool changed)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||||
|
{
|
||||||
|
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||||
|
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
|
||||||
|
for (size_t i = 0; i < subscribers.size(); ++i)
|
||||||
|
{
|
||||||
|
t[i] = this->subscribers[i];
|
||||||
|
}
|
||||||
|
size_t n = this->subscribers.size();
|
||||||
|
|
||||||
|
PresetIndex presets;
|
||||||
|
GetPresets(&presets);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
t[i]->OnPresetChanged(changed);
|
||||||
|
}
|
||||||
|
delete[] t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,12 +886,14 @@ int64_t PiPedalModel::UploadBank(BankFile &bankFile, int64_t uploadAfter)
|
|||||||
return newPreset;
|
return newPreset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request)
|
void PiPedalModel::NextBank(Direction direction)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void PiPedalModel::NextPreset(Direction direction)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PresetIndex index;
|
PresetIndex index;
|
||||||
|
|
||||||
storage.GetPresetIndex(&index);
|
storage.GetPresetIndex(&index);
|
||||||
auto currentPresetId = storage.GetCurrentPresetId();
|
auto currentPresetId = storage.GetCurrentPresetId();
|
||||||
size_t currentPresetIndex = 0;
|
size_t currentPresetIndex = 0;
|
||||||
@@ -796,9 +908,9 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest
|
|||||||
}
|
}
|
||||||
if (index.presets().size() == 0)
|
if (index.presets().size() == 0)
|
||||||
{
|
{
|
||||||
throw PiPedalException("No presets loaded.");
|
return;
|
||||||
}
|
}
|
||||||
if (request.direction < 0)
|
if (direction == Direction::Decrease)
|
||||||
{
|
{
|
||||||
if (currentPresetIndex == 0)
|
if (currentPresetIndex == 0)
|
||||||
{
|
{
|
||||||
@@ -818,6 +930,21 @@ void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoadPreset(-1, index.presets()[currentPresetIndex].instanceId());
|
LoadPreset(-1, index.presets()[currentPresetIndex].instanceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
if (request.direction < 0)
|
||||||
|
{
|
||||||
|
NextPreset();
|
||||||
|
} else {
|
||||||
|
PreviousPreset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception &e)
|
catch (std::exception &e)
|
||||||
{
|
{
|
||||||
@@ -994,8 +1121,6 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if NEW_WIFI_CONFIG
|
#if NEW_WIFI_CONFIG
|
||||||
if (this->storage.SetWifiConfigSettings(wifiConfigSettings))
|
if (this->storage.SetWifiConfigSettings(wifiConfigSettings))
|
||||||
{
|
{
|
||||||
@@ -1065,7 +1190,7 @@ void PiPedalModel::UpdateDnsSd()
|
|||||||
std::string hostName = GetHostName();
|
std::string hostName = GetHostName();
|
||||||
if (serviceName != "" && deviceIdFile.uuid != "")
|
if (serviceName != "" && deviceIdFile.uuid != "")
|
||||||
{
|
{
|
||||||
avahiService->Announce(webPort, serviceName, deviceIdFile.uuid, hostName,true);
|
avahiService->Announce(webPort, serviceName, deviceIdFile.uuid, hostName, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1475,6 +1600,18 @@ void PiPedalModel::SendSetPatchProperty(
|
|||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
// save the property to the preset (currently used to reconstruct snapshots only)
|
||||||
|
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(instanceId);
|
||||||
|
if (pedalboardItem)
|
||||||
|
{
|
||||||
|
json_variant abstractPath = pluginHost.AbstractPath(value);
|
||||||
|
std::ostringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(abstractPath);
|
||||||
|
std::string atomString = ss.str();
|
||||||
|
pedalboardItem->pathProperties_[propertyUri] = atomString;
|
||||||
|
}
|
||||||
|
this->SetPresetChanged(clientId, true);
|
||||||
LV2_Atom *atomValue = atomConverter.ToAtom(value);
|
LV2_Atom *atomValue = atomConverter.ToAtom(value);
|
||||||
|
|
||||||
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
|
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
|
||||||
@@ -1727,11 +1864,19 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem)
|
std::shared_ptr<Lv2PluginInfo> PiPedalModel::GetPluginInfo(const std::string &uri)
|
||||||
{
|
{
|
||||||
|
return pluginHost.GetPluginInfo(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap)
|
||||||
|
{
|
||||||
|
itemMap[pedalboardItem->instanceId()] = pedalboardItem;
|
||||||
|
|
||||||
std::shared_ptr<Lv2PluginInfo> pPlugin = pluginHost.GetPluginInfo(pedalboardItem->uri());
|
std::shared_ptr<Lv2PluginInfo> pPlugin = pluginHost.GetPluginInfo(pedalboardItem->uri());
|
||||||
if (!pPlugin)
|
if (!pPlugin)
|
||||||
{
|
{
|
||||||
|
pedalboardItem->instanceId();
|
||||||
if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI)
|
if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI)
|
||||||
{
|
{
|
||||||
pPlugin = GetSplitterPluginInfo();
|
pPlugin = GetSplitterPluginInfo();
|
||||||
@@ -1753,21 +1898,74 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (pPlugin->piPedalUI())
|
||||||
|
{
|
||||||
|
PiPedalUI::ptr piPedalUI = pPlugin->piPedalUI();
|
||||||
|
for (auto &fileProperty : piPedalUI->fileProperties())
|
||||||
|
{
|
||||||
|
if (!pedalboardItem->pathProperties_.contains(fileProperty->patchProperty()))
|
||||||
|
{
|
||||||
|
// make sure each pedalboard item has a complete list of path properties, even if it doesn't yet have values.
|
||||||
|
pedalboardItem->pathProperties_[fileProperty->patchProperty()] = "null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// an old bug leaks lv2states. Clean it up here.
|
||||||
|
if (pedalboardItem->uri() == EMPTY_PEDALBOARD_ITEM_URI)
|
||||||
|
{
|
||||||
|
pedalboardItem->lv2State(Lv2PluginState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < pedalboardItem->topChain().size(); ++i)
|
for (size_t i = 0; i < pedalboardItem->topChain().size(); ++i)
|
||||||
{
|
{
|
||||||
UpdateDefaults(&(pedalboardItem->topChain()[i]));
|
UpdateDefaults(&(pedalboardItem->topChain()[i]), itemMap);
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < pedalboardItem->bottomChain().size(); ++i)
|
for (size_t i = 0; i < pedalboardItem->bottomChain().size(); ++i)
|
||||||
{
|
{
|
||||||
UpdateDefaults(&(pedalboardItem->bottomChain()[i]));
|
UpdateDefaults(&(pedalboardItem->bottomChain()[i]), itemMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::UpdateDefaults(Snapshot *snapshot, std::unordered_map<int64_t, PedalboardItem *> &itemMap)
|
||||||
|
{
|
||||||
|
if (!snapshot)
|
||||||
|
return;
|
||||||
|
for (size_t i = 0; i < snapshot->values_.size(); ++i)
|
||||||
|
{
|
||||||
|
SnapshotValue &value = snapshot->values_[i];
|
||||||
|
auto f = itemMap.find(value.instanceId_);
|
||||||
|
if (f == itemMap.end())
|
||||||
|
{
|
||||||
|
// plugin is no longer present. Remove from the snapshot.
|
||||||
|
snapshot->values_.erase(snapshot->values_.begin() + i);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
// missing values in snapshots get filled in with plugin defaults at load time,
|
||||||
|
// so we don't need to actually populate the missing values.
|
||||||
|
}
|
||||||
|
for (auto &snapshotValue : snapshot->values_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PiPedalModel::UpdateDefaults(Pedalboard *pedalboard)
|
void PiPedalModel::UpdateDefaults(Pedalboard *pedalboard)
|
||||||
{
|
{
|
||||||
|
// add missing values.
|
||||||
|
std::unordered_map<int64_t, PedalboardItem *> itemMap;
|
||||||
for (size_t i = 0; i < pedalboard->items().size(); ++i)
|
for (size_t i = 0; i < pedalboard->items().size(); ++i)
|
||||||
{
|
{
|
||||||
UpdateDefaults(&(pedalboard->items()[i]));
|
UpdateDefaults(&(pedalboard->items()[i]), itemMap);
|
||||||
|
}
|
||||||
|
// set all missiong values on snapshots to default values.
|
||||||
|
for (auto snapshot : pedalboard->snapshots())
|
||||||
|
{
|
||||||
|
if (snapshot)
|
||||||
|
{
|
||||||
|
UpdateDefaults(snapshot.get(), itemMap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1919,6 +2117,56 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
|
|||||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PiPedalModel::OnNotifyPathPatchPropertyReceived(
|
||||||
|
int64_t instanceId,
|
||||||
|
LV2_URID pathPatchProperty,
|
||||||
|
LV2_Atom *pathProperty)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
|
|
||||||
|
std::string pathPatchPropertyUri = this->pluginHost.Lv2UridToString(pathPatchProperty);
|
||||||
|
std::string atomString = atomConverter.ToString(pathProperty);
|
||||||
|
auto pedalboardItem = this->pedalboard.GetItem(instanceId);
|
||||||
|
|
||||||
|
if (this->audioHost)
|
||||||
|
{
|
||||||
|
this->audioHost->OnNotifyPathPatchPropertyReceived(instanceId,pathPatchPropertyUri,atomString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (pedalboardItem == nullptr)
|
||||||
|
{
|
||||||
|
Lv2Log::error(SS("OnNotifyPathPatchPropertyReceived: " << pathPatchPropertyUri << " discarded."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Lv2Log::error(SS("OnNotifyPathPatchPropertyReceived: " << pathPatchPropertyUri <<": " << atomString));
|
||||||
|
|
||||||
|
auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri);
|
||||||
|
if (i != pedalboardItem->pathProperties_.end())
|
||||||
|
{
|
||||||
|
pedalboardItem->pathProperties_[pathPatchPropertyUri] = atomString;
|
||||||
|
|
||||||
|
std::vector<IPiPedalModelSubscriber *> t;
|
||||||
|
t.reserve(subscribers.size());
|
||||||
|
|
||||||
|
for (auto subscriber : subscribers)
|
||||||
|
{
|
||||||
|
t.push_back(subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto subscriber : t)
|
||||||
|
{
|
||||||
|
subscriber->OnNotifyPathPatchPropertyChanged(
|
||||||
|
instanceId,
|
||||||
|
pathPatchPropertyUri,
|
||||||
|
atomString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
|
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
@@ -2169,6 +2417,15 @@ std::shared_ptr<Lv2Pedalboard> PiPedalModel::GetLv2Pedalboard()
|
|||||||
}
|
}
|
||||||
bool PiPedalModel::LoadCurrentPedalboard()
|
bool PiPedalModel::LoadCurrentPedalboard()
|
||||||
{
|
{
|
||||||
|
if (previousPedalboardLoaded && pedalboard.IsStructureIdentical(previousPedalboard))
|
||||||
|
{
|
||||||
|
// then we can send a snapshot update instead!
|
||||||
|
Snapshot snapshot = pedalboard.MakeSnapshotFromCurrentSettings(previousPedalboard);
|
||||||
|
audioHost->LoadSnapshot(snapshot,pluginHost);
|
||||||
|
this->previousPedalboard = this->pedalboard;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Lv2PedalboardErrorList errorMessages;
|
Lv2PedalboardErrorList errorMessages;
|
||||||
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->pluginHost.CreateLv2Pedalboard(this->pedalboard, errorMessages)};
|
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->pluginHost.CreateLv2Pedalboard(this->pedalboard, errorMessages)};
|
||||||
this->lv2Pedalboard = lv2Pedalboard;
|
this->lv2Pedalboard = lv2Pedalboard;
|
||||||
@@ -2353,7 +2610,7 @@ void PiPedalModel::WaitForAudioDeviceToComeOnline()
|
|||||||
GetAlsaDevices();
|
GetAlsaDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
PiPedalModel::PostHandle PiPedalModel::Post(PostCallback&&fn)
|
PiPedalModel::PostHandle PiPedalModel::Post(PostCallback &&fn)
|
||||||
{
|
{
|
||||||
// I know. odd place to forward this to, but it's a very serviceable dispatcher implementation.
|
// I know. odd place to forward this to, but it's a very serviceable dispatcher implementation.
|
||||||
// Why? because it's there, and PiPedalModel has no thread of its own to do dispatching.
|
// Why? because it's there, and PiPedalModel has no thread of its own to do dispatching.
|
||||||
@@ -2363,16 +2620,16 @@ PiPedalModel::PostHandle PiPedalModel::Post(PostCallback&&fn)
|
|||||||
}
|
}
|
||||||
return hotspotManager->Post(std::move(fn));
|
return hotspotManager->Post(std::move(fn));
|
||||||
}
|
}
|
||||||
PiPedalModel::PostHandle PiPedalModel::PostDelayed(const clock::duration&delay,PostCallback&&fn)
|
PiPedalModel::PostHandle PiPedalModel::PostDelayed(const clock::duration &delay, PostCallback &&fn)
|
||||||
{
|
{
|
||||||
if (!hotspotManager)
|
if (!hotspotManager)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too early. It's not ready yet.");
|
throw std::runtime_error("Too early. It's not ready yet.");
|
||||||
}
|
}
|
||||||
return hotspotManager->PostDelayed(delay,std::move(fn));
|
return hotspotManager->PostDelayed(delay, std::move(fn));
|
||||||
|
|
||||||
}
|
}
|
||||||
bool PiPedalModel::CancelPost(PostHandle handle) {
|
bool PiPedalModel::CancelPost(PostHandle handle)
|
||||||
|
{
|
||||||
if (!hotspotManager)
|
if (!hotspotManager)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too early. It's not ready yet.");
|
throw std::runtime_error("Too early. It's not ready yet.");
|
||||||
@@ -2389,7 +2646,6 @@ void PiPedalModel::CancelNetworkChangingTimer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::string> PiPedalModel::GetKnownWifiNetworks()
|
std::vector<std::string> PiPedalModel::GetKnownWifiNetworks()
|
||||||
{
|
{
|
||||||
if (!this->hotspotManager)
|
if (!this->hotspotManager)
|
||||||
@@ -2399,16 +2655,16 @@ std::vector<std::string> PiPedalModel::GetKnownWifiNetworks()
|
|||||||
return this->hotspotManager->GetKnownWifiNetworks();
|
return this->hotspotManager->GetKnownWifiNetworks();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiPedalModel::OnNetworkChanging(bool ethernetConnected,bool hotspotConnected)
|
void PiPedalModel::OnNetworkChanging(bool ethernetConnected, bool hotspotConnected)
|
||||||
{
|
{
|
||||||
CancelNetworkChangingTimer();
|
CancelNetworkChangingTimer();
|
||||||
this->networkChangingDelayHandle =
|
this->networkChangingDelayHandle =
|
||||||
PostDelayed(std::chrono::seconds(10), // takes a while for network configuration to be fully applied.
|
PostDelayed(std::chrono::seconds(10), // takes a while for network configuration to be fully applied.
|
||||||
[this,ethernetConnected,hotspotConnected]() {
|
[this, ethernetConnected, hotspotConnected]()
|
||||||
|
{
|
||||||
this->networkChangingDelayHandle = 0;
|
this->networkChangingDelayHandle = 0;
|
||||||
OnNetworkChanged(ethernetConnected,hotspotConnected);
|
OnNetworkChanged(ethernetConnected, hotspotConnected);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||||
std::vector<IPiPedalModelSubscriber *> t;
|
std::vector<IPiPedalModelSubscriber *> t;
|
||||||
@@ -2422,7 +2678,6 @@ void PiPedalModel::OnNetworkChanging(bool ethernetConnected,bool hotspotConnecte
|
|||||||
{
|
{
|
||||||
t[i]->OnNetworkChanging(hotspotConnected);
|
t[i]->OnNetworkChanging(hotspotConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnected)
|
void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnected)
|
||||||
{
|
{
|
||||||
@@ -2431,7 +2686,8 @@ void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnecte
|
|||||||
|
|
||||||
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
|
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
switch (eventType)
|
switch (eventType)
|
||||||
{
|
{
|
||||||
case RealtimeMidiEventType::Shutdown:
|
case RealtimeMidiEventType::Shutdown:
|
||||||
@@ -2466,7 +2722,8 @@ void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (const std::exception&e)
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
Lv2Log::error(SS("Failed to process realtime MIDI event. " << e.what()));
|
Lv2Log::error(SS("Failed to process realtime MIDI event. " << e.what()));
|
||||||
}
|
}
|
||||||
@@ -2507,3 +2764,5 @@ void PiPedalModel::RequestShutdown(bool restart)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "Promise.hpp"
|
#include "Promise.hpp"
|
||||||
#include "AtomConverter.hpp"
|
#include "AtomConverter.hpp"
|
||||||
#include "FileEntry.hpp"
|
#include "FileEntry.hpp"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
@@ -58,11 +59,13 @@ namespace pipedal
|
|||||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||||
virtual void OnInputVolumeChanged(float value) = 0;
|
virtual void OnInputVolumeChanged(float value) = 0;
|
||||||
virtual void OnOutputVolumeChanged(float value) = 0;
|
virtual void OnOutputVolumeChanged(float value) = 0;
|
||||||
virtual void OnUpdateStatusChanged(const UpdateStatus&updateStatus) = 0;
|
virtual void OnUpdateStatusChanged(const UpdateStatus &updateStatus) = 0;
|
||||||
virtual void OnLv2StateChanged(int64_t pedalItemId,const Lv2PluginState&newState ) = 0;
|
virtual void OnLv2StateChanged(int64_t pedalItemId, const Lv2PluginState &newState) = 0;
|
||||||
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
||||||
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
|
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
|
||||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
|
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
|
||||||
|
virtual void OnPresetChanged(bool changed) = 0;
|
||||||
|
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
|
||||||
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
|
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
|
||||||
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
|
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
|
||||||
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
|
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
|
||||||
@@ -78,10 +81,11 @@ namespace pipedal
|
|||||||
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
|
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
|
||||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
|
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
|
||||||
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
||||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
|
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding> &bindings) = 0;
|
||||||
|
virtual void OnNotifyPathPatchPropertyChanged(int64_t instanceId, const std::string &pathPatchPropertyString, const std::string &atomString) = 0;
|
||||||
|
|
||||||
//virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
||||||
virtual void OnErrorMessage(const std::string&message) = 0;
|
virtual void OnErrorMessage(const std::string &message) = 0;
|
||||||
virtual void OnLv2PluginsChanging() = 0;
|
virtual void OnLv2PluginsChanging() = 0;
|
||||||
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
|
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
@@ -98,13 +102,12 @@ namespace pipedal
|
|||||||
using PostCallback = std::function<void()>;
|
using PostCallback = std::function<void()>;
|
||||||
using NetworkChangedListener = std::function<void(void)>;
|
using NetworkChangedListener = std::function<void(void)>;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<HotspotManager> hotspotManager;
|
std::unique_ptr<HotspotManager> hotspotManager;
|
||||||
|
|
||||||
std::unique_ptr<Updater> updater;
|
std::unique_ptr<Updater> updater;
|
||||||
UpdateStatus currentUpdateStatus;
|
UpdateStatus currentUpdateStatus;
|
||||||
void OnUpdateStatusChanged(const UpdateStatus&updateStatus);
|
void OnUpdateStatusChanged(const UpdateStatus &updateStatus);
|
||||||
std::function<void(void)> restartListener;
|
std::function<void(void)> restartListener;
|
||||||
|
|
||||||
std::unique_ptr<Lv2PluginChangeMonitor> pluginChangeMonitor;
|
std::unique_ptr<Lv2PluginChangeMonitor> pluginChangeMonitor;
|
||||||
@@ -131,8 +134,10 @@ namespace pipedal
|
|||||||
class AtomOutputListener
|
class AtomOutputListener
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool WantsProperty(uint64_t instanceId,LV2_URID patchProperty) const {
|
bool WantsProperty(uint64_t instanceId, LV2_URID patchProperty) const
|
||||||
if (instanceId != this->instanceId) return false;
|
{
|
||||||
|
if (instanceId != this->instanceId)
|
||||||
|
return false;
|
||||||
return this->propertyUrid == 0 || patchProperty == this->propertyUrid;
|
return this->propertyUrid == 0 || patchProperty == this->propertyUrid;
|
||||||
}
|
}
|
||||||
int64_t clientId;
|
int64_t clientId;
|
||||||
@@ -151,11 +156,14 @@ namespace pipedal
|
|||||||
AtomConverter atomConverter; // must be AFTER pluginHost!
|
AtomConverter atomConverter; // must be AFTER pluginHost!
|
||||||
|
|
||||||
Pedalboard pedalboard;
|
Pedalboard pedalboard;
|
||||||
|
bool previousPedalboardLoaded = false;
|
||||||
|
Pedalboard previousPedalboard;
|
||||||
Storage storage;
|
Storage storage;
|
||||||
bool hasPresetChanged = false;
|
bool hasPresetChanged = false;
|
||||||
|
|
||||||
NetworkChangedListener networkChangedListener;
|
NetworkChangedListener networkChangedListener;
|
||||||
void FireNetworkChanged() {
|
void FireNetworkChanged()
|
||||||
|
{
|
||||||
if (networkChangedListener)
|
if (networkChangedListener)
|
||||||
{
|
{
|
||||||
networkChangedListener();
|
networkChangedListener();
|
||||||
@@ -168,15 +176,18 @@ namespace pipedal
|
|||||||
std::filesystem::path webRoot;
|
std::filesystem::path webRoot;
|
||||||
|
|
||||||
std::vector<IPiPedalModelSubscriber *> subscribers;
|
std::vector<IPiPedalModelSubscriber *> subscribers;
|
||||||
void SetPresetChanged(int64_t clientId, bool value);
|
void SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect = true);
|
||||||
|
void FireSelectedSnapshotChanged(int64_t selectedSnapshot);
|
||||||
void FirePresetsChanged(int64_t clientId);
|
void FirePresetsChanged(int64_t clientId);
|
||||||
|
void FirePresetChanged(bool changed);
|
||||||
void FirePluginPresetsChanged(const std::string &pluginUri);
|
void FirePluginPresetsChanged(const std::string &pluginUri);
|
||||||
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
|
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
|
||||||
void FireChannelSelectionChanged(int64_t clientId);
|
void FireChannelSelectionChanged(int64_t clientId);
|
||||||
void FireBanksChanged(int64_t clientId);
|
void FireBanksChanged(int64_t clientId);
|
||||||
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
|
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
|
||||||
|
|
||||||
void UpdateDefaults(PedalboardItem *pedalboardItem);
|
void UpdateDefaults(Snapshot *snapshot, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
|
||||||
|
void UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered_map<int64_t, PedalboardItem *> &itemMap);
|
||||||
void UpdateDefaults(Pedalboard *pedalboard);
|
void UpdateDefaults(Pedalboard *pedalboard);
|
||||||
class VuSubscription
|
class VuSubscription
|
||||||
{
|
{
|
||||||
@@ -206,22 +217,22 @@ namespace pipedal
|
|||||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
|
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override;
|
||||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
|
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
|
||||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
|
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
|
||||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) override;
|
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
|
||||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
||||||
|
|
||||||
|
void OnNotifyPathPatchPropertyReceived(
|
||||||
void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson);
|
int64_t instanceId,
|
||||||
|
LV2_URID pathPatchProperty,
|
||||||
|
LV2_Atom *pathProperty) override;
|
||||||
|
|
||||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
|
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
|
||||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override;
|
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest &request) override;
|
||||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) override;
|
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) override;
|
||||||
|
|
||||||
PostHandle networkChangingDelayHandle = 0;
|
PostHandle networkChangingDelayHandle = 0;
|
||||||
void CancelNetworkChangingTimer();
|
void CancelNetworkChangingTimer();
|
||||||
|
|
||||||
void OnNetworkChanging(bool ethernetConnected,bool hotspotConnected);
|
void OnNetworkChanging(bool ethernetConnected, bool hotspotConnected);
|
||||||
void OnNetworkChanged(bool ethernetConnected, bool hotspotConnected);
|
void OnNetworkChanged(bool ethernetConnected, bool hotspotConnected);
|
||||||
|
|
||||||
void UpdateVst3Settings(Pedalboard &pedalboard);
|
void UpdateVst3Settings(Pedalboard &pedalboard);
|
||||||
@@ -229,25 +240,39 @@ namespace pipedal
|
|||||||
PiPedalConfiguration configuration;
|
PiPedalConfiguration configuration;
|
||||||
|
|
||||||
void CheckForResourceInitialization(Pedalboard &pedalboard);
|
void CheckForResourceInitialization(Pedalboard &pedalboard);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PiPedalModel();
|
PiPedalModel();
|
||||||
virtual ~PiPedalModel();
|
virtual ~PiPedalModel();
|
||||||
|
|
||||||
|
enum class Direction
|
||||||
|
{
|
||||||
|
Increase,
|
||||||
|
Decrease
|
||||||
|
};
|
||||||
|
void NextBank(Direction direction = Direction::Increase);
|
||||||
|
void PreviousBank() { NextBank(Direction::Decrease); }
|
||||||
|
void NextPreset(Direction direction = Direction::Increase);
|
||||||
|
void PreviousPreset() { NextPreset(Direction::Decrease); }
|
||||||
|
|
||||||
void RequestShutdown(bool restart);
|
void RequestShutdown(bool restart);
|
||||||
|
|
||||||
virtual PostHandle Post(PostCallback&&fn);
|
virtual PostHandle Post(PostCallback &&fn);
|
||||||
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn);
|
virtual PostHandle PostDelayed(const clock::duration &delay, PostCallback &&fn);
|
||||||
virtual bool CancelPost(PostHandle handle);
|
virtual bool CancelPost(PostHandle handle);
|
||||||
|
|
||||||
template<class REP,class PERIOD>
|
template <class REP, class PERIOD>
|
||||||
PostHandle PostDelayed(const std::chrono::duration<REP,PERIOD>&delay,PostCallback&&fn)
|
PostHandle PostDelayed(const std::chrono::duration<REP, PERIOD> &delay, PostCallback &&fn)
|
||||||
{
|
{
|
||||||
return PostDelayed(
|
return PostDelayed(
|
||||||
std::chrono::duration_cast<clock::duration,REP,PERIOD>(delay),
|
std::chrono::duration_cast<clock::duration, REP, PERIOD>(delay),
|
||||||
std::move(fn));
|
std::move(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetNetworkChangedListener(NetworkChangedListener listener) {
|
std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri);
|
||||||
|
|
||||||
|
void SetNetworkChangedListener(NetworkChangedListener listener)
|
||||||
|
{
|
||||||
networkChangedListener = listener;
|
networkChangedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,8 +281,8 @@ namespace pipedal
|
|||||||
void WaitForAudioDeviceToComeOnline();
|
void WaitForAudioDeviceToComeOnline();
|
||||||
|
|
||||||
UpdateStatus GetUpdateStatus();
|
UpdateStatus GetUpdateStatus();
|
||||||
void UpdateNow(const std::string&updateUrl);
|
void UpdateNow(const std::string &updateUrl);
|
||||||
void FireUpdateStatusChanged(const UpdateStatus&updateStatus);
|
void FireUpdateStatusChanged(const UpdateStatus &updateStatus);
|
||||||
uint16_t GetWebPort() const { return webPort; }
|
uint16_t GetWebPort() const { return webPort; }
|
||||||
std::filesystem::path GetPluginUploadDirectory() const;
|
std::filesystem::path GetPluginUploadDirectory() const;
|
||||||
void Close();
|
void Close();
|
||||||
@@ -298,14 +323,15 @@ namespace pipedal
|
|||||||
void PreviewInputVolume(float value);
|
void PreviewInputVolume(float value);
|
||||||
void PreviewOutputVolume(float value);
|
void PreviewOutputVolume(float value);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
void SetPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||||
Pedalboard&GetPedalboard();
|
Pedalboard &GetPedalboard();
|
||||||
std::shared_ptr<Lv2Pedalboard> GetLv2Pedalboard();
|
std::shared_ptr<Lv2Pedalboard> GetLv2Pedalboard();
|
||||||
|
|
||||||
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard);
|
||||||
|
|
||||||
|
void SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshots, int64_t selectedSnapshot);
|
||||||
|
void SetSnapshot(int64_t selectedSnapshot);
|
||||||
|
|
||||||
void GetPresets(PresetIndex *pResult);
|
void GetPresets(PresetIndex *pResult);
|
||||||
|
|
||||||
Pedalboard GetPreset(int64_t instanceId);
|
Pedalboard GetPreset(int64_t instanceId);
|
||||||
@@ -369,11 +395,10 @@ namespace pipedal
|
|||||||
int64_t clientId,
|
int64_t clientId,
|
||||||
int64_t instanceId,
|
int64_t instanceId,
|
||||||
const std::string uri,
|
const std::string uri,
|
||||||
const json_variant&value,
|
const json_variant &value,
|
||||||
std::function<void()> onSuccess,
|
std::function<void()> onSuccess,
|
||||||
std::function<void(const std::string &error)> onError);
|
std::function<void(const std::string &error)> onError);
|
||||||
|
|
||||||
|
|
||||||
BankIndex GetBankIndex() const;
|
BankIndex GetBankIndex() const;
|
||||||
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
|
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||||
int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
|
int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||||
@@ -389,7 +414,7 @@ namespace pipedal
|
|||||||
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||||
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||||
|
|
||||||
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId,const std::string&propertyUri);
|
void MonitorPatchProperty(int64_t clientId, int64_t clientHandle, uint64_t instanceId, const std::string &propertyUri);
|
||||||
void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle);
|
void CancelMonitorPatchProperty(int64_t clientId, int64_t clientHandle);
|
||||||
|
|
||||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||||
@@ -399,15 +424,15 @@ namespace pipedal
|
|||||||
void SetFavorites(const std::map<std::string, bool> &favorites);
|
void SetFavorites(const std::map<std::string, bool> &favorites);
|
||||||
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
||||||
void ForceUpdateCheck();
|
void ForceUpdateCheck();
|
||||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
std::vector<std::string> GetFileList(const UiFileProperty &fileProperty);
|
||||||
std::vector<FileEntry> GetFileList2(const std::string &relativePath,const UiFileProperty&fileProperty);
|
std::vector<FileEntry> GetFileList2(const std::string &relativePath, const UiFileProperty &fileProperty);
|
||||||
|
|
||||||
void DeleteSampleFile(const std::filesystem::path &fileName);
|
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||||
std::string CreateNewSampleDirectory(const std::string&relativePath, const UiFileProperty&uiFileProperty);
|
std::string CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty);
|
||||||
std::string RenameFilePropertyFile(const std::string&oldRelativePath,const std::string&newRelativePath,const UiFileProperty&uiFileProperty);
|
std::string RenameFilePropertyFile(const std::string &oldRelativePath, const std::string &newRelativePath, const UiFileProperty &uiFileProperty);
|
||||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty);
|
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty);
|
||||||
|
|
||||||
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,std::istream&inputStream,size_t streamLength);
|
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &inputStream, size_t streamLength);
|
||||||
uint64_t CreateNewPreset();
|
uint64_t CreateNewPreset();
|
||||||
|
|
||||||
bool LoadCurrentPedalboard();
|
bool LoadCurrentPedalboard();
|
||||||
|
|||||||
@@ -46,8 +46,23 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
|
class PathPatchPropertyChangedBody
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int64_t instanceId_;
|
||||||
|
std::string propertyUri_;
|
||||||
|
std::string atomJson_;
|
||||||
|
DECLARE_JSON_MAP(PathPatchPropertyChangedBody);
|
||||||
|
};
|
||||||
|
JSON_MAP_BEGIN(PathPatchPropertyChangedBody)
|
||||||
|
JSON_MAP_REFERENCE(PathPatchPropertyChangedBody, instanceId)
|
||||||
|
JSON_MAP_REFERENCE(PathPatchPropertyChangedBody, propertyUri)
|
||||||
|
JSON_MAP_REFERENCE(PathPatchPropertyChangedBody, atomJson)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
class GetPatchPropertyBody {
|
|
||||||
|
class GetPatchPropertyBody
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
uint64_t instanceId_;
|
uint64_t instanceId_;
|
||||||
std::string propertyUri_;
|
std::string propertyUri_;
|
||||||
@@ -59,8 +74,8 @@ JSON_MAP_REFERENCE(GetPatchPropertyBody, instanceId)
|
|||||||
JSON_MAP_REFERENCE(GetPatchPropertyBody, propertyUri)
|
JSON_MAP_REFERENCE(GetPatchPropertyBody, propertyUri)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
class CreateNewSampleDirectoryArgs
|
||||||
class CreateNewSampleDirectoryArgs {
|
{
|
||||||
public:
|
public:
|
||||||
std::string relativePath_;
|
std::string relativePath_;
|
||||||
UiFileProperty uiFileProperty_;
|
UiFileProperty uiFileProperty_;
|
||||||
@@ -72,7 +87,8 @@ JSON_MAP_REFERENCE(CreateNewSampleDirectoryArgs, relativePath)
|
|||||||
JSON_MAP_REFERENCE(CreateNewSampleDirectoryArgs, uiFileProperty)
|
JSON_MAP_REFERENCE(CreateNewSampleDirectoryArgs, uiFileProperty)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
class RenameSampleFileArgs {
|
class RenameSampleFileArgs
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
std::string oldRelativePath_;
|
std::string oldRelativePath_;
|
||||||
std::string newRelativePath_;
|
std::string newRelativePath_;
|
||||||
@@ -86,8 +102,8 @@ JSON_MAP_REFERENCE(RenameSampleFileArgs, newRelativePath)
|
|||||||
JSON_MAP_REFERENCE(RenameSampleFileArgs, uiFileProperty)
|
JSON_MAP_REFERENCE(RenameSampleFileArgs, uiFileProperty)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
class Lv2StateChangedBody
|
||||||
class Lv2StateChangedBody {
|
{
|
||||||
public:
|
public:
|
||||||
uint64_t instanceId_;
|
uint64_t instanceId_;
|
||||||
Lv2PluginState state_;
|
Lv2PluginState state_;
|
||||||
@@ -99,9 +115,8 @@ JSON_MAP_REFERENCE(Lv2StateChangedBody, instanceId)
|
|||||||
JSON_MAP_REFERENCE(Lv2StateChangedBody, state)
|
JSON_MAP_REFERENCE(Lv2StateChangedBody, state)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
class SetPatchPropertyBody
|
||||||
|
{
|
||||||
class SetPatchPropertyBody {
|
|
||||||
public:
|
public:
|
||||||
uint64_t instanceId_;
|
uint64_t instanceId_;
|
||||||
std::string propertyUri_;
|
std::string propertyUri_;
|
||||||
@@ -171,7 +186,7 @@ public:
|
|||||||
JSON_MAP_BEGIN(MonitorPatchPropertyBody)
|
JSON_MAP_BEGIN(MonitorPatchPropertyBody)
|
||||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId)
|
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, instanceId)
|
||||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle)
|
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, clientHandle)
|
||||||
JSON_MAP_REFERENCE(MonitorPatchPropertyBody,propertyUri)
|
JSON_MAP_REFERENCE(MonitorPatchPropertyBody, propertyUri)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
class OnLoadPluginPresetBody
|
class OnLoadPluginPresetBody
|
||||||
@@ -240,9 +255,6 @@ JSON_MAP_REFERENCE(FileRequestArgs, relativePath)
|
|||||||
JSON_MAP_REFERENCE(FileRequestArgs, fileProperty)
|
JSON_MAP_REFERENCE(FileRequestArgs, fileProperty)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MonitorPortBody
|
class MonitorPortBody
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -370,6 +382,20 @@ JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, clientId)
|
|||||||
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard)
|
JSON_MAP_REFERENCE(UpdateCurrentPedalboardBody, pedalboard)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
class SetSnapshotsBody
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<std::shared_ptr<Snapshot>> snapshots_;
|
||||||
|
int64_t selectedSnapshot_;
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(SetSnapshotsBody);
|
||||||
|
};
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(SetSnapshotsBody)
|
||||||
|
JSON_MAP_REFERENCE(SetSnapshotsBody, snapshots)
|
||||||
|
JSON_MAP_REFERENCE(SetSnapshotsBody, selectedSnapshot)
|
||||||
|
JSON_MAP_END()
|
||||||
|
|
||||||
class ChannelSelectionChangedBody
|
class ChannelSelectionChangedBody
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -451,7 +477,6 @@ JSON_MAP_REFERENCE(Vst3ControlChangedBody, value)
|
|||||||
JSON_MAP_REFERENCE(Vst3ControlChangedBody, state)
|
JSON_MAP_REFERENCE(Vst3ControlChangedBody, state)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
|
|
||||||
class PiPedalSocketHandler : public SocketHandler, public IPiPedalModelSubscriber
|
class PiPedalSocketHandler : public SocketHandler, public IPiPedalModelSubscriber
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@@ -480,25 +505,25 @@ private:
|
|||||||
PortMonitorSubscription(
|
PortMonitorSubscription(
|
||||||
int64_t subscriptionHandle,
|
int64_t subscriptionHandle,
|
||||||
int64_t instanceId,
|
int64_t instanceId,
|
||||||
const std::string& key)
|
const std::string &key)
|
||||||
:subscriptionHandle(subscriptionHandle),
|
: subscriptionHandle(subscriptionHandle),
|
||||||
instanceId(instanceId),
|
instanceId(instanceId),
|
||||||
key(key)
|
key(key)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
~PortMonitorSubscription() {
|
~PortMonitorSubscription()
|
||||||
|
{
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
PortMonitorSubscription() {}
|
PortMonitorSubscription() {}
|
||||||
PortMonitorSubscription(const PortMonitorSubscription&) = delete;
|
PortMonitorSubscription(const PortMonitorSubscription &) = delete;
|
||||||
PortMonitorSubscription& operator=(const PortMonitorSubscription&) = delete;
|
PortMonitorSubscription &operator=(const PortMonitorSubscription &) = delete;
|
||||||
void Close() {
|
void Close()
|
||||||
std::lock_guard lock {pmMutex};
|
{
|
||||||
|
std::lock_guard lock{pmMutex};
|
||||||
closed = true;
|
closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::mutex pmMutex;
|
std::mutex pmMutex;
|
||||||
|
|
||||||
bool closed = false;
|
bool closed = false;
|
||||||
@@ -532,7 +557,8 @@ public:
|
|||||||
bool finalCleanup = false;
|
bool finalCleanup = false;
|
||||||
void FinalCleanup()
|
void FinalCleanup()
|
||||||
{
|
{
|
||||||
if (finalCleanup) return;
|
if (finalCleanup)
|
||||||
|
return;
|
||||||
finalCleanup = true;
|
finalCleanup = true;
|
||||||
// avoid use after free.
|
// avoid use after free.
|
||||||
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
||||||
@@ -547,12 +573,12 @@ public:
|
|||||||
activeVuSubscriptions.resize(0);
|
activeVuSubscriptions.resize(0);
|
||||||
|
|
||||||
model.RemoveNotificationSubsription(this);
|
model.RemoveNotificationSubsription(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Close()
|
virtual void Close()
|
||||||
{
|
{
|
||||||
if (closed) return;
|
if (closed)
|
||||||
|
return;
|
||||||
closed = true;
|
closed = true;
|
||||||
|
|
||||||
FinalCleanup(); // do it while we can. &model will no longer be valid after this. ( :-( )
|
FinalCleanup(); // do it while we can. &model will no longer be valid after this. ( :-( )
|
||||||
@@ -566,7 +592,8 @@ public:
|
|||||||
std::stringstream imageList;
|
std::stringstream imageList;
|
||||||
const std::filesystem::path &webRoot = model.GetWebRoot() / "img";
|
const std::filesystem::path &webRoot = model.GetWebRoot() / "img";
|
||||||
bool firstTime = true;
|
bool firstTime = true;
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
|
for (const auto &entry : std::filesystem::directory_iterator(webRoot))
|
||||||
{
|
{
|
||||||
if (!firstTime)
|
if (!firstTime)
|
||||||
@@ -576,7 +603,8 @@ public:
|
|||||||
firstTime = false;
|
firstTime = false;
|
||||||
imageList << entry.path().filename().string();
|
imageList << entry.path().filename().string();
|
||||||
}
|
}
|
||||||
} catch (const std::exception&)
|
}
|
||||||
|
catch (const std::exception &)
|
||||||
{
|
{
|
||||||
Lv2Log::error("Can't list files in %s. Image files will not be pre-loaded in the client.", webRoot.c_str());
|
Lv2Log::error("Can't list files in %s. Image files will not be pre-loaded in the client.", webRoot.c_str());
|
||||||
}
|
}
|
||||||
@@ -814,7 +842,7 @@ public:
|
|||||||
{
|
{
|
||||||
std::shared_ptr<PortMonitorSubscription> result;
|
std::shared_ptr<PortMonitorSubscription> result;
|
||||||
{
|
{
|
||||||
std::lock_guard lock (activePortMonitorsMutex);
|
std::lock_guard lock(activePortMonitorsMutex);
|
||||||
|
|
||||||
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -832,8 +860,9 @@ public:
|
|||||||
{
|
{
|
||||||
// running on RT_output thread, or on Socket thread.
|
// running on RT_output thread, or on Socket thread.
|
||||||
{
|
{
|
||||||
std::lock_guard lock {subscription->pmMutex};
|
std::lock_guard lock{subscription->pmMutex};
|
||||||
if (subscription->closed) return;
|
if (subscription->closed)
|
||||||
|
return;
|
||||||
if (value == subscription->currentValue)
|
if (value == subscription->currentValue)
|
||||||
return;
|
return;
|
||||||
if (subscription->waitingForAck)
|
if (subscription->waitingForAck)
|
||||||
@@ -846,7 +875,7 @@ public:
|
|||||||
subscription->lastValue = value;
|
subscription->lastValue = value;
|
||||||
subscription->waitingForAck = true;
|
subscription->waitingForAck = true;
|
||||||
}
|
}
|
||||||
SendMonitorPortMessage_Inner(subscription,value);
|
SendMonitorPortMessage_Inner(subscription, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// post-sync send.
|
// post-sync send.
|
||||||
@@ -863,12 +892,14 @@ public:
|
|||||||
{
|
{
|
||||||
// running on PiPedalSocket thread.
|
// running on PiPedalSocket thread.
|
||||||
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
|
std::shared_ptr<PortMonitorSubscription> subscription = getPortMonitorSubscription(subscriptionHandle_);
|
||||||
if (!subscription) return;
|
if (!subscription)
|
||||||
|
return;
|
||||||
|
|
||||||
float value;
|
float value;
|
||||||
{
|
{
|
||||||
std::unique_lock lock {subscription->pmMutex};
|
std::unique_lock lock{subscription->pmMutex};
|
||||||
if (subscription->closed) return;
|
if (subscription->closed)
|
||||||
|
return;
|
||||||
if (subscription->pendingValue)
|
if (subscription->pendingValue)
|
||||||
{
|
{
|
||||||
value = subscription->currentValue;
|
value = subscription->currentValue;
|
||||||
@@ -892,7 +923,7 @@ public:
|
|||||||
Lv2Log::debug("Failed to monitor port output. (%s)", e.what());
|
Lv2Log::debug("Failed to monitor port output. (%s)", e.what());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void MonitorPort(int replyTo,MonitorPortBody &body)
|
void MonitorPort(int replyTo, MonitorPortBody &body)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
||||||
int64_t subscriptionHandle = model.MonitorPort(
|
int64_t subscriptionHandle = model.MonitorPort(
|
||||||
@@ -971,7 +1002,8 @@ public:
|
|||||||
ControlChangedBody message;
|
ControlChangedBody message;
|
||||||
pReader->read(&message);
|
pReader->read(&message);
|
||||||
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
||||||
} else if (message == "setControl")
|
}
|
||||||
|
else if (message == "setControl")
|
||||||
{
|
{
|
||||||
ControlChangedBody message;
|
ControlChangedBody message;
|
||||||
pReader->read(&message);
|
pReader->read(&message);
|
||||||
@@ -1029,7 +1061,7 @@ public:
|
|||||||
else if (message == "getUpdateStatus")
|
else if (message == "getUpdateStatus")
|
||||||
{
|
{
|
||||||
UpdateStatus updateStatus = model.GetUpdateStatus();
|
UpdateStatus updateStatus = model.GetUpdateStatus();
|
||||||
this->Reply(replyTo,"getUpdateStatus",updateStatus);
|
this->Reply(replyTo, "getUpdateStatus", updateStatus);
|
||||||
}
|
}
|
||||||
else if (message == "updateNow")
|
else if (message == "updateNow")
|
||||||
{
|
{
|
||||||
@@ -1037,7 +1069,7 @@ public:
|
|||||||
pReader->read(&updateUrl);
|
pReader->read(&updateUrl);
|
||||||
model.UpdateNow(updateUrl);
|
model.UpdateNow(updateUrl);
|
||||||
bool result = true;
|
bool result = true;
|
||||||
this->Reply(replyTo,"updateNow",result);
|
this->Reply(replyTo, "updateNow", result);
|
||||||
}
|
}
|
||||||
else if (message == "getJackStatus")
|
else if (message == "getJackStatus")
|
||||||
{
|
{
|
||||||
@@ -1053,7 +1085,6 @@ public:
|
|||||||
{
|
{
|
||||||
std::vector<std::string> channels = this->model.GetKnownWifiNetworks();
|
std::vector<std::string> channels = this->model.GetKnownWifiNetworks();
|
||||||
this->Reply(replyTo, "getWifiChannels", channels);
|
this->Reply(replyTo, "getWifiChannels", channels);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (message == "getWifiChannels")
|
else if (message == "getWifiChannels")
|
||||||
{
|
{
|
||||||
@@ -1199,6 +1230,18 @@ public:
|
|||||||
this->model.UpdateCurrentPedalboard(body.clientId_, body.pedalboard_);
|
this->model.UpdateCurrentPedalboard(body.clientId_, body.pedalboard_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (message == "setSnapshot")
|
||||||
|
{
|
||||||
|
int64_t snapshotIndex = -1;
|
||||||
|
pReader->read(&snapshotIndex);
|
||||||
|
this->model.SetSnapshot(snapshotIndex);
|
||||||
|
}
|
||||||
|
else if (message == "setSnapshots")
|
||||||
|
{
|
||||||
|
SetSnapshotsBody body;
|
||||||
|
pReader->read(&body);
|
||||||
|
this->model.SetSnapshots(body.snapshots_, body.selectedSnapshot_);
|
||||||
|
}
|
||||||
else if (message == "currentPedalboard")
|
else if (message == "currentPedalboard")
|
||||||
{
|
{
|
||||||
auto pedalboard = model.GetCurrentPedalboardCopy();
|
auto pedalboard = model.GetCurrentPedalboardCopy();
|
||||||
@@ -1347,6 +1390,23 @@ public:
|
|||||||
this->SendError(replyTo, std::string(e.what()));
|
this->SendError(replyTo, std::string(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (message == "nextBank")
|
||||||
|
{
|
||||||
|
model.NextBank();
|
||||||
|
}
|
||||||
|
else if (message == "previousBank")
|
||||||
|
{
|
||||||
|
model.PreviousBank();
|
||||||
|
}
|
||||||
|
else if (message == "nextPreset")
|
||||||
|
{
|
||||||
|
model.NextPreset();
|
||||||
|
}
|
||||||
|
else if (message == "previousPreset")
|
||||||
|
{
|
||||||
|
model.PreviousPreset();
|
||||||
|
}
|
||||||
|
|
||||||
else if (message == "renamePresetItem")
|
else if (message == "renamePresetItem")
|
||||||
{
|
{
|
||||||
RenamePresetBody body;
|
RenamePresetBody body;
|
||||||
@@ -1373,17 +1433,14 @@ public:
|
|||||||
{
|
{
|
||||||
SetPatchPropertyBody body;
|
SetPatchPropertyBody body;
|
||||||
pReader->read(&body);
|
pReader->read(&body);
|
||||||
model.SendSetPatchProperty(clientId,body.instanceId_,body.propertyUri_,body.value_,
|
model.SendSetPatchProperty(clientId, body.instanceId_, body.propertyUri_, body.value_, [this, replyTo]()
|
||||||
[this, replyTo] () {
|
{
|
||||||
this->JsonReply(replyTo, "setPatchProperty", "true");
|
this->JsonReply(replyTo, "setPatchProperty", "true");
|
||||||
|
|
||||||
},
|
},
|
||||||
[this, replyTo] (const std::string&error) {
|
[this, replyTo](const std::string &error)
|
||||||
|
{
|
||||||
this->SendError(replyTo, error.c_str());
|
this->SendError(replyTo, error.c_str());
|
||||||
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (message == "getPatchProperty")
|
else if (message == "getPatchProperty")
|
||||||
@@ -1410,7 +1467,7 @@ public:
|
|||||||
MonitorPortBody body;
|
MonitorPortBody body;
|
||||||
pReader->read(&body);
|
pReader->read(&body);
|
||||||
|
|
||||||
MonitorPort(replyTo,body);
|
MonitorPort(replyTo, body);
|
||||||
}
|
}
|
||||||
else if (message == "unmonitorPort")
|
else if (message == "unmonitorPort")
|
||||||
{
|
{
|
||||||
@@ -1492,7 +1549,6 @@ public:
|
|||||||
else if (message == "forceUpdateCheck")
|
else if (message == "forceUpdateCheck")
|
||||||
{
|
{
|
||||||
this->model.ForceUpdateCheck();
|
this->model.ForceUpdateCheck();
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (message == "setSystemMidiBindings")
|
else if (message == "setSystemMidiBindings")
|
||||||
{
|
{
|
||||||
@@ -1503,26 +1559,26 @@ public:
|
|||||||
else if (message == "getSystemMidiBindings")
|
else if (message == "getSystemMidiBindings")
|
||||||
{
|
{
|
||||||
std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings();
|
std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings();
|
||||||
this->Reply(replyTo,"getSystemMidiBindings",bindings);
|
this->Reply(replyTo, "getSystemMidiBindings", bindings);
|
||||||
}
|
}
|
||||||
else if (message == "requestFileList")
|
else if (message == "requestFileList")
|
||||||
{
|
{
|
||||||
UiFileProperty fileProperty;
|
UiFileProperty fileProperty;
|
||||||
pReader->read(&fileProperty);
|
pReader->read(&fileProperty);
|
||||||
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
||||||
this->Reply(replyTo,"requestFileList",list);
|
this->Reply(replyTo, "requestFileList", list);
|
||||||
}
|
}
|
||||||
else if (message == "requestFileList2")
|
else if (message == "requestFileList2")
|
||||||
{
|
{
|
||||||
FileRequestArgs requestArgs;
|
FileRequestArgs requestArgs;
|
||||||
pReader->read(&requestArgs);
|
pReader->read(&requestArgs);
|
||||||
std::vector<FileEntry> list = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
|
std::vector<FileEntry> list = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
|
||||||
this->Reply(replyTo,"requestFileList2",list);
|
this->Reply(replyTo, "requestFileList2", list);
|
||||||
}
|
}
|
||||||
else if (message == "newPreset")
|
else if (message == "newPreset")
|
||||||
{
|
{
|
||||||
int64_t presetId = this->model.CreateNewPreset();
|
int64_t presetId = this->model.CreateNewPreset();
|
||||||
this->Reply(replyTo,"newPreset",presetId);
|
this->Reply(replyTo, "newPreset", presetId);
|
||||||
}
|
}
|
||||||
else if (message == "deleteUserFile")
|
else if (message == "deleteUserFile")
|
||||||
{
|
{
|
||||||
@@ -1530,29 +1586,30 @@ public:
|
|||||||
pReader->read(&fileName);
|
pReader->read(&fileName);
|
||||||
|
|
||||||
this->model.DeleteSampleFile(fileName);
|
this->model.DeleteSampleFile(fileName);
|
||||||
this->Reply(replyTo,"deleteUserFile",true);
|
this->Reply(replyTo, "deleteUserFile", true);
|
||||||
}
|
}
|
||||||
else if (message == "createNewSampleDirectory")
|
else if (message == "createNewSampleDirectory")
|
||||||
{
|
{
|
||||||
CreateNewSampleDirectoryArgs args;
|
CreateNewSampleDirectoryArgs args;
|
||||||
pReader->read(&args);
|
pReader->read(&args);
|
||||||
|
|
||||||
std::string newFileName = this->model.CreateNewSampleDirectory(args.relativePath_,args.uiFileProperty_);
|
std::string newFileName = this->model.CreateNewSampleDirectory(args.relativePath_, args.uiFileProperty_);
|
||||||
this->Reply(replyTo,"createNewSampleDirectory",newFileName);
|
this->Reply(replyTo, "createNewSampleDirectory", newFileName);
|
||||||
}
|
}
|
||||||
else if (message == "renameFilePropertyFile")
|
else if (message == "renameFilePropertyFile")
|
||||||
{
|
{
|
||||||
RenameSampleFileArgs args;
|
RenameSampleFileArgs args;
|
||||||
pReader->read(&args);
|
pReader->read(&args);
|
||||||
|
|
||||||
std::string newFileName = this->model.RenameFilePropertyFile(args.oldRelativePath_,args.newRelativePath_,args.uiFileProperty_);
|
std::string newFileName = this->model.RenameFilePropertyFile(args.oldRelativePath_, args.newRelativePath_, args.uiFileProperty_);
|
||||||
this->Reply(replyTo,"renameFilePropertyFile",newFileName);
|
this->Reply(replyTo, "renameFilePropertyFile", newFileName);
|
||||||
} else if (message == "getFilePropertyDirectoryTree"){
|
}
|
||||||
|
else if (message == "getFilePropertyDirectoryTree")
|
||||||
|
{
|
||||||
UiFileProperty uiFileProperty;
|
UiFileProperty uiFileProperty;
|
||||||
pReader->read(&uiFileProperty);
|
pReader->read(&uiFileProperty);
|
||||||
FilePropertyDirectoryTree::ptr result = model.GetFilePropertydirectoryTree(uiFileProperty);
|
FilePropertyDirectoryTree::ptr result = model.GetFilePropertydirectoryTree(uiFileProperty);
|
||||||
this->Reply(replyTo,"GetFilePropertydirectoryTree",result);
|
this->Reply(replyTo, "GetFilePropertydirectoryTree", result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (message == "setOnboarding")
|
else if (message == "setOnboarding")
|
||||||
@@ -1633,37 +1690,37 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void OnUpdateStatusChanged(const UpdateStatus&updateStatus) {
|
virtual void OnUpdateStatusChanged(const UpdateStatus &updateStatus)
|
||||||
Send("onUpdateStatusChanged",updateStatus);
|
{
|
||||||
|
Send("onUpdateStatusChanged", updateStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnLv2StateChanged(int64_t instanceId, const Lv2PluginState &state)
|
virtual void OnLv2StateChanged(int64_t instanceId, const Lv2PluginState &state)
|
||||||
{
|
{
|
||||||
Lv2StateChangedBody message { (uint64_t)instanceId, state};
|
Lv2StateChangedBody message{(uint64_t)instanceId, state};
|
||||||
Send("onLv2StateChanged",message);
|
Send("onLv2StateChanged", message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnLv2PluginsChanging() override {
|
virtual void OnLv2PluginsChanging() override
|
||||||
Send("onLv2PluginsChanging",true);
|
{
|
||||||
|
Send("onLv2PluginsChanging", true);
|
||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
virtual void OnNetworkChanging(bool hotspotConnected) override {
|
virtual void OnNetworkChanging(bool hotspotConnected) override
|
||||||
try {
|
{
|
||||||
Send("onNetworkChanging",hotspotConnected);
|
try
|
||||||
|
{
|
||||||
|
Send("onNetworkChanging", hotspotConnected);
|
||||||
Flush();
|
Flush();
|
||||||
|
}
|
||||||
} catch (const std::exception&ignored)
|
catch (const std::exception &ignored)
|
||||||
{
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
virtual void OnErrorMessage(const std::string &message)
|
||||||
|
|
||||||
|
|
||||||
virtual void OnErrorMessage(const std::string&message)
|
|
||||||
{
|
{
|
||||||
Send("onErrorMessage",message);
|
Send("onErrorMessage", message);
|
||||||
}
|
}
|
||||||
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value)
|
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value)
|
||||||
// {
|
// {
|
||||||
@@ -1675,8 +1732,9 @@ private:
|
|||||||
// Send("onPatchPropertyChanged",body);
|
// Send("onPatchPropertyChanged",body);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
|
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding> &bindings)
|
||||||
Send("onSystemMidiBindingsChanged",bindings);
|
{
|
||||||
|
Send("onSystemMidiBindingsChanged", bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites)
|
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites)
|
||||||
@@ -1696,6 +1754,16 @@ private:
|
|||||||
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
|
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
|
||||||
Send("onChannelSelectionChanged", body);
|
Send("onChannelSelectionChanged", body);
|
||||||
}
|
}
|
||||||
|
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) override
|
||||||
|
{
|
||||||
|
Send("onSelectedSnapshotChanged", selectedSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnPresetChanged(bool changed) override
|
||||||
|
{
|
||||||
|
Send("onPresetChanged", changed);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets)
|
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets)
|
||||||
{
|
{
|
||||||
PresetsChangedBody body;
|
PresetsChangedBody body;
|
||||||
@@ -1852,8 +1920,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flush() {
|
void Flush()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
int outstandingNotifyAtomOutputs = 0;
|
int outstandingNotifyAtomOutputs = 0;
|
||||||
|
|
||||||
@@ -1910,7 +1978,7 @@ private:
|
|||||||
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.propertyUri == atomProperty)
|
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.propertyUri == atomProperty)
|
||||||
{
|
{
|
||||||
// better to erase than overwrite, since it provides better idempotence.
|
// better to erase than overwrite, since it provides better idempotence.
|
||||||
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()+i);
|
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin() + i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1930,6 +1998,16 @@ private:
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
virtual void OnNotifyPathPatchPropertyChanged(int64_t instanceId, const std::string &pathPatchPropertyString, const std::string &atomString)
|
||||||
|
{
|
||||||
|
PathPatchPropertyChangedBody body;
|
||||||
|
body.instanceId_ = instanceId;
|
||||||
|
body.propertyUri_ = pathPatchPropertyString;
|
||||||
|
body.atomJson_ = atomString;
|
||||||
|
|
||||||
|
Send("onNotifyPathPatchPropertyChanged", body);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl)
|
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl)
|
||||||
{
|
{
|
||||||
NotifyMidiListenerBody body;
|
NotifyMidiListenerBody body;
|
||||||
@@ -2014,4 +2092,3 @@ std::shared_ptr<ISocketFactory> pipedal::MakePiPedalSocketFactory(PiPedalModel &
|
|||||||
{
|
{
|
||||||
return std::make_shared<PiPedalSocketFactory>(model);
|
return std::make_shared<PiPedalSocketFactory>(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
#include "AtomConverter.hpp"
|
||||||
#include "PluginHost.hpp"
|
#include "PluginHost.hpp"
|
||||||
#include <lilv/lilv.h>
|
#include <lilv/lilv.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
#include "StdErrorCapture.hpp"
|
#include "StdErrorCapture.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
#include "Locale.hpp"
|
#include "Locale.hpp"
|
||||||
|
|
||||||
@@ -188,6 +190,9 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
|||||||
patch__readable = lilv_new_uri(pWorld, LV2_PATCH__readable);
|
patch__readable = lilv_new_uri(pWorld, LV2_PATCH__readable);
|
||||||
|
|
||||||
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
|
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
|
||||||
|
|
||||||
|
mod__fileTypes = lilv_new_uri(pWorld,"http://moddevices.com/ns/mod#fileTypes");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginHost::LilvUris::Free()
|
void PluginHost::LilvUris::Free()
|
||||||
@@ -618,6 +623,20 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
|
|||||||
std::make_shared<UiFileProperty>(
|
std::make_shared<UiFileProperty>(
|
||||||
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
||||||
|
|
||||||
|
AutoLilvNodes mod__fileTypes = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
|
||||||
|
LILV_FOREACH(nodes,i,mod__fileTypes)
|
||||||
|
{
|
||||||
|
// "nam,nammodel"
|
||||||
|
AutoLilvNode lilvfileType {lilv_nodes_get(mod__fileTypes, i)};
|
||||||
|
std::string fileTypes = lilvfileType.AsString();
|
||||||
|
|
||||||
|
for (std::string&type: split(fileTypes,','))
|
||||||
|
{
|
||||||
|
fileProperty->fileTypes().push_back(UiFileType(SS(type << " file"),SS('.' << type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mod__fileTypes)
|
||||||
|
{
|
||||||
AutoLilvNodes dc_types = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->dc__format, nullptr);
|
AutoLilvNodes dc_types = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->dc__format, nullptr);
|
||||||
LILV_FOREACH(nodes, i, dc_types)
|
LILV_FOREACH(nodes, i, dc_types)
|
||||||
{
|
{
|
||||||
@@ -626,6 +645,7 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
|
|||||||
std::string label = "";
|
std::string label = "";
|
||||||
fileProperty->fileTypes().push_back(UiFileType(label, fileType));
|
fileProperty->fileTypes().push_back(UiFileType(label, fileType));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileProperties.push_back(
|
fileProperties.push_back(
|
||||||
fileProperty);
|
fileProperty);
|
||||||
@@ -1479,6 +1499,46 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
json_variant PluginHost::MapPath(const json_variant&json)
|
||||||
|
{
|
||||||
|
AtomConverter converter(GetMapFeature());
|
||||||
|
return converter.MapPath(json,GetPluginStoragePath());
|
||||||
|
|
||||||
|
}
|
||||||
|
json_variant PluginHost::AbstractPath(const json_variant&json)
|
||||||
|
{
|
||||||
|
AtomConverter converter(GetMapFeature());
|
||||||
|
return converter.AbstractPath(json,GetPluginStoragePath());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string PluginHost::MapPath(const std::string &abstractPath)
|
||||||
|
{
|
||||||
|
auto storagePath = GetPluginStoragePath();
|
||||||
|
if (abstractPath.length() == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return SS(storagePath << '/' << abstractPath);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
std::string PluginHost::AbstractPath(const std::string&path)
|
||||||
|
{
|
||||||
|
auto storagePath = GetPluginStoragePath();
|
||||||
|
if (path.starts_with(storagePath))
|
||||||
|
{
|
||||||
|
auto start = storagePath.length();
|
||||||
|
if (start < path.length() && path[start] == '/')
|
||||||
|
{
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
return path.substr(start);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
// void PiPedalHostLogError(const std::string &error)
|
// void PiPedalHostLogError(const std::string &error)
|
||||||
// {
|
// {
|
||||||
// Lv2Log::error("%s",error.c_str());
|
// Lv2Log::error("%s",error.c_str());
|
||||||
|
|||||||
@@ -729,6 +729,10 @@ namespace pipedal
|
|||||||
AutoLilvNode mod__label;
|
AutoLilvNode mod__label;
|
||||||
|
|
||||||
AutoLilvNode dc__format;
|
AutoLilvNode dc__format;
|
||||||
|
|
||||||
|
AutoLilvNode mod__fileTypes;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
LilvUris* lilvUris = nullptr;
|
LilvUris* lilvUris = nullptr;
|
||||||
|
|
||||||
@@ -815,6 +819,12 @@ namespace pipedal
|
|||||||
void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory);
|
void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory);
|
||||||
void ReloadPlugins();
|
void ReloadPlugins();
|
||||||
|
|
||||||
|
// equivalent to LV2 MapPath AbstractPath features.
|
||||||
|
std::string MapPath(const std::string &abstractPath);
|
||||||
|
std::string AbstractPath(const std::string&path);
|
||||||
|
json_variant MapPath(const json_variant&json);
|
||||||
|
json_variant AbstractPath(const json_variant&json);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::set<std::string> pluginsThatHaveBeenCheckedForResources;
|
std::set<std::string> pluginsThatHaveBeenCheckedForResources;
|
||||||
|
|||||||
@@ -0,0 +1,467 @@
|
|||||||
|
// Copyright (c) 2024 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 "PresetBundle.hpp"
|
||||||
|
#include "PiPedalModel.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include "Pedalboard.hpp"
|
||||||
|
#include "Banks.hpp"
|
||||||
|
#include "json.hpp"
|
||||||
|
#include <sstream>
|
||||||
|
#include "lv2/atom/atom.h"
|
||||||
|
#include "ZipFile.hpp"
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
|
static std::string ToString(const std::vector<uint8_t> &value)
|
||||||
|
{
|
||||||
|
const char *start = (const char *)(&value[0]);
|
||||||
|
const char *end = start + value.size();
|
||||||
|
while (end != start && end[-1] == 0)
|
||||||
|
{
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> ToBinary(const std::string &value)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> result(value.length() + 1);
|
||||||
|
for (size_t i = 0; i < value.length(); ++i)
|
||||||
|
{
|
||||||
|
result[i] = (uint8_t)value[i];
|
||||||
|
}
|
||||||
|
result[value.length()] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PresetBundleWriterImpl : public PresetBundleWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using self = PresetBundleWriterImpl;
|
||||||
|
using super = PresetBundleWriter;
|
||||||
|
|
||||||
|
PresetBundleWriterImpl()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadPresets(PiPedalModel &model, const std::string &presetJson)
|
||||||
|
{
|
||||||
|
BankFile bankFile;
|
||||||
|
|
||||||
|
std::istringstream s(presetJson);
|
||||||
|
json_reader reader(s);
|
||||||
|
|
||||||
|
reader.read(&bankFile);
|
||||||
|
GatherMediaPaths(model, bankFile);
|
||||||
|
configFiles["bankFile.json"] = presetJson;
|
||||||
|
|
||||||
|
pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadPluginPresets(PiPedalModel &model, const std::string pluginPresetJson)
|
||||||
|
{
|
||||||
|
PluginPresets pluginPresets;
|
||||||
|
|
||||||
|
std::istringstream s(pluginPresetJson);
|
||||||
|
json_reader reader(s);
|
||||||
|
|
||||||
|
reader.read(&pluginPresets);
|
||||||
|
|
||||||
|
GatherMediaPaths(model, pluginPresets);
|
||||||
|
configFiles["pluginPresets.json"] = pluginPresetJson;
|
||||||
|
|
||||||
|
pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||||
|
}
|
||||||
|
virtual ~PresetBundleWriterImpl() noexcept;
|
||||||
|
|
||||||
|
virtual void WriteToFile(const std::filesystem::path &filePath) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name);
|
||||||
|
|
||||||
|
void GatherMediaPaths(PiPedalModel &model, BankFile &bankFile);
|
||||||
|
void GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPreset);
|
||||||
|
|
||||||
|
std::map<std::string, std::string> configFiles;
|
||||||
|
std::set<std::string> mediaPaths;
|
||||||
|
std::set<std::string> usedPlugins;
|
||||||
|
std::vector<std::map<std::string, std::string>> pluginMetadata;
|
||||||
|
|
||||||
|
std::filesystem::path pluginUploadDirectory;
|
||||||
|
};
|
||||||
|
|
||||||
|
void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
|
||||||
|
ZipFileWriter::ptr zipFile = ZipFileWriter::Create(filePath);
|
||||||
|
|
||||||
|
for (const auto &configFile : configFiles)
|
||||||
|
{
|
||||||
|
|
||||||
|
zipFile->WriteFile(configFile.first, configFile.second.data(), configFile.second.size());
|
||||||
|
}
|
||||||
|
std::string metadata;
|
||||||
|
{
|
||||||
|
std::ostringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(this->pluginMetadata);
|
||||||
|
metadata = ss.str();
|
||||||
|
zipFile->WriteFile("pluginsUsed.json", metadata.data(), metadata.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &mediaPath : mediaPaths)
|
||||||
|
{
|
||||||
|
std::string zipName = (std::filesystem::path("media") / std::filesystem::path(mediaPath)).string();
|
||||||
|
std::filesystem::path sourcePath = this->pluginUploadDirectory / std::filesystem::path(mediaPath);
|
||||||
|
if (std::filesystem::exists(sourcePath))
|
||||||
|
{
|
||||||
|
zipFile->WriteFile(zipName, sourcePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::warning(SS("Media file not found: " << sourcePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipFile->Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PresetBundleWriterImpl::GatherMediaPaths(PiPedalModel &model, BankFile &bankFile)
|
||||||
|
{
|
||||||
|
for (auto &preset : bankFile.presets())
|
||||||
|
{
|
||||||
|
Pedalboard &pedalboard = preset->preset();
|
||||||
|
auto items = pedalboard.GetAllPlugins();
|
||||||
|
for (auto plugin : items)
|
||||||
|
{
|
||||||
|
AddUsedPlugin(model, plugin->uri(), plugin->pluginName());
|
||||||
|
const Lv2PluginState &state = plugin->lv2State();
|
||||||
|
for (const auto &value : state.values_)
|
||||||
|
{
|
||||||
|
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||||
|
{
|
||||||
|
std::string path = ToString(value.second.value_);
|
||||||
|
if (!mediaPaths.contains(path))
|
||||||
|
{
|
||||||
|
mediaPaths.insert(std::move(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void PresetBundleWriterImpl::GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPresets)
|
||||||
|
{
|
||||||
|
AddUsedPlugin(model, pluginPresets.pluginUri_, "");
|
||||||
|
|
||||||
|
for (auto &preset : pluginPresets.presets_)
|
||||||
|
{
|
||||||
|
const Lv2PluginState &state = preset.state_;
|
||||||
|
for (const auto &value : state.values_)
|
||||||
|
{
|
||||||
|
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||||
|
{
|
||||||
|
std::string path = ToString(value.second.value_);
|
||||||
|
if (!mediaPaths.contains(path))
|
||||||
|
{
|
||||||
|
mediaPaths.insert(std::move(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PresetBundleWriter::ptr PresetBundleWriter::CreatePresetsFile(PiPedalModel &model, const std::string &presetJson)
|
||||||
|
{
|
||||||
|
auto result = std::make_unique<PresetBundleWriterImpl>();
|
||||||
|
result->LoadPresets(model, presetJson);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
PresetBundleWriter::ptr PresetBundleWriter::CreatePluginPresetsFile(PiPedalModel &model, const std::string &pluginPresetJson)
|
||||||
|
{
|
||||||
|
auto result = std::make_unique<PresetBundleWriterImpl>();
|
||||||
|
result->LoadPluginPresets(model, pluginPresetJson);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PresetBundleReaderImpl : public PresetBundleReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PresetBundleReaderImpl()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &path)
|
||||||
|
{
|
||||||
|
this->pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||||
|
zipFile = ZipFileReader::Create(path);
|
||||||
|
auto s = zipFile->GetFileInputStream("pluginPresets.json");
|
||||||
|
json_reader reader(s);
|
||||||
|
reader.read(&pluginPresets);
|
||||||
|
}
|
||||||
|
void LoadPluginPresetFile(PiPedalModel &model, const std::filesystem::path &path)
|
||||||
|
{
|
||||||
|
this->pluginUploadDirectory = model.GetPluginUploadDirectory();
|
||||||
|
zipFile = ZipFileReader::Create(path);
|
||||||
|
auto s = zipFile->GetFileInputStream("bankFile.json");
|
||||||
|
json_reader reader(s);
|
||||||
|
reader.read(&bankFile);
|
||||||
|
}
|
||||||
|
virtual ~PresetBundleReaderImpl() noexcept;
|
||||||
|
|
||||||
|
virtual void ExtractMediaFiles() override;
|
||||||
|
virtual std::string GetPresetJson() override;
|
||||||
|
virtual std::string GetPluginPresetsJson() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ExtractMediaFile(const std::string &zipFileName);
|
||||||
|
bool IsSameFile(const std::string &zipFileName, const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
return zipFile->CompareFiles(zipFileName, filePath);
|
||||||
|
}
|
||||||
|
void RenameMediaFileProperty(const std::string oldName, const std::string &newName);
|
||||||
|
|
||||||
|
std::filesystem::path pluginUploadDirectory;
|
||||||
|
// BankFile bankFile;
|
||||||
|
ZipFileReader::ptr zipFile;
|
||||||
|
|
||||||
|
BankFile bankFile;
|
||||||
|
PluginPresets pluginPresets;
|
||||||
|
};
|
||||||
|
|
||||||
|
void PresetBundleReaderImpl::RenameMediaFileProperty(const std::string oldName, const std::string &newName)
|
||||||
|
{
|
||||||
|
for (auto &preset : bankFile.presets())
|
||||||
|
{
|
||||||
|
Pedalboard &pedalboard = preset->preset();
|
||||||
|
auto items = pedalboard.GetAllPlugins();
|
||||||
|
for (auto plugin : items)
|
||||||
|
{
|
||||||
|
Lv2PluginState &state = plugin->lv2State();
|
||||||
|
for (auto &value : state.values_)
|
||||||
|
{
|
||||||
|
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||||
|
{
|
||||||
|
std::string path = ToString(value.second.value_);
|
||||||
|
if (path == oldName)
|
||||||
|
{
|
||||||
|
state.values_.at(value.first).value_ = ToBinary(newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto preset : pluginPresets.presets_)
|
||||||
|
{
|
||||||
|
Lv2PluginState state = preset.state_;
|
||||||
|
for (auto &value : state.values_)
|
||||||
|
{
|
||||||
|
if (value.second.atomType_ == LV2_ATOM__Path)
|
||||||
|
{
|
||||||
|
std::string path = ToString(value.second.value_);
|
||||||
|
if (path == oldName)
|
||||||
|
{
|
||||||
|
state.values_.at(value.first).value_ = ToBinary(newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PresetBundleReader::ptr PresetBundleReader::LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
std::unique_ptr<PresetBundleReaderImpl> result = std::make_unique<PresetBundleReaderImpl>();
|
||||||
|
result->LoadPresetsFile(model, filePath);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
PresetBundleReader::ptr PresetBundleReader::LoadPluginPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
std::unique_ptr<PresetBundleReaderImpl> result = std::make_unique<PresetBundleReaderImpl>();
|
||||||
|
result->LoadPluginPresetsFile(model, filePath);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PresetBundleReaderImpl::ExtractMediaFiles()
|
||||||
|
{
|
||||||
|
auto zipFiles = zipFile->GetFiles();
|
||||||
|
for (const auto &zipFile : zipFiles)
|
||||||
|
{
|
||||||
|
if (zipFile != "bankFile.json")
|
||||||
|
{
|
||||||
|
if (!zipFile.ends_with('/')) // don't process diretories.
|
||||||
|
{
|
||||||
|
ExtractMediaFile(zipFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ExtractFileVersion(std::string &baseName, int &n)
|
||||||
|
{
|
||||||
|
if (baseName.ends_with(')'))
|
||||||
|
{
|
||||||
|
size_t endPos = baseName.length() - 1;
|
||||||
|
int startPos = baseName.find_last_of('(');
|
||||||
|
if (startPos != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string number = baseName.substr(startPos + 1, endPos - (startPos + 1));
|
||||||
|
std::istringstream ss(number);
|
||||||
|
ss >> n;
|
||||||
|
if (!ss.fail())
|
||||||
|
{
|
||||||
|
while (startPos > 0 && baseName[startPos - 1] == ' ')
|
||||||
|
{
|
||||||
|
--startPos;
|
||||||
|
}
|
||||||
|
baseName = baseName.substr(0, startPos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
static std::filesystem::path NextFileName(const std::filesystem::path &fileName)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
auto fileNameOnly = fileName.filename().string();
|
||||||
|
auto extPos = fileNameOnly.find_last_of('.');
|
||||||
|
if (extPos == std::string::npos)
|
||||||
|
{
|
||||||
|
extPos = fileNameOnly.length();
|
||||||
|
}
|
||||||
|
std::string baseName = fileNameOnly.substr(0, extPos);
|
||||||
|
ExtractFileVersion(baseName, n);
|
||||||
|
if (n >= 1024)
|
||||||
|
{
|
||||||
|
// something has gone badly wrong.
|
||||||
|
throw new std::runtime_error(SS("Can't increase the version number of " << fileName));
|
||||||
|
}
|
||||||
|
std::string newFileName = SS(baseName << "(" << (n + 1) << ')' << fileNameOnly.substr(extPos));
|
||||||
|
return fileName.parent_path() / newFileName;
|
||||||
|
}
|
||||||
|
void PresetBundleReaderImpl::ExtractMediaFile(const std::string &zipFileName)
|
||||||
|
{
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
if (zipFileName.starts_with("media/"))
|
||||||
|
{
|
||||||
|
std::string baseName = zipFileName.substr(6);
|
||||||
|
fs::path targetFileName = this->pluginUploadDirectory / std::filesystem::path(baseName);
|
||||||
|
bool renamed = false;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (fs::exists(targetFileName))
|
||||||
|
{
|
||||||
|
if (IsSameFile(zipFileName, targetFileName))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renamed = true;
|
||||||
|
targetFileName = NextFileName(targetFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
zipFile->ExtractTo(zipFileName, targetFileName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (renamed)
|
||||||
|
{
|
||||||
|
std::string newName = fs::path(baseName).parent_path() / targetFileName.filename();
|
||||||
|
RenameMediaFileProperty(baseName, newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PresetBundleReaderImpl::GetPresetJson()
|
||||||
|
{
|
||||||
|
std::ostringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(this->bankFile);
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PresetBundleReaderImpl::GetPluginPresetsJson()
|
||||||
|
{
|
||||||
|
std::ostringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(this->pluginPresets);
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
PresetBundleReaderImpl::~PresetBundleReaderImpl() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
PresetBundleReader::~PresetBundleReader() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PresetBundleWriterImpl::~PresetBundleWriterImpl() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
PresetBundleWriter::~PresetBundleWriter() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetadata
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PluginMetadata(std::shared_ptr<Lv2PluginInfo> &pluginInfo)
|
||||||
|
{
|
||||||
|
metadata_["uri"] = pluginInfo->uri();
|
||||||
|
metadata_["name"] = pluginInfo->name();
|
||||||
|
metadata_["brand"] = pluginInfo->brand();
|
||||||
|
metadata_["authorName"] = pluginInfo->author_name();
|
||||||
|
metadata_["authorHomePage"] = pluginInfo->author_homepage();
|
||||||
|
}
|
||||||
|
PluginMetadata(const std::string &uri, const std::string &name)
|
||||||
|
{
|
||||||
|
metadata_["uri"] = uri;
|
||||||
|
metadata_["name"] = name.empty() ? uri : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::string> metadata_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void PresetBundleWriterImpl::AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name)
|
||||||
|
{
|
||||||
|
if (!usedPlugins.contains(pluginUri))
|
||||||
|
{
|
||||||
|
usedPlugins.insert(pluginUri);
|
||||||
|
|
||||||
|
auto pluginInfo = model.GetPluginInfo(pluginUri);
|
||||||
|
|
||||||
|
if (pluginInfo)
|
||||||
|
{
|
||||||
|
// use dictionaries because it's more version tolerant.
|
||||||
|
PluginMetadata metadata(pluginInfo);
|
||||||
|
this->pluginMetadata.push_back(std::move(metadata.metadata_));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PluginMetadata metadata(pluginUri, name);
|
||||||
|
this->pluginMetadata.push_back(std::move(metadata.metadata_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2024 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 <string>
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
class PiPedalModel;
|
||||||
|
|
||||||
|
class PresetBundleWriter {
|
||||||
|
public:
|
||||||
|
using self = PresetBundleWriter;
|
||||||
|
using ptr = std::unique_ptr<self>;
|
||||||
|
|
||||||
|
virtual ~PresetBundleWriter() noexcept = 0;
|
||||||
|
|
||||||
|
virtual void WriteToFile(const std::filesystem::path&filePath) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
static ptr CreatePresetsFile(PiPedalModel&model,const std::string&presetJson);
|
||||||
|
static ptr CreatePluginPresetsFile(PiPedalModel&model,const std::string&presetJson);
|
||||||
|
};
|
||||||
|
|
||||||
|
class PresetBundleReader {
|
||||||
|
public:
|
||||||
|
using self = PresetBundleReader;
|
||||||
|
using ptr = std::unique_ptr<self>;
|
||||||
|
|
||||||
|
virtual ~PresetBundleReader() noexcept = 0;
|
||||||
|
|
||||||
|
virtual void ExtractMediaFiles() = 0;
|
||||||
|
virtual std::string GetPresetJson() = 0;
|
||||||
|
virtual std::string GetPluginPresetsJson() = 0;
|
||||||
|
static ptr LoadPresetsFile(PiPedalModel&model,const std::filesystem::path &path);
|
||||||
|
static ptr LoadPluginPresetsFile(PiPedalModel&model,const std::filesystem::path &path);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,8 +26,12 @@
|
|||||||
#include "lv2/atom/atom.h"
|
#include "lv2/atom/atom.h"
|
||||||
#include "RealtimeMidiEventType.hpp"
|
#include "RealtimeMidiEventType.hpp"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
|
class IndexedSnapshot;
|
||||||
|
|
||||||
enum class RingBufferCommand : int64_t
|
enum class RingBufferCommand : int64_t
|
||||||
{
|
{
|
||||||
@@ -39,6 +43,10 @@ namespace pipedal
|
|||||||
AudioStopped,
|
AudioStopped,
|
||||||
SetVuSubscriptions,
|
SetVuSubscriptions,
|
||||||
FreeVuSubscriptions,
|
FreeVuSubscriptions,
|
||||||
|
|
||||||
|
LoadSnapshot,
|
||||||
|
FreeSnapshot,
|
||||||
|
|
||||||
SendVuUpdate,
|
SendVuUpdate,
|
||||||
AckVuUpdate,
|
AckVuUpdate,
|
||||||
|
|
||||||
@@ -68,6 +76,8 @@ namespace pipedal
|
|||||||
|
|
||||||
RealtimeMidiEvent,
|
RealtimeMidiEvent,
|
||||||
|
|
||||||
|
SendPathPropertyBuffer,
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -449,6 +459,11 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
write(RingBufferCommand::SetVuSubscriptions, configuration);
|
write(RingBufferCommand::SetVuSubscriptions, configuration);
|
||||||
}
|
}
|
||||||
|
void LoadSnapshot(IndexedSnapshot*snapshot)
|
||||||
|
{
|
||||||
|
write(RingBufferCommand::LoadSnapshot,snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
void AckMidiProgramRequest(int64_t requestId)
|
void AckMidiProgramRequest(int64_t requestId)
|
||||||
{
|
{
|
||||||
write(RingBufferCommand::AckMidiProgramChange,requestId);
|
write(RingBufferCommand::AckMidiProgramChange,requestId);
|
||||||
@@ -485,11 +500,20 @@ namespace pipedal
|
|||||||
write(RingBufferCommand::EffectReplaced, pedalboard);
|
write(RingBufferCommand::EffectReplaced, pedalboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FreeSnapshot(IndexedSnapshot*snapshot)
|
||||||
|
{
|
||||||
|
write(RingBufferCommand::FreeSnapshot,snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
void WriteLv2ErrorMessage(int64_t instanceId, const char*message)
|
void WriteLv2ErrorMessage(int64_t instanceId, const char*message)
|
||||||
{
|
{
|
||||||
size_t length = strlen(message);
|
size_t length = strlen(message);
|
||||||
write(RingBufferCommand::Lv2ErrorMessage,instanceId,length,(uint8_t*)message);
|
write(RingBufferCommand::Lv2ErrorMessage,instanceId,length,(uint8_t*)message);
|
||||||
}
|
}
|
||||||
|
void SendPathPropertyBuffer(PatchPropertyWriter::Buffer*buffer)
|
||||||
|
{
|
||||||
|
write(RingBufferCommand::SendPathPropertyBuffer,buffer);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef RingBufferReader<true, false> RealtimeRingBufferReader;
|
typedef RingBufferReader<true, false> RealtimeRingBufferReader;
|
||||||
|
|||||||
@@ -165,23 +165,55 @@ Lv2PluginInfo::ptr g_splitterPluginInfo = std::make_shared<Lv2PluginInfo>(makeSp
|
|||||||
|
|
||||||
Lv2PluginInfo::ptr pipedal::GetSplitterPluginInfo() { return g_splitterPluginInfo; }
|
Lv2PluginInfo::ptr pipedal::GetSplitterPluginInfo() { return g_splitterPluginInfo; }
|
||||||
|
|
||||||
|
SplitEffect::SplitEffect(
|
||||||
|
uint64_t instanceId,
|
||||||
|
double sampleRate,
|
||||||
|
const std::vector<float *> &inputs)
|
||||||
|
: instanceId(instanceId), inputs(inputs), sampleRate(sampleRate)
|
||||||
|
|
||||||
|
{
|
||||||
|
controlIndex["splitType"] = SPLIT_TYPE_CTL;
|
||||||
|
controlIndex["select"] = SELECT_CTL;
|
||||||
|
controlIndex["mix"] = MIX_CTL;
|
||||||
|
controlIndex["panL"] = PANL_CTL;
|
||||||
|
controlIndex["volL"] = VOLL_CTL;
|
||||||
|
controlIndex["panR"] = PANR_CTL;
|
||||||
|
controlIndex["volR"] = VOLR_CTL;
|
||||||
|
|
||||||
|
Lv2PluginInfo::ptr puginInfo = g_splitterPluginInfo;
|
||||||
|
|
||||||
|
for (auto&port: puginInfo->ports())
|
||||||
|
{
|
||||||
|
if (port->is_control_port() && port->is_input())
|
||||||
|
{
|
||||||
|
auto index = port->index();
|
||||||
|
if (index < defaultInputControlValues.size()) {
|
||||||
|
defaultInputControlValues[index] = port->default_value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SplitEffect::GetMaxInputControl() const { return MAX_INPUT_CONTROL;}
|
||||||
|
bool SplitEffect::IsInputControl(uint64_t index) const
|
||||||
|
{
|
||||||
|
return index >= 0 && index < MAX_INPUT_CONTROL;
|
||||||
|
}
|
||||||
|
float SplitEffect::GetDefaultInputControlValue(uint64_t index) const
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= MAX_INPUT_CONTROL) return 0;
|
||||||
|
return defaultInputControlValues[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int SplitEffect::GetControlIndex(const std::string &symbol) const
|
int SplitEffect::GetControlIndex(const std::string &symbol) const
|
||||||
{
|
{
|
||||||
if (symbol == "splitType")
|
auto i = controlIndex.find(symbol);
|
||||||
return SPLIT_TYPE_CTL;
|
if (i == controlIndex.end())
|
||||||
if (symbol == "select")
|
{
|
||||||
return SELECT_CTL;
|
|
||||||
if (symbol == "mix")
|
|
||||||
return MIX_CTL;
|
|
||||||
if (symbol == "panL")
|
|
||||||
return PANL_CTL;
|
|
||||||
if (symbol == "volL")
|
|
||||||
return VOLL_CTL;
|
|
||||||
if (symbol == "panR")
|
|
||||||
return PANR_CTL;
|
|
||||||
if (symbol == "volR")
|
|
||||||
return VOLR_CTL;
|
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
return i->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SplitEffect::Activate()
|
void SplitEffect::Activate()
|
||||||
@@ -344,6 +376,8 @@ float SplitEffect::GetControlValue(int portIndex) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void SplitEffect::SetControl(int index, float value)
|
void SplitEffect::SetControl(int index, float value)
|
||||||
{
|
{
|
||||||
switch (index)
|
switch (index)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
#include "PiPedalMath.hpp"
|
#include "PiPedalMath.hpp"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace pipedal
|
namespace pipedal
|
||||||
{
|
{
|
||||||
@@ -75,6 +77,9 @@ namespace pipedal
|
|||||||
|
|
||||||
const double MIX_TRANSITION_TIME_S = 0.1;
|
const double MIX_TRANSITION_TIME_S = 0.1;
|
||||||
double sampleRate;
|
double sampleRate;
|
||||||
|
|
||||||
|
std::unordered_map<std::string,int> controlIndex;
|
||||||
|
|
||||||
std::vector<float *> inputs;
|
std::vector<float *> inputs;
|
||||||
std::vector<float *> topInputs;
|
std::vector<float *> topInputs;
|
||||||
std::vector<float *> bottomInputs;
|
std::vector<float *> bottomInputs;
|
||||||
@@ -111,6 +116,8 @@ namespace pipedal
|
|||||||
float blendDxLBottom = 0;
|
float blendDxLBottom = 0;
|
||||||
float blendDxRBottom = 0;
|
float blendDxRBottom = 0;
|
||||||
|
|
||||||
|
std::vector<float> defaultInputControlValues;
|
||||||
|
|
||||||
int32_t blendFadeSamples;
|
int32_t blendFadeSamples;
|
||||||
|
|
||||||
bool selectA = true;
|
bool selectA = true;
|
||||||
@@ -288,27 +295,23 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint64_t instanceId;
|
uint64_t instanceId;
|
||||||
|
virtual bool IsLv2Effect() const override { return true; }
|
||||||
|
|
||||||
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
|
virtual uint8_t *GetAtomInputBuffer() { return nullptr; }
|
||||||
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
|
virtual uint8_t *GetAtomOutputBuffer() { return nullptr; }
|
||||||
virtual void RequestPatchProperty(LV2_URID uridUri) {}
|
|
||||||
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {}
|
virtual void GatherPatchProperties(RealtimePatchPropertyRequest *pRequest) {}
|
||||||
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
|
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
|
||||||
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
|
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
|
||||||
virtual bool GetLv2State(Lv2PluginState*state) { return false; }
|
virtual bool GetLv2State(Lv2PluginState*state) override { return false; }
|
||||||
|
virtual void SetLv2State(Lv2PluginState&state) override { }
|
||||||
virtual bool HasErrorMessage() const { return false; }
|
virtual bool HasErrorMessage() const { return false; }
|
||||||
const char* TakeErrorMessage() { return ""; }
|
const char* TakeErrorMessage() { return ""; }
|
||||||
virtual bool IsVst3() const { return false; }
|
virtual bool IsVst3() const { return false; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SplitEffect(
|
SplitEffect(
|
||||||
uint64_t instanceId,
|
uint64_t instanceId,
|
||||||
double sampleRate,
|
double sampleRate,
|
||||||
const std::vector<float *> &inputs)
|
const std::vector<float *> &inputs);
|
||||||
: instanceId(instanceId), inputs(inputs), sampleRate(sampleRate)
|
|
||||||
|
|
||||||
{
|
|
||||||
}
|
|
||||||
~SplitEffect()
|
~SplitEffect()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -321,6 +324,12 @@ namespace pipedal
|
|||||||
static constexpr int VOLL_CTL = 4;
|
static constexpr int VOLL_CTL = 4;
|
||||||
static constexpr int PANR_CTL = 5;
|
static constexpr int PANR_CTL = 5;
|
||||||
static constexpr int VOLR_CTL = 6;
|
static constexpr int VOLR_CTL = 6;
|
||||||
|
static constexpr int MAX_INPUT_CONTROL =7;
|
||||||
|
|
||||||
|
virtual uint64_t GetMaxInputControl() const override;
|
||||||
|
virtual bool IsInputControl(uint64_t index) const override;
|
||||||
|
virtual float GetDefaultInputControlValue(uint64_t index) const override;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
virtual uint64_t GetInstanceId() const
|
virtual uint64_t GetInstanceId() const
|
||||||
@@ -356,7 +365,10 @@ namespace pipedal
|
|||||||
return GetControlValue(index);
|
return GetControlValue(index);
|
||||||
}
|
}
|
||||||
virtual float GetControlValue(int portIndex) const;
|
virtual float GetControlValue(int portIndex) const;
|
||||||
virtual void SetControl(int index, float value);
|
virtual void SetControl(int index, float value) override;
|
||||||
|
virtual void SetPatchProperty(LV2_URID uridUri, size_t size, LV2_Atom *value) override {}
|
||||||
|
virtual void RequestPatchProperty(LV2_URID uridUri) {}
|
||||||
|
virtual void RequestAllPathPatchProperties() {}
|
||||||
|
|
||||||
|
|
||||||
virtual int GetNumberOfOutputAudioPorts() const
|
virtual int GetNumberOfOutputAudioPorts() const
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ void Storage::SavePluginPresetIndex()
|
|||||||
throw PiPedalException(SS("Can't write to " << path));
|
throw PiPedalException(SS("Can't write to " << path));
|
||||||
}
|
}
|
||||||
|
|
||||||
json_writer writer(os, false);
|
json_writer writer(os, true);
|
||||||
writer.write(this->pluginPresetIndex);
|
writer.write(this->pluginPresetIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +361,7 @@ void Storage::SaveBankIndex()
|
|||||||
{
|
{
|
||||||
pipedal::ofstream_synced os;
|
pipedal::ofstream_synced os;
|
||||||
os.open(GetIndexFileName(), std::ios_base::trunc);
|
os.open(GetIndexFileName(), std::ios_base::trunc);
|
||||||
json_writer writer(os, false);
|
json_writer writer(os, true);
|
||||||
writer.write(this->bankIndex);
|
writer.write(this->bankIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +436,7 @@ void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile)
|
|||||||
{
|
{
|
||||||
pipedal::ofstream_synced s;
|
pipedal::ofstream_synced s;
|
||||||
s.open(fileName, std::ios_base::trunc);
|
s.open(fileName, std::ios_base::trunc);
|
||||||
json_writer writer(s, false);
|
json_writer writer(s, true);
|
||||||
writer.write(bankFile);
|
writer.write(bankFile);
|
||||||
if (std::filesystem::exists(backupFile))
|
if (std::filesystem::exists(backupFile))
|
||||||
{
|
{
|
||||||
@@ -722,7 +722,7 @@ void Storage::SaveChannelSelection()
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
pipedal::ofstream_synced s(fileName);
|
pipedal::ofstream_synced s(fileName);
|
||||||
json_writer writer(s, false);
|
json_writer writer(s, true);
|
||||||
writer.write(this->jackChannelSelection);
|
writer.write(this->jackChannelSelection);
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
@@ -911,7 +911,7 @@ int64_t Storage::UploadBank(BankFile &bankFile, int64_t uploadAfter)
|
|||||||
{
|
{
|
||||||
throw PiPedalException("Can't write to bank file.");
|
throw PiPedalException("Can't write to bank file.");
|
||||||
}
|
}
|
||||||
json_writer writer(f, false);
|
json_writer writer(f, true);
|
||||||
writer.write(bankFile);
|
writer.write(bankFile);
|
||||||
|
|
||||||
lastBank = this->bankIndex.addBank(lastBank, bankFile.name());
|
lastBank = this->bankIndex.addBank(lastBank, bankFile.name());
|
||||||
@@ -933,7 +933,7 @@ void Storage::SaveUserSettings()
|
|||||||
{
|
{
|
||||||
throw PiPedalException("Unable to write to " + ((std::string)path));
|
throw PiPedalException("Unable to write to " + ((std::string)path));
|
||||||
}
|
}
|
||||||
json_writer writer(f, false);
|
json_writer writer(f, true);
|
||||||
writer.write(userSettings);
|
writer.write(userSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1048,7 +1048,7 @@ void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset)
|
|||||||
std::filesystem::path path = GetCurrentPresetPath();
|
std::filesystem::path path = GetCurrentPresetPath();
|
||||||
|
|
||||||
pipedal::ofstream_synced f(path);
|
pipedal::ofstream_synced f(path);
|
||||||
json_writer writer(f, false);
|
json_writer writer(f, true);
|
||||||
writer.write(currentPreset);
|
writer.write(currentPreset);
|
||||||
}
|
}
|
||||||
catch (std::exception &)
|
catch (std::exception &)
|
||||||
@@ -1128,7 +1128,7 @@ void Storage::SavePluginPresets(const std::string &pluginUri, const PluginPreset
|
|||||||
{
|
{
|
||||||
throw PiPedalException(SS("Can't write to " << path));
|
throw PiPedalException(SS("Can't write to " << path));
|
||||||
}
|
}
|
||||||
json_writer writer(os, false);
|
json_writer writer(os, true);
|
||||||
writer.write(presets);
|
writer.write(presets);
|
||||||
}
|
}
|
||||||
if (std::filesystem::exists(path))
|
if (std::filesystem::exists(path))
|
||||||
@@ -1370,7 +1370,7 @@ void Storage::SetFavorites(const std::map<std::string, bool> &favorites)
|
|||||||
f.open(fileName);
|
f.open(fileName);
|
||||||
if (f.is_open())
|
if (f.is_open())
|
||||||
{
|
{
|
||||||
json_writer writer(f, false);
|
json_writer writer(f, true);
|
||||||
writer.write(favorites);
|
writer.write(favorites);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1399,7 +1399,7 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
|
|||||||
f.open(fileName);
|
f.open(fileName);
|
||||||
if (f.is_open())
|
if (f.is_open())
|
||||||
{
|
{
|
||||||
json_writer writer(f, false);
|
json_writer writer(f, true);
|
||||||
writer.write(jackConfiguration);
|
writer.write(jackConfiguration);
|
||||||
}
|
}
|
||||||
#if JACK_HOST
|
#if JACK_HOST
|
||||||
@@ -1414,7 +1414,7 @@ void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
|
|||||||
f.open(fileName);
|
f.open(fileName);
|
||||||
if (f.is_open())
|
if (f.is_open())
|
||||||
{
|
{
|
||||||
json_writer writer(f, false);
|
json_writer writer(f, true);
|
||||||
writer.write(bindings);
|
writer.write(bindings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ TemporaryFile::TemporaryFile(const std::filesystem::path&directory)
|
|||||||
this->path = filename;
|
this->path = filename;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
void TemporaryFile::Detach() {
|
||||||
|
this->path.clear();
|
||||||
|
}
|
||||||
TemporaryFile::~TemporaryFile()
|
TemporaryFile::~TemporaryFile()
|
||||||
{
|
{
|
||||||
if (!path.empty())
|
if (!path.empty())
|
||||||
|
|||||||
@@ -22,10 +22,16 @@
|
|||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
class TemporaryFile {
|
class TemporaryFile {
|
||||||
public:
|
public:
|
||||||
|
TemporaryFile() {}
|
||||||
TemporaryFile(const TemporaryFile&) = delete;
|
TemporaryFile(const TemporaryFile&) = delete;
|
||||||
TemporaryFile&operator=(const TemporaryFile&) = delete;
|
TemporaryFile&operator=(const TemporaryFile&) = delete;
|
||||||
|
|
||||||
|
TemporaryFile(TemporaryFile&&other) {
|
||||||
|
this->path = std::move(other.path);
|
||||||
|
}
|
||||||
TemporaryFile(const std::filesystem::path&parentDirectory);
|
TemporaryFile(const std::filesystem::path&parentDirectory);
|
||||||
|
|
||||||
|
void Detach();
|
||||||
~TemporaryFile();
|
~TemporaryFile();
|
||||||
const std::filesystem::path&Path()const { return path;}
|
const std::filesystem::path&Path()const { return path;}
|
||||||
std::string str() const { return path.c_str(); }
|
std::string str() const { return path.c_str(); }
|
||||||
|
|||||||
@@ -376,17 +376,6 @@ GithubRelease::GithubRelease(json_variant &v)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
static std::vector<std::string> split(const std::string &s, char delimiter)
|
|
||||||
{
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
std::string token;
|
|
||||||
std::istringstream tokenStream(s);
|
|
||||||
while (std::getline(tokenStream, token, delimiter))
|
|
||||||
{
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
static std::string justTheVersion(const std::string &assetName)
|
static std::string justTheVersion(const std::string &assetName)
|
||||||
{
|
{
|
||||||
// eg. pipedal_1.2.41_arm64.deb
|
// eg. pipedal_1.2.41_arm64.deb
|
||||||
|
|||||||
@@ -54,14 +54,17 @@
|
|||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
const bool ENABLE_KEEP_ALIVE = true;
|
static const bool ENABLE_KEEP_ALIVE = true;
|
||||||
const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
static const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
||||||
|
|
||||||
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;
|
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;
|
||||||
;
|
;
|
||||||
using namespace boost;
|
using namespace boost;
|
||||||
|
using namespace websocketpp::http;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class request_with_file_upload : public websocketpp::http::parser::parser
|
class request_with_file_upload : public websocketpp::http::parser::parser
|
||||||
{
|
{
|
||||||
@@ -516,6 +519,7 @@ inline std::error_code request_with_file_upload::process(std::string::iterator b
|
|||||||
class CustomPpConfig : public websocketpp::config::asio
|
class CustomPpConfig : public websocketpp::config::asio
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using super = websocketpp::config::asio;
|
||||||
typedef CustomPpConfig type;
|
typedef CustomPpConfig type;
|
||||||
typedef websocketpp::config::asio base;
|
typedef websocketpp::config::asio base;
|
||||||
|
|
||||||
@@ -523,6 +527,7 @@ public:
|
|||||||
typedef pipedal_elog elog_type;
|
typedef pipedal_elog elog_type;
|
||||||
typedef pipedal_alog alog_type;
|
typedef pipedal_alog alog_type;
|
||||||
|
|
||||||
|
|
||||||
typedef request_with_file_upload request_type;
|
typedef request_with_file_upload request_type;
|
||||||
|
|
||||||
struct transport_config : public base::transport_config
|
struct transport_config : public base::transport_config
|
||||||
@@ -538,6 +543,8 @@ public:
|
|||||||
|
|
||||||
typedef websocketpp::transport::asio::endpoint<transport_config>
|
typedef websocketpp::transport::asio::endpoint<transport_config>
|
||||||
transport_type;
|
transport_type;
|
||||||
|
|
||||||
|
typedef transport_type::transport_con_type x;
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t CustomPpConfig::max_http_body_size = MAX_READ_SIZE;
|
size_t CustomPpConfig::max_http_body_size = MAX_READ_SIZE;
|
||||||
@@ -693,8 +700,19 @@ namespace pipedal
|
|||||||
ss << size;
|
ss << size;
|
||||||
request.replace_header(HttpField::content_length, ss.str());
|
request.replace_header(HttpField::content_length, ss.str());
|
||||||
}
|
}
|
||||||
virtual void setBody(const std::string &body) { request.set_body(body); }
|
virtual void setBody(const std::string &body) override{ request.set_body(body); }
|
||||||
virtual void keepAlive(bool value)
|
virtual void setBodyFile(std::shared_ptr<TemporaryFile>&bodyFile) override {
|
||||||
|
// cast away const to do what ther request would do if it had that method.
|
||||||
|
|
||||||
|
request.set_body_file(bodyFile->Path(),true);
|
||||||
|
bodyFile->Detach();
|
||||||
|
}
|
||||||
|
virtual void setBodyFile(std::filesystem::path&path, bool deleteWhenDone) override {
|
||||||
|
// cast away const to do what ther request would do if it had that method.
|
||||||
|
request.set_body_file(path,deleteWhenDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void keepAlive(bool value) override
|
||||||
{
|
{
|
||||||
if ((!value) || (!ENABLE_KEEP_ALIVE))
|
if ((!value) || (!ENABLE_KEEP_ALIVE))
|
||||||
{
|
{
|
||||||
@@ -1126,7 +1144,7 @@ namespace pipedal
|
|||||||
|
|
||||||
Lv2Log::error("Failed to open session: %s", e.what());
|
Lv2Log::error("Failed to open session: %s", e.what());
|
||||||
}
|
}
|
||||||
m_endpoint.close(hdl,websocketpp::close::status::abnormal_close, "");
|
//m_endpoint.close(hdl,websocketpp::close::status::abnormal_close, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_fail(connection_hdl hdl)
|
void on_fail(connection_hdl hdl)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "Uri.hpp"
|
#include "Uri.hpp"
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include "TemporaryFile.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -40,6 +41,9 @@ public:
|
|||||||
virtual void set(const std::string&key, const std::string&value) = 0;
|
virtual void set(const std::string&key, const std::string&value) = 0;
|
||||||
virtual void setContentLength(size_t size) = 0;
|
virtual void setContentLength(size_t size) = 0;
|
||||||
virtual void setBody(const std::string&body) = 0;
|
virtual void setBody(const std::string&body) = 0;
|
||||||
|
virtual void setBodyFile(std::shared_ptr<TemporaryFile>&temporaryFile) = 0;
|
||||||
|
virtual void setBodyFile(std::filesystem::path&path, bool deleteWhenDone) = 0;
|
||||||
|
|
||||||
virtual void keepAlive(bool value) = 0;
|
virtual void keepAlive(bool value) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,24 +30,41 @@
|
|||||||
#include "PiPedalUI.hpp"
|
#include "PiPedalUI.hpp"
|
||||||
#include "ofstream_synced.hpp"
|
#include "ofstream_synced.hpp"
|
||||||
#include "UpdaterSecurity.hpp"
|
#include "UpdaterSecurity.hpp"
|
||||||
|
#include "TemporaryFile.hpp"
|
||||||
|
#include "PresetBundle.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#define OLD_PRESET_EXTENSION ".piPreset"
|
||||||
#define PRESET_EXTENSION ".piPreset"
|
#define PRESET_EXTENSION ".piPreset"
|
||||||
#define BANK_EXTENSION ".piBank"
|
#define OLD_BANK_EXTENSION ".piBank"
|
||||||
|
#define BANK_EXTENSION ".piBankBundle"
|
||||||
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
|
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
|
||||||
|
|
||||||
|
static const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
||||||
|
|
||||||
|
static const std::string PLUGIN_PRESETS_MIME_TYPE = "application/vnd.pipedal.pluginPresets";
|
||||||
|
static const std::string PRESET_MIME_TYPE = "application/vnd.pipedal.preset";
|
||||||
|
static const std::string BANK_MIME_TYPE = "application/vnd.pipedal.bank";
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
using namespace boost::system;
|
using namespace boost::system;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
|
||||||
|
static bool IsZipFile(const std::filesystem::path &path)
|
||||||
|
{
|
||||||
|
std::ifstream f(path);
|
||||||
|
if (!f.is_open()) return false;
|
||||||
|
|
||||||
|
char c[4];
|
||||||
|
memset(c,0,sizeof(c));
|
||||||
|
|
||||||
|
f >> c[0] >> c[1] >> c[2] >> c[3];
|
||||||
|
|
||||||
|
// official file header according to PKware documetnation.
|
||||||
|
return c[0] == 0x50 && c[1] == 0x4B && c[2] == 0x03 && c[3] == 0x04;
|
||||||
|
|
||||||
|
|
||||||
static std::vector<std::string> split(const std::string& s, char delimiter) {
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
std::string token;
|
|
||||||
std::istringstream tokenStream(s);
|
|
||||||
while (std::getline(tokenStream, token, delimiter)) {
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtensionChecker {
|
class ExtensionChecker {
|
||||||
@@ -192,7 +209,14 @@ public:
|
|||||||
std::string name;
|
std::string name;
|
||||||
std::string content;
|
std::string content;
|
||||||
GetPluginPresets(request_uri, &name, &content);
|
GetPluginPresets(request_uri, &name, &content);
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
|
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||||
|
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content);
|
||||||
|
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||||
|
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||||
|
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
res.setContentLength(content.length());
|
res.setContentLength(content.length());
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
||||||
@@ -204,9 +228,14 @@ public:
|
|||||||
std::string content;
|
std::string content;
|
||||||
GetPreset(request_uri, &name, &content);
|
GetPreset(request_uri, &name, &content);
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||||
|
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||||
|
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||||
|
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, PRESET_MIME_TYPE);
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
res.setContentLength(content.length());
|
res.setContentLength(contentLength);
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -216,10 +245,18 @@ public:
|
|||||||
std::string content;
|
std::string content;
|
||||||
GetBank(request_uri, &name, &content);
|
GetBank(request_uri, &name, &content);
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||||
|
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||||
|
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||||
|
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, BANK_MIME_TYPE);
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||||
res.setContentLength(content.length());
|
res.setContentLength(contentLength);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw PiPedalException("Not found.");
|
throw PiPedalException("Not found.");
|
||||||
@@ -252,11 +289,18 @@ public:
|
|||||||
std::string name;
|
std::string name;
|
||||||
std::string content;
|
std::string content;
|
||||||
GetPluginPresets(request_uri, &name, &content);
|
GetPluginPresets(request_uri, &name, &content);
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
|
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||||
|
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content);
|
||||||
|
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||||
|
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||||
|
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
res.setContentLength(content.length());
|
res.setContentLength(contentLength);
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
||||||
res.setBody(content);
|
res.setBodyFile(tmpFile);
|
||||||
}
|
}
|
||||||
else if (segment == "downloadPreset")
|
else if (segment == "downloadPreset")
|
||||||
{
|
{
|
||||||
@@ -264,11 +308,17 @@ public:
|
|||||||
std::string content;
|
std::string content;
|
||||||
GetPreset(request_uri, &name, &content);
|
GetPreset(request_uri, &name, &content);
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||||
|
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||||
|
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||||
|
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||||
|
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, PRESET_MIME_TYPE);
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
res.setContentLength(content.length());
|
res.setContentLength(contentLength);
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
||||||
res.setBody(content);
|
res.setBodyFile(tmpFile);
|
||||||
}
|
}
|
||||||
else if (segment == "downloadBank")
|
else if (segment == "downloadBank")
|
||||||
{
|
{
|
||||||
@@ -276,11 +326,18 @@ public:
|
|||||||
std::string content;
|
std::string content;
|
||||||
GetBank(request_uri, &name, &content);
|
GetBank(request_uri, &name, &content);
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||||
|
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||||
|
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||||
|
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, BANK_MIME_TYPE);
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
res.setContentLength(content.length());
|
res.setContentLength(contentLength);
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||||
res.setBody(content);
|
res.setBodyFile(tmpFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -315,6 +372,32 @@ public:
|
|||||||
return fileNames[0];
|
return fileNames[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static bool HasSingleRootDirectory(ZipFileReader&zipFile) {
|
||||||
|
bool hasDirectory = false;
|
||||||
|
std::string previousDirectory;
|
||||||
|
for (auto& file: zipFile.GetFiles())
|
||||||
|
{
|
||||||
|
auto pos = file.find('/');
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
// a file in the root.
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
std::string currentRootDirectory = file.substr(0,pos);
|
||||||
|
if (hasDirectory) {
|
||||||
|
if (currentRootDirectory != previousDirectory)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasDirectory = true;
|
||||||
|
previousDirectory = currentRootDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
virtual void post_response(
|
virtual void post_response(
|
||||||
const uri &request_uri,
|
const uri &request_uri,
|
||||||
HttpRequest &req,
|
HttpRequest &req,
|
||||||
@@ -327,11 +410,28 @@ public:
|
|||||||
|
|
||||||
if (segment == "uploadPluginPresets")
|
if (segment == "uploadPluginPresets")
|
||||||
{
|
{
|
||||||
json_reader reader(req.get_body_input_stream());
|
|
||||||
PluginPresets presets;
|
PluginPresets presets;
|
||||||
|
fs::path filePath = req.get_body_temporary_file();
|
||||||
|
if (filePath.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unexpected.");
|
||||||
|
}
|
||||||
|
if (IsZipFile(filePath))
|
||||||
|
{
|
||||||
|
auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model),filePath);
|
||||||
|
presetReader->ExtractMediaFiles();
|
||||||
|
|
||||||
|
std::stringstream ss(presetReader->GetPluginPresetsJson());
|
||||||
|
json_reader reader(ss);
|
||||||
reader.read(&presets);
|
reader.read(&presets);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
json_reader reader(req.get_body_input_stream());
|
||||||
|
reader.read(&presets);
|
||||||
|
}
|
||||||
model->UploadPluginPresets(presets);
|
model->UploadPluginPresets(presets);
|
||||||
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
res.set(HttpField::content_type, "application/json");
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
std::stringstream sResult;
|
std::stringstream sResult;
|
||||||
@@ -343,7 +443,27 @@ public:
|
|||||||
}
|
}
|
||||||
else if (segment == "uploadPreset")
|
else if (segment == "uploadPreset")
|
||||||
{
|
{
|
||||||
|
|
||||||
|
BankFile bankFile;
|
||||||
|
|
||||||
|
fs::path filePath = req.get_body_temporary_file();
|
||||||
|
if (filePath.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unexpected.");
|
||||||
|
}
|
||||||
|
if (IsZipFile(filePath))
|
||||||
|
{
|
||||||
|
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath);
|
||||||
|
presetReader->ExtractMediaFiles();
|
||||||
|
|
||||||
|
std::stringstream ss(presetReader->GetPresetJson());
|
||||||
|
json_reader reader(ss);
|
||||||
|
reader.read(&bankFile);
|
||||||
|
} else {
|
||||||
|
// legacy json format, no zip, no media files.
|
||||||
json_reader reader(req.get_body_input_stream());
|
json_reader reader(req.get_body_input_stream());
|
||||||
|
reader.read(&bankFile);
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t uploadAfter = -1;
|
uint64_t uploadAfter = -1;
|
||||||
std::string strUploadAfter = request_uri.query("uploadAfter");
|
std::string strUploadAfter = request_uri.query("uploadAfter");
|
||||||
@@ -351,10 +471,6 @@ public:
|
|||||||
{
|
{
|
||||||
uploadAfter = std::stol(strUploadAfter);
|
uploadAfter = std::stol(strUploadAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
BankFile bankFile;
|
|
||||||
reader.read(&bankFile);
|
|
||||||
|
|
||||||
uint64_t instanceId = model->UploadPreset(bankFile, uploadAfter);
|
uint64_t instanceId = model->UploadPreset(bankFile, uploadAfter);
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
res.set(HttpField::content_type, "application/json");
|
||||||
@@ -363,12 +479,31 @@ public:
|
|||||||
sResult << instanceId;
|
sResult << instanceId;
|
||||||
std::string result = sResult.str();
|
std::string result = sResult.str();
|
||||||
res.setContentLength(result.length());
|
res.setContentLength(result.length());
|
||||||
|
|
||||||
res.setBody(result);
|
res.setBody(result);
|
||||||
}
|
}
|
||||||
else if (segment == "uploadBank")
|
else if (segment == "uploadBank")
|
||||||
{
|
{
|
||||||
|
|
||||||
|
BankFile bankFile;
|
||||||
|
|
||||||
|
fs::path filePath = req.get_body_temporary_file();
|
||||||
|
if (filePath.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unexpected.");
|
||||||
|
}
|
||||||
|
if (IsZipFile(filePath))
|
||||||
|
{
|
||||||
|
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath);
|
||||||
|
presetReader->ExtractMediaFiles();
|
||||||
|
|
||||||
|
std::stringstream ss(presetReader->GetPresetJson());
|
||||||
|
json_reader reader(ss);
|
||||||
|
reader.read(&bankFile);
|
||||||
|
} else {
|
||||||
|
// legacy json format, no zip, no media files.
|
||||||
json_reader reader(req.get_body_input_stream());
|
json_reader reader(req.get_body_input_stream());
|
||||||
|
reader.read(&bankFile);
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t uploadAfter = -1;
|
uint64_t uploadAfter = -1;
|
||||||
std::string strUploadAfter = request_uri.query("uploadAfter");
|
std::string strUploadAfter = request_uri.query("uploadAfter");
|
||||||
@@ -377,9 +512,6 @@ public:
|
|||||||
uploadAfter = std::stol(strUploadAfter);
|
uploadAfter = std::stol(strUploadAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
BankFile bankFile;
|
|
||||||
reader.read(&bankFile);
|
|
||||||
|
|
||||||
uint64_t instanceId = model->UploadBank(bankFile, uploadAfter);
|
uint64_t instanceId = model->UploadBank(bankFile, uploadAfter);
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
res.set(HttpField::content_type, "application/json");
|
||||||
@@ -394,10 +526,10 @@ public:
|
|||||||
{
|
{
|
||||||
res.set(HttpField::content_type, "application/json");
|
res.set(HttpField::content_type, "application/json");
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
const std::string instanceId = request_uri.query("id");
|
std::string instanceId = request_uri.query("id");
|
||||||
const std::string directory = request_uri.query("directory");
|
std::string directory = request_uri.query("directory");
|
||||||
const std::string filename = request_uri.query("filename");
|
std::string filename = request_uri.query("filename");
|
||||||
const std::string patchProperty = request_uri.query("property");
|
std::string patchProperty = request_uri.query("property");
|
||||||
|
|
||||||
|
|
||||||
if (patchProperty.length() == 0 && directory.length() == 0)
|
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||||
@@ -415,9 +547,14 @@ public:
|
|||||||
{
|
{
|
||||||
ExtensionChecker extensionChecker { request_uri.query("ext") };
|
ExtensionChecker extensionChecker { request_uri.query("ext") };
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto zipFile = ZipFile::Create(req.get_body_temporary_file());
|
auto zipFile = ZipFileReader::Create(req.get_body_temporary_file());
|
||||||
std::vector<std::string> files = zipFile->GetFiles();
|
std::vector<std::string> files = zipFile->GetFiles();
|
||||||
|
bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile);
|
||||||
|
if (!hasSingleRootDirectory) {
|
||||||
|
directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string();
|
||||||
|
}
|
||||||
for (const auto&inputFile : files)
|
for (const auto&inputFile : files)
|
||||||
{
|
{
|
||||||
if (!inputFile.ends_with("/")) // don't process directory entries.
|
if (!inputFile.ends_with("/")) // don't process directory entries.
|
||||||
|
|||||||
@@ -27,14 +27,20 @@
|
|||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
ZipFile::ZipFile()
|
ZipFileReader::ZipFileReader()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
ZipFile::~ZipFile()
|
ZipFileReader::~ZipFileReader()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ZipFileWriter::ZipFileWriter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ZipFileWriter::~ZipFileWriter()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZipFileImpl : public ZipFile
|
class ZipFileImpl : public ZipFileReader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ZipFileImpl(const std::filesystem::path &path)
|
ZipFileImpl(const std::filesystem::path &path)
|
||||||
@@ -49,6 +55,7 @@ public:
|
|||||||
}
|
}
|
||||||
virtual ~ZipFileImpl();
|
virtual ~ZipFileImpl();
|
||||||
virtual std::vector<std::string> GetFiles() override;
|
virtual std::vector<std::string> GetFiles() override;
|
||||||
|
virtual bool CompareFiles(const std::string &zipName, const std::filesystem::path& path) override;
|
||||||
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path &path) override;
|
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path &path) override;
|
||||||
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) override;
|
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) override;
|
||||||
virtual size_t GetFileSize(const std::string&filename) override;
|
virtual size_t GetFileSize(const std::string&filename) override;
|
||||||
@@ -59,9 +66,9 @@ private:
|
|||||||
zip_t *zipFile = nullptr;
|
zip_t *zipFile = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
ZipFile::ptr ZipFile::Create(const std::filesystem::path &path)
|
ZipFileReader::ptr ZipFileReader::Create(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
return std::shared_ptr<ZipFile>(new ZipFileImpl(path));
|
return std::shared_ptr<ZipFileReader>(new ZipFileImpl(path));
|
||||||
}
|
}
|
||||||
ZipFileImpl::~ZipFileImpl()
|
ZipFileImpl::~ZipFileImpl()
|
||||||
{
|
{
|
||||||
@@ -88,6 +95,85 @@ std::vector<std::string> ZipFileImpl::GetFiles()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ZipFileImpl::CompareFiles(const std::string &zipName, const std::filesystem::path &path)
|
||||||
|
{
|
||||||
|
auto fi = nameMap.find(zipName);
|
||||||
|
if (fi == nameMap.end())
|
||||||
|
{
|
||||||
|
// must call GetFiles() firest.
|
||||||
|
throw std::runtime_error("Zip content file not found.");
|
||||||
|
}
|
||||||
|
zip_int64_t fileIndex = fi->second;
|
||||||
|
|
||||||
|
zip_stat_t zip_stat;
|
||||||
|
if (zip_stat_index(zipFile, fileIndex, 0, &zip_stat) < 0) {
|
||||||
|
throw std::runtime_error(SS("Failed to get file stats: " << zip_strerror(zipFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto targetSize = std::filesystem::file_size(path);
|
||||||
|
if (targetSize != zip_stat.size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_file_t *fIn = zip_fopen_index(this->zipFile, fileIndex, 0);
|
||||||
|
if (fIn == nullptr)
|
||||||
|
{
|
||||||
|
zip_error_t *error = zip_get_error(this->zipFile);
|
||||||
|
const char *strError = zip_error_strerror(error);
|
||||||
|
|
||||||
|
throw std::runtime_error(SS("Failed to read zip content file. " << strError));
|
||||||
|
}
|
||||||
|
Finally t{[fIn]() mutable
|
||||||
|
{
|
||||||
|
zip_fclose(fIn);
|
||||||
|
}};
|
||||||
|
|
||||||
|
FILE*fTarget = fopen(path.c_str(),"r");
|
||||||
|
if (fTarget == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(SS("Failed to read file " << path << "."));
|
||||||
|
}
|
||||||
|
Finally ffTarget{[fTarget]() mutable {
|
||||||
|
fclose(fTarget);
|
||||||
|
}};
|
||||||
|
|
||||||
|
constexpr int BUFFER_SIZE = 16 * 1024;
|
||||||
|
std::vector<char> vBuff(BUFFER_SIZE);
|
||||||
|
std::vector<char> vBuff2(BUFFER_SIZE);
|
||||||
|
char *pBuff = (char *)&(vBuff[0]);
|
||||||
|
char *pBuff2 = (char *)&(vBuff2[0]);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
||||||
|
if (nRead == -1)
|
||||||
|
{
|
||||||
|
zip_error_t *error = zip_file_get_error(fIn);
|
||||||
|
const char *strError = zip_error_strerror(error);
|
||||||
|
throw std::runtime_error(SS("Error reading zip content file." << strError));
|
||||||
|
}
|
||||||
|
auto nTargetRead = fread(pBuff2,1,BUFFER_SIZE,fTarget);
|
||||||
|
|
||||||
|
if ((zip_int64_t)nTargetRead != nRead)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nRead == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (zip_int64_t i = 0; i < nRead; ++i)
|
||||||
|
{
|
||||||
|
if (pBuff2[i] != pBuff[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::path &path)
|
void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
auto fi = nameMap.find(zipName);
|
auto fi = nameMap.find(zipName);
|
||||||
@@ -120,7 +206,7 @@ void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::p
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
||||||
if (nRead = 0)
|
if (nRead == 0)
|
||||||
break;
|
break;
|
||||||
if (nRead == -1)
|
if (nRead == -1)
|
||||||
{
|
{
|
||||||
@@ -215,3 +301,68 @@ size_t ZipFileImpl::GetFileSize(const std::string&filename)
|
|||||||
}
|
}
|
||||||
return stat.size;
|
return stat.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ZipFileWriterImpl : public ZipFileWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ZipFileWriterImpl(const std::filesystem::path &path)
|
||||||
|
: path(path)
|
||||||
|
{
|
||||||
|
int errorOp = 0;
|
||||||
|
zipFile = zip_open(path.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorOp);
|
||||||
|
if (zipFile == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Can't open zip file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtual ~ZipFileWriterImpl();
|
||||||
|
|
||||||
|
virtual void Close() override;
|
||||||
|
|
||||||
|
virtual void WriteFile(const std::string &zipFilename, const std::filesystem::path&sourceFilePath) override;
|
||||||
|
virtual void WriteFile(const std::string&filename, const void*buffer, size_t length) override;
|
||||||
|
private:
|
||||||
|
std::map<std::string, zip_int64_t> nameMap; // avoid o(2) extraction operations.
|
||||||
|
const std::filesystem::path path;
|
||||||
|
zip_t *zipFile = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
ZipFileWriter::ptr ZipFileWriter::Create(const std::filesystem::path &path)
|
||||||
|
{
|
||||||
|
return std::make_unique<ZipFileWriterImpl>(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZipFileWriterImpl::Close() {
|
||||||
|
if (zipFile) {
|
||||||
|
zip_close(zipFile);
|
||||||
|
zipFile = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ZipFileWriterImpl::~ZipFileWriterImpl()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZipFileWriterImpl::WriteFile(const std::string &filename, const std::filesystem::path &path)
|
||||||
|
{
|
||||||
|
zip_error_t error;
|
||||||
|
zip_source_t *source = zip_source_file_create(path.c_str(),0,-1,&error);
|
||||||
|
if (!source) {
|
||||||
|
throw std::runtime_error(SS("Unable to create zip source for file " << path));
|
||||||
|
}
|
||||||
|
if (zip_file_add(zipFile,filename.c_str(),source,ZIP_FL_ENC_UTF_8) < 0)
|
||||||
|
{
|
||||||
|
zip_source_free(source);
|
||||||
|
throw std::runtime_error(SS("Unable to create add file " << path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ZipFileWriterImpl::WriteFile(const std::string&filename, const void*buffer, size_t length)
|
||||||
|
{
|
||||||
|
zip_source_t *source = zip_source_buffer(zipFile,buffer,length,0);
|
||||||
|
|
||||||
|
if (zip_file_add(zipFile,filename.c_str(),source,ZIP_FL_ENC_UTF_8) < 0)
|
||||||
|
{
|
||||||
|
zip_source_free(source);
|
||||||
|
throw std::runtime_error(SS("Failed to add file to zip: " << zip_strerror(zipFile)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
|
class ZipFileFileReader {
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
zip_file_t*file = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
class zip_file_input_stream_buf : public std::streambuf {
|
class zip_file_input_stream_buf : public std::streambuf {
|
||||||
private:
|
private:
|
||||||
zip_file_t* file;
|
zip_file_t* file;
|
||||||
@@ -51,21 +57,44 @@ namespace pipedal {
|
|||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
class ZipFile {
|
class ZipFileReader {
|
||||||
protected:
|
protected:
|
||||||
ZipFile();
|
ZipFileReader();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using ptr = std::shared_ptr<ZipFile>;
|
using ptr = std::shared_ptr<ZipFileReader>;
|
||||||
static ptr Create(const std::filesystem::path &path);
|
static ptr Create(const std::filesystem::path &path);
|
||||||
ZipFile(const ZipFile&) = delete;
|
ZipFileReader(const ZipFileReader&) = delete;
|
||||||
ZipFile&operator=(const ZipFile&) = delete;
|
ZipFileReader&operator=(const ZipFileReader&) = delete;
|
||||||
virtual ~ZipFile();
|
virtual ~ZipFileReader();
|
||||||
|
|
||||||
virtual std::vector<std::string> GetFiles() = 0;
|
virtual std::vector<std::string> GetFiles() = 0;
|
||||||
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path& path) = 0;
|
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path& path) = 0;
|
||||||
|
virtual bool CompareFiles(const std::string &zipName, const std::filesystem::path& path) = 0;
|
||||||
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) = 0;
|
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) = 0;
|
||||||
virtual size_t GetFileSize(const std::string&filename) = 0;
|
virtual size_t GetFileSize(const std::string&filename) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ZipFileWriter;
|
||||||
|
|
||||||
|
|
||||||
|
class ZipFileWriter {
|
||||||
|
protected:
|
||||||
|
ZipFileWriter();
|
||||||
|
|
||||||
|
public:
|
||||||
|
using self = ZipFileWriter();
|
||||||
|
using ptr = std::shared_ptr<ZipFileWriter>;
|
||||||
|
static ptr Create(const std::filesystem::path &path);
|
||||||
|
|
||||||
|
ZipFileWriter(const ZipFileReader&) = delete;
|
||||||
|
ZipFileWriter&operator=(const ZipFileWriter&) = delete;
|
||||||
|
virtual ~ZipFileWriter();
|
||||||
|
|
||||||
|
virtual void Close() = 0;
|
||||||
|
|
||||||
|
virtual void WriteFile(const std::string &filename, const std::filesystem::path&path) = 0;
|
||||||
|
virtual void WriteFile(const std::string&filename, const void*buffer, size_t length) = 0;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,53 +1,55 @@
|
|||||||
- X test reconnect sequence in client.
|
ToobFreeverb has a big memory leak!
|
||||||
|
|
||||||
|
Do we have to deactivate both Audio handles after an underrun? I think so.
|
||||||
|
|
||||||
|
Error and message and loading overlays have to go above PerformanceView
|
||||||
|
|
||||||
|
Back Button sequence for performance view.
|
||||||
|
|
||||||
|
Review state changing code. Do we need this anymmore? I think not.
|
||||||
|
|
||||||
|
|
||||||
|
Review current handleNotifyPatchProperty. I tink server-side support needs to be
|
||||||
|
removed, and rely on handleNotifyPathPatchPropertyChanged instead.
|
||||||
|
|
||||||
|
private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) {
|
||||||
|
|
||||||
|
Midi bindings.
|
||||||
|
|
||||||
|
SEGV at void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
|
||||||
|
bool changed = this->audioHost->UpdatePluginState(*item);
|
||||||
|
PiPedalModel.cpp:375
|
||||||
|
|
||||||
|
because audioHost is gone.
|
||||||
|
revamp shutdown procedure for the audio thread.
|
||||||
|
write a marker on exit, and don't release
|
||||||
|
audioHost UNTIL the service thread has read the close marker.
|
||||||
|
|
||||||
|
- continuing corrupt memory.
|
||||||
|
- LV2 STATE and properties DO NOT MATCH in saved files.
|
||||||
|
- LV2_STATE is not populated wiht properties are.
|
||||||
|
- properties are not popluate when LV_sate Is.
|
||||||
|
- FINISH PATH property running updates code.
|
||||||
|
Maybe we should be using LV2_State (if we can capture it
|
||||||
|
propertly. )
|
||||||
|
|
||||||
|
|
||||||
-X remove 10-pipedal-networkmanager.rules before testing installer.
|
|
||||||
|
|
||||||
- new signing procedure.
|
|
||||||
|
|
||||||
- verify that scanning doesn't cause overruns.
|
- verify that scanning doesn't cause overruns.
|
||||||
- verify address change behaviour in client
|
- verify address change behaviour in client
|
||||||
- verify stripping in installer.
|
|
||||||
- verify update with no keyring.
|
|
||||||
-verify clean removal of dhcpcd and nm_p2p2d
|
-verify clean removal of dhcpcd and nm_p2p2d
|
||||||
|
|
||||||
X Review docs changes once we go live.
|
X Review docs changes once we go live.
|
||||||
|
|
||||||
- Back button after reloads on Android.
|
- Localisation: support non-UTF8 code pages.
|
||||||
|
|
||||||
Localisation: support non-UTF8 code pages.
|
|
||||||
- unicode commandline arguments.
|
- unicode commandline arguments.
|
||||||
- review unicode filenames (this is probably ok)
|
- review unicode filenames (this is probably ok)
|
||||||
|
|
||||||
- make app use the same theme settings as the website. (make the website use the same theme as the app)
|
|
||||||
- BUG: gcs when we have an animated output control
|
- BUG: gcs when we have an animated output control
|
||||||
- versioning.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Package: raspi-firmware
|
|
||||||
Version: 1:1.20240902-1
|
|
||||||
|
|
||||||
Package: raspi-firmware
|
|
||||||
Version: 1.20220830+ds-1
|
|
||||||
|
|
||||||
|
|
||||||
Package: firmware-brcm80211
|
|
||||||
Source: firmware-nonfree
|
|
||||||
Version: 1:20230625-2+rpt3
|
|
||||||
|
|
||||||
Package: f
|
|
||||||
|
|
||||||
|
|
||||||
downgrade:
|
|
||||||
sudo apt install raspi-firmware=1.20220830+ds-1
|
|
||||||
sudo apt install firmware-brcm80211=1:20230625-2+rpt2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Pri Description
|
Pri Description
|
||||||
-----------------
|
-----------------
|
||||||
8 Migrate to Vite toolchain.
|
|
||||||
5 Re-use plugin instances when rebuilding pedalboard.
|
5 Re-use plugin instances when rebuilding pedalboard.
|
||||||
|
8 Migrate to Vite toolchain.
|
||||||
|
|||||||