From ca7c076ed6e034cb771ebc8f47276a9590f4c32d Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:44:00 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E5=88=86=E3=81=91=E3=81=A6=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sing/sequencerStateMachine/common.ts | 222 +++ .../sequencerStateMachine/stateMachine.ts | 71 + .../states/addNoteState.ts | 152 ++ .../states/drawPitchState.ts | 199 +++ .../states/erasePitchState.ts | 137 ++ .../sequencerStateMachine/states/idleState.ts | 105 ++ .../states/moveNoteState.ts | 181 +++ .../states/resizeNoteLeftState.ts | 175 ++ .../states/resizeNoteRightState.ts | 175 ++ .../states/selectNotesWithRectState.ts | 107 ++ .../stateMachineBase.ts => stateMachine.ts} | 21 +- .../stateMachine/sequencerStateMachine.ts | 1420 ----------------- 12 files changed, 1529 insertions(+), 1436 deletions(-) create mode 100644 src/components/Sing/sequencerStateMachine/common.ts create mode 100644 src/components/Sing/sequencerStateMachine/stateMachine.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/addNoteState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/drawPitchState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/erasePitchState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/idleState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/moveNoteState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts create mode 100644 src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts rename src/sing/{stateMachine/stateMachineBase.ts => stateMachine.ts} (91%) delete mode 100644 src/sing/stateMachine/sequencerStateMachine.ts diff --git a/src/components/Sing/sequencerStateMachine/common.ts b/src/components/Sing/sequencerStateMachine/common.ts new file mode 100644 index 0000000000..fe59057e5c --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/common.ts @@ -0,0 +1,222 @@ +import { ComputedRef, Ref } from "vue"; +import { StateDefinitions } from "@/sing/stateMachine"; +import { Rect } from "@/sing/utility"; +import { PREVIEW_SOUND_DURATION } from "@/sing/viewHelper"; +import { Store } from "@/store"; +import { Note, SequencerEditTarget } from "@/store/type"; +import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; +import { NoteId, TrackId } from "@/type/preload"; + +export type PositionOnSequencer = { + readonly x: number; + readonly y: number; + readonly ticks: number; + readonly noteNumber: number; + readonly frame: number; + readonly frequency: number; +}; + +export type Input = + | { + readonly targetArea: "SequencerBody"; + readonly mouseEvent: MouseEvent; + readonly cursorPos: PositionOnSequencer; + } + | { + readonly targetArea: "Note"; + readonly mouseEvent: MouseEvent; + readonly cursorPos: PositionOnSequencer; + readonly note: Note; + } + | { + readonly targetArea: "NoteLeftEdge"; + readonly mouseEvent: MouseEvent; + readonly cursorPos: PositionOnSequencer; + readonly note: Note; + } + | { + readonly targetArea: "NoteRightEdge"; + readonly mouseEvent: MouseEvent; + readonly cursorPos: PositionOnSequencer; + readonly note: Note; + }; + +export type ComputedRefs = { + readonly snapTicks: ComputedRef; + readonly editTarget: ComputedRef; + readonly selectedTrackId: ComputedRef; + readonly notesInSelectedTrack: ComputedRef; + readonly selectedNoteIds: ComputedRef>; + readonly editorFrameRate: ComputedRef; +}; + +export type Refs = { + readonly nowPreviewing: Ref; + readonly previewNotes: Ref; + readonly previewRectForRectSelect: Ref; + readonly previewPitchEdit: Ref< + | { type: "draw"; data: number[]; startFrame: number } + | { type: "erase"; startFrame: number; frameLength: number } + | undefined + >; + readonly guideLineTicks: Ref; +}; + +export type PartialStore = { + state: Pick< + Store["state"], + "tpqn" | "sequencerSnapType" | "sequencerEditTarget" | "editorFrameRate" + >; + getters: Pick< + Store["getters"], + "SELECTED_TRACK_ID" | "SELECTED_TRACK" | "SELECTED_NOTE_IDS" + >; + actions: Pick< + Store["actions"], + | "SELECT_NOTES" + | "DESELECT_NOTES" + | "DESELECT_ALL_NOTES" + | "PLAY_PREVIEW_SOUND" + | "COMMAND_ADD_NOTES" + | "COMMAND_UPDATE_NOTES" + | "COMMAND_SET_PITCH_EDIT_DATA" + | "COMMAND_ERASE_PITCH_EDIT_DATA" + >; +}; + +export type Context = ComputedRefs & Refs & { readonly store: PartialStore }; + +export type SequencerStateDefinitions = StateDefinitions< + [ + { + id: "idle"; + factoryArgs: undefined; + }, + { + id: "addNote"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + }; + }, + { + id: "moveNote"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + targetNoteIds: Set; + mouseDownNoteId: NoteId; + }; + }, + { + id: "resizeNoteLeft"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + targetNoteIds: Set; + mouseDownNoteId: NoteId; + }; + }, + { + id: "resizeNoteRight"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + targetNoteIds: Set; + mouseDownNoteId: NoteId; + }; + }, + { + id: "selectNotesWithRect"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + }; + }, + { + id: "drawPitch"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + }; + }, + { + id: "erasePitch"; + factoryArgs: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + }; + }, + ] +>; + +/** + * カーソル位置に対応する補助線の位置を取得する。 + */ +export const getGuideLineTicks = ( + cursorPos: PositionOnSequencer, + context: Context, +) => { + const cursorTicks = cursorPos.ticks; + const snapTicks = context.snapTicks.value; + // NOTE: 入力を補助する線の判定の境目はスナップ幅の3/4の位置 + return Math.round(cursorTicks / snapTicks - 0.25) * snapTicks; +}; + +/** + * 指定されたノートのみを選択状態にする。 + */ +export const selectOnlyThisNote = (context: Context, note: Note) => { + void context.store.actions.DESELECT_ALL_NOTES(); + void context.store.actions.SELECT_NOTES({ noteIds: [note.id] }); +}; + +/** + * mousedown時のノート選択・選択解除の処理を実行する。 + */ +export const executeNotesSelectionProcess = ( + context: Context, + mouseEvent: MouseEvent, + mouseDownNote: Note, +) => { + if (mouseEvent.shiftKey) { + // Shiftキーが押されている場合は選択ノートまでの範囲選択 + let minIndex = context.notesInSelectedTrack.value.length - 1; + let maxIndex = 0; + for (let i = 0; i < context.notesInSelectedTrack.value.length; i++) { + const noteId = context.notesInSelectedTrack.value[i].id; + if ( + context.selectedNoteIds.value.has(noteId) || + noteId === mouseDownNote.id + ) { + minIndex = Math.min(minIndex, i); + maxIndex = Math.max(maxIndex, i); + } + } + const noteIdsToSelect: NoteId[] = []; + for (let i = minIndex; i <= maxIndex; i++) { + const noteId = context.notesInSelectedTrack.value[i].id; + if (!context.selectedNoteIds.value.has(noteId)) { + noteIdsToSelect.push(noteId); + } + } + void context.store.actions.SELECT_NOTES({ noteIds: noteIdsToSelect }); + } else if (isOnCommandOrCtrlKeyDown(mouseEvent)) { + // CommandキーかCtrlキーが押されている場合 + if (context.selectedNoteIds.value.has(mouseDownNote.id)) { + // 選択中のノートなら選択解除 + void context.store.actions.DESELECT_NOTES({ + noteIds: [mouseDownNote.id], + }); + return; + } + // 未選択のノートなら選択に追加 + void context.store.actions.SELECT_NOTES({ noteIds: [mouseDownNote.id] }); + } else if (!context.selectedNoteIds.value.has(mouseDownNote.id)) { + // 選択中のノートでない場合は選択状態にする + void selectOnlyThisNote(context, mouseDownNote); + void context.store.actions.PLAY_PREVIEW_SOUND({ + noteNumber: mouseDownNote.noteNumber, + duration: PREVIEW_SOUND_DURATION, + }); + } +}; diff --git a/src/components/Sing/sequencerStateMachine/stateMachine.ts b/src/components/Sing/sequencerStateMachine/stateMachine.ts new file mode 100644 index 0000000000..1390b798cb --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/stateMachine.ts @@ -0,0 +1,71 @@ +import { computed, ref } from "vue"; +import { + ComputedRefs, + Context, + Input, + PartialStore, + Refs, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { getNoteDuration } from "@/sing/domain"; +import { StateMachine } from "@/sing/stateMachine"; + +import { IdleState } from "@/components/Sing/sequencerStateMachine/states/idleState"; +import { AddNoteState } from "@/components/Sing/sequencerStateMachine/states/addNoteState"; +import { MoveNoteState } from "@/components/Sing/sequencerStateMachine/states/moveNoteState"; +import { ResizeNoteLeftState } from "@/components/Sing/sequencerStateMachine/states/resizeNoteLeftState"; +import { ResizeNoteRightState } from "@/components/Sing/sequencerStateMachine/states/resizeNoteRightState"; +import { SelectNotesWithRectState } from "@/components/Sing/sequencerStateMachine/states/selectNotesWithRectState"; +import { DrawPitchState } from "@/components/Sing/sequencerStateMachine/states/drawPitchState"; +import { ErasePitchState } from "@/components/Sing/sequencerStateMachine/states/erasePitchState"; + +export const useSequencerStateMachine = (store: PartialStore) => { + const computedRefs: ComputedRefs = { + snapTicks: computed(() => + getNoteDuration(store.state.sequencerSnapType, store.state.tpqn), + ), + editTarget: computed(() => store.state.sequencerEditTarget), + selectedTrackId: computed(() => store.getters.SELECTED_TRACK_ID), + notesInSelectedTrack: computed(() => store.getters.SELECTED_TRACK.notes), + selectedNoteIds: computed(() => store.getters.SELECTED_NOTE_IDS), + editorFrameRate: computed(() => store.state.editorFrameRate), + }; + const refs: Refs = { + nowPreviewing: ref(false), + previewNotes: ref([]), + previewRectForRectSelect: ref(undefined), + previewPitchEdit: ref(undefined), + guideLineTicks: ref(0), + }; + const stateMachine = new StateMachine< + SequencerStateDefinitions, + Input, + Context + >( + { + idle: () => new IdleState(), + addNote: (args) => new AddNoteState(args), + moveNote: (args) => new MoveNoteState(args), + resizeNoteLeft: (args) => new ResizeNoteLeftState(args), + resizeNoteRight: (args) => new ResizeNoteRightState(args), + selectNotesWithRect: (args) => new SelectNotesWithRectState(args), + drawPitch: (args) => new DrawPitchState(args), + erasePitch: (args) => new ErasePitchState(args), + }, + new IdleState(), + { + ...computedRefs, + ...refs, + store, + }, + ); + return { + stateMachine, + nowPreviewing: computed(() => refs.nowPreviewing.value), + previewNotes: computed(() => refs.previewNotes.value), + previewRectForRectSelect: computed( + () => refs.previewRectForRectSelect.value, + ), + guideLineTicks: computed(() => refs.guideLineTicks.value), + }; +}; diff --git a/src/components/Sing/sequencerStateMachine/states/addNoteState.ts b/src/components/Sing/sequencerStateMachine/states/addNoteState.ts new file mode 100644 index 0000000000..4c4f9c8273 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/addNoteState.ts @@ -0,0 +1,152 @@ +import { SetNextState, State } from "@/sing/stateMachine"; +import { + Context, + getGuideLineTicks, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { NoteId, TrackId } from "@/type/preload"; +import { Note } from "@/store/type"; +import { + getButton, + getDoremiFromNoteNumber, + PREVIEW_SOUND_DURATION, +} from "@/sing/viewHelper"; +import { clamp } from "@/sing/utility"; + +export class AddNoteState + implements State +{ + readonly id = "addNote"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + + private currentCursorPos: PositionOnSequencer; + private innerContext: + | { + noteToAdd: Note; + previewRequestId: number; + executePreviewProcess: boolean; + } + | undefined; + + constructor(args: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + }) { + this.cursorPosAtStart = args.cursorPosAtStart; + this.targetTrackId = args.targetTrackId; + + this.currentCursorPos = args.cursorPosAtStart; + } + + private previewAdd(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const noteToAdd = this.innerContext.noteToAdd; + const snapTicks = context.snapTicks.value; + const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; + const noteDuration = Math.round(dragTicks / snapTicks) * snapTicks; + const noteEndPos = noteToAdd.position + noteDuration; + const previewNotes = context.previewNotes.value; + + const editedNotes = new Map(); + for (const note of previewNotes) { + const duration = Math.max(snapTicks, noteDuration); + if (note.duration !== duration) { + editedNotes.set(note.id, { ...note, duration }); + } + } + if (editedNotes.size !== 0) { + context.previewNotes.value = previewNotes.map((value) => { + return editedNotes.get(value.id) ?? value; + }); + } + context.guideLineTicks.value = noteEndPos; + } + + onEnter(context: Context) { + const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); + const noteToAdd = { + id: NoteId(crypto.randomUUID()), + position: Math.max(0, guideLineTicks), + duration: context.snapTicks.value, + noteNumber: clamp(this.cursorPosAtStart.noteNumber, 0, 127), + lyric: getDoremiFromNoteNumber(this.cursorPosAtStart.noteNumber), + }; + + context.previewNotes.value = [noteToAdd]; + context.nowPreviewing.value = true; + + const previewIfNeeded = () => { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (this.innerContext.executePreviewProcess) { + this.previewAdd(context); + this.innerContext.executePreviewProcess = false; + } + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); + }; + const previewRequestId = requestAnimationFrame(previewIfNeeded); + + this.innerContext = { + noteToAdd, + executePreviewProcess: false, + previewRequestId, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.innerContext.executePreviewProcess = true; + } else if (input.mouseEvent.type === "mouseup") { + if (mouseButton === "LEFT_BUTTON") { + setNextState("idle", undefined); + } + } + } + } + + onExit(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const previewNotes = context.previewNotes.value; + const previewNoteIds = previewNotes.map((value) => value.id); + + cancelAnimationFrame(this.innerContext.previewRequestId); + + void context.store.actions.COMMAND_ADD_NOTES({ + notes: context.previewNotes.value, + trackId: this.targetTrackId, + }); + void context.store.actions.SELECT_NOTES({ noteIds: previewNoteIds }); + + if (previewNotes.length === 1) { + void context.store.actions.PLAY_PREVIEW_SOUND({ + noteNumber: previewNotes[0].noteNumber, + duration: PREVIEW_SOUND_DURATION, + }); + } + context.previewNotes.value = []; + context.nowPreviewing.value = false; + } +} diff --git a/src/components/Sing/sequencerStateMachine/states/drawPitchState.ts b/src/components/Sing/sequencerStateMachine/states/drawPitchState.ts new file mode 100644 index 0000000000..43d7114013 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/drawPitchState.ts @@ -0,0 +1,199 @@ +import { SetNextState, State } from "@/sing/stateMachine"; +import { + Context, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { TrackId } from "@/type/preload"; +import { + applyGaussianFilter, + createArray, + linearInterpolation, +} from "@/sing/utility"; +import { getButton } from "@/sing/viewHelper"; + +export class DrawPitchState + implements State +{ + readonly id = "drawPitch"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + + private currentCursorPos: PositionOnSequencer; + + private innerContext: + | { + previewRequestId: number; + executePreviewProcess: boolean; + prevCursorPos: PositionOnSequencer; + } + | undefined; + + constructor(args: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + }) { + this.cursorPosAtStart = args.cursorPosAtStart; + this.targetTrackId = args.targetTrackId; + + this.currentCursorPos = args.cursorPosAtStart; + } + + private previewDrawPitch(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (context.previewPitchEdit.value == undefined) { + throw new Error("previewPitchEdit.value is undefined."); + } + if (context.previewPitchEdit.value.type !== "draw") { + throw new Error("previewPitchEdit.value.type is not draw."); + } + const cursorFrame = this.currentCursorPos.frame; + const cursorFrequency = this.currentCursorPos.frequency; + const prevCursorFrame = this.innerContext.prevCursorPos.frame; + const prevCursorFrequency = this.innerContext.prevCursorPos.frequency; + if (cursorFrame < 0) { + return; + } + const tempPitchEdit = { + ...context.previewPitchEdit.value, + data: [...context.previewPitchEdit.value.data], + }; + + if (cursorFrame < tempPitchEdit.startFrame) { + const numOfFramesToUnshift = tempPitchEdit.startFrame - cursorFrame; + tempPitchEdit.data = createArray(numOfFramesToUnshift, () => 0).concat( + tempPitchEdit.data, + ); + tempPitchEdit.startFrame = cursorFrame; + } + + const lastFrame = tempPitchEdit.startFrame + tempPitchEdit.data.length - 1; + if (cursorFrame > lastFrame) { + const numOfFramesToPush = cursorFrame - lastFrame; + tempPitchEdit.data = tempPitchEdit.data.concat( + createArray(numOfFramesToPush, () => 0), + ); + } + + if (cursorFrame === prevCursorFrame) { + const i = cursorFrame - tempPitchEdit.startFrame; + tempPitchEdit.data[i] = cursorFrequency; + } else if (cursorFrame < prevCursorFrame) { + for (let i = cursorFrame; i <= prevCursorFrame; i++) { + tempPitchEdit.data[i - tempPitchEdit.startFrame] = Math.exp( + linearInterpolation( + cursorFrame, + Math.log(cursorFrequency), + prevCursorFrame, + Math.log(prevCursorFrequency), + i, + ), + ); + } + } else { + for (let i = prevCursorFrame; i <= cursorFrame; i++) { + tempPitchEdit.data[i - tempPitchEdit.startFrame] = Math.exp( + linearInterpolation( + prevCursorFrame, + Math.log(prevCursorFrequency), + cursorFrame, + Math.log(cursorFrequency), + i, + ), + ); + } + } + + context.previewPitchEdit.value = tempPitchEdit; + this.innerContext.prevCursorPos = this.currentCursorPos; + } + + onEnter(context: Context) { + context.previewPitchEdit.value = { + type: "draw", + data: [this.cursorPosAtStart.frequency], + startFrame: this.cursorPosAtStart.frame, + }; + context.nowPreviewing.value = true; + + const previewIfNeeded = () => { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (this.innerContext.executePreviewProcess) { + this.previewDrawPitch(context); + this.innerContext.executePreviewProcess = false; + } + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); + }; + const previewRequestId = requestAnimationFrame(previewIfNeeded); + + this.innerContext = { + executePreviewProcess: false, + previewRequestId, + prevCursorPos: this.cursorPosAtStart, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.innerContext.executePreviewProcess = true; + } else if (input.mouseEvent.type === "mouseup") { + if (mouseButton === "LEFT_BUTTON") { + setNextState("idle", undefined); + } + } + } + } + + onExit(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (context.previewPitchEdit.value == undefined) { + throw new Error("previewPitchEdit is undefined."); + } + if (context.previewPitchEdit.value.type !== "draw") { + throw new Error("previewPitchEdit.type is not draw."); + } + + cancelAnimationFrame(this.innerContext.previewRequestId); + + // カーソルを動かさずにマウスのボタンを離したときに1フレームのみの変更になり、 + // 1フレームの変更はピッチ編集ラインとして表示されないので、無視する + if (context.previewPitchEdit.value.data.length >= 2) { + // 平滑化を行う + let data = context.previewPitchEdit.value.data; + data = data.map((value) => Math.log(value)); + applyGaussianFilter(data, 0.7); + data = data.map((value) => Math.exp(value)); + + void context.store.actions.COMMAND_SET_PITCH_EDIT_DATA({ + pitchArray: data, + startFrame: context.previewPitchEdit.value.startFrame, + trackId: this.targetTrackId, + }); + } + + context.previewPitchEdit.value = undefined; + context.nowPreviewing.value = false; + } +} diff --git a/src/components/Sing/sequencerStateMachine/states/erasePitchState.ts b/src/components/Sing/sequencerStateMachine/states/erasePitchState.ts new file mode 100644 index 0000000000..2229ea52a5 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/erasePitchState.ts @@ -0,0 +1,137 @@ +import { SetNextState, State } from "@/sing/stateMachine"; +import { + Context, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { TrackId } from "@/type/preload"; +import { getButton } from "@/sing/viewHelper"; + +export class ErasePitchState + implements State +{ + readonly id = "erasePitch"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + + private currentCursorPos: PositionOnSequencer; + + private innerContext: + | { + previewRequestId: number; + executePreviewProcess: boolean; + } + | undefined; + + constructor(args: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + }) { + this.cursorPosAtStart = args.cursorPosAtStart; + this.targetTrackId = args.targetTrackId; + + this.currentCursorPos = args.cursorPosAtStart; + } + + private previewErasePitch(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (context.previewPitchEdit.value == undefined) { + throw new Error("previewPitchEdit.value is undefined."); + } + if (context.previewPitchEdit.value.type !== "erase") { + throw new Error("previewPitchEdit.value.type is not erase."); + } + const cursorFrame = Math.max(0, this.currentCursorPos.frame); + const tempPitchEdit = { ...context.previewPitchEdit.value }; + + if (tempPitchEdit.startFrame > cursorFrame) { + tempPitchEdit.frameLength += tempPitchEdit.startFrame - cursorFrame; + tempPitchEdit.startFrame = cursorFrame; + } + + const lastFrame = tempPitchEdit.startFrame + tempPitchEdit.frameLength - 1; + if (lastFrame < cursorFrame) { + tempPitchEdit.frameLength += cursorFrame - lastFrame; + } + + context.previewPitchEdit.value = tempPitchEdit; + } + + onEnter(context: Context) { + context.previewPitchEdit.value = { + type: "erase", + startFrame: this.cursorPosAtStart.frame, + frameLength: 1, + }; + context.nowPreviewing.value = true; + + const previewIfNeeded = () => { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (this.innerContext.executePreviewProcess) { + this.previewErasePitch(context); + this.innerContext.executePreviewProcess = false; + } + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); + }; + const previewRequestId = requestAnimationFrame(previewIfNeeded); + + this.innerContext = { + executePreviewProcess: false, + previewRequestId, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.innerContext.executePreviewProcess = true; + } else if (input.mouseEvent.type === "mouseup") { + if (mouseButton === "LEFT_BUTTON") { + setNextState("idle", undefined); + } + } + } + } + + onExit(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (context.previewPitchEdit.value == undefined) { + throw new Error("previewPitchEdit is undefined."); + } + if (context.previewPitchEdit.value.type !== "erase") { + throw new Error("previewPitchEdit.type is not erase."); + } + + cancelAnimationFrame(this.innerContext.previewRequestId); + + void context.store.actions.COMMAND_ERASE_PITCH_EDIT_DATA({ + startFrame: context.previewPitchEdit.value.startFrame, + frameLength: context.previewPitchEdit.value.frameLength, + trackId: this.targetTrackId, + }); + + context.previewPitchEdit.value = undefined; + context.nowPreviewing.value = false; + } +} diff --git a/src/components/Sing/sequencerStateMachine/states/idleState.ts b/src/components/Sing/sequencerStateMachine/states/idleState.ts new file mode 100644 index 0000000000..6221b53ef1 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/idleState.ts @@ -0,0 +1,105 @@ +import { SetNextState, State } from "@/sing/stateMachine"; +import { + Context, + executeNotesSelectionProcess, + getGuideLineTicks, + Input, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { getButton, isSelfEventTarget } from "@/sing/viewHelper"; +import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; + +export class IdleState + implements State +{ + readonly id = "idle"; + + onEnter() {} + + process({ + input, + context, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + const mouseButton = getButton(input.mouseEvent); + const selectedTrackId = context.selectedTrackId.value; + + if (context.editTarget.value === "NOTE") { + if (input.targetArea === "SequencerBody") { + context.guideLineTicks.value = getGuideLineTicks( + input.cursorPos, + context, + ); + } + if ( + input.mouseEvent.type === "mousedown" && + mouseButton === "LEFT_BUTTON" && + isSelfEventTarget(input.mouseEvent) + ) { + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.shiftKey) { + setNextState("selectNotesWithRect", { + cursorPosAtStart: input.cursorPos, + }); + } else { + void context.store.actions.DESELECT_ALL_NOTES(); + setNextState("addNote", { + cursorPosAtStart: input.cursorPos, + targetTrackId: selectedTrackId, + }); + } + } else if (input.targetArea === "Note") { + executeNotesSelectionProcess(context, input.mouseEvent, input.note); + setNextState("moveNote", { + cursorPosAtStart: input.cursorPos, + targetTrackId: selectedTrackId, + targetNoteIds: context.selectedNoteIds.value, + mouseDownNoteId: input.note.id, + }); + } else if (input.targetArea === "NoteLeftEdge") { + executeNotesSelectionProcess(context, input.mouseEvent, input.note); + setNextState("resizeNoteLeft", { + cursorPosAtStart: input.cursorPos, + targetTrackId: selectedTrackId, + targetNoteIds: context.selectedNoteIds.value, + mouseDownNoteId: input.note.id, + }); + } else if (input.targetArea === "NoteRightEdge") { + executeNotesSelectionProcess(context, input.mouseEvent, input.note); + setNextState("resizeNoteRight", { + cursorPosAtStart: input.cursorPos, + targetTrackId: selectedTrackId, + targetNoteIds: context.selectedNoteIds.value, + mouseDownNoteId: input.note.id, + }); + } + } + } else if (context.editTarget.value === "PITCH") { + if ( + input.mouseEvent.type === "mousedown" && + mouseButton === "LEFT_BUTTON" && + input.targetArea === "SequencerBody" + ) { + // TODO: Ctrlが押されているときではなく、 + // ピッチ削除ツールのときにErasePitchStateに遷移するようにする + if (isOnCommandOrCtrlKeyDown(input.mouseEvent)) { + setNextState("erasePitch", { + cursorPosAtStart: input.cursorPos, + targetTrackId: selectedTrackId, + }); + } else { + setNextState("drawPitch", { + cursorPosAtStart: input.cursorPos, + targetTrackId: selectedTrackId, + }); + } + } + } + } + + onExit() {} +} diff --git a/src/components/Sing/sequencerStateMachine/states/moveNoteState.ts b/src/components/Sing/sequencerStateMachine/states/moveNoteState.ts new file mode 100644 index 0000000000..5a9f607662 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/moveNoteState.ts @@ -0,0 +1,181 @@ +import { getOrThrow } from "@/helpers/mapHelper"; +import { State, SetNextState } from "@/sing/stateMachine"; +import { clamp } from "@/sing/utility"; +import { getButton, PREVIEW_SOUND_DURATION } from "@/sing/viewHelper"; +import { Note } from "@/store/type"; +import { TrackId, NoteId } from "@/type/preload"; +import { + Context, + getGuideLineTicks, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; + +export class MoveNoteState + implements State +{ + readonly id = "moveNote"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + private readonly targetNoteIds: Set; + private readonly mouseDownNoteId: NoteId; + + private currentCursorPos: PositionOnSequencer; + + private innerContext: + | { + targetNotesAtStart: Map; + previewRequestId: number; + executePreviewProcess: boolean; + edited: boolean; + guideLineTicksAtStart: number; + } + | undefined; + + constructor(args: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + targetNoteIds: Set; + mouseDownNoteId: NoteId; + }) { + if (!args.targetNoteIds.has(args.mouseDownNoteId)) { + throw new Error("mouseDownNoteId is not included in targetNoteIds."); + } + this.cursorPosAtStart = args.cursorPosAtStart; + this.targetTrackId = args.targetTrackId; + this.targetNoteIds = args.targetNoteIds; + this.mouseDownNoteId = args.mouseDownNoteId; + + this.currentCursorPos = args.cursorPosAtStart; + } + + private previewMove(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const snapTicks = context.snapTicks.value; + const previewNotes = context.previewNotes.value; + const targetNotesAtStart = this.innerContext.targetNotesAtStart; + const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); + const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; + const notePos = mouseDownNote.position; + const newNotePos = + Math.round((notePos + dragTicks) / snapTicks) * snapTicks; + const movingTicks = newNotePos - notePos; + const movingSemitones = + this.currentCursorPos.noteNumber - this.cursorPosAtStart.noteNumber; + + const editedNotes = new Map(); + for (const note of previewNotes) { + const targetNoteAtStart = getOrThrow(targetNotesAtStart, note.id); + const position = Math.max(0, targetNoteAtStart.position + movingTicks); + const noteNumber = clamp( + targetNoteAtStart.noteNumber + movingSemitones, + 0, + 127, + ); + + if (note.position !== position || note.noteNumber !== noteNumber) { + editedNotes.set(note.id, { ...note, noteNumber, position }); + } + } + + if (editedNotes.size !== 0) { + context.previewNotes.value = previewNotes.map((value) => { + return editedNotes.get(value.id) ?? value; + }); + this.innerContext.edited = true; + } + + context.guideLineTicks.value = + this.innerContext.guideLineTicksAtStart + movingTicks; + } + + onEnter(context: Context) { + const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); + const targetNotesArray = context.notesInSelectedTrack.value.filter( + (value) => this.targetNoteIds.has(value.id), + ); + const targetNotesMap = new Map(); + for (const targetNote of targetNotesArray) { + targetNotesMap.set(targetNote.id, targetNote); + } + + context.previewNotes.value = [...targetNotesArray]; + context.nowPreviewing.value = true; + + const previewIfNeeded = () => { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (this.innerContext.executePreviewProcess) { + this.previewMove(context); + this.innerContext.executePreviewProcess = false; + } + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); + }; + const previewRequestId = requestAnimationFrame(previewIfNeeded); + + this.innerContext = { + targetNotesAtStart: targetNotesMap, + executePreviewProcess: false, + previewRequestId, + edited: false, + guideLineTicksAtStart: guideLineTicks, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.innerContext.executePreviewProcess = true; + } else if (input.mouseEvent.type === "mouseup") { + if (mouseButton === "LEFT_BUTTON") { + setNextState("idle", undefined); + } + } + } + } + + onExit(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const previewNotes = context.previewNotes.value; + const previewNoteIds = previewNotes.map((value) => value.id); + + cancelAnimationFrame(this.innerContext.previewRequestId); + + void context.store.actions.COMMAND_UPDATE_NOTES({ + notes: previewNotes, + trackId: this.targetTrackId, + }); + void context.store.actions.SELECT_NOTES({ + noteIds: previewNoteIds, + }); + + if (previewNotes.length === 1) { + void context.store.actions.PLAY_PREVIEW_SOUND({ + noteNumber: previewNotes[0].noteNumber, + duration: PREVIEW_SOUND_DURATION, + }); + } + context.previewNotes.value = []; + context.nowPreviewing.value = false; + } +} diff --git a/src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts b/src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts new file mode 100644 index 0000000000..62e05a63bb --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts @@ -0,0 +1,175 @@ +import { getOrThrow } from "@/helpers/mapHelper"; +import { State, SetNextState } from "@/sing/stateMachine"; +import { getButton, PREVIEW_SOUND_DURATION } from "@/sing/viewHelper"; +import { Note } from "@/store/type"; +import { TrackId, NoteId } from "@/type/preload"; +import { + Context, + getGuideLineTicks, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { clamp } from "@/sing/utility"; + +export class ResizeNoteLeftState + implements State +{ + readonly id = "resizeNoteLeft"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + private readonly targetNoteIds: Set; + private readonly mouseDownNoteId: NoteId; + + private currentCursorPos: PositionOnSequencer; + + private innerContext: + | { + targetNotesAtStart: Map; + previewRequestId: number; + executePreviewProcess: boolean; + edited: boolean; + guideLineTicksAtStart: number; + } + | undefined; + + constructor(args: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + targetNoteIds: Set; + mouseDownNoteId: NoteId; + }) { + if (!args.targetNoteIds.has(args.mouseDownNoteId)) { + throw new Error("mouseDownNoteId is not included in targetNoteIds."); + } + this.cursorPosAtStart = args.cursorPosAtStart; + this.targetTrackId = args.targetTrackId; + this.targetNoteIds = args.targetNoteIds; + this.mouseDownNoteId = args.mouseDownNoteId; + + this.currentCursorPos = args.cursorPosAtStart; + } + + private previewResizeLeft(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const snapTicks = context.snapTicks.value; + const previewNotes = context.previewNotes.value; + const targetNotesAtStart = this.innerContext.targetNotesAtStart; + const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); + const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; + const notePos = mouseDownNote.position; + const newNotePos = + Math.round((notePos + dragTicks) / snapTicks) * snapTicks; + const movingTicks = newNotePos - notePos; + + const editedNotes = new Map(); + for (const note of previewNotes) { + const targetNoteAtStart = getOrThrow(targetNotesAtStart, note.id); + const notePos = targetNoteAtStart.position; + const noteEndPos = + targetNoteAtStart.position + targetNoteAtStart.duration; + const position = clamp(notePos + movingTicks, 0, noteEndPos - snapTicks); + const duration = noteEndPos - position; + + if (note.position !== position || note.duration !== duration) { + editedNotes.set(note.id, { ...note, position, duration }); + } + } + if (editedNotes.size !== 0) { + context.previewNotes.value = previewNotes.map((value) => { + return editedNotes.get(value.id) ?? value; + }); + this.innerContext.edited = true; + } + + context.guideLineTicks.value = newNotePos; + } + + onEnter(context: Context) { + const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); + const targetNotesArray = context.notesInSelectedTrack.value.filter( + (value) => this.targetNoteIds.has(value.id), + ); + const targetNotesMap = new Map(); + for (const targetNote of targetNotesArray) { + targetNotesMap.set(targetNote.id, targetNote); + } + + context.previewNotes.value = [...targetNotesArray]; + context.nowPreviewing.value = true; + + const previewIfNeeded = () => { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (this.innerContext.executePreviewProcess) { + this.previewResizeLeft(context); + this.innerContext.executePreviewProcess = false; + } + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); + }; + const previewRequestId = requestAnimationFrame(previewIfNeeded); + + this.innerContext = { + targetNotesAtStart: targetNotesMap, + executePreviewProcess: false, + previewRequestId, + edited: false, + guideLineTicksAtStart: guideLineTicks, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.innerContext.executePreviewProcess = true; + } else if ( + input.mouseEvent.type === "mouseup" && + mouseButton === "LEFT_BUTTON" + ) { + setNextState("idle", undefined); + } + } + } + + onExit(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const previewNotes = context.previewNotes.value; + const previewNoteIds = previewNotes.map((value) => value.id); + + cancelAnimationFrame(this.innerContext.previewRequestId); + + void context.store.actions.COMMAND_UPDATE_NOTES({ + notes: previewNotes, + trackId: this.targetTrackId, + }); + void context.store.actions.SELECT_NOTES({ noteIds: previewNoteIds }); + + if (previewNotes.length === 1) { + void context.store.actions.PLAY_PREVIEW_SOUND({ + noteNumber: previewNotes[0].noteNumber, + duration: PREVIEW_SOUND_DURATION, + }); + } + context.previewNotes.value = []; + context.nowPreviewing.value = false; + } +} diff --git a/src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts b/src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts new file mode 100644 index 0000000000..cec0446a78 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts @@ -0,0 +1,175 @@ +import { State, SetNextState } from "@/sing/stateMachine"; +import { getButton, PREVIEW_SOUND_DURATION } from "@/sing/viewHelper"; +import { NoteId, TrackId } from "@/type/preload"; +import { + Context, + getGuideLineTicks, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { Note } from "@/store/type"; +import { getOrThrow } from "@/helpers/mapHelper"; + +export class ResizeNoteRightState + implements State +{ + readonly id = "resizeNoteRight"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + private readonly targetNoteIds: Set; + private readonly mouseDownNoteId: NoteId; + + private currentCursorPos: PositionOnSequencer; + + private innerContext: + | { + targetNotesAtStart: Map; + previewRequestId: number; + executePreviewProcess: boolean; + edited: boolean; + guideLineTicksAtStart: number; + } + | undefined; + + constructor(args: { + cursorPosAtStart: PositionOnSequencer; + targetTrackId: TrackId; + targetNoteIds: Set; + mouseDownNoteId: NoteId; + }) { + if (!args.targetNoteIds.has(args.mouseDownNoteId)) { + throw new Error("mouseDownNoteId is not included in targetNoteIds."); + } + this.cursorPosAtStart = args.cursorPosAtStart; + this.targetTrackId = args.targetTrackId; + this.targetNoteIds = args.targetNoteIds; + this.mouseDownNoteId = args.mouseDownNoteId; + + this.currentCursorPos = args.cursorPosAtStart; + } + + private previewResizeRight(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const snapTicks = context.snapTicks.value; + const previewNotes = context.previewNotes.value; + const targetNotesAtStart = this.innerContext.targetNotesAtStart; + const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); + const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; + const noteEndPos = mouseDownNote.position + mouseDownNote.duration; + const newNoteEndPos = + Math.round((noteEndPos + dragTicks) / snapTicks) * snapTicks; + const movingTicks = newNoteEndPos - noteEndPos; + + const editedNotes = new Map(); + for (const note of previewNotes) { + const targetNoteAtStart = getOrThrow(targetNotesAtStart, note.id); + const notePos = targetNoteAtStart.position; + const noteEndPos = + targetNoteAtStart.position + targetNoteAtStart.duration; + const duration = Math.max(snapTicks, noteEndPos + movingTicks - notePos); + + if (note.duration !== duration) { + editedNotes.set(note.id, { ...note, duration }); + } + } + if (editedNotes.size !== 0) { + context.previewNotes.value = previewNotes.map((value) => { + return editedNotes.get(value.id) ?? value; + }); + this.innerContext.edited = true; + } + + context.guideLineTicks.value = newNoteEndPos; + } + + onEnter(context: Context) { + const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); + const targetNotesArray = context.notesInSelectedTrack.value.filter( + (value) => this.targetNoteIds.has(value.id), + ); + const targetNotesMap = new Map(); + for (const targetNote of targetNotesArray) { + targetNotesMap.set(targetNote.id, targetNote); + } + + context.previewNotes.value = [...targetNotesArray]; + context.nowPreviewing.value = true; + + const previewIfNeeded = () => { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + if (this.innerContext.executePreviewProcess) { + this.previewResizeRight(context); + this.innerContext.executePreviewProcess = false; + } + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); + }; + const previewRequestId = requestAnimationFrame(previewIfNeeded); + + this.innerContext = { + targetNotesAtStart: targetNotesMap, + executePreviewProcess: false, + previewRequestId, + edited: false, + guideLineTicksAtStart: guideLineTicks, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.innerContext.executePreviewProcess = true; + } else if ( + input.mouseEvent.type === "mouseup" && + mouseButton === "LEFT_BUTTON" + ) { + setNextState("idle", undefined); + } + } + } + + onExit(context: Context) { + if (this.innerContext == undefined) { + throw new Error("innerContext is undefined."); + } + const previewNotes = context.previewNotes.value; + const previewNoteIds = previewNotes.map((value) => value.id); + + cancelAnimationFrame(this.innerContext.previewRequestId); + + void context.store.actions.COMMAND_UPDATE_NOTES({ + notes: previewNotes, + trackId: this.targetTrackId, + }); + void context.store.actions.SELECT_NOTES({ + noteIds: previewNoteIds, + }); + + if (previewNotes.length === 1) { + void context.store.actions.PLAY_PREVIEW_SOUND({ + noteNumber: previewNotes[0].noteNumber, + duration: PREVIEW_SOUND_DURATION, + }); + } + context.previewNotes.value = []; + context.nowPreviewing.value = false; + } +} diff --git a/src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts b/src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts new file mode 100644 index 0000000000..44d3785106 --- /dev/null +++ b/src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts @@ -0,0 +1,107 @@ +import { SetNextState, State } from "@/sing/stateMachine"; +import { + Context, + Input, + PositionOnSequencer, + SequencerStateDefinitions, +} from "@/components/Sing/sequencerStateMachine/common"; +import { getButton } from "@/sing/viewHelper"; +import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; +import { NoteId } from "@/type/preload"; + +export class SelectNotesWithRectState + implements State +{ + readonly id = "selectNotesWithRect"; + + private readonly cursorPosAtStart: PositionOnSequencer; + + private currentCursorPos: PositionOnSequencer; + private additive: boolean; + + constructor(args: { cursorPosAtStart: PositionOnSequencer }) { + this.cursorPosAtStart = args.cursorPosAtStart; + + this.currentCursorPos = args.cursorPosAtStart; + this.additive = false; + } + + private updatePreviewRect(context: Context) { + const startX = Math.min(this.cursorPosAtStart.x, this.currentCursorPos.x); + const endX = Math.max(this.cursorPosAtStart.x, this.currentCursorPos.x); + const startY = Math.min(this.cursorPosAtStart.y, this.currentCursorPos.y); + const endY = Math.max(this.cursorPosAtStart.y, this.currentCursorPos.y); + + context.previewRectForRectSelect.value = { + x: startX, + y: startY, + width: Math.max(1, endX - startX), + height: Math.max(1, endY - startY), + }; + } + + onEnter(context: Context) { + this.updatePreviewRect(context); + } + + process({ + input, + context, + setNextState, + }: { + input: Input; + context: Context; + setNextState: SetNextState; + }) { + const mouseButton = getButton(input.mouseEvent); + if (input.targetArea === "SequencerBody") { + if (input.mouseEvent.type === "mousemove") { + this.currentCursorPos = input.cursorPos; + this.updatePreviewRect(context); + } else if ( + input.mouseEvent.type === "mouseup" && + mouseButton === "LEFT_BUTTON" + ) { + this.additive = isOnCommandOrCtrlKeyDown(input.mouseEvent); + setNextState("idle", undefined); + } + } + } + + onExit(context: Context) { + context.previewRectForRectSelect.value = undefined; + + const startTicks = Math.min( + this.cursorPosAtStart.ticks, + this.currentCursorPos.ticks, + ); + const endTicks = Math.max( + this.cursorPosAtStart.ticks, + this.currentCursorPos.ticks, + ); + const startNoteNumber = Math.min( + this.cursorPosAtStart.noteNumber, + this.currentCursorPos.noteNumber, + ); + const endNoteNumber = Math.max( + this.cursorPosAtStart.noteNumber, + this.currentCursorPos.noteNumber, + ); + + const noteIdsToSelect: NoteId[] = []; + for (const note of context.notesInSelectedTrack.value) { + if ( + note.position + note.duration >= startTicks && + note.position <= endTicks && + note.noteNumber >= startNoteNumber && + note.noteNumber <= endNoteNumber + ) { + noteIdsToSelect.push(note.id); + } + } + if (!this.additive) { + void context.store.actions.DESELECT_ALL_NOTES(); + } + void context.store.actions.SELECT_NOTES({ noteIds: noteIdsToSelect }); + } +} diff --git a/src/sing/stateMachine/stateMachineBase.ts b/src/sing/stateMachine.ts similarity index 91% rename from src/sing/stateMachine/stateMachineBase.ts rename to src/sing/stateMachine.ts index 859da6ccea..e692a9c515 100644 --- a/src/sing/stateMachine/stateMachineBase.ts +++ b/src/sing/stateMachine.ts @@ -85,15 +85,10 @@ export interface State< /** * ステートのファクトリ関数を表す型。 */ -type StateFactories< - T extends StateDefinition[], - U extends StateId, - Input, - Context, -> = { - [P in U]: ( - args: FactoryArgs, - ) => State & { readonly id: P }; +type StateFactories = { + [U in StateId]: ( + args: FactoryArgs, + ) => State & { readonly id: U }; }; /** @@ -110,7 +105,6 @@ export class StateMachine< > { private readonly stateFactories: StateFactories< StateDefinitions, - StateId, Input, Context >; @@ -130,12 +124,7 @@ export class StateMachine< * @param context ステート間で共有されるコンテキスト。 */ constructor( - stateFactories: StateFactories< - StateDefinitions, - StateId, - Input, - Context - >, + stateFactories: StateFactories, initialState: State, context: Context, ) { diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts deleted file mode 100644 index e06e88ac52..0000000000 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ /dev/null @@ -1,1420 +0,0 @@ -/** - * このファイルのコードは実装中で、現在使われていません。 - * issue: https://github.com/VOICEVOX/voicevox/issues/2041 - */ - -import { computed, ComputedRef, ref, Ref } from "vue"; -import { - applyGaussianFilter, - clamp, - createArray, - linearInterpolation, - Rect, -} from "@/sing/utility"; -import { - State, - SetNextState, - StateMachine, - StateDefinitions, -} from "@/sing/stateMachine/stateMachineBase"; -import { - getButton, - getDoremiFromNoteNumber, - isSelfEventTarget, - PREVIEW_SOUND_DURATION, -} from "@/sing/viewHelper"; -import { Note, SequencerEditTarget } from "@/store/type"; -import { NoteId, TrackId } from "@/type/preload"; -import { getOrThrow } from "@/helpers/mapHelper"; -import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; -import { getNoteDuration } from "@/sing/domain"; -import { Store } from "@/store"; - -export type PositionOnSequencer = { - readonly x: number; - readonly y: number; - readonly ticks: number; - readonly noteNumber: number; - readonly frame: number; - readonly frequency: number; -}; - -type Input = - | { - readonly targetArea: "SequencerBody"; - readonly mouseEvent: MouseEvent; - readonly cursorPos: PositionOnSequencer; - } - | { - readonly targetArea: "Note"; - readonly mouseEvent: MouseEvent; - readonly cursorPos: PositionOnSequencer; - readonly note: Note; - } - | { - readonly targetArea: "NoteLeftEdge"; - readonly mouseEvent: MouseEvent; - readonly cursorPos: PositionOnSequencer; - readonly note: Note; - } - | { - readonly targetArea: "NoteRightEdge"; - readonly mouseEvent: MouseEvent; - readonly cursorPos: PositionOnSequencer; - readonly note: Note; - }; - -type ComputedRefs = { - readonly snapTicks: ComputedRef; - readonly editTarget: ComputedRef; - readonly selectedTrackId: ComputedRef; - readonly notesInSelectedTrack: ComputedRef; - readonly selectedNoteIds: ComputedRef>; - readonly editorFrameRate: ComputedRef; -}; - -type Refs = { - readonly nowPreviewing: Ref; - readonly previewNotes: Ref; - readonly previewRectForRectSelect: Ref; - readonly previewPitchEdit: Ref< - | { type: "draw"; data: number[]; startFrame: number } - | { type: "erase"; startFrame: number; frameLength: number } - | undefined - >; - readonly guideLineTicks: Ref; -}; - -type PartialStore = { - state: Pick< - Store["state"], - "tpqn" | "sequencerSnapType" | "sequencerEditTarget" | "editorFrameRate" - >; - getters: Pick< - Store["getters"], - "SELECTED_TRACK_ID" | "SELECTED_TRACK" | "SELECTED_NOTE_IDS" - >; - actions: Pick< - Store["actions"], - | "SELECT_NOTES" - | "DESELECT_NOTES" - | "DESELECT_ALL_NOTES" - | "PLAY_PREVIEW_SOUND" - | "COMMAND_ADD_NOTES" - | "COMMAND_UPDATE_NOTES" - | "COMMAND_SET_PITCH_EDIT_DATA" - | "COMMAND_ERASE_PITCH_EDIT_DATA" - >; -}; - -type Context = ComputedRefs & Refs & { readonly store: PartialStore }; - -type SequencerStateDefinitions = StateDefinitions< - [ - { - id: "idle"; - factoryArgs: undefined; - }, - { - id: "addNote"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - }; - }, - { - id: "moveNote"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - targetNoteIds: Set; - mouseDownNoteId: NoteId; - }; - }, - { - id: "resizeNoteLeft"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - targetNoteIds: Set; - mouseDownNoteId: NoteId; - }; - }, - { - id: "resizeNoteRight"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - targetNoteIds: Set; - mouseDownNoteId: NoteId; - }; - }, - { - id: "selectNotesWithRect"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - }; - }, - { - id: "drawPitch"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - }; - }, - { - id: "erasePitch"; - factoryArgs: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - }; - }, - ] ->; - -const getGuideLineTicks = ( - cursorPos: PositionOnSequencer, - context: Context, -) => { - const cursorTicks = cursorPos.ticks; - const snapTicks = context.snapTicks.value; - // NOTE: 入力を補助する線の判定の境目はスナップ幅の3/4の位置 - return Math.round(cursorTicks / snapTicks - 0.25) * snapTicks; -}; - -const selectOnlyThisNote = (context: Context, note: Note) => { - void context.store.actions.DESELECT_ALL_NOTES(); - void context.store.actions.SELECT_NOTES({ noteIds: [note.id] }); -}; - -/** - * mousedown時のノート選択・選択解除の処理を実行する。 - */ -const executeNotesSelectionProcess = ( - context: Context, - mouseEvent: MouseEvent, - mouseDownNote: Note, -) => { - if (mouseEvent.shiftKey) { - // Shiftキーが押されている場合は選択ノートまでの範囲選択 - let minIndex = context.notesInSelectedTrack.value.length - 1; - let maxIndex = 0; - for (let i = 0; i < context.notesInSelectedTrack.value.length; i++) { - const noteId = context.notesInSelectedTrack.value[i].id; - if ( - context.selectedNoteIds.value.has(noteId) || - noteId === mouseDownNote.id - ) { - minIndex = Math.min(minIndex, i); - maxIndex = Math.max(maxIndex, i); - } - } - const noteIdsToSelect: NoteId[] = []; - for (let i = minIndex; i <= maxIndex; i++) { - const noteId = context.notesInSelectedTrack.value[i].id; - if (!context.selectedNoteIds.value.has(noteId)) { - noteIdsToSelect.push(noteId); - } - } - void context.store.actions.SELECT_NOTES({ noteIds: noteIdsToSelect }); - } else if (isOnCommandOrCtrlKeyDown(mouseEvent)) { - // CommandキーかCtrlキーが押されている場合 - if (context.selectedNoteIds.value.has(mouseDownNote.id)) { - // 選択中のノートなら選択解除 - void context.store.actions.DESELECT_NOTES({ - noteIds: [mouseDownNote.id], - }); - return; - } - // 未選択のノートなら選択に追加 - void context.store.actions.SELECT_NOTES({ noteIds: [mouseDownNote.id] }); - } else if (!context.selectedNoteIds.value.has(mouseDownNote.id)) { - // 選択中のノートでない場合は選択状態にする - void selectOnlyThisNote(context, mouseDownNote); - void context.store.actions.PLAY_PREVIEW_SOUND({ - noteNumber: mouseDownNote.noteNumber, - duration: PREVIEW_SOUND_DURATION, - }); - } -}; - -class IdleState implements State { - readonly id = "idle"; - - onEnter() {} - - process({ - input, - context, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - const mouseButton = getButton(input.mouseEvent); - const selectedTrackId = context.selectedTrackId.value; - - if (context.editTarget.value === "NOTE") { - if (input.targetArea === "SequencerBody") { - context.guideLineTicks.value = getGuideLineTicks( - input.cursorPos, - context, - ); - } - if ( - input.mouseEvent.type === "mousedown" && - mouseButton === "LEFT_BUTTON" && - isSelfEventTarget(input.mouseEvent) - ) { - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.shiftKey) { - setNextState("selectNotesWithRect", { - cursorPosAtStart: input.cursorPos, - }); - } else { - void context.store.actions.DESELECT_ALL_NOTES(); - setNextState("addNote", { - cursorPosAtStart: input.cursorPos, - targetTrackId: selectedTrackId, - }); - } - } else if (input.targetArea === "Note") { - executeNotesSelectionProcess(context, input.mouseEvent, input.note); - setNextState("moveNote", { - cursorPosAtStart: input.cursorPos, - targetTrackId: selectedTrackId, - targetNoteIds: context.selectedNoteIds.value, - mouseDownNoteId: input.note.id, - }); - } else if (input.targetArea === "NoteLeftEdge") { - executeNotesSelectionProcess(context, input.mouseEvent, input.note); - setNextState("resizeNoteLeft", { - cursorPosAtStart: input.cursorPos, - targetTrackId: selectedTrackId, - targetNoteIds: context.selectedNoteIds.value, - mouseDownNoteId: input.note.id, - }); - } else if (input.targetArea === "NoteRightEdge") { - executeNotesSelectionProcess(context, input.mouseEvent, input.note); - setNextState("resizeNoteRight", { - cursorPosAtStart: input.cursorPos, - targetTrackId: selectedTrackId, - targetNoteIds: context.selectedNoteIds.value, - mouseDownNoteId: input.note.id, - }); - } - } - } else if (context.editTarget.value === "PITCH") { - if ( - input.mouseEvent.type === "mousedown" && - mouseButton === "LEFT_BUTTON" && - input.targetArea === "SequencerBody" - ) { - // TODO: Ctrlが押されているときではなく、 - // ピッチ削除ツールのときにErasePitchStateに遷移するようにする - if (isOnCommandOrCtrlKeyDown(input.mouseEvent)) { - setNextState("erasePitch", { - cursorPosAtStart: input.cursorPos, - targetTrackId: selectedTrackId, - }); - } else { - setNextState("drawPitch", { - cursorPosAtStart: input.cursorPos, - targetTrackId: selectedTrackId, - }); - } - } - } - } - - onExit() {} -} - -class AddNoteState implements State { - readonly id = "addNote"; - - private readonly cursorPosAtStart: PositionOnSequencer; - private readonly targetTrackId: TrackId; - - private currentCursorPos: PositionOnSequencer; - private innerContext: - | { - noteToAdd: Note; - previewRequestId: number; - executePreviewProcess: boolean; - } - | undefined; - - constructor(args: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - }) { - this.cursorPosAtStart = args.cursorPosAtStart; - this.targetTrackId = args.targetTrackId; - - this.currentCursorPos = args.cursorPosAtStart; - } - - private previewAdd(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const noteToAdd = this.innerContext.noteToAdd; - const snapTicks = context.snapTicks.value; - const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; - const noteDuration = Math.round(dragTicks / snapTicks) * snapTicks; - const noteEndPos = noteToAdd.position + noteDuration; - const previewNotes = context.previewNotes.value; - - const editedNotes = new Map(); - for (const note of previewNotes) { - const duration = Math.max(snapTicks, noteDuration); - if (note.duration !== duration) { - editedNotes.set(note.id, { ...note, duration }); - } - } - if (editedNotes.size !== 0) { - context.previewNotes.value = previewNotes.map((value) => { - return editedNotes.get(value.id) ?? value; - }); - } - context.guideLineTicks.value = noteEndPos; - } - - onEnter(context: Context) { - const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); - const noteToAdd = { - id: NoteId(crypto.randomUUID()), - position: Math.max(0, guideLineTicks), - duration: context.snapTicks.value, - noteNumber: clamp(this.cursorPosAtStart.noteNumber, 0, 127), - lyric: getDoremiFromNoteNumber(this.cursorPosAtStart.noteNumber), - }; - - context.previewNotes.value = [noteToAdd]; - context.nowPreviewing.value = true; - - const previewIfNeeded = () => { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (this.innerContext.executePreviewProcess) { - this.previewAdd(context); - this.innerContext.executePreviewProcess = false; - } - this.innerContext.previewRequestId = - requestAnimationFrame(previewIfNeeded); - }; - const previewRequestId = requestAnimationFrame(previewIfNeeded); - - this.innerContext = { - noteToAdd, - executePreviewProcess: false, - previewRequestId, - }; - } - - process({ - input, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.innerContext.executePreviewProcess = true; - } else if (input.mouseEvent.type === "mouseup") { - if (mouseButton === "LEFT_BUTTON") { - setNextState("idle", undefined); - } - } - } - } - - onExit(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const previewNotes = context.previewNotes.value; - const previewNoteIds = previewNotes.map((value) => value.id); - - cancelAnimationFrame(this.innerContext.previewRequestId); - - void context.store.actions.COMMAND_ADD_NOTES({ - notes: context.previewNotes.value, - trackId: this.targetTrackId, - }); - void context.store.actions.SELECT_NOTES({ noteIds: previewNoteIds }); - - if (previewNotes.length === 1) { - void context.store.actions.PLAY_PREVIEW_SOUND({ - noteNumber: previewNotes[0].noteNumber, - duration: PREVIEW_SOUND_DURATION, - }); - } - context.previewNotes.value = []; - context.nowPreviewing.value = false; - } -} - -class MoveNoteState - implements State -{ - readonly id = "moveNote"; - - private readonly cursorPosAtStart: PositionOnSequencer; - private readonly targetTrackId: TrackId; - private readonly targetNoteIds: Set; - private readonly mouseDownNoteId: NoteId; - - private currentCursorPos: PositionOnSequencer; - - private innerContext: - | { - targetNotesAtStart: Map; - previewRequestId: number; - executePreviewProcess: boolean; - edited: boolean; - guideLineTicksAtStart: number; - } - | undefined; - - constructor(args: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - targetNoteIds: Set; - mouseDownNoteId: NoteId; - }) { - if (!args.targetNoteIds.has(args.mouseDownNoteId)) { - throw new Error("mouseDownNoteId is not included in targetNoteIds."); - } - this.cursorPosAtStart = args.cursorPosAtStart; - this.targetTrackId = args.targetTrackId; - this.targetNoteIds = args.targetNoteIds; - this.mouseDownNoteId = args.mouseDownNoteId; - - this.currentCursorPos = args.cursorPosAtStart; - } - - private previewMove(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const snapTicks = context.snapTicks.value; - const previewNotes = context.previewNotes.value; - const targetNotesAtStart = this.innerContext.targetNotesAtStart; - const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); - const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; - const notePos = mouseDownNote.position; - const newNotePos = - Math.round((notePos + dragTicks) / snapTicks) * snapTicks; - const movingTicks = newNotePos - notePos; - const movingSemitones = - this.currentCursorPos.noteNumber - this.cursorPosAtStart.noteNumber; - - const editedNotes = new Map(); - for (const note of previewNotes) { - const targetNoteAtStart = getOrThrow(targetNotesAtStart, note.id); - const position = Math.max(0, targetNoteAtStart.position + movingTicks); - const noteNumber = clamp( - targetNoteAtStart.noteNumber + movingSemitones, - 0, - 127, - ); - - if (note.position !== position || note.noteNumber !== noteNumber) { - editedNotes.set(note.id, { ...note, noteNumber, position }); - } - } - - if (editedNotes.size !== 0) { - context.previewNotes.value = previewNotes.map((value) => { - return editedNotes.get(value.id) ?? value; - }); - this.innerContext.edited = true; - } - - context.guideLineTicks.value = - this.innerContext.guideLineTicksAtStart + movingTicks; - } - - onEnter(context: Context) { - const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); - const targetNotesArray = context.notesInSelectedTrack.value.filter( - (value) => this.targetNoteIds.has(value.id), - ); - const targetNotesMap = new Map(); - for (const targetNote of targetNotesArray) { - targetNotesMap.set(targetNote.id, targetNote); - } - - context.previewNotes.value = [...targetNotesArray]; - context.nowPreviewing.value = true; - - const previewIfNeeded = () => { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (this.innerContext.executePreviewProcess) { - this.previewMove(context); - this.innerContext.executePreviewProcess = false; - } - this.innerContext.previewRequestId = - requestAnimationFrame(previewIfNeeded); - }; - const previewRequestId = requestAnimationFrame(previewIfNeeded); - - this.innerContext = { - targetNotesAtStart: targetNotesMap, - executePreviewProcess: false, - previewRequestId, - edited: false, - guideLineTicksAtStart: guideLineTicks, - }; - } - - process({ - input, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.innerContext.executePreviewProcess = true; - } else if (input.mouseEvent.type === "mouseup") { - if (mouseButton === "LEFT_BUTTON") { - setNextState("idle", undefined); - } - } - } - } - - onExit(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const previewNotes = context.previewNotes.value; - const previewNoteIds = previewNotes.map((value) => value.id); - - cancelAnimationFrame(this.innerContext.previewRequestId); - - void context.store.actions.COMMAND_UPDATE_NOTES({ - notes: previewNotes, - trackId: this.targetTrackId, - }); - void context.store.actions.SELECT_NOTES({ - noteIds: previewNoteIds, - }); - - if (previewNotes.length === 1) { - void context.store.actions.PLAY_PREVIEW_SOUND({ - noteNumber: previewNotes[0].noteNumber, - duration: PREVIEW_SOUND_DURATION, - }); - } - context.previewNotes.value = []; - context.nowPreviewing.value = false; - } -} - -class ResizeNoteLeftState - implements State -{ - readonly id = "resizeNoteLeft"; - - private readonly cursorPosAtStart: PositionOnSequencer; - private readonly targetTrackId: TrackId; - private readonly targetNoteIds: Set; - private readonly mouseDownNoteId: NoteId; - - private currentCursorPos: PositionOnSequencer; - - private innerContext: - | { - targetNotesAtStart: Map; - previewRequestId: number; - executePreviewProcess: boolean; - edited: boolean; - guideLineTicksAtStart: number; - } - | undefined; - - constructor(args: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - targetNoteIds: Set; - mouseDownNoteId: NoteId; - }) { - if (!args.targetNoteIds.has(args.mouseDownNoteId)) { - throw new Error("mouseDownNoteId is not included in targetNoteIds."); - } - this.cursorPosAtStart = args.cursorPosAtStart; - this.targetTrackId = args.targetTrackId; - this.targetNoteIds = args.targetNoteIds; - this.mouseDownNoteId = args.mouseDownNoteId; - - this.currentCursorPos = args.cursorPosAtStart; - } - - private previewResizeLeft(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const snapTicks = context.snapTicks.value; - const previewNotes = context.previewNotes.value; - const targetNotesAtStart = this.innerContext.targetNotesAtStart; - const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); - const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; - const notePos = mouseDownNote.position; - const newNotePos = - Math.round((notePos + dragTicks) / snapTicks) * snapTicks; - const movingTicks = newNotePos - notePos; - - const editedNotes = new Map(); - for (const note of previewNotes) { - const targetNoteAtStart = getOrThrow(targetNotesAtStart, note.id); - const notePos = targetNoteAtStart.position; - const noteEndPos = - targetNoteAtStart.position + targetNoteAtStart.duration; - const position = clamp(notePos + movingTicks, 0, noteEndPos - snapTicks); - const duration = noteEndPos - position; - - if (note.position !== position || note.duration !== duration) { - editedNotes.set(note.id, { ...note, position, duration }); - } - } - if (editedNotes.size !== 0) { - context.previewNotes.value = previewNotes.map((value) => { - return editedNotes.get(value.id) ?? value; - }); - this.innerContext.edited = true; - } - - context.guideLineTicks.value = newNotePos; - } - - onEnter(context: Context) { - const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); - const targetNotesArray = context.notesInSelectedTrack.value.filter( - (value) => this.targetNoteIds.has(value.id), - ); - const targetNotesMap = new Map(); - for (const targetNote of targetNotesArray) { - targetNotesMap.set(targetNote.id, targetNote); - } - - context.previewNotes.value = [...targetNotesArray]; - context.nowPreviewing.value = true; - - const previewIfNeeded = () => { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (this.innerContext.executePreviewProcess) { - this.previewResizeLeft(context); - this.innerContext.executePreviewProcess = false; - } - this.innerContext.previewRequestId = - requestAnimationFrame(previewIfNeeded); - }; - const previewRequestId = requestAnimationFrame(previewIfNeeded); - - this.innerContext = { - targetNotesAtStart: targetNotesMap, - executePreviewProcess: false, - previewRequestId, - edited: false, - guideLineTicksAtStart: guideLineTicks, - }; - } - - process({ - input, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.innerContext.executePreviewProcess = true; - } else if ( - input.mouseEvent.type === "mouseup" && - mouseButton === "LEFT_BUTTON" - ) { - setNextState("idle", undefined); - } - } - } - - onExit(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const previewNotes = context.previewNotes.value; - const previewNoteIds = previewNotes.map((value) => value.id); - - cancelAnimationFrame(this.innerContext.previewRequestId); - - void context.store.actions.COMMAND_UPDATE_NOTES({ - notes: previewNotes, - trackId: this.targetTrackId, - }); - void context.store.actions.SELECT_NOTES({ noteIds: previewNoteIds }); - - if (previewNotes.length === 1) { - void context.store.actions.PLAY_PREVIEW_SOUND({ - noteNumber: previewNotes[0].noteNumber, - duration: PREVIEW_SOUND_DURATION, - }); - } - context.previewNotes.value = []; - context.nowPreviewing.value = false; - } -} - -class ResizeNoteRightState - implements State -{ - readonly id = "resizeNoteRight"; - - private readonly cursorPosAtStart: PositionOnSequencer; - private readonly targetTrackId: TrackId; - private readonly targetNoteIds: Set; - private readonly mouseDownNoteId: NoteId; - - private currentCursorPos: PositionOnSequencer; - - private innerContext: - | { - targetNotesAtStart: Map; - previewRequestId: number; - executePreviewProcess: boolean; - edited: boolean; - guideLineTicksAtStart: number; - } - | undefined; - - constructor(args: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - targetNoteIds: Set; - mouseDownNoteId: NoteId; - }) { - if (!args.targetNoteIds.has(args.mouseDownNoteId)) { - throw new Error("mouseDownNoteId is not included in targetNoteIds."); - } - this.cursorPosAtStart = args.cursorPosAtStart; - this.targetTrackId = args.targetTrackId; - this.targetNoteIds = args.targetNoteIds; - this.mouseDownNoteId = args.mouseDownNoteId; - - this.currentCursorPos = args.cursorPosAtStart; - } - - private previewResizeRight(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const snapTicks = context.snapTicks.value; - const previewNotes = context.previewNotes.value; - const targetNotesAtStart = this.innerContext.targetNotesAtStart; - const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); - const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; - const noteEndPos = mouseDownNote.position + mouseDownNote.duration; - const newNoteEndPos = - Math.round((noteEndPos + dragTicks) / snapTicks) * snapTicks; - const movingTicks = newNoteEndPos - noteEndPos; - - const editedNotes = new Map(); - for (const note of previewNotes) { - const targetNoteAtStart = getOrThrow(targetNotesAtStart, note.id); - const notePos = targetNoteAtStart.position; - const noteEndPos = - targetNoteAtStart.position + targetNoteAtStart.duration; - const duration = Math.max(snapTicks, noteEndPos + movingTicks - notePos); - - if (note.duration !== duration) { - editedNotes.set(note.id, { ...note, duration }); - } - } - if (editedNotes.size !== 0) { - context.previewNotes.value = previewNotes.map((value) => { - return editedNotes.get(value.id) ?? value; - }); - this.innerContext.edited = true; - } - - context.guideLineTicks.value = newNoteEndPos; - } - - onEnter(context: Context) { - const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); - const targetNotesArray = context.notesInSelectedTrack.value.filter( - (value) => this.targetNoteIds.has(value.id), - ); - const targetNotesMap = new Map(); - for (const targetNote of targetNotesArray) { - targetNotesMap.set(targetNote.id, targetNote); - } - - context.previewNotes.value = [...targetNotesArray]; - context.nowPreviewing.value = true; - - const previewIfNeeded = () => { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (this.innerContext.executePreviewProcess) { - this.previewResizeRight(context); - this.innerContext.executePreviewProcess = false; - } - this.innerContext.previewRequestId = - requestAnimationFrame(previewIfNeeded); - }; - const previewRequestId = requestAnimationFrame(previewIfNeeded); - - this.innerContext = { - targetNotesAtStart: targetNotesMap, - executePreviewProcess: false, - previewRequestId, - edited: false, - guideLineTicksAtStart: guideLineTicks, - }; - } - - process({ - input, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.innerContext.executePreviewProcess = true; - } else if ( - input.mouseEvent.type === "mouseup" && - mouseButton === "LEFT_BUTTON" - ) { - setNextState("idle", undefined); - } - } - } - - onExit(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const previewNotes = context.previewNotes.value; - const previewNoteIds = previewNotes.map((value) => value.id); - - cancelAnimationFrame(this.innerContext.previewRequestId); - - void context.store.actions.COMMAND_UPDATE_NOTES({ - notes: previewNotes, - trackId: this.targetTrackId, - }); - void context.store.actions.SELECT_NOTES({ - noteIds: previewNoteIds, - }); - - if (previewNotes.length === 1) { - void context.store.actions.PLAY_PREVIEW_SOUND({ - noteNumber: previewNotes[0].noteNumber, - duration: PREVIEW_SOUND_DURATION, - }); - } - context.previewNotes.value = []; - context.nowPreviewing.value = false; - } -} - -class SelectNotesWithRectState - implements State -{ - readonly id = "selectNotesWithRect"; - - private readonly cursorPosAtStart: PositionOnSequencer; - - private currentCursorPos: PositionOnSequencer; - private additive: boolean; - - constructor(args: { cursorPosAtStart: PositionOnSequencer }) { - this.cursorPosAtStart = args.cursorPosAtStart; - - this.currentCursorPos = args.cursorPosAtStart; - this.additive = false; - } - - private updatePreviewRect(context: Context) { - const startX = Math.min(this.cursorPosAtStart.x, this.currentCursorPos.x); - const endX = Math.max(this.cursorPosAtStart.x, this.currentCursorPos.x); - const startY = Math.min(this.cursorPosAtStart.y, this.currentCursorPos.y); - const endY = Math.max(this.cursorPosAtStart.y, this.currentCursorPos.y); - - context.previewRectForRectSelect.value = { - x: startX, - y: startY, - width: Math.max(1, endX - startX), - height: Math.max(1, endY - startY), - }; - } - - onEnter(context: Context) { - this.updatePreviewRect(context); - } - - process({ - input, - context, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.updatePreviewRect(context); - } else if ( - input.mouseEvent.type === "mouseup" && - mouseButton === "LEFT_BUTTON" - ) { - this.additive = isOnCommandOrCtrlKeyDown(input.mouseEvent); - setNextState("idle", undefined); - } - } - } - - onExit(context: Context) { - context.previewRectForRectSelect.value = undefined; - - const startTicks = Math.min( - this.cursorPosAtStart.ticks, - this.currentCursorPos.ticks, - ); - const endTicks = Math.max( - this.cursorPosAtStart.ticks, - this.currentCursorPos.ticks, - ); - const startNoteNumber = Math.min( - this.cursorPosAtStart.noteNumber, - this.currentCursorPos.noteNumber, - ); - const endNoteNumber = Math.max( - this.cursorPosAtStart.noteNumber, - this.currentCursorPos.noteNumber, - ); - - const noteIdsToSelect: NoteId[] = []; - for (const note of context.notesInSelectedTrack.value) { - if ( - note.position + note.duration >= startTicks && - note.position <= endTicks && - note.noteNumber >= startNoteNumber && - note.noteNumber <= endNoteNumber - ) { - noteIdsToSelect.push(note.id); - } - } - if (!this.additive) { - void context.store.actions.DESELECT_ALL_NOTES(); - } - void context.store.actions.SELECT_NOTES({ noteIds: noteIdsToSelect }); - } -} - -class DrawPitchState - implements State -{ - readonly id = "drawPitch"; - - private readonly cursorPosAtStart: PositionOnSequencer; - private readonly targetTrackId: TrackId; - - private currentCursorPos: PositionOnSequencer; - - private innerContext: - | { - previewRequestId: number; - executePreviewProcess: boolean; - prevCursorPos: PositionOnSequencer; - } - | undefined; - - constructor(args: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - }) { - this.cursorPosAtStart = args.cursorPosAtStart; - this.targetTrackId = args.targetTrackId; - - this.currentCursorPos = args.cursorPosAtStart; - } - - private previewDrawPitch(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (context.previewPitchEdit.value == undefined) { - throw new Error("previewPitchEdit.value is undefined."); - } - if (context.previewPitchEdit.value.type !== "draw") { - throw new Error("previewPitchEdit.value.type is not draw."); - } - const cursorFrame = this.currentCursorPos.frame; - const cursorFrequency = this.currentCursorPos.frequency; - const prevCursorFrame = this.innerContext.prevCursorPos.frame; - const prevCursorFrequency = this.innerContext.prevCursorPos.frequency; - if (cursorFrame < 0) { - return; - } - const tempPitchEdit = { - ...context.previewPitchEdit.value, - data: [...context.previewPitchEdit.value.data], - }; - - if (cursorFrame < tempPitchEdit.startFrame) { - const numOfFramesToUnshift = tempPitchEdit.startFrame - cursorFrame; - tempPitchEdit.data = createArray(numOfFramesToUnshift, () => 0).concat( - tempPitchEdit.data, - ); - tempPitchEdit.startFrame = cursorFrame; - } - - const lastFrame = tempPitchEdit.startFrame + tempPitchEdit.data.length - 1; - if (cursorFrame > lastFrame) { - const numOfFramesToPush = cursorFrame - lastFrame; - tempPitchEdit.data = tempPitchEdit.data.concat( - createArray(numOfFramesToPush, () => 0), - ); - } - - if (cursorFrame === prevCursorFrame) { - const i = cursorFrame - tempPitchEdit.startFrame; - tempPitchEdit.data[i] = cursorFrequency; - } else if (cursorFrame < prevCursorFrame) { - for (let i = cursorFrame; i <= prevCursorFrame; i++) { - tempPitchEdit.data[i - tempPitchEdit.startFrame] = Math.exp( - linearInterpolation( - cursorFrame, - Math.log(cursorFrequency), - prevCursorFrame, - Math.log(prevCursorFrequency), - i, - ), - ); - } - } else { - for (let i = prevCursorFrame; i <= cursorFrame; i++) { - tempPitchEdit.data[i - tempPitchEdit.startFrame] = Math.exp( - linearInterpolation( - prevCursorFrame, - Math.log(prevCursorFrequency), - cursorFrame, - Math.log(cursorFrequency), - i, - ), - ); - } - } - - context.previewPitchEdit.value = tempPitchEdit; - this.innerContext.prevCursorPos = this.currentCursorPos; - } - - onEnter(context: Context) { - context.previewPitchEdit.value = { - type: "draw", - data: [this.cursorPosAtStart.frequency], - startFrame: this.cursorPosAtStart.frame, - }; - context.nowPreviewing.value = true; - - const previewIfNeeded = () => { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (this.innerContext.executePreviewProcess) { - this.previewDrawPitch(context); - this.innerContext.executePreviewProcess = false; - } - this.innerContext.previewRequestId = - requestAnimationFrame(previewIfNeeded); - }; - const previewRequestId = requestAnimationFrame(previewIfNeeded); - - this.innerContext = { - executePreviewProcess: false, - previewRequestId, - prevCursorPos: this.cursorPosAtStart, - }; - } - - process({ - input, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.innerContext.executePreviewProcess = true; - } else if (input.mouseEvent.type === "mouseup") { - if (mouseButton === "LEFT_BUTTON") { - setNextState("idle", undefined); - } - } - } - } - - onExit(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (context.previewPitchEdit.value == undefined) { - throw new Error("previewPitchEdit is undefined."); - } - if (context.previewPitchEdit.value.type !== "draw") { - throw new Error("previewPitchEdit.type is not draw."); - } - - cancelAnimationFrame(this.innerContext.previewRequestId); - - // カーソルを動かさずにマウスのボタンを離したときに1フレームのみの変更になり、 - // 1フレームの変更はピッチ編集ラインとして表示されないので、無視する - if (context.previewPitchEdit.value.data.length >= 2) { - // 平滑化を行う - let data = context.previewPitchEdit.value.data; - data = data.map((value) => Math.log(value)); - applyGaussianFilter(data, 0.7); - data = data.map((value) => Math.exp(value)); - - void context.store.actions.COMMAND_SET_PITCH_EDIT_DATA({ - pitchArray: data, - startFrame: context.previewPitchEdit.value.startFrame, - trackId: this.targetTrackId, - }); - } - - context.previewPitchEdit.value = undefined; - context.nowPreviewing.value = false; - } -} - -class ErasePitchState - implements State -{ - readonly id = "erasePitch"; - - private readonly cursorPosAtStart: PositionOnSequencer; - private readonly targetTrackId: TrackId; - - private currentCursorPos: PositionOnSequencer; - - private innerContext: - | { - previewRequestId: number; - executePreviewProcess: boolean; - } - | undefined; - - constructor(args: { - cursorPosAtStart: PositionOnSequencer; - targetTrackId: TrackId; - }) { - this.cursorPosAtStart = args.cursorPosAtStart; - this.targetTrackId = args.targetTrackId; - - this.currentCursorPos = args.cursorPosAtStart; - } - - private previewErasePitch(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (context.previewPitchEdit.value == undefined) { - throw new Error("previewPitchEdit.value is undefined."); - } - if (context.previewPitchEdit.value.type !== "erase") { - throw new Error("previewPitchEdit.value.type is not erase."); - } - const cursorFrame = Math.max(0, this.currentCursorPos.frame); - const tempPitchEdit = { ...context.previewPitchEdit.value }; - - if (tempPitchEdit.startFrame > cursorFrame) { - tempPitchEdit.frameLength += tempPitchEdit.startFrame - cursorFrame; - tempPitchEdit.startFrame = cursorFrame; - } - - const lastFrame = tempPitchEdit.startFrame + tempPitchEdit.frameLength - 1; - if (lastFrame < cursorFrame) { - tempPitchEdit.frameLength += cursorFrame - lastFrame; - } - - context.previewPitchEdit.value = tempPitchEdit; - } - - onEnter(context: Context) { - context.previewPitchEdit.value = { - type: "erase", - startFrame: this.cursorPosAtStart.frame, - frameLength: 1, - }; - context.nowPreviewing.value = true; - - const previewIfNeeded = () => { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (this.innerContext.executePreviewProcess) { - this.previewErasePitch(context); - this.innerContext.executePreviewProcess = false; - } - this.innerContext.previewRequestId = - requestAnimationFrame(previewIfNeeded); - }; - const previewRequestId = requestAnimationFrame(previewIfNeeded); - - this.innerContext = { - executePreviewProcess: false, - previewRequestId, - }; - } - - process({ - input, - setNextState, - }: { - input: Input; - context: Context; - setNextState: SetNextState; - }) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - if (input.mouseEvent.type === "mousemove") { - this.currentCursorPos = input.cursorPos; - this.innerContext.executePreviewProcess = true; - } else if (input.mouseEvent.type === "mouseup") { - if (mouseButton === "LEFT_BUTTON") { - setNextState("idle", undefined); - } - } - } - } - - onExit(context: Context) { - if (this.innerContext == undefined) { - throw new Error("innerContext is undefined."); - } - if (context.previewPitchEdit.value == undefined) { - throw new Error("previewPitchEdit is undefined."); - } - if (context.previewPitchEdit.value.type !== "erase") { - throw new Error("previewPitchEdit.type is not erase."); - } - - cancelAnimationFrame(this.innerContext.previewRequestId); - - void context.store.actions.COMMAND_ERASE_PITCH_EDIT_DATA({ - startFrame: context.previewPitchEdit.value.startFrame, - frameLength: context.previewPitchEdit.value.frameLength, - trackId: this.targetTrackId, - }); - - context.previewPitchEdit.value = undefined; - context.nowPreviewing.value = false; - } -} - -export const useSequencerStateMachine = (store: PartialStore) => { - const computedRefs: ComputedRefs = { - snapTicks: computed(() => - getNoteDuration(store.state.sequencerSnapType, store.state.tpqn), - ), - editTarget: computed(() => store.state.sequencerEditTarget), - selectedTrackId: computed(() => store.getters.SELECTED_TRACK_ID), - notesInSelectedTrack: computed(() => store.getters.SELECTED_TRACK.notes), - selectedNoteIds: computed(() => store.getters.SELECTED_NOTE_IDS), - editorFrameRate: computed(() => store.state.editorFrameRate), - }; - const refs: Refs = { - nowPreviewing: ref(false), - previewNotes: ref([]), - previewRectForRectSelect: ref(undefined), - previewPitchEdit: ref(undefined), - guideLineTicks: ref(0), - }; - const stateMachine = new StateMachine< - SequencerStateDefinitions, - Input, - Context - >( - { - idle: () => new IdleState(), - addNote: (args) => new AddNoteState(args), - moveNote: (args) => new MoveNoteState(args), - resizeNoteLeft: (args) => new ResizeNoteLeftState(args), - resizeNoteRight: (args) => new ResizeNoteRightState(args), - selectNotesWithRect: (args) => new SelectNotesWithRectState(args), - drawPitch: (args) => new DrawPitchState(args), - erasePitch: (args) => new ErasePitchState(args), - }, - new IdleState(), - { - ...computedRefs, - ...refs, - store, - }, - ); - return { - stateMachine, - nowPreviewing: computed(() => refs.nowPreviewing.value), - previewNotes: computed(() => refs.previewNotes.value), - previewRectForRectSelect: computed( - () => refs.previewRectForRectSelect.value, - ), - guideLineTicks: computed(() => refs.guideLineTicks.value), - }; -}; From 61af0dccd445335bd26f916ea699f9d13f25b26b Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Sat, 25 Jan 2025 18:41:39 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=BC=E3=83=88?= =?UTF-8?q?=E9=96=A2=E9=80=A3=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC=E3=82=B6=E3=83=96=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sequencerStateMachine/common.ts | 0 .../states/addNoteState.ts | 2 +- .../states/drawPitchState.ts | 2 +- .../states/erasePitchState.ts | 2 +- .../sequencerStateMachine/states/idleState.ts | 2 +- .../states/moveNoteState.ts | 2 +- .../states/resizeNoteLeftState.ts | 2 +- .../states/resizeNoteRightState.ts | 2 +- .../states/selectNotesWithRectState.ts | 2 +- .../useSequencerStateMachine.ts} | 18 +++++++++--------- 10 files changed, 17 insertions(+), 17 deletions(-) rename src/{components/Sing => composables}/sequencerStateMachine/common.ts (100%) rename src/{components/Sing => composables}/sequencerStateMachine/states/addNoteState.ts (98%) rename src/{components/Sing => composables}/sequencerStateMachine/states/drawPitchState.ts (99%) rename src/{components/Sing => composables}/sequencerStateMachine/states/erasePitchState.ts (98%) rename src/{components/Sing => composables}/sequencerStateMachine/states/idleState.ts (98%) rename src/{components/Sing => composables}/sequencerStateMachine/states/moveNoteState.ts (99%) rename src/{components/Sing => composables}/sequencerStateMachine/states/resizeNoteLeftState.ts (99%) rename src/{components/Sing => composables}/sequencerStateMachine/states/resizeNoteRightState.ts (99%) rename src/{components/Sing => composables}/sequencerStateMachine/states/selectNotesWithRectState.ts (98%) rename src/{components/Sing/sequencerStateMachine/stateMachine.ts => composables/sequencerStateMachine/useSequencerStateMachine.ts} (69%) diff --git a/src/components/Sing/sequencerStateMachine/common.ts b/src/composables/sequencerStateMachine/common.ts similarity index 100% rename from src/components/Sing/sequencerStateMachine/common.ts rename to src/composables/sequencerStateMachine/common.ts diff --git a/src/components/Sing/sequencerStateMachine/states/addNoteState.ts b/src/composables/sequencerStateMachine/states/addNoteState.ts similarity index 98% rename from src/components/Sing/sequencerStateMachine/states/addNoteState.ts rename to src/composables/sequencerStateMachine/states/addNoteState.ts index 4c4f9c8273..41dbe89269 100644 --- a/src/components/Sing/sequencerStateMachine/states/addNoteState.ts +++ b/src/composables/sequencerStateMachine/states/addNoteState.ts @@ -5,7 +5,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { NoteId, TrackId } from "@/type/preload"; import { Note } from "@/store/type"; import { diff --git a/src/components/Sing/sequencerStateMachine/states/drawPitchState.ts b/src/composables/sequencerStateMachine/states/drawPitchState.ts similarity index 99% rename from src/components/Sing/sequencerStateMachine/states/drawPitchState.ts rename to src/composables/sequencerStateMachine/states/drawPitchState.ts index 43d7114013..8ff036a7da 100644 --- a/src/components/Sing/sequencerStateMachine/states/drawPitchState.ts +++ b/src/composables/sequencerStateMachine/states/drawPitchState.ts @@ -4,7 +4,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { TrackId } from "@/type/preload"; import { applyGaussianFilter, diff --git a/src/components/Sing/sequencerStateMachine/states/erasePitchState.ts b/src/composables/sequencerStateMachine/states/erasePitchState.ts similarity index 98% rename from src/components/Sing/sequencerStateMachine/states/erasePitchState.ts rename to src/composables/sequencerStateMachine/states/erasePitchState.ts index 2229ea52a5..1c218887b4 100644 --- a/src/components/Sing/sequencerStateMachine/states/erasePitchState.ts +++ b/src/composables/sequencerStateMachine/states/erasePitchState.ts @@ -4,7 +4,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { TrackId } from "@/type/preload"; import { getButton } from "@/sing/viewHelper"; diff --git a/src/components/Sing/sequencerStateMachine/states/idleState.ts b/src/composables/sequencerStateMachine/states/idleState.ts similarity index 98% rename from src/components/Sing/sequencerStateMachine/states/idleState.ts rename to src/composables/sequencerStateMachine/states/idleState.ts index 6221b53ef1..af4f93b04c 100644 --- a/src/components/Sing/sequencerStateMachine/states/idleState.ts +++ b/src/composables/sequencerStateMachine/states/idleState.ts @@ -5,7 +5,7 @@ import { getGuideLineTicks, Input, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { getButton, isSelfEventTarget } from "@/sing/viewHelper"; import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; diff --git a/src/components/Sing/sequencerStateMachine/states/moveNoteState.ts b/src/composables/sequencerStateMachine/states/moveNoteState.ts similarity index 99% rename from src/components/Sing/sequencerStateMachine/states/moveNoteState.ts rename to src/composables/sequencerStateMachine/states/moveNoteState.ts index 5a9f607662..558bbc963a 100644 --- a/src/components/Sing/sequencerStateMachine/states/moveNoteState.ts +++ b/src/composables/sequencerStateMachine/states/moveNoteState.ts @@ -10,7 +10,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; export class MoveNoteState implements State diff --git a/src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts b/src/composables/sequencerStateMachine/states/resizeNoteLeftState.ts similarity index 99% rename from src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts rename to src/composables/sequencerStateMachine/states/resizeNoteLeftState.ts index 62e05a63bb..12ed07aa05 100644 --- a/src/components/Sing/sequencerStateMachine/states/resizeNoteLeftState.ts +++ b/src/composables/sequencerStateMachine/states/resizeNoteLeftState.ts @@ -9,7 +9,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { clamp } from "@/sing/utility"; export class ResizeNoteLeftState diff --git a/src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts b/src/composables/sequencerStateMachine/states/resizeNoteRightState.ts similarity index 99% rename from src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts rename to src/composables/sequencerStateMachine/states/resizeNoteRightState.ts index cec0446a78..ced2cec143 100644 --- a/src/components/Sing/sequencerStateMachine/states/resizeNoteRightState.ts +++ b/src/composables/sequencerStateMachine/states/resizeNoteRightState.ts @@ -7,7 +7,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { Note } from "@/store/type"; import { getOrThrow } from "@/helpers/mapHelper"; diff --git a/src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts b/src/composables/sequencerStateMachine/states/selectNotesWithRectState.ts similarity index 98% rename from src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts rename to src/composables/sequencerStateMachine/states/selectNotesWithRectState.ts index 44d3785106..405594fc0d 100644 --- a/src/components/Sing/sequencerStateMachine/states/selectNotesWithRectState.ts +++ b/src/composables/sequencerStateMachine/states/selectNotesWithRectState.ts @@ -4,7 +4,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { getButton } from "@/sing/viewHelper"; import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; import { NoteId } from "@/type/preload"; diff --git a/src/components/Sing/sequencerStateMachine/stateMachine.ts b/src/composables/sequencerStateMachine/useSequencerStateMachine.ts similarity index 69% rename from src/components/Sing/sequencerStateMachine/stateMachine.ts rename to src/composables/sequencerStateMachine/useSequencerStateMachine.ts index 1390b798cb..71b7d632a5 100644 --- a/src/components/Sing/sequencerStateMachine/stateMachine.ts +++ b/src/composables/sequencerStateMachine/useSequencerStateMachine.ts @@ -6,18 +6,18 @@ import { PartialStore, Refs, SequencerStateDefinitions, -} from "@/components/Sing/sequencerStateMachine/common"; +} from "@/composables/sequencerStateMachine/common"; import { getNoteDuration } from "@/sing/domain"; import { StateMachine } from "@/sing/stateMachine"; -import { IdleState } from "@/components/Sing/sequencerStateMachine/states/idleState"; -import { AddNoteState } from "@/components/Sing/sequencerStateMachine/states/addNoteState"; -import { MoveNoteState } from "@/components/Sing/sequencerStateMachine/states/moveNoteState"; -import { ResizeNoteLeftState } from "@/components/Sing/sequencerStateMachine/states/resizeNoteLeftState"; -import { ResizeNoteRightState } from "@/components/Sing/sequencerStateMachine/states/resizeNoteRightState"; -import { SelectNotesWithRectState } from "@/components/Sing/sequencerStateMachine/states/selectNotesWithRectState"; -import { DrawPitchState } from "@/components/Sing/sequencerStateMachine/states/drawPitchState"; -import { ErasePitchState } from "@/components/Sing/sequencerStateMachine/states/erasePitchState"; +import { IdleState } from "@/composables/sequencerStateMachine/states/idleState"; +import { AddNoteState } from "@/composables/sequencerStateMachine/states/addNoteState"; +import { MoveNoteState } from "@/composables/sequencerStateMachine/states/moveNoteState"; +import { ResizeNoteLeftState } from "@/composables/sequencerStateMachine/states/resizeNoteLeftState"; +import { ResizeNoteRightState } from "@/composables/sequencerStateMachine/states/resizeNoteRightState"; +import { SelectNotesWithRectState } from "@/composables/sequencerStateMachine/states/selectNotesWithRectState"; +import { DrawPitchState } from "@/composables/sequencerStateMachine/states/drawPitchState"; +import { ErasePitchState } from "@/composables/sequencerStateMachine/states/erasePitchState"; export const useSequencerStateMachine = (store: PartialStore) => { const computedRefs: ComputedRefs = { From 680e79d3d3cc4d90515392717be5bc14778f9df3 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:10:21 +0900 Subject: [PATCH 3/3] =?UTF-8?q?useSequencerStateMachine=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=80=81=E9=96=A2=E9=80=A3=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useSequencerStateMachine.ts | 71 ------------------- src/composables/useSequencerStateMachine.ts | 42 +++++++++++ .../sequencerStateMachine/common.ts | 0 src/sing/sequencerStateMachine/index.ts | 32 +++++++++ .../states/addNoteState.ts | 2 +- .../states/drawPitchState.ts | 2 +- .../states/erasePitchState.ts | 2 +- .../sequencerStateMachine/states/idleState.ts | 2 +- .../states/moveNoteState.ts | 2 +- .../states/resizeNoteLeftState.ts | 2 +- .../states/resizeNoteRightState.ts | 2 +- .../states/selectNotesWithRectState.ts | 2 +- 12 files changed, 82 insertions(+), 79 deletions(-) delete mode 100644 src/composables/sequencerStateMachine/useSequencerStateMachine.ts create mode 100644 src/composables/useSequencerStateMachine.ts rename src/{composables => sing}/sequencerStateMachine/common.ts (100%) create mode 100644 src/sing/sequencerStateMachine/index.ts rename src/{composables => sing}/sequencerStateMachine/states/addNoteState.ts (98%) rename src/{composables => sing}/sequencerStateMachine/states/drawPitchState.ts (99%) rename src/{composables => sing}/sequencerStateMachine/states/erasePitchState.ts (98%) rename src/{composables => sing}/sequencerStateMachine/states/idleState.ts (98%) rename src/{composables => sing}/sequencerStateMachine/states/moveNoteState.ts (99%) rename src/{composables => sing}/sequencerStateMachine/states/resizeNoteLeftState.ts (99%) rename src/{composables => sing}/sequencerStateMachine/states/resizeNoteRightState.ts (99%) rename src/{composables => sing}/sequencerStateMachine/states/selectNotesWithRectState.ts (98%) diff --git a/src/composables/sequencerStateMachine/useSequencerStateMachine.ts b/src/composables/sequencerStateMachine/useSequencerStateMachine.ts deleted file mode 100644 index 71b7d632a5..0000000000 --- a/src/composables/sequencerStateMachine/useSequencerStateMachine.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { computed, ref } from "vue"; -import { - ComputedRefs, - Context, - Input, - PartialStore, - Refs, - SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; -import { getNoteDuration } from "@/sing/domain"; -import { StateMachine } from "@/sing/stateMachine"; - -import { IdleState } from "@/composables/sequencerStateMachine/states/idleState"; -import { AddNoteState } from "@/composables/sequencerStateMachine/states/addNoteState"; -import { MoveNoteState } from "@/composables/sequencerStateMachine/states/moveNoteState"; -import { ResizeNoteLeftState } from "@/composables/sequencerStateMachine/states/resizeNoteLeftState"; -import { ResizeNoteRightState } from "@/composables/sequencerStateMachine/states/resizeNoteRightState"; -import { SelectNotesWithRectState } from "@/composables/sequencerStateMachine/states/selectNotesWithRectState"; -import { DrawPitchState } from "@/composables/sequencerStateMachine/states/drawPitchState"; -import { ErasePitchState } from "@/composables/sequencerStateMachine/states/erasePitchState"; - -export const useSequencerStateMachine = (store: PartialStore) => { - const computedRefs: ComputedRefs = { - snapTicks: computed(() => - getNoteDuration(store.state.sequencerSnapType, store.state.tpqn), - ), - editTarget: computed(() => store.state.sequencerEditTarget), - selectedTrackId: computed(() => store.getters.SELECTED_TRACK_ID), - notesInSelectedTrack: computed(() => store.getters.SELECTED_TRACK.notes), - selectedNoteIds: computed(() => store.getters.SELECTED_NOTE_IDS), - editorFrameRate: computed(() => store.state.editorFrameRate), - }; - const refs: Refs = { - nowPreviewing: ref(false), - previewNotes: ref([]), - previewRectForRectSelect: ref(undefined), - previewPitchEdit: ref(undefined), - guideLineTicks: ref(0), - }; - const stateMachine = new StateMachine< - SequencerStateDefinitions, - Input, - Context - >( - { - idle: () => new IdleState(), - addNote: (args) => new AddNoteState(args), - moveNote: (args) => new MoveNoteState(args), - resizeNoteLeft: (args) => new ResizeNoteLeftState(args), - resizeNoteRight: (args) => new ResizeNoteRightState(args), - selectNotesWithRect: (args) => new SelectNotesWithRectState(args), - drawPitch: (args) => new DrawPitchState(args), - erasePitch: (args) => new ErasePitchState(args), - }, - new IdleState(), - { - ...computedRefs, - ...refs, - store, - }, - ); - return { - stateMachine, - nowPreviewing: computed(() => refs.nowPreviewing.value), - previewNotes: computed(() => refs.previewNotes.value), - previewRectForRectSelect: computed( - () => refs.previewRectForRectSelect.value, - ), - guideLineTicks: computed(() => refs.guideLineTicks.value), - }; -}; diff --git a/src/composables/useSequencerStateMachine.ts b/src/composables/useSequencerStateMachine.ts new file mode 100644 index 0000000000..233db4acaa --- /dev/null +++ b/src/composables/useSequencerStateMachine.ts @@ -0,0 +1,42 @@ +import { computed, ref } from "vue"; +import { + ComputedRefs, + PartialStore, + Refs, +} from "@/sing/sequencerStateMachine/common"; +import { getNoteDuration } from "@/sing/domain"; +import { createSequencerStateMachine } from "@/sing/sequencerStateMachine"; + +export const useSequencerStateMachine = (store: PartialStore) => { + const computedRefs: ComputedRefs = { + snapTicks: computed(() => + getNoteDuration(store.state.sequencerSnapType, store.state.tpqn), + ), + editTarget: computed(() => store.state.sequencerEditTarget), + selectedTrackId: computed(() => store.getters.SELECTED_TRACK_ID), + notesInSelectedTrack: computed(() => store.getters.SELECTED_TRACK.notes), + selectedNoteIds: computed(() => store.getters.SELECTED_NOTE_IDS), + editorFrameRate: computed(() => store.state.editorFrameRate), + }; + const refs: Refs = { + nowPreviewing: ref(false), + previewNotes: ref([]), + previewRectForRectSelect: ref(undefined), + previewPitchEdit: ref(undefined), + guideLineTicks: ref(0), + }; + const stateMachine = createSequencerStateMachine({ + ...computedRefs, + ...refs, + store, + }); + return { + stateMachine, + nowPreviewing: computed(() => refs.nowPreviewing.value), + previewNotes: computed(() => refs.previewNotes.value), + previewRectForRectSelect: computed( + () => refs.previewRectForRectSelect.value, + ), + guideLineTicks: computed(() => refs.guideLineTicks.value), + }; +}; diff --git a/src/composables/sequencerStateMachine/common.ts b/src/sing/sequencerStateMachine/common.ts similarity index 100% rename from src/composables/sequencerStateMachine/common.ts rename to src/sing/sequencerStateMachine/common.ts diff --git a/src/sing/sequencerStateMachine/index.ts b/src/sing/sequencerStateMachine/index.ts new file mode 100644 index 0000000000..5eff85b6e0 --- /dev/null +++ b/src/sing/sequencerStateMachine/index.ts @@ -0,0 +1,32 @@ +import { + Context, + Input, + SequencerStateDefinitions, +} from "@/sing/sequencerStateMachine/common"; +import { StateMachine } from "@/sing/stateMachine"; + +import { IdleState } from "@/sing/sequencerStateMachine/states/idleState"; +import { AddNoteState } from "@/sing/sequencerStateMachine/states/addNoteState"; +import { MoveNoteState } from "@/sing/sequencerStateMachine/states/moveNoteState"; +import { ResizeNoteLeftState } from "@/sing/sequencerStateMachine/states/resizeNoteLeftState"; +import { ResizeNoteRightState } from "@/sing/sequencerStateMachine/states/resizeNoteRightState"; +import { SelectNotesWithRectState } from "@/sing/sequencerStateMachine/states/selectNotesWithRectState"; +import { DrawPitchState } from "@/sing/sequencerStateMachine/states/drawPitchState"; +import { ErasePitchState } from "@/sing/sequencerStateMachine/states/erasePitchState"; + +export const createSequencerStateMachine = (context: Context) => { + return new StateMachine( + { + idle: () => new IdleState(), + addNote: (args) => new AddNoteState(args), + moveNote: (args) => new MoveNoteState(args), + resizeNoteLeft: (args) => new ResizeNoteLeftState(args), + resizeNoteRight: (args) => new ResizeNoteRightState(args), + selectNotesWithRect: (args) => new SelectNotesWithRectState(args), + drawPitch: (args) => new DrawPitchState(args), + erasePitch: (args) => new ErasePitchState(args), + }, + new IdleState(), + context, + ); +}; diff --git a/src/composables/sequencerStateMachine/states/addNoteState.ts b/src/sing/sequencerStateMachine/states/addNoteState.ts similarity index 98% rename from src/composables/sequencerStateMachine/states/addNoteState.ts rename to src/sing/sequencerStateMachine/states/addNoteState.ts index 41dbe89269..d4633bdfcd 100644 --- a/src/composables/sequencerStateMachine/states/addNoteState.ts +++ b/src/sing/sequencerStateMachine/states/addNoteState.ts @@ -5,7 +5,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { NoteId, TrackId } from "@/type/preload"; import { Note } from "@/store/type"; import { diff --git a/src/composables/sequencerStateMachine/states/drawPitchState.ts b/src/sing/sequencerStateMachine/states/drawPitchState.ts similarity index 99% rename from src/composables/sequencerStateMachine/states/drawPitchState.ts rename to src/sing/sequencerStateMachine/states/drawPitchState.ts index 8ff036a7da..de8f9819f8 100644 --- a/src/composables/sequencerStateMachine/states/drawPitchState.ts +++ b/src/sing/sequencerStateMachine/states/drawPitchState.ts @@ -4,7 +4,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { TrackId } from "@/type/preload"; import { applyGaussianFilter, diff --git a/src/composables/sequencerStateMachine/states/erasePitchState.ts b/src/sing/sequencerStateMachine/states/erasePitchState.ts similarity index 98% rename from src/composables/sequencerStateMachine/states/erasePitchState.ts rename to src/sing/sequencerStateMachine/states/erasePitchState.ts index 1c218887b4..c1ee27f93c 100644 --- a/src/composables/sequencerStateMachine/states/erasePitchState.ts +++ b/src/sing/sequencerStateMachine/states/erasePitchState.ts @@ -4,7 +4,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { TrackId } from "@/type/preload"; import { getButton } from "@/sing/viewHelper"; diff --git a/src/composables/sequencerStateMachine/states/idleState.ts b/src/sing/sequencerStateMachine/states/idleState.ts similarity index 98% rename from src/composables/sequencerStateMachine/states/idleState.ts rename to src/sing/sequencerStateMachine/states/idleState.ts index af4f93b04c..286ca8087d 100644 --- a/src/composables/sequencerStateMachine/states/idleState.ts +++ b/src/sing/sequencerStateMachine/states/idleState.ts @@ -5,7 +5,7 @@ import { getGuideLineTicks, Input, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { getButton, isSelfEventTarget } from "@/sing/viewHelper"; import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; diff --git a/src/composables/sequencerStateMachine/states/moveNoteState.ts b/src/sing/sequencerStateMachine/states/moveNoteState.ts similarity index 99% rename from src/composables/sequencerStateMachine/states/moveNoteState.ts rename to src/sing/sequencerStateMachine/states/moveNoteState.ts index 558bbc963a..a68d526437 100644 --- a/src/composables/sequencerStateMachine/states/moveNoteState.ts +++ b/src/sing/sequencerStateMachine/states/moveNoteState.ts @@ -10,7 +10,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; export class MoveNoteState implements State diff --git a/src/composables/sequencerStateMachine/states/resizeNoteLeftState.ts b/src/sing/sequencerStateMachine/states/resizeNoteLeftState.ts similarity index 99% rename from src/composables/sequencerStateMachine/states/resizeNoteLeftState.ts rename to src/sing/sequencerStateMachine/states/resizeNoteLeftState.ts index 12ed07aa05..77b072d1e3 100644 --- a/src/composables/sequencerStateMachine/states/resizeNoteLeftState.ts +++ b/src/sing/sequencerStateMachine/states/resizeNoteLeftState.ts @@ -9,7 +9,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { clamp } from "@/sing/utility"; export class ResizeNoteLeftState diff --git a/src/composables/sequencerStateMachine/states/resizeNoteRightState.ts b/src/sing/sequencerStateMachine/states/resizeNoteRightState.ts similarity index 99% rename from src/composables/sequencerStateMachine/states/resizeNoteRightState.ts rename to src/sing/sequencerStateMachine/states/resizeNoteRightState.ts index ced2cec143..2dd08b92c2 100644 --- a/src/composables/sequencerStateMachine/states/resizeNoteRightState.ts +++ b/src/sing/sequencerStateMachine/states/resizeNoteRightState.ts @@ -7,7 +7,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { Note } from "@/store/type"; import { getOrThrow } from "@/helpers/mapHelper"; diff --git a/src/composables/sequencerStateMachine/states/selectNotesWithRectState.ts b/src/sing/sequencerStateMachine/states/selectNotesWithRectState.ts similarity index 98% rename from src/composables/sequencerStateMachine/states/selectNotesWithRectState.ts rename to src/sing/sequencerStateMachine/states/selectNotesWithRectState.ts index 405594fc0d..22294cb871 100644 --- a/src/composables/sequencerStateMachine/states/selectNotesWithRectState.ts +++ b/src/sing/sequencerStateMachine/states/selectNotesWithRectState.ts @@ -4,7 +4,7 @@ import { Input, PositionOnSequencer, SequencerStateDefinitions, -} from "@/composables/sequencerStateMachine/common"; +} from "@/sing/sequencerStateMachine/common"; import { getButton } from "@/sing/viewHelper"; import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; import { NoteId } from "@/type/preload";