Skip to content

Commit

Permalink
鍵盤クリック時とノート編集時に音を鳴らす機能を実装
Browse files Browse the repository at this point in the history
  • Loading branch information
sigprogramming committed Dec 16, 2023
1 parent 1331abb commit acd5751
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 69 deletions.
54 changes: 47 additions & 7 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ import {
keyInfos,
getDoremiFromNoteNumber,
getNumOfMeasures,
ZOOM_X_MIN,
ZOOM_X_MAX,
ZOOM_X_STEP,
ZOOM_Y_MIN,
ZOOM_Y_MAX,
ZOOM_Y_STEP,
PREVIEW_SOUND_DURATION,
} from "@/helpers/singHelper";
export default defineComponent({
Expand All @@ -193,13 +200,6 @@ export default defineComponent({
SequencerPhraseIndicator,
},
setup() {
const ZOOM_X_MIN = 0.2;
const ZOOM_X_MAX = 1;
const ZOOM_X_STEP = 0.05;
const ZOOM_Y_MIN = 0.35;
const ZOOM_Y_MAX = 1;
const ZOOM_Y_STEP = 0.05;
enum DragMode {
NONE = "NONE",
MOVE = "MOVE",
Expand Down Expand Up @@ -334,6 +334,10 @@ export default defineComponent({
lyric,
},
});
store.dispatch("PLAY_PREVIEW_SOUND", {
noteNumber,
duration: PREVIEW_SOUND_DURATION,
});
};
// マウスダウン
Expand All @@ -359,6 +363,20 @@ export default defineComponent({
if (dragMode.value !== DragMode.NONE) {
cancelAnimationFrame(dragId.value);
dragMode.value = DragMode.NONE;
if (selectedNoteIds.value.length === 1) {
const selectedNoteId = selectedNoteIds.value[0];
const selectedNote = state.score.notes.find((value) => {
return value.id === selectedNoteId;
});
if (!selectedNote) {
throw new Error("selectedNote is undefined.");
}
store.dispatch("PLAY_PREVIEW_SOUND", {
noteNumber: selectedNote.noteNumber,
duration: PREVIEW_SOUND_DURATION,
});
}
return;
}
};
Expand Down Expand Up @@ -582,9 +600,11 @@ export default defineComponent({
// キーボードイベント
const handleNotesArrowUp = () => {
let changedNoteNumber: number | undefined;
const newNotes = state.score.notes.map((note) => {
if (selectedNoteIds.value.includes(note.id)) {
const noteNumber = Math.min(note.noteNumber + 1, 127);
changedNoteNumber = noteNumber;
return {
...note,
noteNumber,
Expand All @@ -597,12 +617,23 @@ export default defineComponent({
return;
}
store.dispatch("REPLACE_ALL_NOTES", { notes: newNotes });
if (
changedNoteNumber !== undefined &&
selectedNoteIds.value.length === 1
) {
store.dispatch("PLAY_PREVIEW_SOUND", {
noteNumber: changedNoteNumber,
duration: PREVIEW_SOUND_DURATION,
});
}
};
const handleNotesArrowDown = () => {
let changedNoteNumber: number | undefined;
const newNotes = state.score.notes.map((note) => {
if (selectedNoteIds.value.includes(note.id)) {
const noteNumber = Math.max(note.noteNumber - 1, 0);
changedNoteNumber = noteNumber;
return {
...note,
noteNumber,
Expand All @@ -615,6 +646,15 @@ export default defineComponent({
return;
}
store.dispatch("REPLACE_ALL_NOTES", { notes: newNotes });
if (
changedNoteNumber !== undefined &&
selectedNoteIds.value.length === 1
) {
store.dispatch("PLAY_PREVIEW_SOUND", {
noteNumber: changedNoteNumber,
duration: PREVIEW_SOUND_DURATION,
});
}
};
const handleNotesArrowRight = () => {
Expand Down
133 changes: 91 additions & 42 deletions src/components/Sing/SequencerKeys.vue
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
<template>
<div ref="sequencerKeys" class="sequencer-keys">
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height">
<defs>
<symbol id="white-keys">
<g v-for="(whiteKeyInfo, index) in whiteKeyInfos" :key="index">
<rect
:x="whiteKeyRects[index].x - 0.5"
:y="whiteKeyRects[index].y + 0.5"
:width="whiteKeyRects[index].width"
:height="whiteKeyRects[index].height"
class="sequencer-keys-item-white"
:title="whiteKeyInfo.name"
/>
<text
v-if="whiteKeyInfo.pitch === 'C'"
font-size="10"
:x="blackKeyWidth + 2"
:y="whiteKeyRects[index].y + whiteKeyRects[index].height - 4"
class="sequencer-keys-item-pitchname"
>
{{ whiteKeyInfo.name }}
</text>
</g>
</symbol>
<symbol id="black-keys">
<rect
v-for="(blackKeyInfo, index) in blackKeyInfos"
:key="index"
:x="blackKeyRects[index].x - 0.5"
:y="blackKeyRects[index].y + 0.5"
:width="blackKeyRects[index].width"
:height="blackKeyRects[index].height"
rx="2"
ry="2"
class="sequencer-keys-item-black"
:title="blackKeyInfo.name"
/>
</symbol>
</defs>
<use href="#white-keys" :y="-offset" />
<use href="#black-keys" :y="-offset" />
<g
v-for="(whiteKeyInfo, index) in whiteKeyInfos"
:key="index"
@mousedown="onMouseDown(whiteKeyInfo.noteNumber)"
@mouseenter="onMouseEnter(whiteKeyInfo.noteNumber)"
>
<rect
:x="whiteKeyRects[index].x - 0.5"
:y="whiteKeyRects[index].y + 0.5 - offset"
:width="whiteKeyRects[index].width"
:height="whiteKeyRects[index].height"
:title="whiteKeyInfo.name"
:class="
noteNumberOfKeyBeingPressed === whiteKeyInfo.noteNumber
? 'white-key-being-pressed'
: 'white-key'
"
/>
<text
v-if="whiteKeyInfo.pitch === 'C'"
font-size="10"
:x="blackKeyWidth + 2"
:y="whiteKeyRects[index].y + whiteKeyRects[index].height - 4 - offset"
class="pitchname"
>
{{ whiteKeyInfo.name }}
</text>
</g>
<rect
v-for="(blackKeyInfo, index) in blackKeyInfos"
:key="index"
:x="blackKeyRects[index].x - 0.5"
:y="blackKeyRects[index].y + 0.5 - offset"
:width="blackKeyRects[index].width"
:height="blackKeyRects[index].height"
rx="2"
ry="2"
:title="blackKeyInfo.name"
:class="
noteNumberOfKeyBeingPressed === blackKeyInfo.noteNumber
? 'black-key-being-pressed'
: 'black-key'
"
@mousedown="onMouseDown(blackKeyInfo.noteNumber)"
@mouseenter="onMouseEnter(blackKeyInfo.noteNumber)"
/>
</svg>
</div>
</template>
Expand Down Expand Up @@ -103,10 +110,37 @@ export default defineComponent({
};
});
});
const noteNumberOfKeyBeingPressed = ref<number | undefined>();
const sequencerKeys = ref<HTMLElement | null>(null);
let resizeObserver: ResizeObserver | undefined;
const onMouseDown = (noteNumber: number) => {
noteNumberOfKeyBeingPressed.value = noteNumber;
store.dispatch("PLAY_PREVIEW_SOUND", { noteNumber });
};
const onMouseUp = () => {
if (noteNumberOfKeyBeingPressed.value !== undefined) {
const noteNumber = noteNumberOfKeyBeingPressed.value;
noteNumberOfKeyBeingPressed.value = undefined;
store.dispatch("STOP_PREVIEW_SOUND", { noteNumber });
}
};
const onMouseEnter = (noteNumber: number) => {
if (
noteNumberOfKeyBeingPressed.value !== undefined &&
noteNumberOfKeyBeingPressed.value !== noteNumber
) {
store.dispatch("STOP_PREVIEW_SOUND", {
noteNumber: noteNumberOfKeyBeingPressed.value,
});
noteNumberOfKeyBeingPressed.value = noteNumber;
store.dispatch("PLAY_PREVIEW_SOUND", { noteNumber });
}
};
onMounted(() => {
const sequencerKeysElement = sequencerKeys.value;
if (!sequencerKeysElement) {
Expand All @@ -124,10 +158,13 @@ export default defineComponent({
}
});
resizeObserver.observe(sequencerKeysElement);
document.addEventListener("mouseup", onMouseUp);
});
onUnmounted(() => {
resizeObserver?.disconnect();
document.removeEventListener("mouseup", onMouseUp);
});
return {
Expand All @@ -141,6 +178,9 @@ export default defineComponent({
whiteKeyRects,
blackKeyRects,
sequencerKeys,
noteNumberOfKeyBeingPressed,
onMouseDown,
onMouseEnter,
};
},
});
Expand All @@ -157,16 +197,25 @@ export default defineComponent({
overflow: hidden;
}
.sequencer-keys-item-white {
.white-key {
fill: #fff;
stroke: #ccc;
}
.sequencer-keys-item-black {
.white-key-being-pressed {
fill: colors.$primary;
stroke: colors.$primary;
}
.black-key {
fill: #5a5a5a;
}
.sequencer-keys-item-pitchname {
.black-key-being-pressed {
fill: colors.$primary;
}
.pitchname {
fill: #555;
}
</style>
31 changes: 19 additions & 12 deletions src/components/Sing/SequencerNote.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
getKeyBaseHeight,
tickToBaseX,
noteNumberToBaseY,
PREVIEW_SOUND_DURATION,
} from "@/helpers/singHelper";
export default defineComponent({
Expand Down Expand Up @@ -126,34 +127,40 @@ export default defineComponent({
}
};
const selectThisNote = () => {
const noteIds = [...state.selectedNoteIds, props.note.id];
store.dispatch("SET_SELECTED_NOTE_IDS", {
noteIds,
});
store.dispatch("PLAY_PREVIEW_SOUND", {
noteNumber: props.note.noteNumber,
duration: PREVIEW_SOUND_DURATION,
});
};
const handleKeydown = (event: KeyboardEvent) => {
emit("handleNotesKeydown", event);
};
const handleMouseDown = (event: MouseEvent) => {
if (!state.selectedNoteIds.includes(props.note.id)) {
const noteIds = [...state.selectedNoteIds, props.note.id];
store.dispatch("SET_SELECTED_NOTE_IDS", {
noteIds,
});
selectThisNote();
} else {
emit("handleDragMoveStart", event);
}
};
const handleDragRightStart = (event: MouseEvent) => {
const noteIds = [...state.selectedNoteIds, props.note.id];
store.dispatch("SET_SELECTED_NOTE_IDS", {
noteIds,
});
if (!state.selectedNoteIds.includes(props.note.id)) {
selectThisNote();
}
emit("handleDragRightStart", event);
};
const handleDragLeftStart = (event: MouseEvent) => {
const noteIds = [...state.selectedNoteIds, props.note.id];
store.dispatch("SET_SELECTED_NOTE_IDS", {
noteIds,
});
if (!state.selectedNoteIds.includes(props.note.id)) {
selectThisNote();
}
emit("handleDragLeftStart", event);
};
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/singHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ export const DEFAULT_BEAT_TYPE = 4;
const BASE_X_PER_QUARTER_NOTE = 120;
const BASE_Y_PER_NOTE_NUMBER = 30;

export const ZOOM_X_MIN = 0.2;
export const ZOOM_X_MAX = 1;
export const ZOOM_X_STEP = 0.05;
export const ZOOM_Y_MIN = 0.35;
export const ZOOM_Y_MAX = 1;
export const ZOOM_Y_STEP = 0.05;
export const PREVIEW_SOUND_DURATION = 0.1;

export function noteNumberToFrequency(noteNumber: number) {
return 440 * 2 ** ((noteNumber - 69) / 12);
}
Expand Down
Loading

0 comments on commit acd5751

Please sign in to comment.