Skip to content

Commit

Permalink
#986 一応ながら追加/削除/歌詞入力が行えるようにする
Browse files Browse the repository at this point in the history
  • Loading branch information
Romot committed Jan 5, 2023
1 parent 749b48a commit 88582e5
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 10 deletions.
103 changes: 93 additions & 10 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,41 @@
y.pitch === 'C' ? 'key-c' : ''
} ${y.pitch === 'F' ? 'key-f' : ''}`"
:id="`sequencer-cell-${x}-${y.midi}`"
@click="addNote(x, y.midi)"
/>
</div>
<div
v-for="(note, index) in notes"
:key="index"
class="sequencer-note"
@dblclick="removeNote(index)"
v-bind:style="{
background: '#ddd',
position: 'absolute',
border: '1px solid #333',
left: `${note.position}px`,
bottom: `${(note.midi + 1) * 24}px`,
bottom: `${note.midi * 24}px`,
width: `${note.duration}px`,
height: '24px',
zIndex: 10,
}"
>
{{ note.midi }}
<input
type="text"
class="sequencer-note-lyric"
:value="note.lyric"
@input="(e) => setLyric(index, e)"
/>
<div class="sequencer-note-bar" />
</div>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent, computed, ref } from "vue";
import { defineComponent, computed } from "vue";
import { useStore } from "@/store";
import { midiKeys } from "@/helpers/singHelper";
import {
midiKeys,
getPitchFromMidi,
getDoremiFromMidi,
} from "@/helpers/singHelper";
export default defineComponent({
name: "SingScoreSequencer",
Expand All @@ -66,19 +75,63 @@ export default defineComponent({
const minDuration = resolution * 4 * 16;
const lastNote = store.state.score?.notes.slice(-1)[0];
const totalDuration = lastNote
? Math.min(lastNote.position + lastNote.duration, minDuration)
? Math.max(lastNote.position + lastNote.duration, minDuration)
: minDuration;
// NOTE: グリッド幅1/16: 設定できるようにする必要あり
const gridDuration = resolution / 4;
return [...Array(Math.ceil(totalDuration / gridDuration)).keys()];
return [...Array(Math.ceil(totalDuration / gridDuration)).keys()].map(
(gridNum) => gridNum * gridDuration
);
});
const notes = computed(() => store.state.score?.notes);
const timeSignatures = computed(() => store.state.score?.timeSignatures);
const zoomX = computed(() => store.state.sequencerZoomX);
const zoomY = computed(() => store.state.sequencerZoomY);
const addNote = (position: number, midi: number) => {
// NOTE: フォーカスなど動作変更
store.dispatch("ADD_NOTE", {
note: {
position,
midi,
duration: 120,
lyric: getDoremiFromMidi(midi),
},
});
};
const removeNote = (index: number) => {
store.dispatch("REMOVE_NOTE", { index });
};
const setLyric = (index: number, event: Event) => {
if (!(event.target instanceof HTMLInputElement)) {
return;
}
if (event.target.value && store.state.score) {
const currentNote = store.state.score?.notes[index];
const lyric = event.target.value;
store.dispatch("CHANGE_NOTE", {
index,
note: {
...currentNote,
lyric,
},
});
}
};
return {
timeSignatures,
gridY,
gridX,
notes,
zoomX,
zoomY,
getPitchFromMidi,
addNote,
removeNote,
setLyric,
};
},
});
Expand Down Expand Up @@ -184,5 +237,35 @@ export default defineComponent({
&.key-c {
border-bottom: 1px solid #ccc;
}
&:hover {
background: rgba(colors.$primary-light-rgb, 0.5);
cursor: pointer;
}
}
.sequencer-note {
position: absolute;
}
.sequencer-note-lyric {
background: white;
border: 0;
border: 1px solid colors.$primary-light;
border-radius: 2px 2px 0 0;
font-weight: bold;
outline: none;
position: absolute;
top: -20px;
left: 4px;
width: 3rem;
}
.sequencer-note-bar {
background: colors.$primary;
border-radius: 2px;
height: 24px;
padding: 0 4px;
width: 100%;
}
</style>
19 changes: 19 additions & 0 deletions src/helpers/singHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ export function getPitchFromMidi(midi: number): string {
return mapPitches[pitchPos];
}

export function getDoremiFromMidi(midi: number): string {
const mapPitches: Array<string> = [
"ド",
"ド",
"レ",
"レ",
"ミ",
"ファ",
"ファ",
"ソ",
"ソ",
"ラ",
"ラ",
"ラ",
];
const pitchPos = midi % 12;
return mapPitches[pitchPos];
}

export function getOctaveFromMidi(midi: number): number {
return Math.floor(midi / 12) - 1;
}
Expand Down
46 changes: 46 additions & 0 deletions src/store/singing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,52 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
},
},

ADD_NOTE: {
mutation(state, { note }: { note: Note }) {
if (state.score) {
const notes = [...state.score.notes].concat(note).sort((a, b) => {
return a.position < b.position ? -1 : 1;
});
state.score.notes = notes;
}
},
// ノートを追加する
// NOTE: 重複削除など別途追加
async action({ state, commit }, { note }: { note: Note }) {
if (state.score === undefined) {
throw new Error("Score is not initialized.");
}
commit("ADD_NOTE", { note });
},
},

CHANGE_NOTE: {
mutation(state, { index, note }: { index: number; note: Note }) {
state.score?.notes.splice(index, 1, note);
},
async action(
{ state, commit },
{ index, note }: { index: number; note: Note }
) {
if (state.score === undefined) {
throw new Error("Score is not initialized.");
}
commit("CHANGE_NOTE", { index, note });
},
},

REMOVE_NOTE: {
mutation(state, { index }: { index: number }) {
state.score?.notes.splice(index, 1);
},
async action({ state, commit }, { index }: { index: number }) {
if (state.score === undefined) {
throw new Error("Score is not initialized.");
}
commit("REMOVE_NOTE", { index });
},
},

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

ADD_NOTE: {
mutation: { note: Note };
action(payload: { note: Note }): void;
};

CHANGE_NOTE: {
mutation: { index: number; note: Note };
action(payload: { index: number; note: Note }): void;
};

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

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

0 comments on commit 88582e5

Please sign in to comment.