Skip to content

Commit

Permalink
[project-s] 拍子周りの修正とリファクタリング (VOICEVOX#1621)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigprogramming authored Oct 25, 2023
1 parent be89dae commit 07cec39
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 91 deletions.
51 changes: 35 additions & 16 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,10 @@
import { defineComponent, computed, ref, onMounted } from "vue";
import { v4 as uuidv4 } from "uuid";
import { useStore } from "@/store";
import { TimeSignature } from "@/store/type";
import SequencerKeys from "@/components/Sing/SequencerKeys.vue";
import SequencerNote from "@/components/Sing/SequencerNote.vue";
import {
getMeasureDuration,
getMeasureNum,
getNoteDuration,
getKeyBaseHeight,
tickToBaseX,
Expand All @@ -144,6 +142,7 @@ import {
baseYToNoteNumber,
keyInfos,
getDoremiFromNoteNumber,
getNumOfMeasures,
} from "@/helpers/singHelper";
export default defineComponent({
Expand Down Expand Up @@ -175,14 +174,28 @@ export default defineComponent({
const tpqn = computed(() => state.score?.tpqn ?? 480);
// ノート
const notes = computed(() => state.score?.notes ?? []);
// テンポ
const tempos = computed(() => {
return (
state.score?.tempos ?? [
{
position: 0,
tempo: 120,
},
]
);
});
// 拍子
const defaultTimeSignature: TimeSignature = {
position: 0,
beats: 4,
beatType: 4,
};
const timeSignature = computed(() => {
return state.score?.timeSignatures?.[0] ?? defaultTimeSignature;
const timeSignatures = computed(() => {
return (
state.score?.timeSignatures ?? [
{
measureNumber: 1,
beats: 4,
beatType: 4,
},
]
);
});
// ズーム状態
const zoomX = computed(() => state.sequencerZoomX);
Expand All @@ -208,8 +221,9 @@ export default defineComponent({
return gridCellBaseHeight * zoomY.value;
});
const numOfGridColumns = computed(() => {
const beats = timeSignature.value.beats;
const beatType = timeSignature.value.beatType;
// TODO: 複数拍子に対応する
const beats = timeSignatures.value[0].beats;
const beatType = timeSignatures.value[0].beatType;
const measureDuration = getMeasureDuration(beats, beatType, tpqn.value);
const numOfGridColumnsPerMeasure = Math.round(
measureDuration / gridCellTicks.value
Expand All @@ -219,17 +233,22 @@ export default defineComponent({
// NOTE: いったん最後尾に足した場合は伸びるようにする
const numOfMeasures = Math.max(
minNumOfMeasures,
getMeasureNum(notes.value, measureDuration) + 1
getNumOfMeasures(
notes.value,
tempos.value,
timeSignatures.value,
tpqn.value
) + 1
);
return numOfGridColumnsPerMeasure * numOfMeasures;
});
const beatsPerMeasure = computed(() => {
return timeSignature.value.beats;
return timeSignatures.value[0].beats;
});
const beatWidth = computed(() => {
const beatType = timeSignature.value.beatType;
const quarterNotesPerBeat = 4 / beatType;
const beatTicks = tpqn.value * quarterNotesPerBeat;
const beatType = timeSignatures.value[0].beatType;
const wholeNoteDuration = tpqn.value * 4;
const beatTicks = wholeNoteDuration / beatType;
return tickToBaseX(beatTicks, tpqn.value) * zoomX.value;
});
// スクロール位置
Expand Down
16 changes: 11 additions & 5 deletions src/components/Sing/ToolBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
<script lang="ts">
import { defineComponent, computed, watch, ref } from "vue";
import { useStore } from "@/store";
import { getSnapTypes, isTriplet } from "@/helpers/singHelper";
import { BEAT_TYPES, getSnapTypes, isTriplet } from "@/helpers/singHelper";
export default defineComponent({
name: "SingToolBar",
Expand Down Expand Up @@ -135,17 +135,23 @@ export default defineComponent({
const setTempoInputBuffer = (tempoStr: string | number | null) => {
const tempo = Number(tempoStr);
if (!Number.isFinite(tempo) || tempo <= 0) return;
if (!Number.isFinite(tempo) || tempo <= 0) {
return;
}
tempoInputBuffer.value = tempo;
};
const setBeatsInputBuffer = (beatsStr: string | number | null) => {
const beats = Number(beatsStr);
if (!Number.isInteger(beats) || beats <= 0) return;
if (!Number.isInteger(beats) || beats <= 0) {
return;
}
beatsInputBuffer.value = beats;
};
const setBeatTypeInputBuffer = (beatTypeStr: string | number | null) => {
const beatType = Number(beatTypeStr);
if (!Number.isInteger(beatType) || beatType <= 0) return;
if (!Number.isInteger(beatType) || !BEAT_TYPES.includes(beatType)) {
return;
}
beatTypeInputBuffer.value = beatType;
};
Expand Down Expand Up @@ -219,7 +225,7 @@ export default defineComponent({
if (beats === 0 || beatType === 0) return;
await store.dispatch("SET_TIME_SIGNATURE", {
timeSignature: {
position: 0,
measureNumber: 1,
beats: beats,
beatType: beatType,
},
Expand Down
87 changes: 79 additions & 8 deletions src/helpers/singHelper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Note } from "@/store/type";
import { Note, Score, Tempo, TimeSignature } from "@/store/type";

export const BEAT_TYPES = [2, 4, 8, 16];

export const DEFAULT_TPQN = 480;
export const DEFAULT_TEMPO = 120;
export const DEFAULT_BEATS = 4;
export const DEFAULT_BEAT_TYPE = 4;

const BASE_X_PER_QUARTER_NOTE = 120;
const BASE_Y_PER_NOTE_NUMBER = 30;
Expand All @@ -7,6 +14,40 @@ export function noteNumberToFrequency(noteNumber: number) {
return 440 * 2 ** ((noteNumber - 69) / 12);
}

// NOTE: 戻り値の単位はtick
export function getTimeSignaturePositions(
timeSignatures: TimeSignature[],
tpqn: number
) {
const tsPositions: number[] = [0];
for (let i = 0; i < timeSignatures.length - 1; i++) {
const ts = timeSignatures[i];
const tsPosition = tsPositions[i];
const nextTs = timeSignatures[i + 1];
const measureDuration = getMeasureDuration(ts.beats, ts.beatType, tpqn);
const numOfMeasures = nextTs.measureNumber - ts.measureNumber;
const nextTsPosition = tsPosition + measureDuration * numOfMeasures;
tsPositions.push(nextTsPosition);
}
return tsPositions;
}

export function tickToMeasureNumber(
ticks: number,
timeSignatures: TimeSignature[],
tpqn: number
) {
const tsPositions = getTimeSignaturePositions(timeSignatures, tpqn);
const nextTsIndex = tsPositions.findIndex((value) => ticks < value);
const lastTsIndex = tsPositions.length - 1;
const tsIndex = nextTsIndex !== -1 ? nextTsIndex - 1 : lastTsIndex;
const ts = timeSignatures[tsIndex];
const tsPosition = tsPositions[tsIndex];
const ticksWithinTs = ticks - tsPosition;
const measureDuration = getMeasureDuration(ts.beats, ts.beatType, tpqn);
return ts.measureNumber + Math.floor(ticksWithinTs / measureDuration);
}

// NOTE: 戻り値の単位はtick
export function getMeasureDuration(
beats: number,
Expand All @@ -17,14 +58,24 @@ export function getMeasureDuration(
return (wholeNoteDuration / beatType) * beats;
}

// TODO: getNumOfMeasuresに変更する
export function getMeasureNum(notes: Note[], measureDuration: number) {
if (notes.length === 0) {
return 0;
export function getNumOfMeasures(
notes: Note[],
tempos: Tempo[],
timeSignatures: TimeSignature[],
tpqn: number
) {
const tsPositions = getTimeSignaturePositions(timeSignatures, tpqn);
let maxTicks = 0;
const lastTsPosition = tsPositions[tsPositions.length - 1];
const lastTempoPosition = tempos[tempos.length - 1].position;
maxTicks = Math.max(maxTicks, lastTsPosition);
maxTicks = Math.max(maxTicks, lastTempoPosition);
if (notes.length > 0) {
const lastNote = notes[notes.length - 1];
const lastNoteEndPosition = lastNote.position + lastNote.duration;
maxTicks = Math.max(maxTicks, lastNoteEndPosition);
}
const lastNote = notes[notes.length - 1];
const maxTicks = lastNote.position + lastNote.duration;
return Math.ceil(maxTicks / measureDuration);
return tickToMeasureNumber(maxTicks, timeSignatures, tpqn);
}

// NOTE: 戻り値の単位はtick
Expand Down Expand Up @@ -159,3 +210,23 @@ export function round(value: number, digits: number) {
const powerOf10 = 10 ** digits;
return Math.round(value * powerOf10) / powerOf10;
}

export const createEmptyScore = (): Score => {
return {
tpqn: DEFAULT_TPQN,
tempos: [
{
position: 0,
tempo: DEFAULT_TEMPO,
},
],
timeSignatures: [
{
measureNumber: 1,
beats: DEFAULT_BEATS,
beatType: DEFAULT_BEAT_TYPE,
},
],
notes: [],
};
};
Loading

0 comments on commit 07cec39

Please sign in to comment.