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

ソング:ループの機能追加 #2506

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5b4e401
マージ
romot-co Jan 20, 2025
c16da4b
マージ
sigprogramming Oct 2, 2024
63bfded
LoopLaneをマージ
romot-co Jan 20, 2025
bd96d8d
ループエリアの設定
romot-co Jan 20, 2025
a416b14
拍子変更に追随したスナップ
romot-co Jan 21, 2025
1f97082
Container/Presentationに分割
romot-co Jan 26, 2025
7b25f3c
Merge remote-tracking branch 'origin/main' into add/loop_2224
romot-co Jan 26, 2025
2ea81f9
プロジェクト周りのテストを通るようにする+0区間のバグ修正
romot-co Jan 27, 2025
f5acf33
分割およびstorybook追加
romot-co Jan 27, 2025
426cf3d
fix: update snapshot for frameSynthesisFrameSynthesisPost test
Hiroshiba Jan 27, 2025
88822b2
リファクタリングおよびコンポーネント分割
romot-co Jan 28, 2025
ab3fa78
Merge remote-tracking branch 'origin/add/loop_2224' into add/loop_2224
romot-co Jan 28, 2025
b71a7b4
[update snapshots]
romot-co Jan 28, 2025
73a4b15
(スナップショットを更新)
github-actions[bot] Jan 28, 2025
113a83f
Remove LoopLane component from ScoreSequencer.vue
romot-co Jan 28, 2025
6a012cd
Merge branch 'main' into add/loop_2224
romot-co Jan 28, 2025
143577b
Merge branch 'main' into add/loop_2224
romot-co Jan 30, 2025
2c83591
storeをStorybookから利用する
romot-co Jan 30, 2025
5cc9979
各storyを表示できるようにする [update snapshots]
romot-co Jan 30, 2025
43016f9
(スナップショットを更新)
github-actions[bot] Jan 30, 2025
c6b5717
Merge branch 'main' into add/loop_2224
romot-co Feb 1, 2025
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
2 changes: 2 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Quasar, Dialog, Loading, Notify } from "quasar";
import iconSet from "quasar/icon-set/material-icons";
import { withThemeByDataAttribute } from "@storybook/addon-themes";
import { addActionsWithEmits } from "./utils/argTypesEnhancers";
import { store, storeKey } from "@/store";
import { markdownItPlugin } from "@/plugins/markdownItPlugin";

import "@quasar/extras/material-icons/material-icons.css";
Expand All @@ -29,6 +30,7 @@ setup((app) => {
},
});
app.use(markdownItPlugin);
app.use(store, storeKey);
});

const preview: Preview = {
Expand Down
85 changes: 49 additions & 36 deletions src/components/Sing/SequencerRuler/Container.vue
Original file line number Diff line number Diff line change
@@ -1,34 +1,57 @@
<template>
<Presentation
:offset
:numMeasures
:tpqn
:width
:numMeasures="props.numMeasures"
:playheadX
:playheadTicks
:offset="props.offset"
:tempos
:timeSignatures
:tpqn
:sequencerZoomX
:uiLocked
:playheadTicks
:sequencerSnapType
:getSnappedTickFromOffsetX
:uiLocked
@update:playheadTicks="updatePlayheadTicks"
@removeTempo="removeTempo"
@removeTimeSignature="removeTimeSignature"
@setTempo="setTempo"
@setTimeSignature="setTimeSignature"
@deselectAllNotes="deselectAllNotes"
/>
>
<!-- TODO: 各コンポーネントもなるべく疎にしたつもりだが、少なくともplayheadまわりがリファクタリング必要そう -->
<template #grid>
<GridLaneContainer
:numMeasures="props.numMeasures"
:offset="props.offset"
/>
</template>
<template #changes>
<ValueChangesLaneContainer
:offset="props.offset"
:numMeasures="props.numMeasures"
@setPlayheadPosition="updatePlayheadTicks"
/>
</template>
<template #loop>
<LoopLaneContainer
:offset="props.offset"
:numMeasures="props.numMeasures"
/>
</template>
</Presentation>
</template>

<script setup lang="ts">
import { computed } from "vue";
import Presentation from "./Presentation.vue";
import GridLaneContainer from "./GridLane/Container.vue";
import ValueChangesLaneContainer from "./ValueChangesLane/Container.vue";
import LoopLaneContainer from "./LoopLane/Container.vue";
import { useStore } from "@/store";
import { Tempo, TimeSignature } from "@/store/type";
import { useSequencerRuler } from "@/composables/useSequencerRuler";

defineOptions({
name: "SequencerRuler",
});

withDefaults(
const props = withDefaults(
defineProps<{
offset: number;
numMeasures: number;
Expand All @@ -42,40 +65,30 @@ withDefaults(
const store = useStore();

const tpqn = computed(() => store.state.tpqn);
const tempos = computed(() => store.state.tempos);
const timeSignatures = computed(() => store.state.timeSignatures);
const tempos = computed(() => store.state.tempos);
const sequencerZoomX = computed(() => store.state.sequencerZoomX);
const uiLocked = computed(() => store.getters.UI_LOCKED);
const sequencerSnapType = computed(() => store.state.sequencerSnapType);

const playheadTicks = computed(() => store.getters.PLAYHEAD_POSITION);
const uiLocked = computed(() => store.getters.UI_LOCKED);

// ルーラーおよび内部レーンで共通化した計算ロジック
const { width, playheadX, getSnappedTickFromOffsetX } = useSequencerRuler({
offset: computed(() => props.offset),
numMeasures: computed(() => props.numMeasures),
tpqn,
timeSignatures,
sequencerZoomX,
playheadTicks,
sequencerSnapType,
});

// NOTE: usePlayheadPositionができたら再生ヘッド周辺を置き換える
const updatePlayheadTicks = (ticks: number) => {
void store.actions.SET_PLAYHEAD_POSITION({ position: ticks });
};

const deselectAllNotes = () => {
void store.actions.DESELECT_ALL_NOTES();
};

const setTempo = (tempo: Tempo) => {
void store.actions.COMMAND_SET_TEMPO({
tempo,
});
};
const setTimeSignature = (timeSignature: TimeSignature) => {
void store.actions.COMMAND_SET_TIME_SIGNATURE({
timeSignature,
});
};
const removeTempo = (position: number) => {
void store.actions.COMMAND_REMOVE_TEMPO({
position,
});
};
const removeTimeSignature = (measureNumber: number) => {
void store.actions.COMMAND_REMOVE_TIME_SIGNATURE({
measureNumber,
});
};
</script>
46 changes: 46 additions & 0 deletions src/components/Sing/SequencerRuler/GridLane/Container.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<Presentation
:tpqn
:sequencerZoomX
:numMeasures
:timeSignatures
:tsPositions
:offset
:width
:endTicks
/>
</template>

<script setup lang="ts">
import { computed } from "vue";
import Presentation from "./Presentation.vue";
import { useStore } from "@/store";
import { useSequencerRuler } from "@/composables/useSequencerRuler";

defineOptions({
name: "GridLaneContainer",
});

const props = defineProps<{
numMeasures: number;
offset: number;
}>();

const store = useStore();

const tpqn = computed(() => store.state.tpqn);
const sequencerZoomX = computed(() => store.state.sequencerZoomX);
const timeSignatures = computed(() => store.state.timeSignatures);
const playheadTicks = computed(() => store.getters.PLAYHEAD_POSITION);
const sequencerSnapType = computed(() => store.state.sequencerSnapType);

const { tsPositions, width, endTicks } = useSequencerRuler({
offset: computed(() => props.offset),
numMeasures: computed(() => props.numMeasures),
tpqn,
timeSignatures,
sequencerZoomX,
playheadTicks,
sequencerSnapType,
});
</script>
144 changes: 144 additions & 0 deletions src/components/Sing/SequencerRuler/GridLane/Presentation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:width
:height
shape-rendering="crispEdges"
>
<defs>
<pattern
v-for="(gridPattern, patternIndex) in gridPatterns"
:id="`grid-lane-measure-${patternIndex}`"
:key="`pattern-${patternIndex}`"
patternUnits="userSpaceOnUse"
:x="-offset + gridPattern.x"
:width="gridPattern.patternWidth"
:height
>
<!-- 拍線(小節の最初を除く) -->
<line
v-for="n in gridPattern.beatsPerMeasure"
:key="n"
:x1="gridPattern.beatWidth * n"
:x2="gridPattern.beatWidth * n"
y1="28"
:y2="height"
class="grid-lane-beat-line"
/>
</pattern>
</defs>
<rect
v-for="(gridPattern, index) in gridPatterns"
:key="`grid-${index}`"
:x="0.5 + gridPattern.x - offset"
y="0"
:height
:width="gridPattern.width"
:fill="`url(#grid-lane-measure-${index})`"
/>
<!-- 小節線と小節番号 -->
<template v-for="measureInfo in measureInfos" :key="measureInfo.number">
<line
:x1="measureInfo.x - offset"
:x2="measureInfo.x - offset"
y1="0"
:y2="height"
class="grid-lane-measure-line"
:class="{ 'first-measure-line': measureInfo.number === 1 }"
/>
<text
:x="measureInfo.x - offset + 4"
y="16"
class="grid-lane-measure-number"
>
{{ measureInfo.number }}
</text>
</template>
</svg>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";
import { TimeSignature } from "@/store/type";
import { useSequencerGrid } from "@/composables/useSequencerGridPattern";
import { getMeasureDuration } from "@/sing/domain";
import { tickToBaseX } from "@/sing/viewHelper";

defineOptions({
name: "GridLanePresentation",
});

const props = defineProps<{
tpqn: number;
sequencerZoomX: number;
numMeasures: number;
timeSignatures: TimeSignature[];
offset: number;
width: number;
endTicks: number;
tsPositions: number[];
}>();

const height = ref(40);

const gridPatterns = useSequencerGrid({
timeSignatures: computed(() => props.timeSignatures),
tpqn: computed(() => props.tpqn),
sequencerZoomX: computed(() => props.sequencerZoomX),
numMeasures: computed(() => props.numMeasures),
});

const measureInfos = computed(() => {
return props.timeSignatures.flatMap((timeSignature, i) => {
const measureDuration = getMeasureDuration(
timeSignature.beats,
timeSignature.beatType,
props.tpqn,
);
const nextTsPosition =
i !== props.timeSignatures.length - 1
? props.tsPositions[i + 1]
: props.endTicks;
const start = props.tsPositions[i];
const end = nextTsPosition;
const numMeasures = Math.floor((end - start) / measureDuration);
return Array.from({ length: numMeasures }, (_, index) => {
const measureNumber = timeSignature.measureNumber + index;
const measurePosition = start + index * measureDuration;
const baseX = tickToBaseX(measurePosition, props.tpqn);
return {
number: measureNumber,
x: Math.round(baseX * props.sequencerZoomX),
};
});
});
});
</script>

<style scoped lang="scss">
@use "@/styles/v2/variables" as vars;

.grid-lane-beat-line {
backface-visibility: hidden;
stroke: var(--scheme-color-sing-ruler-beat-line);
stroke-width: 1px;
}

.grid-lane-measure-line {
backface-visibility: hidden;
stroke: var(--scheme-color-sing-ruler-measure-line);
stroke-width: 1px;

// NOTE: 最初の小節線を非表示。必要に応じて再表示・位置合わせする
&.first-measure-line {
stroke: var(--scheme-color-sing-ruler-surface);
}
}

.grid-lane-measure-number {
font-size: 12px;
font-weight: bold;
fill: var(--scheme-color-on-surface-variant);
user-select: none;
}
</style>
Loading