Skip to content

Commit

Permalink
ソング:シーケンサーのグリッドをコンポーネント化する (#2087)
Browse files Browse the repository at this point in the history
* グリッドをコンポーネント化した

* 最後の小節でマウスホイールで縮小を行ったときに表示がおかしくなるのを修正

* PreviewModeの文字列を変更
  • Loading branch information
sigprogramming authored May 21, 2024
1 parent df8aabd commit e434b2e
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 184 deletions.
208 changes: 31 additions & 177 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<SequencerRuler
class="sequencer-ruler"
:offset="scrollX"
:num-of-measures="numOfMeasures"
:num-measures="numMeasures"
/>
<!-- 鍵盤 -->
<SequencerKeys
Expand Down Expand Up @@ -36,74 +36,7 @@
<!-- キャラクター全身 -->
<CharacterPortrait />
<!-- グリッド -->
<!-- NOTE: 現状オクターブごとの罫線なし -->
<svg
xmlns="http://www.w3.org/2000/svg"
:width="gridWidth"
:height="gridHeight"
class="sequencer-grid"
>
<defs>
<pattern
id="sequencer-grid-octave-cells"
patternUnits="userSpaceOnUse"
:width="gridCellWidth"
:height="gridCellHeight * 12"
>
<rect
v-for="(keyInfo, index) in keyInfos"
:key="index"
x="0"
:y="gridCellHeight * index"
:width="gridCellWidth"
:height="gridCellHeight"
:class="`sequencer-grid-cell sequencer-grid-cell-${keyInfo.color}`"
/>
<template v-for="(keyInfo, index) in keyInfos" :key="index">
<line
v-if="keyInfo.pitch === 'C'"
x1="0"
:x2="gridCellWidth"
:y1="gridCellHeight * (index + 1)"
:y2="gridCellHeight * (index + 1)"
stroke-width="1"
class="sequencer-grid-octave-line"
/>
</template>
</pattern>
<pattern
id="sequencer-grid-measure"
patternUnits="userSpaceOnUse"
:width="beatWidth * beatsPerMeasure"
:height="gridHeight"
>
<line
v-for="n in beatsPerMeasure"
:key="n"
:x1="beatWidth * (n - 1)"
:x2="beatWidth * (n - 1)"
y1="0"
y2="100%"
stroke-width="1"
:class="`sequencer-grid-${n === 1 ? 'measure' : 'beat'}-line`"
/>
</pattern>
</defs>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="url(#sequencer-grid-octave-cells)"
/>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="url(#sequencer-grid-measure)"
/>
</svg>
<SequencerGrid />
<div
v-if="editTarget === 'NOTE' && showGuideLine"
class="sequencer-guideline"
Expand Down Expand Up @@ -171,7 +104,7 @@
width: `${Math.abs(cursorX - rectSelectStartX)}px`,
height: `${Math.abs(cursorY - rectSelectStartY)}px`,
}"
/>
></div>
<SequencerPhraseIndicator
v-for="phraseInfo in phraseInfos"
:key="phraseInfo.key"
Expand Down Expand Up @@ -234,9 +167,7 @@ import { useStore } from "@/store";
import { Note, SequencerEditTarget } from "@/store/type";
import {
getEndTicksOfPhrase,
getMeasureDuration,
getNoteDuration,
getNumOfMeasures,
getStartTicksOfPhrase,
noteNumberToFrequency,
tickToSecond,
Expand All @@ -261,6 +192,7 @@ import {
GridAreaInfo,
getButton,
} from "@/sing/viewHelper";
import SequencerGrid from "@/components/Sing/SequencerGrid.vue";
import SequencerRuler from "@/components/Sing/SequencerRuler.vue";
import SequencerKeys from "@/components/Sing/SequencerKeys.vue";
import SequencerNote from "@/components/Sing/SequencerNote.vue";
Expand All @@ -279,10 +211,10 @@ import { useLyricInput } from "@/composables/useLyricInput";
import { ExhaustiveError } from "@/type/utility";
type PreviewMode =
| "ADD"
| "MOVE"
| "RESIZE_RIGHT"
| "RESIZE_LEFT"
| "ADD_NOTE"
| "MOVE_NOTE"
| "RESIZE_NOTE_RIGHT"
| "RESIZE_NOTE_LEFT"
| "DRAW_PITCH"
| "ERASE_PITCH";
Expand All @@ -291,20 +223,13 @@ const isSelfEventTarget = (event: UIEvent) => {
return event.target === event.currentTarget;
};
const store = useStore();
const { warn } = createLogger("ScoreSequencer");
const store = useStore();
const state = store.state;
// 分解能(Ticks Per Quarter Note)
// TPQN、テンポ、ノーツ
const tpqn = computed(() => state.tpqn);
// テンポ
const tempos = computed(() => state.tempos);
// 拍子
const timeSignatures = computed(() => state.timeSignatures);
// ノート
const notes = computed(() => store.getters.SELECTED_TRACK.notes);
const isNoteSelected = computed(() => {
return state.selectedNoteIds.size > 0;
Expand Down Expand Up @@ -334,49 +259,15 @@ const snapTicks = computed(() => {
return getNoteDuration(state.sequencerSnapType, tpqn.value);
});
// シーケンサグリッド
const gridCellTicks = snapTicks; // ひとまずスナップ幅=グリッドセル幅
const gridCellWidth = computed(() => {
return tickToBaseX(gridCellTicks.value, tpqn.value) * zoomX.value;
});
// グリッド
const gridCellBaseHeight = getKeyBaseHeight();
const gridCellHeight = computed(() => {
return gridCellBaseHeight * zoomY.value;
});
const numOfMeasures = computed(() => {
// NOTE: 最低長: 仮32小節...スコア長(曲長さ)が決まっていないため、無限スクロール化する or 最後尾に足した場合は伸びるようにするなど?
const minNumOfMeasures = 32;
// NOTE: いったん最後尾に足した場合は伸びるようにする
return Math.max(
minNumOfMeasures,
getNumOfMeasures(
notes.value,
tempos.value,
timeSignatures.value,
tpqn.value,
) + 1,
);
});
const beatsPerMeasure = computed(() => {
return timeSignatures.value[0].beats;
});
const beatWidth = computed(() => {
const beatType = timeSignatures.value[0].beatType;
const wholeNoteDuration = tpqn.value * 4;
const beatTicks = wholeNoteDuration / beatType;
return tickToBaseX(beatTicks, tpqn.value) * zoomX.value;
});
const gridWidth = computed(() => {
// TODO: 複数拍子に対応する
const beats = timeSignatures.value[0].beats;
const beatType = timeSignatures.value[0].beatType;
const measureDuration = getMeasureDuration(beats, beatType, tpqn.value);
const numOfGridColumns =
Math.round(measureDuration / gridCellTicks.value) * numOfMeasures.value;
return gridCellWidth.value * numOfGridColumns;
});
const gridHeight = computed(() => {
return gridCellHeight.value * keyInfos.length;
return gridCellBaseHeight * zoomY.value * keyInfos.length;
});
// 小節の数
const numMeasures = computed(() => {
return store.getters.SEQUENCER_NUM_MEASURES;
});
// スクロール位置
Expand Down Expand Up @@ -428,7 +319,7 @@ const onNoteLyricBlur = () => {
// プレビュー
// FIXME: 関連する値を1つのobjectにまとめる
const nowPreviewing = ref(false);
let previewMode: PreviewMode = "ADD";
let previewMode: PreviewMode = "ADD_NOTE";
let previewRequestId = 0;
let previewStartEditTarget: SequencerEditTarget = "NOTE";
let executePreviewProcess = false;
Expand Down Expand Up @@ -740,16 +631,16 @@ const previewErasePitch = () => {
const preview = () => {
if (executePreviewProcess) {
if (previewMode === "ADD") {
if (previewMode === "ADD_NOTE") {
previewAdd();
}
if (previewMode === "MOVE") {
if (previewMode === "MOVE_NOTE") {
previewMove();
}
if (previewMode === "RESIZE_RIGHT") {
if (previewMode === "RESIZE_NOTE_RIGHT") {
previewResizeRight();
}
if (previewMode === "RESIZE_LEFT") {
if (previewMode === "RESIZE_NOTE_LEFT") {
previewResizeLeft();
}
if (previewMode === "DRAW_PITCH") {
Expand Down Expand Up @@ -809,7 +700,7 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => {
const guideLineTicks =
Math.round(cursorTicks / snapTicks.value - 0.25) * snapTicks.value;
const copiedNotes: Note[] = [];
if (mode === "ADD") {
if (mode === "ADD_NOTE") {
if (cursorNoteNumber < 0) {
return;
}
Expand Down Expand Up @@ -857,7 +748,7 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => {
dragStartNoteNumber = cursorNoteNumber;
dragStartGuideLineTicks = guideLineTicks;
draggingNoteId = note.id;
edited = mode === "ADD";
edited = mode === "ADD_NOTE";
copiedNotesForPreview.clear();
for (const copiedNote of copiedNotes) {
copiedNotesForPreview.set(copiedNote.id, copiedNote);
Expand Down Expand Up @@ -905,7 +796,7 @@ const endPreview = () => {
// 編集ターゲットがノートのときにプレビューを開始した場合の処理
if (edited) {
if (previewMode === "ADD") {
if (previewMode === "ADD_NOTE") {
store.dispatch("COMMAND_ADD_NOTES", { notes: previewNotes.value });
store.dispatch("SELECT_NOTES", {
noteIds: previewNotes.value.map((value) => value.id),
Expand Down Expand Up @@ -968,7 +859,7 @@ const onNoteBarMouseDown = (event: MouseEvent, note: Note) => {
}
if (mouseButton === "LEFT_BUTTON") {
startPreview(event, "MOVE", note);
startPreview(event, "MOVE_NOTE", note);
} else if (!state.selectedNoteIds.has(note.id)) {
selectOnlyThis(note);
}
Expand All @@ -985,7 +876,7 @@ const onNoteLeftEdgeMouseDown = (event: MouseEvent, note: Note) => {
}
if (mouseButton === "LEFT_BUTTON") {
startPreview(event, "RESIZE_LEFT", note);
startPreview(event, "RESIZE_NOTE_LEFT", note);
} else if (!state.selectedNoteIds.has(note.id)) {
selectOnlyThis(note);
}
Expand All @@ -1002,7 +893,7 @@ const onNoteRightEdgeMouseDown = (event: MouseEvent, note: Note) => {
}
if (mouseButton === "LEFT_BUTTON") {
startPreview(event, "RESIZE_RIGHT", note);
startPreview(event, "RESIZE_NOTE_RIGHT", note);
} else if (!state.selectedNoteIds.has(note.id)) {
selectOnlyThis(note);
}
Expand Down Expand Up @@ -1041,7 +932,7 @@ const onMouseDown = (event: MouseEvent) => {
rectSelectStartX.value = cursorX.value;
rectSelectStartY.value = cursorY.value;
} else {
startPreview(event, "ADD");
startPreview(event, "ADD_NOTE");
}
} else {
store.dispatch("DESELECT_ALL_NOTES");
Expand Down Expand Up @@ -1337,6 +1228,7 @@ const onWheel = (event: WheelEvent) => {
newZoomX = Math.max(ZOOM_X_MIN, newZoomX);
const scrollLeft = sequencerBodyElement.scrollLeft;
const scrollTop = sequencerBodyElement.scrollTop;
guideLineX.value = 0; // 補助線がはみ出さないように位置を一旦0にする
store.dispatch("SET_ZOOM_X", { zoomX: newZoomX }).then(() => {
const cursorBaseX = (scrollLeft + cursorX.value) / oldZoomX;
Expand Down Expand Up @@ -1415,7 +1307,7 @@ onActivated(() => {
yToScroll = scrollY.value;
}
// 実際にスクロールする
nextTick().then(() => {
nextTick(() => {
sequencerBodyElement.scrollTo(xToScroll, yToScroll);
});
});
Expand Down Expand Up @@ -1610,44 +1502,6 @@ const contextMenuData = ref<ContextMenuItemData[]>([
}
}
.sequencer-grid {
display: block;
pointer-events: none;
}
.sequencer-grid-cell {
display: block;
stroke: rgba(colors.$sequencer-sub-divider-rgb, 0.3);
stroke-width: 1;
}
.sequencer-grid-octave-cell {
stroke: colors.$sequencer-main-divider;
}
.sequencer-grid-octave-line {
backface-visibility: hidden;
stroke: colors.$sequencer-main-divider;
}
.sequencer-grid-cell-white {
fill: colors.$sequencer-whitekey-cell;
}
.sequencer-grid-cell-black {
fill: colors.$sequencer-blackkey-cell;
}
.sequencer-grid-measure-line {
backface-visibility: hidden;
stroke: colors.$sequencer-main-divider;
}
.sequencer-grid-beat-line {
backface-visibility: hidden;
stroke: colors.$sequencer-sub-divider;
}
.sequencer-guideline {
position: absolute;
top: 0;
Expand Down
Loading

0 comments on commit e434b2e

Please sign in to comment.