Skip to content

Commit

Permalink
project-s: テンポ・拍子の表示と設定を行う機能を追加 (#1025)
Browse files Browse the repository at this point in the history
* テンポ・拍子の表示と設定を行う機能を追加

* 不要な引数チェックを削除、テンポを四捨五入するように変更

* positionの値のチェックを追加

* 値が0の場合リターンするように修正

* ADDからSETに名前を変更
  • Loading branch information
sigprogramming authored Nov 25, 2022
1 parent 7a45861 commit 99a02ed
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 11 deletions.
132 changes: 122 additions & 10 deletions src/components/Sing/ToolBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,46 @@
<button type="button" class="sing-button-temp">戻る</button>
<button type="button" class="sing-button-temp">再生</button>
<div class="sing-player-position">00:00</div>
<input type="number" value="120" class="sing-bpm" />
<input type="number" value="4" class="sing-tempo" />/
<input type="number" value="4" class="sing-tempo" />
<q-input
type="number"
:model-value="tempoInputBuffer"
dense
hide-bottom-space
class="sing-tempo"
@update:model-value="setTempoInputBuffer"
@change="setTempo()"
>
<template v-slot:prepend>
<div />
</template>
</q-input>
<q-input
type="number"
:model-value="beatsInputBuffer"
dense
hide-bottom-space
class="sing-time-signature"
@update:model-value="setBeatsInputBuffer"
@change="setTimeSignature()"
>
<template v-slot:prepend>
<div />
</template>
</q-input>
/
<q-input
type="number"
:model-value="beatTypeInputBuffer"
dense
hide-bottom-space
class="sing-time-signature"
@update:model-value="setBeatTypeInputBuffer"
@change="setTimeSignature()"
>
<template v-slot:prepend>
<div />
</template>
</q-input>
</div>
<div class="sing-setting">
<input type="range" min="0" max="100" class="sing-volume" />
Expand All @@ -25,7 +62,7 @@
</template>

<script lang="ts">
import { defineComponent, computed } from "vue";
import { defineComponent, computed, watch, ref } from "vue";
import { useStore } from "@/store";
export default defineComponent({
Expand Down Expand Up @@ -62,12 +99,87 @@ export default defineComponent({
)?.iconPath
);
const tempoInputBuffer = ref(0);
const beatsInputBuffer = ref(0);
const beatTypeInputBuffer = ref(0);
const setTempoInputBuffer = (tempoStr: string) => {
const tempo = Number(tempoStr);
if (Number.isNaN(tempo) || tempo <= 0) return;
tempoInputBuffer.value = tempo;
};
const setBeatsInputBuffer = (beatsStr: string) => {
const beats = Number(beatsStr);
if (!Number.isInteger(beats) || beats <= 0) return;
beatsInputBuffer.value = beats;
};
const setBeatTypeInputBuffer = (beatTypeStr: string) => {
const beatType = Number(beatTypeStr);
if (!Number.isInteger(beatType) || beatType <= 0) return;
beatTypeInputBuffer.value = beatType;
};
const tempos = computed(() => store.state.score?.tempos);
const timeSignatures = computed(() => store.state.score?.timeSignatures);
watch(
tempos,
() => {
tempoInputBuffer.value = tempos.value?.[0].tempo ?? 0;
},
{ deep: true }
);
watch(
timeSignatures,
() => {
beatsInputBuffer.value = timeSignatures.value?.[0].beats ?? 0;
},
{ deep: true }
);
watch(
timeSignatures,
() => {
beatTypeInputBuffer.value = timeSignatures.value?.[0].beatType ?? 0;
},
{ deep: true }
);
const setTempo = async () => {
const tempo = tempoInputBuffer.value;
if (tempo === 0) return;
await store.dispatch("SET_TEMPO", {
tempo: {
position: 0,
tempo: tempo,
},
});
};
const setTimeSignature = async () => {
const beats = beatsInputBuffer.value;
const beatType = beatTypeInputBuffer.value;
if (beats === 0 || beatType === 0) return;
await store.dispatch("SET_TIME_SIGNATURE", {
timeSignature: {
position: 0,
beats: beats,
beatType: beatType,
},
});
};
return {
isShowSinger,
toggleShowSinger,
userOrderedCharacterInfos,
selectedCharacterInfo,
selectedStyleIconPath,
tempoInputBuffer,
beatsInputBuffer,
beatTypeInputBuffer,
setTempoInputBuffer,
setBeatsInputBuffer,
setBeatTypeInputBuffer,
setTempo,
setTimeSignature,
};
},
});
Expand Down Expand Up @@ -120,14 +232,14 @@ export default defineComponent({
margin: 0 4px;
}
.sing-bpm {
.sing-tempo {
margin: 0 4px;
width: 56px;
width: 64px;
}
.sing-tempo {
.sing-time-signature {
margin: 0 4px;
width: 32px;
width: 36px;
}
.sing-player-position {
Expand Down
160 changes: 159 additions & 1 deletion src/store/singing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Note, Score, SingingStoreState, SingingStoreTypes } from "./type";
import {
Score,
Tempo,
TimeSignature,
Note,
SingingStoreState,
SingingStoreTypes,
} from "./type";
import { createPartialStore } from "./vuex";
import { createUILockAction } from "./ui";
import { Midi } from "@tonejs/midi";
Expand Down Expand Up @@ -80,6 +87,17 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
timeSignatures: [{ position: 0, beats: 4, beatType: 4 }],
notes: [],
};
if (score.tempos.length !== 1 || score.tempos[0].position !== 0) {
throw new Error("Tempo does not exist at the beginning of the score.");
}
if (
score.timeSignatures.length !== 1 ||
score.timeSignatures[0].position !== 0
) {
throw new Error(
"Time signature does not exist at the beginning of the score."
);
}
return score;
},
},
Expand All @@ -93,6 +111,146 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
},
},

SET_TEMPO: {
mutation(state, { index, tempo }: { index: number; tempo: Tempo }) {
state.score?.tempos.splice(index, 0, tempo);
},
// テンポを設定する。既に同じ位置にテンポが存在する場合は置き換える。
async action({ state, commit }, { tempo }: { tempo: Tempo }) {
const score = state.score;
if (score === undefined || score.tempos.length === 0) {
throw new Error("Score is not initialized.");
}
if (
Number.isNaN(tempo.position) ||
tempo.position < 0 ||
Number.isNaN(tempo.tempo) ||
tempo.tempo <= 0
) {
throw new Error("The value is invalid.");
}
const duplicate = score.tempos.some((value) => {
return value.position === tempo.position;
});
const index = score.tempos.findIndex((value) => {
return value.position >= tempo.position;
});
if (index === -1) return;

const round = (value: number, digits: number) => {
const powerOf10 = 10 ** digits;
return Math.round(value * powerOf10) / powerOf10;
};

tempo.tempo = round(tempo.tempo, 2);

if (duplicate) {
commit("REMOVE_TEMPO", { index });
}
commit("SET_TEMPO", { index, tempo });
},
},

REMOVE_TEMPO: {
mutation(state, { index }: { index: number }) {
state.score?.tempos.splice(index, 1);
},
// テンポを削除する。先頭のテンポの場合はデフォルトのテンポに置き換える。
async action(
{ state, commit, dispatch },
{ position }: { position: number }
) {
const emptyScore = await dispatch("GET_EMPTY_SCORE");
const defaultTempo = emptyScore.tempos[0];

const score = state.score;
if (score === undefined || score.tempos.length === 0) {
throw new Error("Score is not initialized.");
}
const index = score.tempos.findIndex((value) => {
return value.position === position;
});
if (index === -1) return;

commit("REMOVE_TEMPO", { index });
if (score.tempos.length === 0) {
commit("SET_TEMPO", { index, tempo: defaultTempo });
}
},
},

SET_TIME_SIGNATURE: {
mutation(
state,
{ index, timeSignature }: { index: number; timeSignature: TimeSignature }
) {
state.score?.timeSignatures.splice(index, 0, timeSignature);
},
// 拍子を設定する。既に同じ位置に拍子が存在する場合は置き換える。
async action(
{ state, commit },
{ timeSignature }: { timeSignature: TimeSignature }
) {
const score = state.score;
if (score === undefined || score.timeSignatures.length === 0) {
throw new Error("Score is not initialized.");
}
if (
Number.isNaN(timeSignature.position) ||
timeSignature.position < 0 ||
!Number.isInteger(timeSignature.beats) ||
!Number.isInteger(timeSignature.beatType) ||
timeSignature.beats <= 0 ||
timeSignature.beatType <= 0
) {
throw new Error("The value is invalid.");
}
const duplicate = score.timeSignatures.some((value) => {
return value.position === timeSignature.position;
});
const index = score.timeSignatures.findIndex((value) => {
return value.position >= timeSignature.position;
});
if (index === -1) return;

if (duplicate) {
commit("REMOVE_TIME_SIGNATURE", { index });
}
commit("SET_TIME_SIGNATURE", { index, timeSignature });
},
},

REMOVE_TIME_SIGNATURE: {
mutation(state, { index }: { index: number }) {
state.score?.timeSignatures.splice(index, 1);
},
// 拍子を削除する。先頭の拍子の場合はデフォルトの拍子に置き換える。
async action(
{ state, commit, dispatch },
{ position }: { position: number }
) {
const emptyScore = await dispatch("GET_EMPTY_SCORE");
const defaultTimeSignature = emptyScore.timeSignatures[0];

const score = state.score;
if (score === undefined || score.timeSignatures.length === 0) {
throw new Error("Score is not initialized.");
}
const index = score.timeSignatures.findIndex((value) => {
return value.position === position;
});
if (index === -1) return;

commit("REMOVE_TIME_SIGNATURE", { index });
if (score.timeSignatures.length === 0) {
commit("SET_TIME_SIGNATURE", {
index,
timeSignature: defaultTimeSignature,
});
}
},
},

IMPORT_MIDI_FILE: {
action: createUILockAction(
async ({ dispatch }, { filePath }: { filePath?: string }) => {
Expand Down
20 changes: 20 additions & 0 deletions src/store/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,26 @@ export type SingingStoreTypes = {
action(payload: { score: Score }): void;
};

SET_TEMPO: {
mutation: { index: number; tempo: Tempo };
action(payload: { tempo: Tempo }): void;
};

REMOVE_TEMPO: {
mutation: { index: number };
action(payload: { position: number }): void;
};

SET_TIME_SIGNATURE: {
mutation: { index: number; timeSignature: TimeSignature };
action(payload: { timeSignature: TimeSignature }): void;
};

REMOVE_TIME_SIGNATURE: {
mutation: { index: number };
action(payload: { position: number }): void;
};

IMPORT_MIDI_FILE: {
action(payload: { filePath?: string }): void;
};
Expand Down

0 comments on commit 99a02ed

Please sign in to comment.