Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [project-sequencer-statemachine] 調整 #2526

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/composables/useSequencerStateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IdleStateId,
PartialStore,
Refs,
SequencerStateId,
} from "@/sing/sequencerStateMachine/common";
import { getNoteDuration } from "@/sing/domain";
import { createSequencerStateMachine } from "@/sing/sequencerStateMachine";
Expand Down Expand Up @@ -32,25 +33,32 @@ export const useSequencerStateMachine = (
const refs: Refs = {
nowPreviewing: ref(false),
previewNotes: ref([]),
previewNoteIds: ref(new Set()),
previewRectForRectSelect: ref(undefined),
previewPitchEdit: ref(undefined),
cursorState: ref("UNSET"),
guideLineTicks: ref(0),
};

const currentStateId = ref<SequencerStateId>(initialStateId);
const stateMachine = createSequencerStateMachine(
{
...computedRefs,
...refs,
store,
},
initialStateId,
(stateId: SequencerStateId) => {
currentStateId.value = stateId;
},
);

return {
stateMachine,
currentStateId: computed(() => currentStateId.value),
nowPreviewing: computed(() => refs.nowPreviewing.value),
previewNotes: computed(() => refs.previewNotes.value),
previewNoteIds: computed(() => refs.previewNoteIds.value),
previewRectForRectSelect: computed(
() => refs.previewRectForRectSelect.value,
),
Expand Down
116 changes: 64 additions & 52 deletions src/sing/sequencerStateMachine/common.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ComputedRef, Ref } from "vue";
import { StateDefinitions } from "@/sing/stateMachine";
import { StateDefinitions, StateId } from "@/sing/stateMachine";
import { Rect } from "@/sing/utility";
import { CursorState, 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 = {
Expand All @@ -19,9 +18,15 @@ export type PositionOnSequencer = {
export type Input =
| {
readonly type: "keyboardEvent";
readonly targetArea: "SequencerBody";
readonly targetArea: "Document";
readonly keyboardEvent: KeyboardEvent;
}
| {
readonly type: "mouseEvent";
readonly targetArea: "Window";
readonly mouseEvent: MouseEvent;
readonly cursorPos: PositionOnSequencer;
}
| {
readonly type: "mouseEvent";
readonly targetArea: "SequencerBody";
Expand Down Expand Up @@ -64,6 +69,7 @@ export type ComputedRefs = {
export type Refs = {
readonly nowPreviewing: Ref<boolean>;
readonly previewNotes: Ref<Note[]>;
readonly previewNoteIds: Ref<Set<NoteId>>;
readonly previewRectForRectSelect: Ref<Rect | undefined>;
readonly previewPitchEdit: Ref<
| { type: "draw"; data: number[]; startFrame: number }
Expand Down Expand Up @@ -128,6 +134,7 @@ export type SequencerStateDefinitions = StateDefinitions<
cursorPosAtStart: PositionOnSequencer;
targetTrackId: TrackId;
returnStateId: IdleStateId;
applyImmediatelyAndExit: boolean;
};
},
{
Expand Down Expand Up @@ -186,6 +193,8 @@ export type SequencerStateDefinitions = StateDefinitions<
]
>;

export type SequencerStateId = StateId<SequencerStateDefinitions>;

/**
* カーソル位置に対応する補助線の位置を取得する。
*/
Expand All @@ -200,60 +209,63 @@ export const getGuideLineTicks = (
};

/**
* 指定されたノートのみを選択状態にする。
* 指定されたノートとすでに選択されているノートの間にある全てのノートを選択状態にする。
* @param context シーケンサーのコンテキスト
* @param mouseDownNote マウスでクリックされたノート
*/
export const selectOnlyThisNote = (context: Context, note: Note) => {
void context.store.actions.DESELECT_ALL_NOTES();
void context.store.actions.SELECT_NOTES({ noteIds: [note.id] });
export const selectNotesInRange = (context: Context, mouseDownNote: Note) => {
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 });
};

/**
* mousedown時のノート選択・選択解除の処理を実行する。
* 指定されたノートの選択状態を切り替える。
* 選択されている場合は選択解除し、選択されていない場合は選択状態にする。
* @param context シーケンサーのコンテキスト
* @param note 選択状態を切り替えるノート
*/
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,
export const toggleNoteSelection = (context: Context, note: Note) => {
if (context.selectedNoteIds.value.has(note.id)) {
void context.store.actions.DESELECT_NOTES({
noteIds: [note.id],
});
} else {
void context.store.actions.SELECT_NOTES({ noteIds: [note.id] });
}
};

/**
* 指定されたノートのみを選択状態にし、そのノートのプレビュー音を再生する。
* 他のノートの選択状態は全て解除される。
* @param context シーケンサーのコンテキスト
* @param note 選択してプレビュー音を再生するノート
*/
export const selectOnlyThisNoteAndPlayPreviewSound = (
context: Context,
note: Note,
) => {
void context.store.actions.DESELECT_ALL_NOTES();
void context.store.actions.SELECT_NOTES({ noteIds: [note.id] });
void context.store.actions.PLAY_PREVIEW_SOUND({
noteNumber: note.noteNumber,
duration: PREVIEW_SOUND_DURATION,
});
};
3 changes: 3 additions & 0 deletions src/sing/sequencerStateMachine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
IdleStateId,
Input,
SequencerStateDefinitions,
SequencerStateId,
} from "@/sing/sequencerStateMachine/common";
import { StateMachine } from "@/sing/stateMachine";

Expand All @@ -21,6 +22,7 @@ import { ErasePitchState } from "@/sing/sequencerStateMachine/states/erasePitchS
export const createSequencerStateMachine = (
context: Context,
initialStateId: IdleStateId,
onStateChanged: (stateId: SequencerStateId) => void,
) => {
return new StateMachine<SequencerStateDefinitions, Input, Context>(
{
Expand All @@ -38,5 +40,6 @@ export const createSequencerStateMachine = (
},
context,
initialStateId,
onStateChanged,
);
};
20 changes: 18 additions & 2 deletions src/sing/sequencerStateMachine/states/addNoteState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class AddNoteState
private readonly cursorPosAtStart: PositionOnSequencer;
private readonly targetTrackId: TrackId;
private readonly returnStateId: IdleStateId;
private readonly applyImmediatelyAndExit: boolean;

private currentCursorPos: PositionOnSequencer;
private applyPreview: boolean;
Expand All @@ -40,10 +41,12 @@ export class AddNoteState
cursorPosAtStart: PositionOnSequencer;
targetTrackId: TrackId;
returnStateId: IdleStateId;
applyImmediatelyAndExit: boolean;
}) {
this.cursorPosAtStart = args.cursorPosAtStart;
this.targetTrackId = args.targetTrackId;
this.returnStateId = args.returnStateId;
this.applyImmediatelyAndExit = args.applyImmediatelyAndExit;

this.currentCursorPos = args.cursorPosAtStart;
this.applyPreview = false;
Expand Down Expand Up @@ -75,7 +78,13 @@ export class AddNoteState
context.guideLineTicks.value = noteEndPos;
}

onEnter(context: Context) {
onEnter({
context,
setNextState,
}: {
context: Context;
setNextState: SetNextState<SequencerStateDefinitions>;
}) {
const guideLineTicks = getGuideLineTicks(this.cursorPosAtStart, context);
const noteToAdd = {
id: NoteId(crypto.randomUUID()),
Expand All @@ -87,6 +96,7 @@ export class AddNoteState
const noteEndPos = noteToAdd.position + noteToAdd.duration;

context.previewNotes.value = [noteToAdd];
context.previewNoteIds.value = new Set([noteToAdd.id]);
context.cursorState.value = "DRAW";
context.guideLineTicks.value = noteEndPos;
context.nowPreviewing.value = true;
Expand All @@ -109,6 +119,11 @@ export class AddNoteState
executePreviewProcess: false,
previewRequestId,
};

if (this.applyImmediatelyAndExit) {
this.applyPreview = true;
setNextState(this.returnStateId, undefined);
}
}

process({
Expand All @@ -125,7 +140,7 @@ export class AddNoteState
if (input.type === "mouseEvent") {
const mouseButton = getButton(input.mouseEvent);

if (input.targetArea === "SequencerBody") {
if (input.targetArea === "Window") {
if (input.mouseEvent.type === "mousemove") {
this.currentCursorPos = input.cursorPos;
this.innerContext.executePreviewProcess = true;
Expand Down Expand Up @@ -165,6 +180,7 @@ export class AddNoteState
}

context.previewNotes.value = [];
context.previewNoteIds.value = new Set();
context.cursorState.value = "UNSET";
context.nowPreviewing.value = false;
}
Expand Down
9 changes: 7 additions & 2 deletions src/sing/sequencerStateMachine/states/drawPitchState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ export class DrawPitchState
this.innerContext.prevCursorPos = this.currentCursorPos;
}

onEnter(context: Context) {
onEnter({
context,
}: {
context: Context;
setNextState: SetNextState<SequencerStateDefinitions>;
}) {
context.previewPitchEdit.value = {
type: "draw",
data: [this.cursorPosAtStart.frequency],
Expand Down Expand Up @@ -168,7 +173,7 @@ export class DrawPitchState
if (input.type === "mouseEvent") {
const mouseButton = getButton(input.mouseEvent);

if (input.targetArea === "SequencerBody") {
if (input.targetArea === "Window") {
if (input.mouseEvent.type === "mousemove") {
this.currentCursorPos = input.cursorPos;
this.innerContext.executePreviewProcess = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export class DrawPitchToolIdleState
{
readonly id = "drawPitchToolIdle";

onEnter(context: Context) {
onEnter({
context,
}: {
context: Context;
setNextState: SetNextState<SequencerStateDefinitions>;
}) {
this.updateCursorState(context, context.isCommandOrCtrlKeyDown.value);
}

Expand Down
Loading
Loading