Skip to content
edhofdcPublic

About

Audio & YouTube → MIDI converter with Synthesia-style piano visualizer, AI stem separation, sheet music and multi-instrument synth (Next.js + FastAPI + basic-pitch + Demucs)

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MidiMagic

Ubah MP3/WAV/OGG atau tautan YouTube menjadi MIDI, lalu mainkan di piano visualizer ala Synthesia langsung di browser — lengkap dengan not balok, stem separation, multi-instrumen synth, transpose, dan tautan share.

Semua proses berat (transkripsi AI, pemisahan stem, unduh YouTube) jalan lokal di mesin ini; browser hanya menangani playback, visual, dan notasi.

Audio / YouTube ──> yt-dlp + ffmpeg ──> [Demucs stem AI] ──> basic-pitch (AI audio→MIDI)
                          │                                            │
                          └────────────► preview audio ◄───────────────┘
                                                                       │
                                          Next.js UI  ◄──── MIDI ──────┘
                          (falling notes, 88-key piano, Tone.js synth)

Fitur

Fitur Status Catatan
Upload lokal (MP3/WAV/OGG/FLAC/M4A/Opus/WebM) ✅ drag & drop, maks 60 MB
Input URL YouTube ✅ yt-dlp, batas durasi 15 menit
AI Audio → MIDI (polifonik) ✅ basic-pitch (ONNX, CPU), 3 preset ketepatan
Kualitas hasil ✅ pra-proses audio (normalisasi + band-limit + buang perkusi), gabung fragmen not, buang noise
Fallback transkripsi melodi ✅ librosa pyin (monofonik) bila basic-pitch gagal
AI Stem Separation ✅ Demucs 2-stem (vokal/instrumental) atau 4-stem
Interactive Piano Simulator (88 tuts) ✅ tuts menyala mengikuti not yang berbunyi
Synthesia-style falling notes ✅ canvas, warna per pitch, auto-follow, click-to-seek
Play / Pause / Stop / Seek / Volume ✅ transport dengan range-request audio + level meter
Sustain pedal otomatis ✅ pedal dideteksi dari rekaman (CC64), mode Auto/On/Off
Tempo & Pitch shifting ✅ 25%–200% tanpa ubah pitch; transpose −12…+12 semitone
Multi-Instrument Synth ✅ 4 instrumen berbasis rekaman asli (sample) + 2 sintetis
Export ✅ .mid asli (sudah membawa pedal), .mid transpose/tempo
Share link ✅ slug unik, halaman /share/<slug> read-only + penghitung kunjungan
Layout mobile ✅ bottom tab bar, kartu, piano roll dipendekkan, tanpa scroll horizontal

Arsitektur

Dua proses, karena bagian AI-nya Python:

Bagian Stack Port
web/ — UI Next.js 16 (App Router) · Tailwind v4 · Framer Motion · Tone.js · @tonejs/midi 8891
backend/ — pipeline FastAPI · yt-dlp · Demucs · basic-pitch (ONNX) · librosa · pretty_midi · ffmpeg 8892

State disimpan di SQLite (backend/data/midimagic.db) dengan tabel jobs, shares, events — additive: setiap konversi jadi record baru, tidak pernah menimpa yang lama.

Instalasi

1. Sistem

sudo apt install ffmpeg python3-venv
node -v   # butuh Node 20+ — juga dipakai yt-dlp sebagai JS runtime

YouTube: sejak yt-dlp 2025.x ekstraksi YouTube butuh JS runtime. Backend memanggilnya dengan --js-runtimes node (lihat MIDIMAGIC_YT_JS_RUNTIME). Tanpa itu banyak format hilang dan unduhan sering gagal.

2. Backend

cd backend
python3 -m venv .venv
.venv/bin/pip install -U pip wheel setuptools
.venv/bin/pip install -r requirements.txt

# PyTorch CPU (jangan pakai index default, itu CUDA dan besar)
.venv/bin/pip install --index-url https://download.pytorch.org/whl/cpu torch torchaudio

# basic-pitch dari jalur ONNX: paket normal mem-pin numpy<2 dan gagal build di Python 3.13
.venv/bin/pip install --no-deps basic-pitch
.venv/bin/pip install onnxruntime mir_eval resampy

Catatan Python 3.13: pip install basic-pitch (path TensorFlow) akan gagal: resolvernya menurunkan numpy ke versi tanpa wheel cp313 lalu mencoba build dari source. Pakai urutan --no-deps + onnxruntime di atas — modelnya identik (basic_pitch/saved_models/icassp_2022/nmp.onnx), jauh lebih ringan dan cepat.

3. Sampel instrumen (aset audio)

Instrumennya memakai rekaman asli, bukan oscillator:

Instrumen Sumber Sample
Grand Piano Salamander Grand Piano (Alexander Holm, CC-BY 3.0) 30 (A0–C8)
Electric Piano MusyngKite electric_piano_1 30
Guitar (Nylon) MusyngKite acoustic_guitar_nylon 30
String Ensemble MusyngKite string_ensemble_1 30
python3 scripts/fetch_samples.py      # ~3,5 MB, sekali saja

Skrip mengunduh ke web/public/audio/ dan menulis web/src/lib/sample-manifest.json. Tone.Sampler melakukan pitch-shift antar sample (±1,5 semitone), jadi 30 sample per instrumen sudah cukup — tidak ada artefak yang terdengar.

Analog Synth dan 8-bit Chiptune tetap sintetis (memang itu maksudnya).

4. Frontend

cd web
npm install
cp .env.local.example .env.local   # lalu sesuaikan
npm run build

Menjalankan

# terminal 1 — backend
cd backend && ./run.sh

# terminal 2 — frontend (production)
cd web && npx next start -p 8891 -H 0.0.0.0

Buka http://localhost:8891.

Systemd (opsional, auto-start)

cp deploy/midimagic-api.service deploy/midimagic-web.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now midimagic-api midimagic-web

Konfigurasi

Backend membaca env var berikut (default di backend/app/config.py):

Variabel Default Arti
MIDIMAGIC_PORT 8892 port API
MIDIMAGIC_PUBLIC_ORIGIN http://localhost:8891 dipakai menyusun URL share
MIDIMAGIC_DEMUCS_MODEL htdemucs model Demucs
MIDIMAGIC_WORKERS 1 worker paralel (naikkan hanya kalau RAM cukup)
MIDIMAGIC_YT_MAX_SECONDS 900 batas durasi YouTube
MIDIMAGIC_YT_JS_RUNTIME node JS runtime untuk ekstraksi YouTube (kosongkan untuk mematikan)
MIDIMAGIC_MAX_UPLOAD_MB 60 batas upload
MIDIMAGIC_ANALYSIS_SR 22050 sample rate analisis (transkripsi + deteksi pedal)
MIDIMAGIC_TEMPO (otomatis) override tempo; kosongkan supaya dideteksi dari audio

Frontend: NEXT_PUBLIC_API_BASE di web/.env.local — di-bake saat build, jadi ubah lalu npm run build ulang.

API

Method Path Fungsi
GET /api/health status engine + statistik
POST /api/jobs multipart file atau JSON {youtube_url, options}
GET /api/jobs daftar job
GET /api/jobs/{id} detail + progress + log event
DELETE /api/jobs/{id} hapus job + artefaknya
GET /api/jobs/{id}/midi .mid (query: download, semitones, tempo)
GET /api/jobs/{id}/audio preview mp3, mendukung Range
POST /api/shares buat share {job_id, title}
GET /api/shares/{slug} metadata share
GET /api/shares/{slug}/midi .mid hasil share

Opsi job: stems (bool), stem_mode (vocals|full), target (melody|instrumental|mix), engine (basic-pitch|librosa-pyin), accuracy (fast|balanced|precise), onset_threshold, frame_threshold, min_note_length, min_frequency, max_frequency, suppress_percussion, min_velocity, merge_gap, quantize, tempo.

accuracy memilih satu set threshold; mengirim onset_threshold / frame_threshold / min_note_length secara eksplisit akan menimpainya.

Cara kerja kualitas hasil

Pra-proses audio

Upload dan hasil unduhan YouTube sangat berbeda levelnya, dan membawa rumble di bawah jangkauan piano. Keduanya membuat model "berhalusinasi" not. Jadi setiap input dinormalisasi puncaknya, di-band-limit ke rentang piano, dan perkusinya dibuang (harmonic/percussive separation) sebelum masuk model. Hit drum adalah sumber not palsu nomor satu; kalau pakai Demucs stem, langkah ini dilewati karena drumnya sudah hilang.

Preset ketepatan

Preset Onset Frame Min panjang not Rentang frekuensi
fast 0.60 0.35 90 ms 55–3520 Hz
balanced (default) 0.50 0.30 58 ms 41–4186 Hz
precise 0.38 0.22 42 ms 33–4978 Hz

Onset lebih rendah = lebih sensitif (not pelan ikut tertangkap, tapi lebih banyak sampah).

Pembersihan MIDI

Model memecah satu not piano menjadi 2–3 event berdempetan — itu terdengar seperti stutter dan menggelembungkan jumlah not. Post-process menggabungkan kembali fragmen dengan pitch sama yang jaraknya < 55 ms, memangkas tumpang-tindih, dan membuang bintik ber-velocity rendah yang hampir pasti noise. Pada Canon in D (183 s): 1434 → 839 not (380 fragmen digabung, 251 noise dibuang), dan hasilnya jauh lebih mirip aslinya.

Sustain pedal otomatis

Pedal piano itu terdengar, bukan ditebak. Saat pedal ditahan, senar terus berbunyi melewati jeda antar-not alih-alih teredam. Jadi untuk setiap jeda diukur RMS jeda terhadap RMS not sebelumnya:

  • jeda yang tetap berbunyi → pedal ditahan
  • jeda yang jatuh ke senyap → pedal dilepas

Jeda berbunyi berurutan dirangkai jadi satu region pedal — persis seperti pemain menahan pedal sepanjang frasa. Region yang sangat panjang (> 8 s) dipotong dengan lepas-singkat 120 ms, seperti pemain menekan ulang pedal.

Hasilnya ditulis ke MIDI sebagai CC64, jadi pedalnya ikut di dalam file: pemutar membacanya, dan siapa pun yang mengunduh atau membuka .mid hasil share juga mendapatkannya.

Hasil pengukuran pada dua rekaman piano nyata:

Rekaman Jeda panjang turun ke Coverage pedal
Canon in D (Jacob's Piano) — pedal ditahan terus −4 dB 99%
Rachmaninoff Op. 39 No. 6 — staccato/marcato −33 dB (benar-benar teredam) 88%

Perbedaan −4 dB vs −33 dB itulah yang dipakai algoritma untuk memutuskan.

Mode pedal di UI: Auto (memakai hasil deteksi), On (paksa tahan), Off (paksa lepas). Piano roll menampilkan lampu pedal kecil di atas keyboard saat pedal sedang aktif.

Partitur (dihapus)

Fitur partitur/not-balok sudah dihapus atas permintaan: komponen SheetMusic.tsx, penata balok VexFlow (lib/score.ts), deteksi kunci (lib/key.ts), tab Partitur di HP, dan dependensi vexflow/jspdf/svg2pdf.js — semuanya hilang dari repo, bukan sekadar disembunyikan. Fokus aplikasi sekarang: transkripsi yang presisi + piano roll + playback.

Catatan historis: tempo tetap penting walau partitur sudah tidak ada. Header tempo di MIDI dipakai pemutar untuk memetakan detik ke birama, jadi tempo yang salah tetap membuat playback dan piano roll meleset. Tempo dideteksi dari audio lalu ditulis ke header MIDI (contoh: Op. 39 No. 6 terdeteksi 136 BPM, bukan 120 bawaan model).

Kenapa hasilnya bisa "ngelantur" dari rekaman

Ini bug paling berpengaruh di seluruh pipeline, dan sifatnya senyap.

prepare() dulu memanggil librosa.effects.trim, yang memotong silence di ujung file: rekaman 199.9s menjadi 198.1s sebelum model melihatnya. Karena itu, setiap timestamp hasil transkripsi relatif terhadap file yang sudah dipotong — seluruh MIDI meleset ~1.1s dari rekaman yang sedang kamu bandingkan, tetapi tetap konsisten dengan dirinya sendiri, jadi tidak ada yang terlihat rusak.

Terukur terhadap rekaman asli:

metrik sebelum sesudah
chroma similarity 0.581 0.879
jendela 10s dengan kemiripan ≥0.85 0/20 15/20
lag terbaik +1.138s +0.023s
not yang didukung spektrum rekaman 55.2% 98.6%

Perbaikannya tiga lapis:

  1. Jangan pernah mengubah panjang audio. Silence di-gate (dinolkan), bukan dipotong, sehingga timeline tetap persis. Setiap langkah yang mengubah panjang (resample, padding stem, trim) diikuti pemeriksaan panjang terhadap sumber dengan peringatan keras di log kalau berubah.
  2. Ukur sisa lag lalu koreksi (pipeline/align.py). Bangun matriks chroma langsung dari event not MIDI (tanpa render audio), bandingkan dengan chroma audio sumber, cari lag terbaik, geser MIDI kalau |lag| > 40 ms. Jalan sebelum deteksi pedal, karena deteksi pedal memasangkan waktu not dengan jeda di audio.
  3. Tampilkan hasilnya di UI sebagai align_ms pada job, supaya regresi langsung kelihatan.

Cek hasilnya sendiri: tombol Asli

Tidak ada cara jujur menilai transkripsi selain mendengarkannya bersamaan dengan rekamannya. Tombol Asli di transport memutar rekaman sumber sejajar dengan piano (disajikan dari /api/jobs/{id}/audio, elemen audio-nya terpisah dari DOM dan jam-nya disetel dari clock pemutar). Kalau masih terasa maju/mundur, ada kontrol geser ±50 ms.

Kalau tombol ini butuh geseran besar, itu bug pipeline, bukan selera — laporkan.

Koreksi manual

Tempo itu heuristik, jadi bisa ditimpa lewat input ♪ = BPM di panel pemutar.

Cache header (penting saat deploy ulang)

Next.js menyajikan halaman yang di-prerender dengan Cache-Control: s-maxage=31536000 — setahun di shared cache — sementara chunk JS-nya ber-hash dan berganti setiap build. Browser/proxy yang memegang HTML setahun itu lalu meminta nama chunk yang sudah tidak ada, hidrasi mati, dan aplikasi tampil berantakan beberapa detik setelah deploy yang tadinya kelihatan baik.

Karena itu next.config.ts memisahkan kebijakannya:

path Cache-Control
/, /share/:slug, halaman lain no-cache, no-store, must-revalidate
/_next/static/** public, max-age=31536000, immutable (benar — nama file ber-hash)

Verifikasi setelah build: curl -sD - -o /dev/null http://127.0.0.1:8891/ | grep -i cache-control harus menunjukkan no-store. Kalau browser sudah terlanjur memegang HTML lama, hard refresh (Ctrl/Cmd+Shift+R) sekali sudah cukup.

Keyboard 88 tuts: kenapa terlihat "kurang"

Roll-nya selalu menggambar 88 tuts (A0=21 → C8=108, 52 tuts putih). Yang berubah adalah zoom: lebar kanvas = 52 × zoom, jadi pada HP 390 px dengan zoom default 26 hanya 14 dari 52 tuts putih yang masuk layar — dan itu terbaca sebagai "pianonya kurang". Slider zoom dulu minimum 14, sehingga di HP mustahil melihat semuanya.

Sekarang: slider turun sampai 5, ada tombol "muat 88 tuts" (menghitung zoom = lebar container / 52), dan ada penanda status di sebelahnya — hijau 88 tuts (A0–C8) kalau semuanya terlihat, kuning 14/52 tuts putih terlihat kalau tidak, dengan tooltip yang menjelaskan.

Catatan implementasi: pembungkus roll berada di dalam tab yang dirender kondisional, jadi useRef biasa bisa kosong saat effect jalan dan lebarnya tidak pernah terukur — di HP ini diam-diam membuat penanda tetap "1/52" walau tombolnya bekerja. Pakai callback ref (useState + ref={setEl}) supaya pengukuran ikut elemen yang benar-benar ter-mount.

Engine transkripsi

Dua engine, dan pemilihannya berpengaruh besar pada presisi.

engine jenis untuk kecepatan (4 vCPU)
transkun (default) event-based neural semi-CRF, khusus piano piano solo ~0.6× durasi audio
basic-pitch frame-based, polifonik umum campuran vokal/instrumen, stem ~0.08× durasi audio
librosa pyin pelacak f0 monofonik satu nada (vokal, seruling) cepat

transkun adalah Transkun (Yujia Yan, MIT) — prediksi interval not langsung via semi-CRF, bukan per-frame, dan memprediksi pedal sustain sendiri (CC64 + CC67). Dipasang di venv terpisah:

bash scripts/setup_transkun.sh

Hasil terukur pada Rachmaninoff Op. 39 No. 6 (199.9s)

Semua metrik diukur terhadap rekaman sumber, bukan prepared.wav:

engine not chroma jendela ≥0.85 recall onset onset luput
basic-pitch 1546 0.879 15/20 0.867 54
transkun (pipeline) 2483 0.894 18/20 1.000 0

Recall 1.000 = transkun menemukan setiap onset yang terdeteksi di rekaman, tanpa satu pun luput. Precision onset-nya (0.185) persis di plafon teoretis untuk pembanding ini: librosa.onset_detect hanya menemukan 405 onset di lagu padat ini, jadi engine mana pun yang mengeluarkan >405 onset tidak mungkin melewati 405/N. Baca recall dan chroma, jangan precision, saat membandingkan engine di sini.

Dua bug yang hanya ketahuan karena diukur

Integrasi ini awalnya membuat hasil lebih buruk, dan penyebabnya bukan modelnya:

1. Pembersihan MIDI yang tidak sadar-engine. post.clean ditala untuk model frame-based, di mana event pendek-dan-lemah hampir selalu noise. Transkun melaporkan durasi tekanan tuts (median 0.054s pada etude cepat) dan sengaja me-restrike nada yang sama. min_note_length=0.058s saja menghapus 864 dari 2565 not, dan recall onset jatuh 0.884 → 0.516. Sekarang transkun memakai "profil event": tidak ada not pendek yang dibuang, tidak ada re-strike yang ditempel.

2. Tanda koreksi alignment terbalik. align.py menggeser MIDI sebesar -lag, padahal konvensinya lag positif = MIDI terlalu cepat, jadi koreksinya +lag. Akibatnya offset 23 ms berubah jadi 70 ms — koreksi menggandakan error alih-alih menghapusnya. Diverifikasi empiris: menggeser MIDI 0.5s lebih lambat melaporkan lag −464 ms.

Sekarang align juga memverifikasi koreksinya sendiri: setelah menggeser, lag diukur ulang, dan kalau sisa offset malah membesar, pergeseran dibatalkan. Estimator chroma itu heuristik dan tandanya mudah terbalik (dan memang terbalik), jadi yang dipercaya bukan koreksinya, tapi efek terukurnya.

Memperpanjang not lewat pedal

Transkun melaporkan durasi tekanan tuts, bukan durasi bunyi. Di bawah pedal, not berbunyi selama pedal ditahan. Tanpa koreksi ini playback terdengar dipetik-petik (nada terpotong pendek di mana-mana). pipeline/pedal_extend.py menahan setiap not sampai pedal dilepas, dibatasi tiga hal: tidak melewati serangan berikutnya pada nada yang sama (restrike meredam senar), tidak melewati akhir rekaman, dan tidak lebih dari MIDIMAGIC_TRANSKUN_MAX_EXTEND (default 6s).

Pada Rachmaninoff: 1213 dari 2483 not diperpanjang, +484s bunyi, median not 0.054s → 0.134s.

Audio panjang (mis. video 1,5 jam)

Bisa, dan batasnya bukan durasi melainkan waktu. Di atas MIDIMAGIC_LONG_AUDIO_SECONDS (default 600s) pipeline berubah bentuk:

  • Transkun jalan per-window — window 240s dengan tumpang tindih 6s (MIDIMAGIC_TRANSKUN_CHUNK / _OVERLAP). Ini soal memori: inference transkun tumbuh seiring panjang input sementara mesin ini cuma punya ~3,6 GB bebas, jadi memproses 98 menit dalam satu panggilan akan OOM. Not yang jatuh di batas window terlihat utuh oleh window tetangganya, lalu duplikatnya dibuang saat merge (nada sama, onset dalam 40ms).
  • Unduhan jadi mono 22,05 kHz. Dua jam WAV stereo 44,1k ≈ 1 GB di disk; bentuk mono 22,05k ≈ 260 MB, dan jalur analisis memang resample ke bentuk itu juga.
  • Progres per-window muncul di log job, dan UI menampilkan perkiraan total memakai SPEED_FACTOR hasil ukur (bukan tebakan).

Terukur: 40 menit audio, 10 window (tests/long_audio_probe.py)

hasil
window 10 @240s (+6s overlap), 0 gagal
wall clock 1671s = 0.70× durasi audio
peak RSS 2245 MB — datar, tidak tumbuh seiring panjang
not 12937 (238 duplikat tumpang tindih dibuang)
pedal 663 event CC
cakupan not terakhir 2397s dari 2400s — tidak ada yang terpotong

Angka 0,70× itu pesimistis: run ini berjalan sambil bersaing CPU dengan build Next.js, dan tiap window men-spawn proses transkun baru yang memuat ulang model (~10–15s × 10). Estimasi wajar untuk mesin idle ≈ 0,63×.

Konsekuensi praktis — perkirakan dari durasi video:

durasi audio transkun (0,63–0,70×) basic-pitch (~0,08×)
10 menit 6–7 menit ~50 detik
40 menit 25–28 menit ~3 menit
98 menit ~1 jam ~8 menit

Jadi: transkun untuk presisi, basic-pitch kalau yang penting selesai cepat — pemilih enginenya ada di Pengaturan lanjutan. Yang menentukan pilihan biasanya apakah materinya piano solo (transkun) atau aransemen campuran (basic-pitch).

Uji

cd backend && .venv/bin/python tests/smoke_api.py data/test/melody_stereo.wav          # cepat
cd backend && .venv/bin/python tests/smoke_api.py data/test/melody_stereo.wav --stems  # ikut Demucs

Skrip membuat file WAV sintetis sendiri (data/test/), menjalankan job sampai selesai, lalu memverifikasi MIDI, preview audio, range request, share, dan export transpose.

Alat diagnosa pipeline (memakai audio dari job yang sudah ada):

cd backend
.venv/bin/python tests/pipeline_check.py data/jobs/<id>/source.wav --preset balanced
.venv/bin/python tests/pedal_probe.py  /tmp/mm_check/prepared.wav /tmp/mm_check/raw.mid
.venv/bin/python tests/gap_shape.py    /tmp/mm_check/prepared.wav /tmp/mm_check/raw.mid

gap_shape.py mencetak bentuk decay di dalam setiap jeda antar-not — inilah data yang menentukan ambang RINGING_RATIO: piano yang dipedal turun hanya beberapa dB, yang teredam jatuh puluhan dB.

Menilai ketepatan terhadap rekaman aslinya:

cd backend
.venv/bin/python tests/accuracy_report.py prepared.wav output.mid
.venv/bin/python tests/key_probe.py       output.mid "A minor"

accuracy_report.py merender MIDI-nya jadi audio (synth harmonik sederhana), lalu membandingkan chroma dengan rekaman asli — time-aligned maupun bebas-waktu (DTW) — plus precision/recall onset terhadap onset detector librosa. key_probe.py mencetak perbandingan beberapa skema pembobotan kunci terhadap kunci yang kamu tahu benar.

diagnose.py menjawab "di mana persisnya hasilnya meleset": kemiripan chroma per jendela 10 detik (bagian mana yang salah), lag global terbaik (geseran atau nada salah), dukungan spektrum per not (berapa persen not benar-benar ada di rekaman), dan sebaran offset onset. Bandingkan selalu terhadap rekaman sumber, bukan prepared.wav — prepared.wav sudah satu timeline dengan MIDI, jadi ia menyembunyikan bug geseran.

Performa (mesin 4 vCPU, tanpa GPU)

Tahap 4 detik audio Lagu 3 menit
Pra-proses (normalisasi + band-limit + HPSS) ~1 s ~10–13 s
Transkripsi basic-pitch (ONNX) ~1,5 s ~4–6 s
Bersihkan MIDI <0,1 s ~0,1 s
Deteksi pedal <0,1 s ~0,3 s
Stem separation Demucs ~30 s ~20–30 menit

Demucs memang berat di CPU. Kalau hanya butuh MIDI cepat, matikan stem separation — pra-proses sudah membuang perkusi, jadi akurasinya masih bagus.

Struktur

midimagic/
├── backend/
│   ├── app/
│   │   ├── main.py            # FastAPI: jobs, shares, range streaming
│   │   ├── jobs.py            # runner + orkestrasi pipeline
│   │   ├── db.py              # SQLite (additive)
│   │   ├── config.py
│   │   └── pipeline/
│   │       ├── fetch.py       # upload / yt-dlp → WAV
│   │       ├── stems.py       # Demucs
│   │       ├── transcribe.py  # pra-proses + basic-pitch / librosa-pyin → MIDI
│   │       ├── sustain.py     # deteksi pedal dari audio → CC64 di MIDI
│   │       ├── align.py       # ukur & koreksi offset MIDI vs rekaman sumber
│   │       └── post.py        # gabung fragmen, buang noise, transpose/tempo
│   ├── tests/
│   │   ├── smoke_api.py       # uji end-to-end lewat HTTP
│   │   ├── pipeline_check.py  # jalankan pipeline pada audio nyata, cetak waktu
│   │   ├── accuracy_report.py # chroma vs rekaman + precision/recall onset
│   │   ├── diagnose.py        # di mana persisnya hasilnya meleset
│   │   ├── key_probe.py       # banding skema pembobotan deteksi kunci
│   │   ├── gap_shape.py       # bentuk decay di jeda antar-not (tuning pedal)
│   │   └── pedal_probe.py     # sebaran rasio energi jeda
│   ├── data/                  # sqlite, artefak job, share
│   └── run.sh
├── web/
│   ├── src/app/{page,layout,globals.css,share/[slug]/page.tsx}
│   ├── src/components/        # Studio, PianoRoll, Transport, BottomTabs (mobile), dll
│   ├── src/lib/               # api, audio (Tone), midi (+CC64),
│   │                          # instruments, useMediaQuery
│   └── public/audio/          # sample instrumen (Salamander + MusyngKite)
└── deploy/                    # unit systemd

Mobile

Breakpoint tunggal di 1023 px (useMediaQuery.ts). Di bawah itu Studio berubah jadi satu kolom + bottom tab bar (Putar · Sumber · Suara · Pustaka), piano roll dipendekkan (250 px + keyboard 74 px), dan overflow-x dikunci supaya tidak ada scroll horizontal. Posisi tab dipertahankan saat memuat hasil baru (otomatis pindah ke Putar). Halaman share memakai breakpoint yang sama.

Kredit

basic-pitch (Spotify) · Transkun (MIT, Yujia Yan) · Demucs (Meta) · Tone.js · yt-dlp · Next.js

About

Audio & YouTube → MIDI converter with Synthesia-style piano visualizer, AI stem separation, sheet music and multi-instrument synth (Next.js + FastAPI + basic-pitch + Demucs)

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages