From fb522beb2085559b1c11488725952c585e30c4b3 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 20 Sep 2024 20:01:24 +0900 Subject: [PATCH 1/9] =?UTF-8?q?change:=20generate=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/generate/page.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/generate/page.module.scss b/src/app/generate/page.module.scss index a38a148..ec01a80 100644 --- a/src/app/generate/page.module.scss +++ b/src/app/generate/page.module.scss @@ -5,7 +5,7 @@ overflow: hidden; display: grid; grid-template-columns: 100px 1fr; - grid-template-rows: 1fr 100px 150px; + grid-template-rows: 1fr 60px 60px; .piano_area { grid-column: 1 / 3; From 9e69aafe93c9d2ca1e99040c86f96b20ba6b417a Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 20 Sep 2024 20:15:47 +0900 Subject: [PATCH 2/9] =?UTF-8?q?change:=20=E3=82=B9=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.js | 2 +- public/images/logo.svg | 20 ++++++++++++++++ .../FreeDrawingCanvas/styles.module.scss | 1 - src/app/page.module.scss | 24 +++++++++++++++++++ src/app/page.tsx | 11 ++++++--- .../components/SpaceKeyIcon/style.module.scss | 2 +- 6 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 public/images/logo.svg create mode 100644 src/app/page.module.scss diff --git a/eslint.config.js b/eslint.config.js index a1373ee..b3a3fe9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -30,7 +30,7 @@ export default [ 'react-hooks': fixupPluginRules(hooksPlugin), }, rules: { - '@next/next/no-img-element': 'error', + '@next/next/no-img-element': 'off', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], 'no-console': [ 'error', diff --git a/public/images/logo.svg b/public/images/logo.svg new file mode 100644 index 0000000..9219924 --- /dev/null +++ b/public/images/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/draw/features/FreeDrawing/components/FreeDrawingCanvas/styles.module.scss b/src/app/draw/features/FreeDrawing/components/FreeDrawingCanvas/styles.module.scss index 3a6dc4c..ba7c7c9 100644 --- a/src/app/draw/features/FreeDrawing/components/FreeDrawingCanvas/styles.module.scss +++ b/src/app/draw/features/FreeDrawing/components/FreeDrawingCanvas/styles.module.scss @@ -1,7 +1,6 @@ .canvas { width: 100%; height: 100%; - border: 0.4px solid #000; cursor: url('/images/pencil-icon.png') 0 32, diff --git a/src/app/page.module.scss b/src/app/page.module.scss new file mode 100644 index 0000000..4d7c9ea --- /dev/null +++ b/src/app/page.module.scss @@ -0,0 +1,24 @@ +.main { + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + + .center { + height: fit-content; + display: flex; + flex-direction: column; + gap: 20px; + + a { + font-size: 2rem; + color: $primary-color; + text-align: center; + cursor: pointer; + + &:hover { + @include hoveredColor($primary-color, $transition-time, color); + } + } + } +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 5dd3fdf..03d54ed 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,12 @@ +import styles from './page.module.scss'; + export default function MainPage() { return ( -
-

Hello, World!

-
+
+
+ logo + start +
+
); } diff --git a/src/app/tap/features/RecordRhythm/components/SpaceKeyIcon/style.module.scss b/src/app/tap/features/RecordRhythm/components/SpaceKeyIcon/style.module.scss index 0a9766b..9291c77 100644 --- a/src/app/tap/features/RecordRhythm/components/SpaceKeyIcon/style.module.scss +++ b/src/app/tap/features/RecordRhythm/components/SpaceKeyIcon/style.module.scss @@ -7,5 +7,5 @@ width: 150px; height: 40px; border-radius: $radius-xl; - box-shadow: inset 0px 0px 10px 0px rgba(0, 0, 0, 0.24); + box-shadow: inset 0px 0px 3px 0px rgba(0, 0, 0, 0.24); } From 32049210e7e6de9f14d19b86c1fa5a56460e4bd7 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 20 Sep 2024 23:16:32 +0900 Subject: [PATCH 3/9] wip: export midi --- .../generate/_components/Controller/index.tsx | 13 ++++++++-- src/const/sample.ts | 25 ++++++++++++++++++ src/functions/midi.spec.ts | 15 +++++++++++ src/functions/midi.ts | 26 +++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/const/sample.ts create mode 100644 src/functions/midi.spec.ts create mode 100644 src/functions/midi.ts diff --git a/src/app/generate/_components/Controller/index.tsx b/src/app/generate/_components/Controller/index.tsx index 708781f..7fb15aa 100644 --- a/src/app/generate/_components/Controller/index.tsx +++ b/src/app/generate/_components/Controller/index.tsx @@ -4,8 +4,15 @@ import IonIcon from '@reacticons/ionicons'; import styles from './index.module.scss'; import IconButton from '@/components/share/IconButton'; +import { music } from '@/samples'; +import { useAtomValue } from 'jotai'; +import { bpnAtom, minNoteDurationAtom } from '@/stores/settings'; +import { exportMidi } from '@/functions/midi'; export default function Controller() { + const bpm = useAtomValue(bpnAtom); + const minNoteDuration = useAtomValue(minNoteDurationAtom); + return (
@@ -37,8 +44,10 @@ export default function Controller() {
- } onClick={() => console.log('clicked')} /> - } onClick={() => console.log('clicked')} /> + } + onClick={() => exportMidi(music, bpm, minNoteDuration)} + />
); diff --git a/src/const/sample.ts b/src/const/sample.ts new file mode 100644 index 0000000..d503783 --- /dev/null +++ b/src/const/sample.ts @@ -0,0 +1,25 @@ +import { Bar, DiatonicChord, Music, Note } from '@/models'; + +const music: Music = new Music({ + bars: [ + new Bar({ + notes: [ + new Note({ + start: 0, + duration: 8, + octave: 4, + scale: 'C', + }), + ], + chordIndex: 0, + chords: [ + new DiatonicChord({ + start: 0, + duration: 16, + octave: 2, + scale: 'C', + }), + ], + }), + ], +}); diff --git a/src/functions/midi.spec.ts b/src/functions/midi.spec.ts new file mode 100644 index 0000000..e4bb359 --- /dev/null +++ b/src/functions/midi.spec.ts @@ -0,0 +1,15 @@ +import { describe, test, expect } from 'vitest'; +import { createMidiFile, save } from './midi'; +import { music } from '@/samples'; + +describe('MIDI', () => { + test('exportMidi', () => { + const midiContent = createMidiFile(music, 1); + + // Blob型であることを確認 + expect(midiContent).toBeInstanceOf(Blob); + + const filename = 'test.mid'; + save(midiContent, filename); + }); +}); diff --git a/src/functions/midi.ts b/src/functions/midi.ts new file mode 100644 index 0000000..4f3ed15 --- /dev/null +++ b/src/functions/midi.ts @@ -0,0 +1,26 @@ +import { Music } from '@/models'; +import { Settings } from '@/types'; + +export function exportMidi(music: Music, bpm: Settings['bpm'], minNoteDuration: Settings['minNoteDuration']) { + const durationRate = 60 / bpm / minNoteDuration; + const bynary = createMidiFile(music, durationRate); + save(bynary as unknown as Blob, 'music.mid'); +} + +/** + * MIDIファイルの中身を作成する + * + * @param music 音楽データ + * @param durationRate 音符の長さの倍率 (note.duration * durationRate = 音符の再生時間) + */ +export function createMidiFile(music: Music, durationRate: number): Blob {} + +export function save(blob: Blob, name: string) { + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', name); + link.click(); + link.remove(); + URL.revokeObjectURL(url); +} From 70d0685175d0c2bf88d11b7be9cb358a5f2caca1 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 20 Sep 2024 23:23:18 +0900 Subject: [PATCH 4/9] =?UTF-8?q?fix:=20=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E6=A9=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/generate/_components/Controller/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/generate/_components/Controller/index.tsx b/src/app/generate/_components/Controller/index.tsx index 53c6dde..98cd74d 100644 --- a/src/app/generate/_components/Controller/index.tsx +++ b/src/app/generate/_components/Controller/index.tsx @@ -8,8 +8,11 @@ import { music } from '@/samples'; import { useAtomValue } from 'jotai'; import { bpnAtom, minNoteDurationAtom } from '@/stores/settings'; import { exportMidi } from '@/functions/midi'; +import usePlayer from '@/hooks/usePlayer'; export default function Controller() { + const { isPlaying, play, pause, nextBar, prevBar, rewind, forward } = usePlayer(); + const bpm = useAtomValue(bpnAtom); const minNoteDuration = useAtomValue(minNoteDurationAtom); From 41c5bc1435b9276bf40cded1be33ee5887e70f23 Mon Sep 17 00:00:00 2001 From: k21091 Date: Fri, 20 Sep 2024 23:44:51 +0900 Subject: [PATCH 5/9] =?UTF-8?q?add:=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + yarn.lock | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/package.json b/package.json index e4f3d3e..1c99082 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@tauri-apps/api": ">=2.0.0-rc.0", "@tauri-apps/plugin-shell": ">=2.0.0-rc.0", "jotai": "^2.9.3", + "midi-writer-js": "^3.1.1", "next": "^14.2.10", "paper": "^0.12.18", "react": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index 0d8cf99..9a27559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2215,6 +2215,25 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== +"@tonaljs/midi@^4.9.0": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@tonaljs/midi/-/midi-4.10.0.tgz#0916bbb1f194d38dc8d1169b2101f0e5b198f805" + integrity sha512-LjwEzcBwJSNMLD+qDwVBWvkmIOAs59TPdlTQlooP3S1Au0jy7T+bZeIbBKtLEcDG14S2zyYZEZ1Mo+cxcumMmg== + dependencies: + "@tonaljs/pitch-note" "6.0.0" + +"@tonaljs/pitch-note@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@tonaljs/pitch-note/-/pitch-note-6.0.0.tgz#dc12c753042fd62d717a61647417b456481da5ff" + integrity sha512-m4Ei7zwSsKwotVfnodA7m1SR7zD5NNIea+V7Mo35EcK32ZJBg+SvxdwgfNNdLO8bkDbVrZIgVYqeP3R3Jq7VFQ== + dependencies: + "@tonaljs/pitch" "5.0.2" + +"@tonaljs/pitch@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@tonaljs/pitch/-/pitch-5.0.2.tgz#ca2dcd60b99e90536f52293d2491187edfbc9967" + integrity sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg== + "@types/aria-query@^5.0.1": version "5.0.4" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" @@ -6514,6 +6533,13 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" +midi-writer-js@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/midi-writer-js/-/midi-writer-js-3.1.1.tgz#8a53bf762a7ea2a77fd8e693236cafe094f617da" + integrity sha512-ruRzUWtmcvD7xQcrKRk9fH+1BELv8x07QJxhpM4PS5n6+MOyPb39ifxTV/oI1hHPgKMSuhnhWw2MaGWUAvH9DA== + dependencies: + "@tonaljs/midi" "^4.9.0" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" From bb41d23d8f11a35c00e8db6d9ceaa7c57e5e0a67 Mon Sep 17 00:00:00 2001 From: k21091 Date: Fri, 20 Sep 2024 23:45:23 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix:=E4=B8=80=E9=83=A8=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=8C=E3=83=86=E3=82=B9=E3=83=88=E7=92=B0=E5=A2=83=E3=81=A7?= =?UTF-8?q?=E4=BD=BF=E3=81=88=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=83=A2=E3=83=83=E3=82=AF=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/midi.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/functions/midi.spec.ts b/src/functions/midi.spec.ts index e4bb359..c29e02b 100644 --- a/src/functions/midi.spec.ts +++ b/src/functions/midi.spec.ts @@ -1,8 +1,14 @@ -import { describe, test, expect } from 'vitest'; +import { describe, test, expect, beforeAll, vi } from 'vitest'; import { createMidiFile, save } from './midi'; import { music } from '@/samples'; describe('MIDI', () => { + // midi.spec.ts のテストファイルで追加 + beforeAll(() => { + global.URL.createObjectURL = vi.fn(() => 'mocked-url'); + // URL.revokeObjectURLのモック + global.URL.revokeObjectURL = vi.fn(); + }); test('exportMidi', () => { const midiContent = createMidiFile(music, 1); From 5ed74824109b8fe7a0b68baa389b8d0889a24543 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Sat, 21 Sep 2024 00:38:50 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20music=20=E3=82=92=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=AA=E3=81=8F=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/generate/_components/Controller/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/generate/_components/Controller/index.tsx b/src/app/generate/_components/Controller/index.tsx index 98cd74d..91165a6 100644 --- a/src/app/generate/_components/Controller/index.tsx +++ b/src/app/generate/_components/Controller/index.tsx @@ -4,15 +4,16 @@ import IonIcon from '@reacticons/ionicons'; import styles from './index.module.scss'; import IconButton from '@/components/share/IconButton'; -import { music } from '@/samples'; import { useAtomValue } from 'jotai'; import { bpnAtom, minNoteDurationAtom } from '@/stores/settings'; import { exportMidi } from '@/functions/midi'; import usePlayer from '@/hooks/usePlayer'; +import { musicAtom } from '@/stores/music'; export default function Controller() { const { isPlaying, play, pause, nextBar, prevBar, rewind, forward } = usePlayer(); + const music = useAtomValue(musicAtom); const bpm = useAtomValue(bpnAtom); const minNoteDuration = useAtomValue(minNoteDurationAtom); From 36b3ae916dcc8693f518628dfe0213029b4ab110 Mon Sep 17 00:00:00 2001 From: k21091 Date: Sat, 21 Sep 2024 00:41:02 +0900 Subject: [PATCH 8/9] =?UTF-8?q?hoge:=E3=83=9E=E3=83=BC=E3=82=B8=E3=81=AE?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=83=97=E3=83=83=E3=82=B7=E3=83=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/midi.ts | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/functions/midi.ts b/src/functions/midi.ts index 4f3ed15..71e3b59 100644 --- a/src/functions/midi.ts +++ b/src/functions/midi.ts @@ -1,5 +1,6 @@ import { Music } from '@/models'; import { Settings } from '@/types'; +import MidiWriter from 'midi-writer-js'; export function exportMidi(music: Music, bpm: Settings['bpm'], minNoteDuration: Settings['minNoteDuration']) { const durationRate = 60 / bpm / minNoteDuration; @@ -13,8 +14,42 @@ export function exportMidi(music: Music, bpm: Settings['bpm'], minNoteDuration: * @param music 音楽データ * @param durationRate 音符の長さの倍率 (note.duration * durationRate = 音符の再生時間) */ -export function createMidiFile(music: Music, durationRate: number): Blob {} +export function createMidiFile(music: Music, durationRate: number): Blob { + const track = new MidiWriter.Track(); + // テンポ設定 + track.setTempo(Math.round(60 / durationRate)); + + // 音符の長さのマッピング + const durationMapping: Record = { + 1: '4', // 全音符 + 0.5: '8', // 1/2音符 + 0.25: '16' // 1/4音符 + }; + + // Musicオブジェクトから各Barを処理 + music.bars.forEach((bar) => { + bar.notes.forEach((note) => { + // マッピングを使用する際、キーを数値から文字列に変換 + const durationStr = durationMapping[Number(Math.round(note.duration * durationRate))] || '4'; + + const startTick = Math.round(note.start * 480 * durationRate); // MIDIのタイムベース(例: 480) + + // MIDIノートを生成 + const midiNote = new MidiWriter.NoteEvent({ + pitch: [`${note.getName()}`], // "C4"のような音名 + duration: durationStr, // 持続時間 + startTick: startTick, // 開始位置 + }); + // トラックにノートを追加 + track.addEvent(midiNote); + }); + }); + + // MIDIファイルを生成 + const writer = new MidiWriter.Writer(track); + return new Blob([writer.buildFile()], { type: 'audio/midi' }); +} export function save(blob: Blob, name: string) { const link = document.createElement('a'); const url = URL.createObjectURL(blob); From cd12cc7d34b8c59880432c8bd348a74ad61737fd Mon Sep 17 00:00:00 2001 From: k21091 Date: Sat, 21 Sep 2024 03:42:22 +0900 Subject: [PATCH 9/9] =?UTF-8?q?wip:=E5=8A=9B=E5=B0=BD=E3=81=8D=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/midi.ts | 73 ++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/src/functions/midi.ts b/src/functions/midi.ts index 71e3b59..d49e1cb 100644 --- a/src/functions/midi.ts +++ b/src/functions/midi.ts @@ -4,7 +4,7 @@ import MidiWriter from 'midi-writer-js'; export function exportMidi(music: Music, bpm: Settings['bpm'], minNoteDuration: Settings['minNoteDuration']) { const durationRate = 60 / bpm / minNoteDuration; - const bynary = createMidiFile(music, durationRate); + const bynary = createMidiFile(music, durationRate, bpm); save(bynary as unknown as Blob, 'music.mid'); } @@ -14,40 +14,43 @@ export function exportMidi(music: Music, bpm: Settings['bpm'], minNoteDuration: * @param music 音楽データ * @param durationRate 音符の長さの倍率 (note.duration * durationRate = 音符の再生時間) */ -export function createMidiFile(music: Music, durationRate: number): Blob { +export function createMidiFile(music: Music, durationRate: number, bpm: number): Blob { + // Cメジャーコードの音名を取得 + const chord = ["C4", "E4", "G4"] const track = new MidiWriter.Track(); - + const cordTrack = new MidiWriter.Track(); + // テンポ設定 - track.setTempo(Math.round(60 / durationRate)); + track.setTempo(bpm); + cordTrack.setTempo(bpm); + // ティック単位の設定 + const ppq = durationRate * bpm; - // 音符の長さのマッピング - const durationMapping: Record = { - 1: '4', // 全音符 - 0.5: '8', // 1/2音符 - 0.25: '16' // 1/4音符 - }; - // Musicオブジェクトから各Barを処理 music.bars.forEach((bar) => { bar.notes.forEach((note) => { - // マッピングを使用する際、キーを数値から文字列に変換 - const durationStr = durationMapping[Number(Math.round(note.duration * durationRate))] || '4'; - - const startTick = Math.round(note.start * 480 * durationRate); // MIDIのタイムベース(例: 480) + const midiNoteNumber = noteNameToMidi(note.getName()); + const durationStr = `T${Math.round(ppq * note.duration)}`; // MIDIノートを生成 const midiNote = new MidiWriter.NoteEvent({ - pitch: [`${note.getName()}`], // "C4"のような音名 + pitch: [midiNoteNumber], // "C4"のような音名 duration: durationStr, // 持続時間 - startTick: startTick, // 開始位置 }); // トラックにノートを追加 track.addEvent(midiNote); }); }); + + // ノートを追加(Cメジャーコードを同時に鳴らす) + cordTrack.addEvent(new MidiWriter.NoteEvent({ + pitch: chord, // ["C4", "E4", "G4"] + duration: `T${Math.round(ppq * 16)}`, // 1/4拍 + })); + // MIDIファイルを生成 - const writer = new MidiWriter.Writer(track); + const writer = new MidiWriter.Writer([track,cordTrack]); return new Blob([writer.buildFile()], { type: 'audio/midi' }); } export function save(blob: Blob, name: string) { @@ -59,3 +62,37 @@ export function save(blob: Blob, name: string) { link.remove(); URL.revokeObjectURL(url); } + +// 音名からMIDIノート番号に変換する関数 +function noteNameToMidi(noteName: string): number { + const noteMap: Record = { + C: 0, + 'C#': 1, + Db: 1, + D: 2, + 'D#': 3, + Eb: 3, + E: 4, + F: 5, + 'F#': 6, + Gb: 6, + G: 7, + 'G#': 8, + Ab: 8, + A: 9, + 'A#': 10, + Bb: 10, + B: 11, + }; + + const note = noteName.slice(0, -1); // 音名部分を抽出 (例: "C#") + const octave = parseInt(noteName.slice(-1)); // オクターブ部分を抽出 (例: "4") + + // noteMapで有効な音名が指定されているか確認 + if (!(note in noteMap)) { + throw new Error(`Invalid note name: ${note}`); + } + + // MIDIノート番号 = (12 * オクターブ) + 音の値 + return 12 * (octave + 1) + noteMap[note as keyof typeof noteMap]; +}