-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: [project-sequencer-statemachine] ファイルを分けて整理 (#2504)
- Loading branch information
1 parent
2779fbe
commit 8c26457
Showing
13 changed files
with
1,532 additions
and
1,436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<number>; | ||
readonly editTarget: ComputedRef<SequencerEditTarget>; | ||
readonly selectedTrackId: ComputedRef<TrackId>; | ||
readonly notesInSelectedTrack: ComputedRef<Note[]>; | ||
readonly selectedNoteIds: ComputedRef<Set<NoteId>>; | ||
readonly editorFrameRate: ComputedRef<number>; | ||
}; | ||
|
||
export type Refs = { | ||
readonly nowPreviewing: Ref<boolean>; | ||
readonly previewNotes: Ref<Note[]>; | ||
readonly previewRectForRectSelect: Ref<Rect | undefined>; | ||
readonly previewPitchEdit: Ref< | ||
| { type: "draw"; data: number[]; startFrame: number } | ||
| { type: "erase"; startFrame: number; frameLength: number } | ||
| undefined | ||
>; | ||
readonly guideLineTicks: Ref<number>; | ||
}; | ||
|
||
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<NoteId>; | ||
mouseDownNoteId: NoteId; | ||
}; | ||
}, | ||
{ | ||
id: "resizeNoteLeft"; | ||
factoryArgs: { | ||
cursorPosAtStart: PositionOnSequencer; | ||
targetTrackId: TrackId; | ||
targetNoteIds: Set<NoteId>; | ||
mouseDownNoteId: NoteId; | ||
}; | ||
}, | ||
{ | ||
id: "resizeNoteRight"; | ||
factoryArgs: { | ||
cursorPosAtStart: PositionOnSequencer; | ||
targetTrackId: TrackId; | ||
targetNoteIds: Set<NoteId>; | ||
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, | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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(), | ||
context, | ||
); | ||
}; |
Oops, something went wrong.