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 16 commits into
base: main
Choose a base branch
from
Open
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
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
Loading