refactor Pattern and Pulse methods.
This commit is contained in:
parent
c6f0b2a8fc
commit
3253b8b98e
|
@ -1,180 +1,9 @@
|
|||
import { invoke } from '@tauri-apps/api/core';
|
||||
import dayjs from 'dayjs';
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { atomWithRefresh } from 'jotai/utils';
|
||||
import { get, reduce } from 'lodash-es';
|
||||
import { useCallback } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
||||
|
||||
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;
|
||||
offset: 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.offset = 0;
|
||||
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 = [];
|
||||
}
|
||||
|
||||
movePulseUp(pulseId: string, step: number) {
|
||||
const index = this.pulses.findIndex((pulse) => pulse.id === pulseId);
|
||||
if (index === -1 || index - step < 0) return;
|
||||
|
||||
const targetIndex = index - step;
|
||||
const targetPulse = this.pulses[targetIndex];
|
||||
const currentPulse = this.pulses[index];
|
||||
|
||||
// Swap the pulses
|
||||
this.pulses[targetIndex] = currentPulse;
|
||||
this.pulses[index] = targetPulse;
|
||||
|
||||
// Swap their order
|
||||
const tempOrder = currentPulse.order;
|
||||
currentPulse.order = targetPulse.order;
|
||||
targetPulse.order = tempOrder;
|
||||
|
||||
// Sort pulses by order
|
||||
this.pulses.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
movePulseDown(pulseId: string, step: number) {
|
||||
const index = this.pulses.findIndex((pulse) => pulse.id === pulseId);
|
||||
if (index === -1 || index + step >= this.pulses.length) return;
|
||||
|
||||
const targetIndex = index + step;
|
||||
const targetPulse = this.pulses[targetIndex];
|
||||
const currentPulse = this.pulses[index];
|
||||
|
||||
// Swap the pulses
|
||||
this.pulses[targetIndex] = currentPulse;
|
||||
this.pulses[index] = targetPulse;
|
||||
|
||||
// Swap their order
|
||||
const tempOrder = currentPulse.order;
|
||||
currentPulse.order = targetPulse.order;
|
||||
targetPulse.order = tempOrder;
|
||||
|
||||
// Sort pulses by order
|
||||
this.pulses.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
addPulse(): Pulse {
|
||||
const maxOrder = reduce(this.pulses, (former, pulse) => Math.max(former, pulse.order), 0);
|
||||
const newPulse = new Pulse(
|
||||
maxOrder + 1,
|
||||
this.smoothRepeat ? get(this.pulses, '[0].width', 0) : get(this.pulses, '[-1].width', 0),
|
||||
this.smoothRepeat
|
||||
? get(this.pulses, '[0].frequency', 1)
|
||||
: get(this.pulses, '[-1].frequency', 1),
|
||||
);
|
||||
this.pulses.push(newPulse);
|
||||
return newPulse;
|
||||
}
|
||||
|
||||
updatePulse(pulseId: string, pulse: Pulse) {
|
||||
const index = this.pulses.findIndex((p) => p.id === pulseId);
|
||||
if (index !== -1) {
|
||||
const { id, order, ...rest } = pulse;
|
||||
this.pulses[index] = { ...this.pulses[index], ...rest };
|
||||
}
|
||||
}
|
||||
|
||||
deletePulse(pulseId: string) {
|
||||
this.pulses = this.pulses.filter((pulse) => pulse.id !== pulseId);
|
||||
this.pulses.sort((a, b) => a.order - b.order);
|
||||
this.pulses.forEach((pulse, index) => {
|
||||
pulse.order = index + 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function totalDuration(pattern: Pattern): number {
|
||||
return reduce(
|
||||
pattern.pulses,
|
||||
(former, pulse) => former + pulse.offset,
|
||||
pattern.smoothRepeat && pattern.pulses.length > 1 ? 100 : 0,
|
||||
);
|
||||
}
|
||||
import { Pattern, Pulse, totalDuration } from './pattern-model';
|
||||
|
||||
export const SearchKeywordAtom = atom<string | null>(null);
|
||||
export const PatternsAtom = atomWithRefresh(async (get) => {
|
||||
|
|
176
src/context/pattern-model.ts
Normal file
176
src/context/pattern-model.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
import dayjs from 'dayjs';
|
||||
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 interface Pulse {
|
||||
order: number;
|
||||
id: number;
|
||||
offset: number;
|
||||
width: number;
|
||||
maniac: boolean;
|
||||
frequency: number;
|
||||
frequencyShifting: FrequencyShifting;
|
||||
controlPoint1: ControlPoint;
|
||||
controlPoint2: ControlPoint;
|
||||
}
|
||||
export function createPulse(order: number, width: number, frequency: number) {
|
||||
return {
|
||||
id: v4(),
|
||||
order,
|
||||
offset: 0,
|
||||
width,
|
||||
maniac: false,
|
||||
frequency,
|
||||
frequencyShifting: FrequencyShifting.Linear,
|
||||
controlPoint1: { x: 0, y: 0 },
|
||||
controlPoint2: { x: 0, y: 0 },
|
||||
} as Pulse;
|
||||
}
|
||||
|
||||
export interface Pattern {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: number;
|
||||
lastModifiedAt: number | null;
|
||||
smoothRepeat: boolean;
|
||||
pulses: Pulse[];
|
||||
}
|
||||
export function createPattern() {
|
||||
return {
|
||||
id: v4(),
|
||||
name: '',
|
||||
createdAt: dayjs().valueOf(),
|
||||
lastModifiedAt: null,
|
||||
smoothRepeat: true,
|
||||
pulses: [],
|
||||
} as Pattern;
|
||||
}
|
||||
|
||||
export function movePulseUp(pattern: Pattern, pulseId: string, step: number) {
|
||||
const index = pattern.pulses.findIndex((pulse) => pulse.id === pulseId);
|
||||
if (index === -1 || index - step < 0) return;
|
||||
|
||||
const targetIndex = index - step;
|
||||
const targetPulse = pattern.pulses[targetIndex];
|
||||
const currentPulse = pattern.pulses[index];
|
||||
|
||||
// Swap the pulses
|
||||
pattern.pulses[targetIndex] = currentPulse;
|
||||
pattern.pulses[index] = targetPulse;
|
||||
|
||||
// Swap their order
|
||||
const tempOrder = currentPulse.order;
|
||||
currentPulse.order = targetPulse.order;
|
||||
targetPulse.order = tempOrder;
|
||||
|
||||
// Sort pulses by order
|
||||
pattern.pulses.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
export function movePulseDown(pattern: Pattern, pulseId: string, step: number) {
|
||||
const index = pattern.pulses.findIndex((pulse) => pulse.id === pulseId);
|
||||
if (index === -1 || index + step >= pattern.pulses.length) return;
|
||||
|
||||
const targetIndex = index + step;
|
||||
const targetPulse = pattern.pulses[targetIndex];
|
||||
const currentPulse = pattern.pulses[index];
|
||||
|
||||
// Swap the pulses
|
||||
pattern.pulses[targetIndex] = currentPulse;
|
||||
pattern.pulses[index] = targetPulse;
|
||||
|
||||
// Swap their order
|
||||
const tempOrder = currentPulse.order;
|
||||
currentPulse.order = targetPulse.order;
|
||||
targetPulse.order = tempOrder;
|
||||
|
||||
// Sort pulses by order
|
||||
pattern.pulses.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
export function addPulse(pattern: Pattern): Pulse {
|
||||
const maxOrder = reduce(pattern.pulses, (former, pulse) => Math.max(former, pulse.order), 0);
|
||||
const newPulse = createPulse(
|
||||
maxOrder + 1,
|
||||
pattern.smoothRepeat
|
||||
? get(pattern.pulses, '[0].width', 0)
|
||||
: get(pattern.pulses, '[-1].width', 0),
|
||||
pattern.smoothRepeat
|
||||
? get(pattern.pulses, '[0].frequency', 1)
|
||||
: get(pattern.pulses, '[-1].frequency', 1),
|
||||
);
|
||||
pattern.pulses.push(newPulse);
|
||||
return newPulse;
|
||||
}
|
||||
|
||||
export function updatePulse(pattern: Pattern, pulseId: string, pulse: Pulse) {
|
||||
const index = pattern.pulses.findIndex((p) => p.id === pulseId);
|
||||
if (index !== -1) {
|
||||
const { id, order, ...rest } = pulse;
|
||||
pattern.pulses[index] = { ...pattern.pulses[index], ...rest };
|
||||
}
|
||||
}
|
||||
|
||||
export function deletePulse(pattern: Pattern, pulseId: string) {
|
||||
pattern.pulses = pattern.pulses.filter((pulse) => pulse.id !== pulseId);
|
||||
pattern.pulses.sort((a, b) => a.order - b.order);
|
||||
pattern.pulses.forEach((pulse, index) => {
|
||||
pulse.order = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
export function durationRemains(pattern: Pattern): number {
|
||||
return 20 * 1000 - totalDuration(pattern) - (pattern.smoothRepeat ? 100 : 0);
|
||||
}
|
||||
|
||||
export function totalDuration(pattern: Pattern): number {
|
||||
return reduce(
|
||||
pattern.pulses,
|
||||
(former, pulse) => former + pulse.offset,
|
||||
pattern.smoothRepeat && pattern.pulses.length > 1 ? 100 : 0,
|
||||
);
|
||||
}
|
12
src/page-components/pattern-editor/PulseCard.tsx
Normal file
12
src/page-components/pattern-editor/PulseCard.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { FC } from 'react';
|
||||
import { Pulse } from '../../context/pattern-model';
|
||||
|
||||
type PulseCardProps = {
|
||||
pulse: Pulse;
|
||||
};
|
||||
|
||||
const PulseCard: FC<PulseCardProps> = ({ pulse }) => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default PulseCard;
|
|
@ -6,13 +6,8 @@ import { FC, useCallback, useMemo } from 'react';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { NotificationType, useNotification } from '../../components/Notifications';
|
||||
import PatternPreview from '../../components/PatternPreview';
|
||||
import {
|
||||
CurrentPatternAtom,
|
||||
Pattern,
|
||||
PatternsAtom,
|
||||
SelectedPatternIdAtom,
|
||||
totalDuration,
|
||||
} from '../../context/Patterns';
|
||||
import { CurrentPatternAtom, PatternsAtom, SelectedPatternIdAtom } from '../../context/Patterns';
|
||||
import { Pattern, totalDuration } from '../../context/pattern-model';
|
||||
import styles from './PatternDetail.module.css';
|
||||
|
||||
const EmptyPromption: FC = () => {
|
||||
|
|
|
@ -5,13 +5,8 @@ import { FC, useCallback, useMemo, useState } from 'react';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { ScrollArea } from '../../components/ScrollArea';
|
||||
import {
|
||||
Pattern,
|
||||
PatternsAtom,
|
||||
SearchKeywordAtom,
|
||||
SelectedPatternIdAtom,
|
||||
totalDuration,
|
||||
} from '../../context/Patterns';
|
||||
import { PatternsAtom, SearchKeywordAtom, SelectedPatternIdAtom } from '../../context/Patterns';
|
||||
import { Pattern, totalDuration } from '../../context/pattern-model';
|
||||
import styles from './Patterns.module.css';
|
||||
|
||||
const PatternCard: FC<{ pattern: Pattern }> = ({ pattern }) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useSetAtom } from 'jotai';
|
|||
import { FC, useActionState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
||||
import { Pattern, SelectedPatternIdAtom, useSavePattern } from '../context/Patterns';
|
||||
import { SelectedPatternIdAtom, useSavePattern } from '../context/Patterns';
|
||||
import styles from './CreatePattern.module.css';
|
||||
|
||||
const CreatePattern: FC = () => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user