add Pattern Context.
This commit is contained in:
parent
19198d57d9
commit
4a2f599bff
157
src/context/Patterns.tsx
Normal file
157
src/context/Patterns.tsx
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { atom } from 'jotai';
|
||||||
|
import { atomFamily } from 'jotai/utils';
|
||||||
|
import { get, reduce } from 'lodash-es';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export enum FrequencyShifting {
|
||||||
|
/**
|
||||||
|
* Change frequency undergoes a linear transformation from previous pulse to current one.
|
||||||
|
*/
|
||||||
|
Linear,
|
||||||
|
/**
|
||||||
|
* Change frequency undergoes a quadratic transformation from previous pulse to current one.
|
||||||
|
*/
|
||||||
|
Quadratic,
|
||||||
|
/**
|
||||||
|
* Change frequency undergoes a cubic transformation from previous pulse to current one.
|
||||||
|
*/
|
||||||
|
Cubic,
|
||||||
|
/**
|
||||||
|
* Change frequency with quick fade in and fade out.
|
||||||
|
*/
|
||||||
|
Ease,
|
||||||
|
/**
|
||||||
|
* Change frequency with spiking within range from previous pulse and current one.
|
||||||
|
*/
|
||||||
|
Pulsating,
|
||||||
|
/**
|
||||||
|
* Based on frequency of previous pulse, take the twice of frequency of current pulse as the peak frequency. Follow the maximum frequency limitation.
|
||||||
|
*/
|
||||||
|
Spiking,
|
||||||
|
/**
|
||||||
|
* Randomize frequency within range from previous frequency and current one.
|
||||||
|
*/
|
||||||
|
Randomize,
|
||||||
|
/**
|
||||||
|
* Randomize frequency within minium and maximum frequency.
|
||||||
|
*/
|
||||||
|
Maniac,
|
||||||
|
/**
|
||||||
|
* Synchronize changes of frequency with pulse width changes.
|
||||||
|
*/
|
||||||
|
Synchronized,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ControlPoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Pulse {
|
||||||
|
order: number;
|
||||||
|
id: number;
|
||||||
|
duration: number;
|
||||||
|
width: number;
|
||||||
|
maniac: boolean;
|
||||||
|
frequency: number;
|
||||||
|
frequencyShifting: FrequencyShifting;
|
||||||
|
controlPoint1: ControlPoint;
|
||||||
|
controlPoint2: ControlPoint;
|
||||||
|
|
||||||
|
constructor(order: number, width: number, frequency: number) {
|
||||||
|
this.id = v4();
|
||||||
|
this.order = order;
|
||||||
|
this.duration = 25;
|
||||||
|
this.width = width;
|
||||||
|
this.maniac = false;
|
||||||
|
this.frequency = frequency;
|
||||||
|
this.frequencyShifting = FrequencyShifting.Linear;
|
||||||
|
this.controlPoint1 = { x: 0, y: 0 };
|
||||||
|
this.controlPoint2 = { x: 0, y: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(other: Pulse): boolean {
|
||||||
|
return this.id === other.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Pattern {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
createdAt: number;
|
||||||
|
lastModifiedAt: number | null;
|
||||||
|
smoothRepeat: boolean;
|
||||||
|
pulses: Pulse[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.id = v4();
|
||||||
|
this.name = '';
|
||||||
|
this.createdAt = dayjs().valueOf();
|
||||||
|
this.lastModifiedAt = null;
|
||||||
|
this.smoothRepeat = true;
|
||||||
|
this.Pulses = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNewPulse(pattern: Pattern): Pulse {
|
||||||
|
const maxOrder = reduce(pattern.pulses, (former, pulse) => Math.max(former, pulse.order), 0);
|
||||||
|
if (pattern.smoothRepeat) {
|
||||||
|
return new Pulse(
|
||||||
|
maxOrder + 1,
|
||||||
|
get(pattern.pulses, '[0].width', 0),
|
||||||
|
get(pattern.pulses, '[0].frequency', 1),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new Pulse(
|
||||||
|
maxOrder + 1,
|
||||||
|
get(pattern.pulses, '[-1].width', 0),
|
||||||
|
get(pattern.pulses, '[-1].frequency', 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function totalDuration(pattern: Pattern): number {
|
||||||
|
return reduce(
|
||||||
|
pattern.smoothRepeat ? pattern.pulses : pattern.pulses.slice(0, -1),
|
||||||
|
(former, pulse) => former + pulse.duration,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PatternsAtom = atomFamily((keyword: string) =>
|
||||||
|
atom(async () => {
|
||||||
|
try {
|
||||||
|
const patterns = await invoke<Pattern[]>('list_patterns', { keyword });
|
||||||
|
return patterns;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[retreiving pattern list]', e);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
export const CurrentPatternAtom = atom<Pattern | null>(null);
|
||||||
|
export const PulsesInCurrentPatternAtom = atom(
|
||||||
|
(get) => get(CurrentPatternAtom)?.pulses ?? [],
|
||||||
|
(get, set, pulse: Pulse) => {
|
||||||
|
const currentPulses = get(CurrentPatternAtom)?.pulses ?? [];
|
||||||
|
const newPulses = currentPulses.map((p) => (p.id === pulse.id ? pulse : p));
|
||||||
|
if (!newPulses.some((p) => p.id === pulse.id)) {
|
||||||
|
newPulses.push(pulse);
|
||||||
|
}
|
||||||
|
newPulses.sort((a, b) => a.order - b.order);
|
||||||
|
const currentPattern = get(CurrentPatternAtom);
|
||||||
|
if (currentPattern) {
|
||||||
|
set(CurrentPatternAtom, {
|
||||||
|
...currentPattern,
|
||||||
|
pulses: newPulses,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
export const CurrentPatternDuration = atom((get) => {
|
||||||
|
const currentPattern = get(CurrentPatternAtom);
|
||||||
|
if (!currentPattern) return 0;
|
||||||
|
return totalDuration(currentPattern);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user