Interim checking, Convolution Reverb
This commit is contained in:
@@ -0,0 +1,620 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { PiPedalArgumentError } from './PiPedalError';
|
||||
import MidiBinding from './MidiBinding';
|
||||
|
||||
|
||||
const SPLIT_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Split";
|
||||
const EMPTY_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Empty";
|
||||
|
||||
|
||||
interface Deserializable<T> {
|
||||
deserialize(input: any): T;
|
||||
}
|
||||
|
||||
export class ControlValue implements Deserializable<ControlValue> {
|
||||
constructor(key?: string, value?: number)
|
||||
{
|
||||
this.key = key??"";
|
||||
this.value = value?? 0;
|
||||
}
|
||||
deserialize(input: any): ControlValue {
|
||||
this.key = input.key;
|
||||
this.value = input.value;
|
||||
return this;
|
||||
}
|
||||
static EmptyArray: ControlValue[] = [];
|
||||
|
||||
static deserializeArray(input: any[]): ControlValue[] {
|
||||
let result: ControlValue[] = [];
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
result[i] = new ControlValue().deserialize(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
setValue(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
key: string;
|
||||
value: number;
|
||||
|
||||
}
|
||||
|
||||
export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
deserializePedalboardItem(input: any): PedalboardItem {
|
||||
this.instanceId = input.instanceId ?? -1;
|
||||
this.uri = input.uri;
|
||||
this.pluginName = input.pluginName;
|
||||
this.isEnabled = input.isEnabled;
|
||||
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
|
||||
|
||||
this.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||
this.vstState = input.vstState ?? "";
|
||||
this.lv2State = input.lv2State;
|
||||
return this;
|
||||
}
|
||||
deserialize(input: any): PedalboardItem {
|
||||
return this.deserializePedalboardItem(input);
|
||||
}
|
||||
static deserializeArray(input: any): PedalboardItem[] {
|
||||
let result: PedalboardItem[] = [];
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
let inputItem: any = input[i];
|
||||
let uri: string = inputItem.uri as string;
|
||||
let outputItem: PedalboardItem;
|
||||
|
||||
if (uri === SPLIT_PEDALBOARD_ITEM_URI) {
|
||||
outputItem = new PedalboardSplitItem().deserialize(inputItem);
|
||||
} else {
|
||||
outputItem = new PedalboardItem().deserialize(inputItem);
|
||||
}
|
||||
result[i] = outputItem;
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getInstanceId() : number {
|
||||
if (this.instanceId === undefined)
|
||||
{
|
||||
throw new PiPedalArgumentError("Item does not have an id.");
|
||||
}
|
||||
return this.instanceId;
|
||||
}
|
||||
isEmpty(): boolean {
|
||||
return this.uri === EMPTY_PEDALBOARD_ITEM_URI;
|
||||
}
|
||||
isSplit(): boolean {
|
||||
return this.uri === SPLIT_PEDALBOARD_ITEM_URI;
|
||||
}
|
||||
|
||||
getControl(key: string): ControlValue {
|
||||
for (let i = 0; i < this.controlValues.length; ++i) {
|
||||
let v = this.controlValues[i];
|
||||
if (v.key === key) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw new PiPedalArgumentError("Invalid key.");
|
||||
|
||||
}
|
||||
|
||||
getControlValue(key: string): number {
|
||||
for (let i = 0; i < this.controlValues.length; ++i) {
|
||||
let v = this.controlValues[i];
|
||||
if (v.key === key) {
|
||||
return v.value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
setControlValue(key: string, value: number): boolean {
|
||||
for (let i = 0; i < this.controlValues.length; ++i) {
|
||||
let v = this.controlValues[i];
|
||||
if (v.key === key) {
|
||||
if (v.value === value) return false;
|
||||
v.setValue(value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
setMidiBinding(midiBinding: MidiBinding): boolean {
|
||||
if (this.midiBindings)
|
||||
{
|
||||
for (let i = 0; i < this.midiBindings.length; ++i)
|
||||
{
|
||||
let binding = this.midiBindings[i];
|
||||
if (midiBinding.symbol === binding.symbol)
|
||||
{
|
||||
if (binding.equals(midiBinding))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
this.midiBindings.splice(i,1,midiBinding);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.midiBindings.push(midiBinding);
|
||||
return true;
|
||||
}
|
||||
this.midiBindings = [ midiBinding];
|
||||
return true;
|
||||
}
|
||||
getMidiBinding(symbol: string): MidiBinding {
|
||||
if (this.midiBindings)
|
||||
{
|
||||
for (let i = 0; i < this.midiBindings.length; ++i)
|
||||
{
|
||||
let midiBinding = this.midiBindings[i];
|
||||
if (midiBinding.symbol === symbol)
|
||||
{
|
||||
return midiBinding;
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = new MidiBinding();
|
||||
result.symbol = symbol;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static EmptyArray: PedalboardItem[] = [];
|
||||
|
||||
instanceId: number = -1;
|
||||
isEnabled: boolean = false;
|
||||
uri: string = "";
|
||||
pluginName?: string;
|
||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||
midiBindings: MidiBinding[] = [];
|
||||
vstState: string = "";
|
||||
lv2State: any = {};
|
||||
};
|
||||
|
||||
|
||||
export enum SplitType {
|
||||
Ab = 0,
|
||||
Mix = 1,
|
||||
Lr = 2
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class PedalboardSplitItem extends PedalboardItem {
|
||||
static PANL_KEY: string = "panL";
|
||||
static PANR_KEY: string = "panR";
|
||||
static VOLL_KEY: string = "volL";
|
||||
static VOLR_KEY: string = "volR";
|
||||
|
||||
static MIX_KEY: string = "mix";
|
||||
static TYPE_KEY: string = "splitType";
|
||||
static SELECT_KEY: string = "select";
|
||||
|
||||
|
||||
deserialize(input: any): PedalboardSplitItem {
|
||||
this.deserializePedalboardItem(input);
|
||||
this.topChain = PedalboardItem.deserializeArray(input.topChain);
|
||||
this.bottomChain = PedalboardItem.deserializeArray(input.bottomChain);
|
||||
|
||||
return this;
|
||||
}
|
||||
getSplitType(): SplitType {
|
||||
let rawValue = this.getControlValue(PedalboardSplitItem.TYPE_KEY);
|
||||
|
||||
if (rawValue < 1) return SplitType.Ab;
|
||||
if (rawValue < 2) return SplitType.Mix;
|
||||
return SplitType.Lr;
|
||||
}
|
||||
getToggleAbControlValue(): ControlValue {
|
||||
let cv = new ControlValue();
|
||||
cv.key = "select";
|
||||
cv.value = this.isASelected() ? 1 : 0;
|
||||
return cv;
|
||||
|
||||
}
|
||||
getMixControl(): ControlValue {
|
||||
return this.getControl(PedalboardSplitItem.MIX_KEY);
|
||||
}
|
||||
getMix(): number {
|
||||
return this.getControlValue(PedalboardSplitItem.MIX_KEY);
|
||||
}
|
||||
isASelected(): boolean {
|
||||
return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalboardSplitItem.SELECT_KEY) === 0;
|
||||
}
|
||||
isBSelected(): boolean {
|
||||
return this.getSplitType() !== SplitType.Ab || this.getControlValue(PedalboardSplitItem.SELECT_KEY) !== 0;
|
||||
}
|
||||
|
||||
topChain: PedalboardItem[] = PedalboardItem.EmptyArray;
|
||||
bottomChain: PedalboardItem[] = PedalboardItem.EmptyArray;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
|
||||
deserialize(input: any): Pedalboard {
|
||||
this.name = input.name;
|
||||
this.items = PedalboardItem.deserializeArray(input.items);
|
||||
this.nextInstanceId = input.nextInstanceId ?? -1;
|
||||
return this;
|
||||
}
|
||||
|
||||
clone(): Pedalboard {
|
||||
return new Pedalboard().deserialize(this);
|
||||
}
|
||||
name: string = "";
|
||||
items: PedalboardItem[] = [];
|
||||
nextInstanceId: number = -1;
|
||||
|
||||
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
|
||||
let it = itemGenerator_(this.items);
|
||||
while (true)
|
||||
{
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
yield v.value;
|
||||
}
|
||||
}
|
||||
|
||||
hasItem(instanceId: number): boolean
|
||||
{
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
if (v.value.instanceId === instanceId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
getFirstSelectableItem(): number
|
||||
{
|
||||
if (this.items.length !== 0)
|
||||
{
|
||||
return this.items[0].instanceId;
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
maybeGetItem(instanceId: number): PedalboardItem | null{
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
if (v.value.instanceId === instanceId)
|
||||
{
|
||||
return v.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getItem(instanceId: number): PedalboardItem {
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
if (v.value.instanceId === instanceId)
|
||||
{
|
||||
return v.value;
|
||||
}
|
||||
}
|
||||
throw new PiPedalArgumentError("Item not found.");
|
||||
}
|
||||
deleteItem_(instanceId: number,items: PedalboardItem[]): number | null
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
let item = items[i];
|
||||
if (item.instanceId === instanceId)
|
||||
{
|
||||
if (items.length > 1) {
|
||||
items.splice(i,1);
|
||||
let nextSelectedItem = i;
|
||||
if (i >= items.length) --nextSelectedItem;
|
||||
return items[nextSelectedItem].instanceId;
|
||||
} else {
|
||||
// replace with an empty item.
|
||||
let newItem = this.createEmptyItem();
|
||||
items[i] = newItem;
|
||||
return newItem.instanceId;
|
||||
|
||||
}
|
||||
} else {
|
||||
if (item.isSplit())
|
||||
{
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
let t = this.deleteItem_(instanceId,splitItem.topChain);
|
||||
if (t != null) return t;
|
||||
|
||||
t = this.deleteItem_(instanceId,splitItem.bottomChain);
|
||||
if (t != null) return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
canDeleteItem_(instanceId: number,items: PedalboardItem[]): boolean
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
let item = items[i];
|
||||
if (item.instanceId === instanceId)
|
||||
{
|
||||
if (items.length > 1) return true;
|
||||
return !item.isEmpty(); // can delete if there's one non-empty item.
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
if (this.canDeleteItem_(instanceId,splitItem.topChain))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (this.canDeleteItem_(instanceId,splitItem.bottomChain))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canDeleteItem(instanceId: number): boolean
|
||||
{
|
||||
return this.canDeleteItem_(instanceId,this.items);
|
||||
}
|
||||
// Returns the next selected instanceId, or null if no deletion occurred.
|
||||
deleteItem(instanceId: number): number | null {
|
||||
return this.deleteItem_(instanceId,this.items);
|
||||
}
|
||||
|
||||
setMidiBinding(instanceId: number, midiBinding: MidiBinding): boolean
|
||||
{
|
||||
let item = this.getItem(instanceId);
|
||||
if (!item) return false;
|
||||
return item.setMidiBinding(midiBinding);
|
||||
}
|
||||
addToStart(item: PedalboardItem)
|
||||
{
|
||||
this.items.splice(0,0,item);
|
||||
}
|
||||
addToEnd(item: PedalboardItem)
|
||||
{
|
||||
this.items.splice(this.items.length,0,item);
|
||||
}
|
||||
static _addRelative(items: PedalboardItem[],newItem: PedalboardItem, instanceId: number, addBefore: boolean): boolean
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
let item = items[i];
|
||||
if (item.instanceId === instanceId)
|
||||
{
|
||||
if (addBefore)
|
||||
{
|
||||
items.splice(i,0,newItem);
|
||||
} else {
|
||||
items.splice(i+1,0,newItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
let split = item as PedalboardSplitItem;
|
||||
if (this._addRelative(split.topChain,newItem,instanceId,addBefore))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (this._addRelative(split.bottomChain,newItem,instanceId,addBefore))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
addBefore(item: PedalboardItem, instanceId: number)
|
||||
{
|
||||
if (item.instanceId === instanceId) return;
|
||||
let result = Pedalboard._addRelative(this.items,item, instanceId, true);
|
||||
if (!result) {
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
|
||||
}
|
||||
addAfter(item: PedalboardItem, instanceId: number)
|
||||
{
|
||||
if (item.instanceId === instanceId) return;
|
||||
let result = Pedalboard._addRelative(this.items,item, instanceId, false);
|
||||
if (!result) {
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ensurePedalboardIds() {
|
||||
if (this.nextInstanceId === -1) {
|
||||
let minId = 1;
|
||||
let it = this.itemsGenerator();
|
||||
while (true) {
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
if (v.value.instanceId) {
|
||||
let t = v.value.instanceId;
|
||||
if (t+1 > minId) minId = t+1;
|
||||
}
|
||||
|
||||
}
|
||||
this.nextInstanceId = minId;
|
||||
}
|
||||
let it = this.itemsGenerator();
|
||||
while (true) {
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
if (v.value.instanceId === -1) {
|
||||
v.value.instanceId = ++this.nextInstanceId;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
createEmptySplit(): PedalboardSplitItem {
|
||||
let result: PedalboardSplitItem = new PedalboardSplitItem();
|
||||
result.uri = SPLIT_PEDALBOARD_ITEM_URI;
|
||||
result.instanceId = ++this.nextInstanceId;
|
||||
result.pluginName = "";
|
||||
result.isEnabled = true;
|
||||
result.topChain = [ this.createEmptyItem()];
|
||||
result.bottomChain = [ this.createEmptyItem()];
|
||||
result.controlValues = [
|
||||
new ControlValue(PedalboardSplitItem.TYPE_KEY, 0),
|
||||
new ControlValue(PedalboardSplitItem.SELECT_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.MIX_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.PANL_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.VOLL_KEY,-3),
|
||||
new ControlValue(PedalboardSplitItem.PANR_KEY,0),
|
||||
new ControlValue(PedalboardSplitItem.VOLR_KEY,-3)
|
||||
];
|
||||
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
createEmptyItem(): PedalboardItem {
|
||||
let result: PedalboardItem = new PedalboardItem();
|
||||
result.uri = EMPTY_PEDALBOARD_ITEM_URI;
|
||||
result.instanceId = ++this.nextInstanceId;
|
||||
result.pluginName = "";
|
||||
result.isEnabled = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
setItemEmpty(item: PedalboardItem)
|
||||
{
|
||||
item.uri = EMPTY_PEDALBOARD_ITEM_URI;
|
||||
item.instanceId = ++this.nextInstanceId;
|
||||
item.pluginName = "";
|
||||
item.isEnabled = true;
|
||||
item.controlValues = [];
|
||||
|
||||
}
|
||||
|
||||
_replaceItem(items: PedalboardItem[], instanceId: number, newItem: PedalboardItem): boolean {
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
let item = items[i];
|
||||
if (item.instanceId === instanceId)
|
||||
{
|
||||
items[i] = newItem;
|
||||
return true;
|
||||
}
|
||||
if (items[i].isSplit())
|
||||
{
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
if (this._replaceItem(splitItem.topChain,instanceId,newItem))
|
||||
return true;
|
||||
if (this._replaceItem(splitItem.bottomChain,instanceId,newItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
replaceItem(instanceId: number, newItem: PedalboardItem)
|
||||
{
|
||||
let result = this._replaceItem(this.items,instanceId,newItem);
|
||||
if (!result)
|
||||
{
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
}
|
||||
_addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean)
|
||||
{
|
||||
for (let i = 0; i < items.length; ++i)
|
||||
{
|
||||
let item = items[i];
|
||||
if (item.instanceId === instanceId)
|
||||
{
|
||||
if (append)
|
||||
{
|
||||
items.splice(i+1,0,newItem);
|
||||
return true;
|
||||
} else {
|
||||
items.splice(i,0,newItem);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (item.isSplit())
|
||||
{
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
if (this._addItem(splitItem.topChain,newItem,instanceId,append)) return true;
|
||||
if (this._addItem(splitItem.bottomChain,newItem,instanceId,append)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addItem(newItem: PedalboardItem, instanceId: number, append: boolean): void
|
||||
{
|
||||
this._addItem(this.items,newItem,instanceId,append);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function* itemGenerator_(items: PedalboardItem[]): Generator<PedalboardItem, void, undefined> {
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
let item = items[i];
|
||||
yield item;
|
||||
if (item.uri === SPLIT_PEDALBOARD_ITEM_URI) {
|
||||
|
||||
let splitItem = item as PedalboardSplitItem;
|
||||
|
||||
let it = itemGenerator_(splitItem.topChain);
|
||||
while (true) {
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
yield v.value;
|
||||
}
|
||||
it = itemGenerator_(splitItem.bottomChain);
|
||||
while (true) {
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
yield v.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user