Tap Tempo, ToobAmp v1.1.72
This commit is contained in:
@@ -35,6 +35,11 @@ function semitone12TETValue(value: number): string {
|
||||
|
||||
}
|
||||
|
||||
export const MIN_TAP_TEMPO_SECONDS = 0.2;
|
||||
export const MAX_TAP_TEMPO_SECONDS = 1.0;
|
||||
export const MIN_TAP_TEMPO_HZ = 0.1;
|
||||
export const MAX_TAP_TEMPO_HZ = 2.0;
|
||||
|
||||
|
||||
export class Port implements Deserializable<Port> {
|
||||
deserialize(input: any): Port {
|
||||
@@ -117,7 +122,7 @@ export class PortGroup {
|
||||
symbol: string = "";
|
||||
name: string = "";
|
||||
sideChainof: string = "";
|
||||
|
||||
|
||||
parent_group: string = "";
|
||||
program_list_id: number = -1;
|
||||
};
|
||||
@@ -654,6 +659,10 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
this.controlType = ControlType.Trigger;
|
||||
}
|
||||
}
|
||||
if (this.controlType === ControlType.Dial) {
|
||||
if (this.canDoTapTempo()) {
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -714,6 +723,7 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
scale_points: ScalePoint[] = [];
|
||||
port_group: string = "";
|
||||
units: Units = Units.none;
|
||||
can_tap_tempo: boolean = false;
|
||||
comment: string = "";
|
||||
is_bypass: boolean = false;
|
||||
is_program_controller: boolean = true;
|
||||
@@ -779,6 +789,34 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
return this.controlType === ControlType.Dial;
|
||||
}
|
||||
|
||||
|
||||
canDoTapTempo(): boolean {
|
||||
if (this.controlType === ControlType.Dial) {
|
||||
let min_value = this.min_value;
|
||||
let max_value = this.max_value;
|
||||
if (min_value > max_value) {
|
||||
let t = min_value;
|
||||
min_value = max_value;
|
||||
max_value = t;
|
||||
}
|
||||
switch (this.units) {
|
||||
case Units.s:
|
||||
return min_value <= MIN_TAP_TEMPO_SECONDS && max_value >= MAX_TAP_TEMPO_SECONDS;
|
||||
case Units.ms:
|
||||
return min_value <= MIN_TAP_TEMPO_SECONDS*1000 && max_value >= MAX_TAP_TEMPO_SECONDS*1000;
|
||||
|
||||
case Units.bpm:
|
||||
return true;
|
||||
case Units.hz:
|
||||
return min_value <= MIN_TAP_TEMPO_HZ && max_value >= MAX_TAP_TEMPO_HZ;
|
||||
default:
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isTuner(): boolean {
|
||||
return this.controlType === ControlType.Tuner;
|
||||
}
|
||||
@@ -894,8 +932,8 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
break;
|
||||
case Units.unknown:
|
||||
if (this.custom_units !== "") {
|
||||
text = this.custom_units.replace("%f",text);
|
||||
}
|
||||
text = this.custom_units.replace("%f", text);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -950,7 +988,7 @@ export class Lv2PatchPropertyInfo {
|
||||
return result;
|
||||
}
|
||||
uri: string = "";
|
||||
writable : boolean = false;
|
||||
writable: boolean = false;
|
||||
readable: boolean = false;
|
||||
label: string = "";
|
||||
index: number = -1;
|
||||
@@ -1045,8 +1083,7 @@ export class UiPlugin implements Deserializable<UiPlugin> {
|
||||
}
|
||||
|
||||
getFilePropertyByUri(patchPropertyUri: string): UiFileProperty | null {
|
||||
for (let i = 0; i < this.fileProperties.length; ++i)
|
||||
{
|
||||
for (let i = 0; i < this.fileProperties.length; ++i) {
|
||||
let fileProperty = this.fileProperties[i];
|
||||
if (fileProperty.patchProperty === patchPropertyUri) {
|
||||
return fileProperty;
|
||||
@@ -1078,7 +1115,7 @@ export class UiPlugin implements Deserializable<UiPlugin> {
|
||||
frequencyPlots: UiFrequencyPlot[] = [];
|
||||
is_vst3: boolean = false;
|
||||
modGui: ModGui | null = null; // null if no mod gui.
|
||||
patchProperties: Lv2PatchPropertyInfo[] = [];
|
||||
patchProperties: Lv2PatchPropertyInfo[] = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ export default class MidiBinding {
|
||||
static BINDING_TYPE_NONE: number = 0;
|
||||
static BINDING_TYPE_NOTE: number = 1;
|
||||
static BINDING_TYPE_CONTROL: number = 2;
|
||||
static BINDING_TYPE_TAP_TEMPO: number = 3;
|
||||
|
||||
setBindingType(bindingType:number)
|
||||
{
|
||||
|
||||
@@ -57,6 +57,7 @@ export enum MidiControlType {
|
||||
MomentarySwitch
|
||||
}
|
||||
|
||||
|
||||
export function getMidiControlType(uiPlugin: UiPlugin | undefined, symbol: string): MidiControlType {
|
||||
if (symbol === "__bypass") {
|
||||
return MidiControlType.Toggle;
|
||||
@@ -87,16 +88,14 @@ export function getMidiControlType(uiPlugin: UiPlugin | undefined, symbol: strin
|
||||
return MidiControlType.Dial;
|
||||
}
|
||||
|
||||
export function canBindToNote(controlType: MidiControlType): boolean {
|
||||
return controlType === MidiControlType.Toggle
|
||||
|| controlType === MidiControlType.Trigger
|
||||
|| controlType === MidiControlType.MomentarySwitch
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface MidiBindingViewProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
midiBinding: MidiBinding;
|
||||
midiControlType: MidiControlType;
|
||||
canDoTapTempo: boolean;
|
||||
onChange: (instanceId: number, newBinding: MidiBinding) => void;
|
||||
}
|
||||
|
||||
@@ -131,6 +130,21 @@ const MidiBindingView =
|
||||
};
|
||||
}
|
||||
|
||||
canDoTapTempo(): boolean {
|
||||
let controlType = this.props.midiControlType;
|
||||
if (controlType == MidiControlType.Dial && this.props.canDoTapTempo) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
canBindToNote(): boolean {
|
||||
let controlType = this.props.midiControlType;
|
||||
return controlType === MidiControlType.Toggle
|
||||
|| controlType === MidiControlType.Trigger
|
||||
|| controlType === MidiControlType.MomentarySwitch
|
||||
}
|
||||
|
||||
|
||||
handleBindingTypeChange(e: any, extra: any) {
|
||||
let newValue = parseInt(e.target.value);
|
||||
let newBinding = this.props.midiBinding.clone();
|
||||
@@ -203,7 +217,7 @@ const MidiBindingView =
|
||||
generateMidiNoteSelects(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
for (let i = 0; i < 127; ++i) {
|
||||
for (let i = 0; i <= 127; ++i) {
|
||||
result.push(
|
||||
<MenuItem key={"k" + i} value={i}>{i} {Utility.midiNoteName(i)}</MenuItem>
|
||||
)
|
||||
@@ -314,10 +328,13 @@ const MidiBindingView =
|
||||
let binding = this.props.midiBinding;
|
||||
let newBinding = binding.clone();
|
||||
if (midiMessage.isNote()) {
|
||||
if (!canBindToNote(this.props.midiControlType)) {
|
||||
if (!this.canBindToNote() && !this.props.canDoTapTempo) {
|
||||
return;
|
||||
}
|
||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE
|
||||
newBinding.bindingType =
|
||||
this.props.canDoTapTempo ?
|
||||
MidiBinding.BINDING_TYPE_TAP_TEMPO
|
||||
: MidiBinding.BINDING_TYPE_NOTE
|
||||
newBinding.note = midiMessage.cc1;
|
||||
} else if (midiMessage.isControl()) {
|
||||
newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL
|
||||
@@ -413,23 +430,28 @@ const MidiBindingView =
|
||||
<div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "left", alignItems: "center", minHeight: 48 }}>
|
||||
<div className={classes.controlDiv} >
|
||||
<Select variant="standard"
|
||||
style={{ width: 80, }}
|
||||
style={{ width: undefined, }}
|
||||
onChange={(e, extra) => this.handleBindingTypeChange(e, extra)}
|
||||
value={midiBinding.bindingType}
|
||||
>
|
||||
<MenuItem value={0}>None</MenuItem>
|
||||
{(canBindToNote(this.props.midiControlType)) && (
|
||||
<MenuItem value={1}>Note</MenuItem>
|
||||
{(this.canBindToNote()) && (
|
||||
<MenuItem key="1" value={1}>Note</MenuItem>
|
||||
)}
|
||||
<MenuItem key="2" value={2}>Control</MenuItem>
|
||||
{(this.canDoTapTempo()) && (
|
||||
<MenuItem key="3" value={3}>Tap Tempo</MenuItem>
|
||||
)}
|
||||
<MenuItem value={2}>Control</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
{
|
||||
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) &&
|
||||
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE
|
||||
|| midiBinding.bindingType === MidiBinding.BINDING_TYPE_TAP_TEMPO
|
||||
) &&
|
||||
(
|
||||
<div className={classes.controlDiv2} >
|
||||
<Select variant="standard"
|
||||
style={{ width: 80 }}
|
||||
style={{}}
|
||||
onChange={(e, extra) => this.handleNoteChange(e, extra)}
|
||||
value={midiBinding.note}
|
||||
|
||||
@@ -569,7 +591,7 @@ const MidiBindingView =
|
||||
(
|
||||
<div className={classes.controlDiv} >
|
||||
<Select variant="standard"
|
||||
style={{ width: 120 }}
|
||||
style={{}}
|
||||
onChange={(e, extra) => this.handleLinearControlTypeChange(e, extra)}
|
||||
value={midiBinding.linearControlType}
|
||||
>
|
||||
|
||||
@@ -69,6 +69,17 @@ const styles = (theme: Theme) => createStyles({
|
||||
}),
|
||||
});
|
||||
|
||||
function canDoTapTempo(uiPlugin: UiPlugin | undefined, symbol: string): boolean {
|
||||
if (!uiPlugin) return false;
|
||||
let port = uiPlugin.getControl(symbol);
|
||||
if (!port) return false;
|
||||
|
||||
if (port.canDoTapTempo()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function not_null<T>(value: T | null) {
|
||||
if (!value) throw Error("Unexpected null value");
|
||||
return value;
|
||||
@@ -200,7 +211,9 @@ export const MidiBindingDialog =
|
||||
</td>
|
||||
<td className={classes.bindingTd}>
|
||||
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
|
||||
midiControlType={ getMidiControlType(plugin, "__bypass") }
|
||||
midiControlType={ getMidiControlType(plugin, "__bypass")
|
||||
}
|
||||
canDoTapTempo={false }
|
||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||
/>
|
||||
</td>
|
||||
@@ -244,6 +257,7 @@ export const MidiBindingDialog =
|
||||
<MidiBindingView instanceId={item.instanceId}
|
||||
midiControlType={getMidiControlType(plugin, symbol)}
|
||||
midiBinding={item.getMidiBinding(symbol)}
|
||||
canDoTapTempo={canDoTapTempo(plugin, symbol)}
|
||||
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -37,6 +37,15 @@ function isMobileDevice() {
|
||||
return (navigator.maxTouchPoints && navigator.maxTouchPoints > 1);
|
||||
}
|
||||
|
||||
function defaultStep(min: number, max: number): number {
|
||||
let range = Math.abs(max - min);
|
||||
if (range <= 1) return 0.01;
|
||||
if (range <= 10) return 0.1;
|
||||
if (range <= 100) return 1;
|
||||
if (range <= 1000) return 10;
|
||||
return 100;
|
||||
}
|
||||
|
||||
interface NumericInputProps extends WithStyles<typeof styles> {
|
||||
ariaLabel: string;
|
||||
value: number;
|
||||
@@ -155,7 +164,7 @@ export const NumericInput =
|
||||
|
||||
render() {
|
||||
//const classes = withStyles.getClasses(this.props);
|
||||
return (<Input style={{ width: 50 }}
|
||||
return (<Input style={{ width: 65 }}
|
||||
inputProps={{
|
||||
'aria-label': this.props.ariaLabel,
|
||||
style: { textAlign: 'right' },
|
||||
@@ -166,7 +175,7 @@ export const NumericInput =
|
||||
autoCapitalize: "off",
|
||||
min: this.props.min,
|
||||
max: this.props.max,
|
||||
step: this.props.step
|
||||
step: this.props.step ? this.props.step: defaultStep(this.props.min, this.props.max)
|
||||
}}
|
||||
onFocus={(e) => {this.handleFocus(e);}}
|
||||
onBlur= {(e) => { this.handleBlur(e); }}
|
||||
|
||||
@@ -42,6 +42,7 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import StopIcon from '@mui/icons-material/Stop';
|
||||
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
||||
import GraphicEqCtl, { UpdateGraphicEqPath } from './GraphicEqCtl';
|
||||
import Units from './Units';
|
||||
|
||||
const MIN_ANGLE = -135;
|
||||
const MAX_ANGLE = 135;
|
||||
@@ -499,6 +500,8 @@ const PluginControl =
|
||||
}
|
||||
private tapStartMs: number = 0;
|
||||
|
||||
|
||||
|
||||
onPointerUp(e: PointerEvent) {
|
||||
|
||||
if (this.isCapturedPointer(e)) {
|
||||
@@ -519,7 +522,7 @@ const PluginControl =
|
||||
let ms = e.timeStamp - this.tapStartMs;
|
||||
if (ms < 200) {
|
||||
this.onPointerTap(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// prevent click from firing on other elements
|
||||
// when the pointer goes up0.
|
||||
|
||||
Reference in New Issue
Block a user