From 136bddbe4d303ffcc96599c5d661f5e39e237042 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Thu, 12 Dec 2024 01:15:52 +0900 Subject: [PATCH 1/7] =?UTF-8?q?MoveNoteState=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stateMachine/sequencerStateMachine.ts | 289 +++++++++++++++++- 1 file changed, 273 insertions(+), 16 deletions(-) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index 7091f1bf3d..d4e6b5622a 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -9,10 +9,13 @@ import { getButton, getDoremiFromNoteNumber, isSelfEventTarget, + keyInfos, PREVIEW_SOUND_DURATION, } from "@/sing/viewHelper"; import { Note, SequencerEditTarget } from "@/store/type"; -import { NoteId } from "@/type/preload"; +import { NoteId, TrackId } from "@/type/preload"; +import { getOrThrow } from "@/helpers/mapHelper"; +import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; export type PositionOnSequencer = { readonly ticks: number; @@ -47,6 +50,9 @@ type Input = type ComputedRefs = { readonly snapTicks: ComputedRef; readonly editTarget: ComputedRef; + readonly selectedTrackId: ComputedRef; + readonly notesInSelectedTrack: ComputedRef; + readonly selectedNoteIds: ComputedRef>; }; type Refs = { @@ -57,14 +63,16 @@ type Refs = { type StoreActions = { readonly deselectAllNotes: () => void; - readonly commandAddNotes: (notes: Note[]) => void; + readonly deselectNotes: (noteIds: NoteId[]) => void; + readonly commandAddNotes: (notes: Note[], trackId: TrackId) => void; + readonly commandUpdateNotes: (notes: Note[], trackId: TrackId) => void; readonly selectNotes: (noteIds: NoteId[]) => void; readonly playPreviewSound: (noteNumber: number, duration?: number) => void; }; type Context = ComputedRefs & Refs & { readonly storeActions: StoreActions }; -type State = IdleState | AddNoteState; +type State = IdleState | AddNoteState | MoveNoteState; const getGuideLineTicks = ( cursorPos: PositionOnSequencer, @@ -76,6 +84,63 @@ const getGuideLineTicks = ( return Math.round(cursorTicks / snapTicks - 0.25) * snapTicks; }; +const selectOnlyThisNote = (context: Context, note: Note) => { + void context.storeActions.deselectAllNotes(); + void context.storeActions.selectNotes([note.id]); + void context.storeActions.playPreviewSound( + note.noteNumber, + PREVIEW_SOUND_DURATION, + ); +}; + +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.storeActions.selectNotes(noteIdsToSelect); + } else if (isOnCommandOrCtrlKeyDown(mouseEvent)) { + // CommandキーかCtrlキーが押されている場合 + if (context.selectedNoteIds.value.has(mouseDownNote.id)) { + // 選択中のノートなら選択解除 + void context.storeActions.deselectNotes([mouseDownNote.id]); + return; + } + // 未選択のノートなら選択に追加 + void context.storeActions.selectNotes([mouseDownNote.id]); + } else if (!context.selectedNoteIds.value.has(mouseDownNote.id)) { + // 選択中のノートでない場合は選択状態にする + void selectOnlyThisNote(context, mouseDownNote); + } +}; + +const getSelectedNotes = (context: Context) => { + return context.notesInSelectedTrack.value.filter((value) => + context.selectedNoteIds.value.has(value.id), + ); +}; + class IdleState implements IState { readonly id = "idle"; @@ -91,14 +156,15 @@ class IdleState implements IState { setNextState: (nextState: State) => void; }) { const mouseButton = getButton(input.mouseEvent); - if (input.targetArea === "SequencerBody") { - context.guideLineTicks.value = getGuideLineTicks( - input.cursorPos, - context, - ); - if (input.mouseEvent.type === "mousedown") { - // TODO: メニューが表示されている場合はメニュー非表示のみ行いたい - if (context.editTarget.value === "NOTE") { + 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") { if (!isSelfEventTarget(input.mouseEvent)) { return; } @@ -110,7 +176,29 @@ class IdleState implements IState { ) { return; } - setNextState(new AddNoteState(input.cursorPos)); + context.storeActions.deselectAllNotes(); + const addNoteState = new AddNoteState( + input.cursorPos, + selectedTrackId, + ); + setNextState(addNoteState); + } + } + } else if (input.targetArea === "Note") { + if (input.mouseEvent.type === "mousedown") { + if (!isSelfEventTarget(input.mouseEvent)) { + return; + } + if (mouseButton === "LEFT_BUTTON") { + executeNotesSelectionProcess(context, input.mouseEvent, input.note); + const selectedNotes = getSelectedNotes(context); + const moveNoteState = new MoveNoteState( + input.cursorPos, + selectedTrackId, + selectedNotes, + input.note.id, + ); + setNextState(moveNoteState); } } } @@ -124,6 +212,7 @@ class AddNoteState implements IState { readonly id = "addNote"; private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; private currentCursorPos: PositionOnSequencer; private innerContext: @@ -134,8 +223,10 @@ class AddNoteState implements IState { } | undefined; - constructor(cursorPosAtStart: PositionOnSequencer) { + constructor(cursorPosAtStart: PositionOnSequencer, targetTrackId: TrackId) { this.cursorPosAtStart = cursorPosAtStart; + this.targetTrackId = targetTrackId; + this.currentCursorPos = cursorPosAtStart; } @@ -166,8 +257,6 @@ class AddNoteState implements IState { } onEnter(context: Context) { - context.storeActions.deselectAllNotes(); - const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); const noteToAdd = { id: NoteId(crypto.randomUUID()), @@ -231,14 +320,182 @@ class AddNoteState implements IState { const previewNoteIds = previewNotes.map((value) => value.id); cancelAnimationFrame(this.innerContext.previewRequestId); - context.storeActions.commandAddNotes(context.previewNotes.value); + + context.storeActions.commandAddNotes( + context.previewNotes.value, + this.targetTrackId, + ); + context.storeActions.selectNotes(previewNoteIds); + + if (previewNotes.length === 1) { + context.storeActions.playPreviewSound( + previewNotes[0].noteNumber, + PREVIEW_SOUND_DURATION, + ); + } + context.nowPreviewing.value = false; + } +} + +class MoveNoteState implements IState { + readonly id = "moveNote"; + + private readonly cursorPosAtStart: PositionOnSequencer; + private readonly targetTrackId: TrackId; + private readonly clonedTargetNotes: Map; + private readonly mouseDownNoteId: NoteId; + + private currentCursorPos: PositionOnSequencer; + + private innerContext: + | { + previewRequestId: number; + executePreviewProcess: boolean; + edited: boolean; + guideLineTicksAtStart: number; + } + | undefined; + + constructor( + cursorPosAtStart: PositionOnSequencer, + targetTrackId: TrackId, + targetNotes: Note[], + mouseDownNoteId: NoteId, + ) { + if (!targetNotes.some((value) => value.id === mouseDownNoteId)) { + throw new Error("mouseDownNote is not included in targetNotes."); + } + this.cursorPosAtStart = cursorPosAtStart; + this.targetTrackId = targetTrackId; + this.clonedTargetNotes = new Map(); + for (const targetNote of targetNotes) { + this.clonedTargetNotes.set(targetNote.id, { ...targetNote }); + } + this.mouseDownNoteId = mouseDownNoteId; + + this.currentCursorPos = 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 clonedTargetNotes = this.clonedTargetNotes; + const mouseDownNote = getOrThrow(clonedTargetNotes, 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 clonedTargetNote = clonedTargetNotes.get(note.id); + if (!clonedTargetNote) { + throw new Error("clonedTargetNote is undefined."); + } + const position = clonedTargetNote.position + movingTicks; + const noteNumber = clonedTargetNote.noteNumber + movingSemitones; + if (note.position !== position || note.noteNumber !== noteNumber) { + editedNotes.set(note.id, { ...note, noteNumber, position }); + } + } + + for (const note of editedNotes.values()) { + if (note.noteNumber < 0 || note.noteNumber >= keyInfos.length) { + // MIDIキー範囲外へはドラッグしない + return; + } + if (note.position < 0) { + // 左端より前はドラッグしない + return; + } + } + + 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); + + context.previewNotes.value = [...this.clonedTargetNotes.values()]; + context.nowPreviewing.value = true; + + const preview = () => { + 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(preview); + }; + const previewRequestId = requestAnimationFrame(preview); + + this.innerContext = { + executePreviewProcess: false, + previewRequestId, + edited: false, + guideLineTicksAtStart: guideLineTicks, + }; + } + + process({ + input, + setNextState, + }: { + input: Input; + context: Context; + setNextState: (nextState: State) => void; + }) { + 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(new IdleState()); + } + } + } + } + + 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); + + context.storeActions.commandUpdateNotes(previewNotes, this.targetTrackId); context.storeActions.selectNotes(previewNoteIds); + if (previewNotes.length === 1) { context.storeActions.playPreviewSound( previewNotes[0].noteNumber, PREVIEW_SOUND_DURATION, ); } + context.previewNotes.value = []; context.nowPreviewing.value = false; } } From 941240abf977a06635f2c5c3e98e0252c8b69807 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Thu, 12 Dec 2024 21:49:19 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sing/stateMachine/sequencerStateMachine.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index d4e6b5622a..e48a4727ca 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -333,6 +333,7 @@ class AddNoteState implements IState { PREVIEW_SOUND_DURATION, ); } + context.previewNotes.value = []; context.nowPreviewing.value = false; } } From e44154aa9f28d17221d64c74a721b02e9c3a3646 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:13:34 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=E3=83=97=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E9=9F=B3=E3=82=92=E5=86=8D=E7=94=9F=E3=81=99=E3=82=8B?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sing/stateMachine/sequencerStateMachine.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index e48a4727ca..2541acc774 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -87,10 +87,6 @@ const getGuideLineTicks = ( const selectOnlyThisNote = (context: Context, note: Note) => { void context.storeActions.deselectAllNotes(); void context.storeActions.selectNotes([note.id]); - void context.storeActions.playPreviewSound( - note.noteNumber, - PREVIEW_SOUND_DURATION, - ); }; const executeNotesSelectionProcess = ( @@ -132,6 +128,10 @@ const executeNotesSelectionProcess = ( } else if (!context.selectedNoteIds.value.has(mouseDownNote.id)) { // 選択中のノートでない場合は選択状態にする void selectOnlyThisNote(context, mouseDownNote); + void context.storeActions.playPreviewSound( + mouseDownNote.noteNumber, + PREVIEW_SOUND_DURATION, + ); } }; From 7e8e0c18d1ec73b51dec968a45a870540ef64841 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:17:02 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sing/stateMachine/sequencerStateMachine.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index 2541acc774..43dea3a8cb 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -89,6 +89,9 @@ const selectOnlyThisNote = (context: Context, note: Note) => { void context.storeActions.selectNotes([note.id]); }; +/** + * mousedown時のノート選択・選択解除の処理を実行する。 + */ const executeNotesSelectionProcess = ( context: Context, mouseEvent: MouseEvent, From f729393f479d08aca9e5c9707e53770f832d0334 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:20:31 +0900 Subject: [PATCH 5/7] =?UTF-8?q?preview=E3=81=8B=E3=82=89previewIfNeeded?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sing/stateMachine/sequencerStateMachine.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index 43dea3a8cb..850425c7e3 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -272,7 +272,7 @@ class AddNoteState implements IState { context.previewNotes.value = [noteToAdd]; context.nowPreviewing.value = true; - const preview = () => { + const previewIfNeeded = () => { if (this.innerContext == undefined) { throw new Error("innerContext is undefined."); } @@ -280,9 +280,10 @@ class AddNoteState implements IState { this.previewAdd(context); this.innerContext.executePreviewProcess = false; } - this.innerContext.previewRequestId = requestAnimationFrame(preview); + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); }; - const previewRequestId = requestAnimationFrame(preview); + const previewRequestId = requestAnimationFrame(previewIfNeeded); this.innerContext = { noteToAdd, @@ -437,7 +438,7 @@ class MoveNoteState implements IState { context.previewNotes.value = [...this.clonedTargetNotes.values()]; context.nowPreviewing.value = true; - const preview = () => { + const previewIfNeeded = () => { if (this.innerContext == undefined) { throw new Error("innerContext is undefined."); } @@ -445,9 +446,10 @@ class MoveNoteState implements IState { this.previewMove(context); this.innerContext.executePreviewProcess = false; } - this.innerContext.previewRequestId = requestAnimationFrame(preview); + this.innerContext.previewRequestId = + requestAnimationFrame(previewIfNeeded); }; - const previewRequestId = requestAnimationFrame(preview); + const previewRequestId = requestAnimationFrame(previewIfNeeded); this.innerContext = { executePreviewProcess: false, From df967229d32cdeb4b7bafa3bddc476de7814b11b Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:29:29 +0900 Subject: [PATCH 6/7] =?UTF-8?q?targetNotes=E3=82=92clone=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stateMachine/sequencerStateMachine.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index 850425c7e3..65c7c30142 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -347,7 +347,7 @@ class MoveNoteState implements IState { private readonly cursorPosAtStart: PositionOnSequencer; private readonly targetTrackId: TrackId; - private readonly clonedTargetNotes: Map; + private readonly targetNotesAtStart: Map; private readonly mouseDownNoteId: NoteId; private currentCursorPos: PositionOnSequencer; @@ -372,9 +372,9 @@ class MoveNoteState implements IState { } this.cursorPosAtStart = cursorPosAtStart; this.targetTrackId = targetTrackId; - this.clonedTargetNotes = new Map(); + this.targetNotesAtStart = new Map(); for (const targetNote of targetNotes) { - this.clonedTargetNotes.set(targetNote.id, { ...targetNote }); + this.targetNotesAtStart.set(targetNote.id, targetNote); } this.mouseDownNoteId = mouseDownNoteId; @@ -387,8 +387,8 @@ class MoveNoteState implements IState { } const snapTicks = context.snapTicks.value; const previewNotes = context.previewNotes.value; - const clonedTargetNotes = this.clonedTargetNotes; - const mouseDownNote = getOrThrow(clonedTargetNotes, this.mouseDownNoteId); + const targetNotesAtStart = this.targetNotesAtStart; + const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; const notePos = mouseDownNote.position; const newNotePos = @@ -399,12 +399,12 @@ class MoveNoteState implements IState { const editedNotes = new Map(); for (const note of previewNotes) { - const clonedTargetNote = clonedTargetNotes.get(note.id); - if (!clonedTargetNote) { - throw new Error("clonedTargetNote is undefined."); + const targetNoteAtStart = targetNotesAtStart.get(note.id); + if (!targetNoteAtStart) { + throw new Error("targetNoteAtStart is undefined."); } - const position = clonedTargetNote.position + movingTicks; - const noteNumber = clonedTargetNote.noteNumber + movingSemitones; + const position = targetNoteAtStart.position + movingTicks; + const noteNumber = targetNoteAtStart.noteNumber + movingSemitones; if (note.position !== position || note.noteNumber !== noteNumber) { editedNotes.set(note.id, { ...note, noteNumber, position }); } @@ -435,7 +435,7 @@ class MoveNoteState implements IState { onEnter(context: Context) { const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context); - context.previewNotes.value = [...this.clonedTargetNotes.values()]; + context.previewNotes.value = [...this.targetNotesAtStart.values()]; context.nowPreviewing.value = true; const previewIfNeeded = () => { From 15746777dd0bda20e0186d4aa8aae26cd4df8a42 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:53:31 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Note=E3=81=AE=E3=82=A4=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=82=B9=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F?= =?UTF-8?q?NoteId=E3=82=92=E6=B8=A1=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stateMachine/sequencerStateMachine.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/sing/stateMachine/sequencerStateMachine.ts b/src/sing/stateMachine/sequencerStateMachine.ts index 65c7c30142..9fe51406ff 100644 --- a/src/sing/stateMachine/sequencerStateMachine.ts +++ b/src/sing/stateMachine/sequencerStateMachine.ts @@ -138,12 +138,6 @@ const executeNotesSelectionProcess = ( } }; -const getSelectedNotes = (context: Context) => { - return context.notesInSelectedTrack.value.filter((value) => - context.selectedNoteIds.value.has(value.id), - ); -}; - class IdleState implements IState { readonly id = "idle"; @@ -194,11 +188,10 @@ class IdleState implements IState { } if (mouseButton === "LEFT_BUTTON") { executeNotesSelectionProcess(context, input.mouseEvent, input.note); - const selectedNotes = getSelectedNotes(context); const moveNoteState = new MoveNoteState( input.cursorPos, selectedTrackId, - selectedNotes, + context.selectedNoteIds.value, input.note.id, ); setNextState(moveNoteState); @@ -347,13 +340,14 @@ class MoveNoteState implements IState { private readonly cursorPosAtStart: PositionOnSequencer; private readonly targetTrackId: TrackId; - private readonly targetNotesAtStart: Map; + private readonly targetNoteIds: Set; private readonly mouseDownNoteId: NoteId; private currentCursorPos: PositionOnSequencer; private innerContext: | { + targetNotesAtStart: Map; previewRequestId: number; executePreviewProcess: boolean; edited: boolean; @@ -364,18 +358,15 @@ class MoveNoteState implements IState { constructor( cursorPosAtStart: PositionOnSequencer, targetTrackId: TrackId, - targetNotes: Note[], + targetNoteIds: Set, mouseDownNoteId: NoteId, ) { - if (!targetNotes.some((value) => value.id === mouseDownNoteId)) { - throw new Error("mouseDownNote is not included in targetNotes."); + if (!targetNoteIds.has(mouseDownNoteId)) { + throw new Error("mouseDownNoteId is not included in targetNoteIds."); } this.cursorPosAtStart = cursorPosAtStart; this.targetTrackId = targetTrackId; - this.targetNotesAtStart = new Map(); - for (const targetNote of targetNotes) { - this.targetNotesAtStart.set(targetNote.id, targetNote); - } + this.targetNoteIds = targetNoteIds; this.mouseDownNoteId = mouseDownNoteId; this.currentCursorPos = cursorPosAtStart; @@ -387,7 +378,7 @@ class MoveNoteState implements IState { } const snapTicks = context.snapTicks.value; const previewNotes = context.previewNotes.value; - const targetNotesAtStart = this.targetNotesAtStart; + const targetNotesAtStart = this.innerContext.targetNotesAtStart; const mouseDownNote = getOrThrow(targetNotesAtStart, this.mouseDownNoteId); const dragTicks = this.currentCursorPos.ticks - this.cursorPosAtStart.ticks; const notePos = mouseDownNote.position; @@ -434,8 +425,15 @@ class MoveNoteState implements IState { 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 = [...this.targetNotesAtStart.values()]; + context.previewNotes.value = [...targetNotesArray]; context.nowPreviewing.value = true; const previewIfNeeded = () => { @@ -452,6 +450,7 @@ class MoveNoteState implements IState { const previewRequestId = requestAnimationFrame(previewIfNeeded); this.innerContext = { + targetNotesAtStart: targetNotesMap, executePreviewProcess: false, previewRequestId, edited: false,