Ewan Claude Opus 4.6 commited on
Commit
a14f449
Β·
1 Parent(s): 83ff7ad

Use direct melodic transcription for piano roll instead of arrangement

Browse files

The arrangement engine (vocals + chord voicings) was producing poor
results because vocal transcription via Basic Pitch is inherently noisy
and chord detection on mixed stems compounds errors. Now the piano roll
uses the "other" stem transcription directly β€” the same data that
produces good guitar tabs. This is the actual transcribed melodic
instruments (guitar, piano, synths) rather than a lossy derivation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

api/server.py CHANGED
@@ -206,10 +206,14 @@ def run_full_transcription(job_id, audio_path, job_dir):
206
  with open(bass_tab_path, 'w') as f:
207
  json.dump(bass_tab, f)
208
 
209
- # Step 7: Merge arrangement + bass into final MIDI
 
 
 
 
210
  job_status[job_id] = {"step": 7, "label": "Assembling final result...", "done": False}
211
  merged_path = job_dir / "transcription.mid"
212
- merge_stems(str(arrangement_path), str(bass_opt), str(merged_path))
213
 
214
  # Clean up large stem files and intermediates
215
  stems_dir = job_dir / "stems"
 
206
  with open(bass_tab_path, 'w') as f:
207
  json.dump(bass_tab, f)
208
 
209
+ # Step 7: Merge melodic transcription + bass into final MIDI
210
+ # Use the "other" stem directly β€” it's the actual transcribed melodic
211
+ # instruments (guitar, piano, synths). The arrangement engine (vocals +
212
+ # chord voicings) introduces too many errors from vocal transcription
213
+ # and chord detection inaccuracy.
214
  job_status[job_id] = {"step": 7, "label": "Assembling final result...", "done": False}
215
  merged_path = job_dir / "transcription.mid"
216
+ merge_stems(str(piano_opt), str(bass_opt), str(merged_path))
217
 
218
  # Clean up large stem files and intermediates
219
  stems_dir = job_dir / "stems"
api/static/assets/{BassTab-S8WkcUIi.js β†’ BassTab-DYMUP74N.js} RENAMED
@@ -1 +1 @@
1
- import{r as S,C as i,j as B}from"./index-BtDnEmF3.js";const O=4,H=["G","D","A","E"];function q({tabData:f,currentTimeRef:P,width:s,height:c}){const M=S.useRef(null),d=S.useRef(null),v=S.useCallback(()=>{const l=M.current;if(!l||!f)return;const t=l.getContext("2d"),a=window.devicePixelRatio||1;(l.width!==s*a||l.height!==c*a)&&(l.width=s*a,l.height=c*a,l.style.width=`${s}px`,l.style.height=`${c}px`,t.scale(a,a));const A=P?.current??0,o=f.strings||4,r=50,w=30,x=40,k=20,E=c-r-w,u=E/(o-1),j=s-x-k,T=x+j*.2,C=j*.8/O;t.fillStyle=i.tabBg,t.fillRect(0,0,s,c),t.font="13px Inter, monospace",t.textAlign="right",t.textBaseline="middle";for(let e=0;e<o;e++){const n=r+e*u;t.fillStyle=i.textMuted;const g=o-1-e;t.fillText(H[g]||"",x-10,n)}t.strokeStyle=i.tabString,t.lineWidth=1;for(let e=0;e<o;e++){const n=r+e*u;t.beginPath(),t.moveTo(x,n),t.lineTo(s-k,n),t.stroke()}const F=f.events||[];t.textAlign="center",t.textBaseline="middle",t.font="bold 14px Inter, monospace";for(const e of F){const n=T+(e.time-A)*C;if(n<x-30||n>s+30)continue;const g=e.time<=A,I=g&&e.time+e.duration>A;for(let m=0;m<o;m++){const p=e.frets[m];if(p==null)continue;const L=o-1-m,R=r+L*u,W=t.measureText(String(p)).width,b=Math.max(W+8,18),y=18;I&&(t.fillStyle="rgba(139, 92, 246, 0.3)",t.beginPath(),t.roundRect(n-b/2,R-y/2,b,y,4),t.fill()),t.fillStyle=i.tabBg,t.fillRect(n-b/2,R-y/2,b,y),t.fillStyle=g?i.tabFretPlayed:i.tabFret,t.fillText(String(p),n,R)}}t.strokeStyle=i.tabProgressLine,t.lineWidth=2,t.shadowColor="rgba(139, 92, 246, 0.5)",t.shadowBlur=8,t.beginPath(),t.moveTo(T,r-10),t.lineTo(T,r+(o-1)*u+10),t.stroke(),t.shadowBlur=0,t.fillStyle=i.textMuted,t.font="bold 16px Inter, sans-serif",t.textAlign="center",t.textBaseline="middle";const h=r+E/2;t.fillText("T",15,h-12),t.fillText("A",15,h),t.fillText("B",15,h+12),d.current=requestAnimationFrame(v)},[f,P,s,c]);return S.useEffect(()=>(d.current=requestAnimationFrame(v),()=>{d.current&&cancelAnimationFrame(d.current)}),[v]),f?B.jsx("canvas",{ref:M,style:{display:"block",width:"100%",height:"100%"}}):B.jsx("div",{className:"tab-empty",children:B.jsx("p",{children:"No bass tab data available."})})}export{q as default};
 
1
+ import{r as S,C as i,j as B}from"./index-8qL2GjUN.js";const O=4,H=["G","D","A","E"];function q({tabData:f,currentTimeRef:P,width:s,height:c}){const M=S.useRef(null),d=S.useRef(null),v=S.useCallback(()=>{const l=M.current;if(!l||!f)return;const t=l.getContext("2d"),a=window.devicePixelRatio||1;(l.width!==s*a||l.height!==c*a)&&(l.width=s*a,l.height=c*a,l.style.width=`${s}px`,l.style.height=`${c}px`,t.scale(a,a));const A=P?.current??0,o=f.strings||4,r=50,w=30,x=40,k=20,E=c-r-w,u=E/(o-1),j=s-x-k,T=x+j*.2,C=j*.8/O;t.fillStyle=i.tabBg,t.fillRect(0,0,s,c),t.font="13px Inter, monospace",t.textAlign="right",t.textBaseline="middle";for(let e=0;e<o;e++){const n=r+e*u;t.fillStyle=i.textMuted;const g=o-1-e;t.fillText(H[g]||"",x-10,n)}t.strokeStyle=i.tabString,t.lineWidth=1;for(let e=0;e<o;e++){const n=r+e*u;t.beginPath(),t.moveTo(x,n),t.lineTo(s-k,n),t.stroke()}const F=f.events||[];t.textAlign="center",t.textBaseline="middle",t.font="bold 14px Inter, monospace";for(const e of F){const n=T+(e.time-A)*C;if(n<x-30||n>s+30)continue;const g=e.time<=A,I=g&&e.time+e.duration>A;for(let m=0;m<o;m++){const p=e.frets[m];if(p==null)continue;const L=o-1-m,R=r+L*u,W=t.measureText(String(p)).width,b=Math.max(W+8,18),y=18;I&&(t.fillStyle="rgba(139, 92, 246, 0.3)",t.beginPath(),t.roundRect(n-b/2,R-y/2,b,y,4),t.fill()),t.fillStyle=i.tabBg,t.fillRect(n-b/2,R-y/2,b,y),t.fillStyle=g?i.tabFretPlayed:i.tabFret,t.fillText(String(p),n,R)}}t.strokeStyle=i.tabProgressLine,t.lineWidth=2,t.shadowColor="rgba(139, 92, 246, 0.5)",t.shadowBlur=8,t.beginPath(),t.moveTo(T,r-10),t.lineTo(T,r+(o-1)*u+10),t.stroke(),t.shadowBlur=0,t.fillStyle=i.textMuted,t.font="bold 16px Inter, sans-serif",t.textAlign="center",t.textBaseline="middle";const h=r+E/2;t.fillText("T",15,h-12),t.fillText("A",15,h),t.fillText("B",15,h+12),d.current=requestAnimationFrame(v)},[f,P,s,c]);return S.useEffect(()=>(d.current=requestAnimationFrame(v),()=>{d.current&&cancelAnimationFrame(d.current)}),[v]),f?B.jsx("canvas",{ref:M,style:{display:"block",width:"100%",height:"100%"}}):B.jsx("div",{className:"tab-empty",children:B.jsx("p",{children:"No bass tab data available."})})}export{q as default};
api/static/assets/{GuitarTab-CwGZfqd9.js β†’ GuitarTab-CAe9Arke.js} RENAMED
@@ -1 +1 @@
1
- import{r as v,C as s,j as P}from"./index-BtDnEmF3.js";const O=4,G=["E","B","G","D","A","E"];function N({tabData:u,currentTimeRef:E,width:i,height:a,chords:g}){const M=v.useRef(null),m=v.useRef(null),h=v.useCallback(()=>{const l=M.current;if(!l||!u)return;const t=l.getContext("2d"),c=window.devicePixelRatio||1;(l.width!==i*c||l.height!==a*c)&&(l.width=i*c,l.height=a*c,l.style.width=`${i}px`,l.style.height=`${a}px`,t.scale(c,c));const d=E?.current??0,r=u.strings||6,o=50,C=30,f=40,k=20,F=a-o-C,p=F/(r-1),I=i-f-k,b=f+I*.2,j=I*.8/O;t.fillStyle=s.tabBg,t.fillRect(0,0,i,a),t.font="13px Inter, monospace",t.textAlign="right",t.textBaseline="middle";for(let e=0;e<r;e++){const n=o+e*p;t.fillStyle=s.textMuted;const x=r-1-e;t.fillText(G[x]||"",f-10,n)}t.strokeStyle=s.tabString,t.lineWidth=1;for(let e=0;e<r;e++){const n=o+e*p;t.beginPath(),t.moveTo(f,n),t.lineTo(i-k,n),t.stroke()}if(g&&g.length>0){t.font="12px Inter, sans-serif",t.textAlign="center",t.textBaseline="bottom";for(const e of g){const n=b+(e.start_time-d)*j;if(n<f-50||n>i+50)continue;const x=e.start_time<=d;t.fillStyle=x?s.tabFretPlayed:"rgba(139, 92, 246, 0.5)",t.fillText(e.chord_name,n,o-8)}}const L=u.events||[];t.textAlign="center",t.textBaseline="middle",t.font="bold 14px Inter, monospace";for(const e of L){const n=b+(e.time-d)*j;if(n<f-30||n>i+30)continue;const x=e.time<=d,W=x&&e.time+e.duration>d;for(let y=0;y<r;y++){const S=e.frets[y];if(S==null)continue;const _=r-1-y,R=o+_*p,w=t.measureText(String(S)).width,A=Math.max(w+8,18),T=18;W&&(t.fillStyle="rgba(139, 92, 246, 0.3)",t.beginPath(),t.roundRect(n-A/2,R-T/2,A,T,4),t.fill()),t.fillStyle=s.tabBg,t.fillRect(n-A/2,R-T/2,A,T),t.fillStyle=x?s.tabFretPlayed:s.tabFret,t.fillText(String(S),n,R)}}t.strokeStyle=s.tabProgressLine,t.lineWidth=2,t.shadowColor="rgba(139, 92, 246, 0.5)",t.shadowBlur=8,t.beginPath(),t.moveTo(b,o-10),t.lineTo(b,o+(r-1)*p+10),t.stroke(),t.shadowBlur=0,t.fillStyle=s.textMuted,t.font="bold 16px Inter, sans-serif",t.textAlign="center",t.textBaseline="middle";const B=o+F/2;t.fillText("T",15,B-12),t.fillText("A",15,B),t.fillText("B",15,B+12),m.current=requestAnimationFrame(h)},[u,E,i,a,g]);return v.useEffect(()=>(m.current=requestAnimationFrame(h),()=>{m.current&&cancelAnimationFrame(m.current)}),[h]),u?P.jsx("canvas",{ref:M,style:{display:"block",width:"100%",height:"100%"}}):P.jsx("div",{className:"tab-empty",children:P.jsx("p",{children:"No guitar tab data available."})})}export{N as default};
 
1
+ import{r as v,C as s,j as P}from"./index-8qL2GjUN.js";const O=4,G=["E","B","G","D","A","E"];function N({tabData:u,currentTimeRef:E,width:i,height:a,chords:g}){const M=v.useRef(null),m=v.useRef(null),h=v.useCallback(()=>{const l=M.current;if(!l||!u)return;const t=l.getContext("2d"),c=window.devicePixelRatio||1;(l.width!==i*c||l.height!==a*c)&&(l.width=i*c,l.height=a*c,l.style.width=`${i}px`,l.style.height=`${a}px`,t.scale(c,c));const d=E?.current??0,r=u.strings||6,o=50,C=30,f=40,k=20,F=a-o-C,p=F/(r-1),I=i-f-k,b=f+I*.2,j=I*.8/O;t.fillStyle=s.tabBg,t.fillRect(0,0,i,a),t.font="13px Inter, monospace",t.textAlign="right",t.textBaseline="middle";for(let e=0;e<r;e++){const n=o+e*p;t.fillStyle=s.textMuted;const x=r-1-e;t.fillText(G[x]||"",f-10,n)}t.strokeStyle=s.tabString,t.lineWidth=1;for(let e=0;e<r;e++){const n=o+e*p;t.beginPath(),t.moveTo(f,n),t.lineTo(i-k,n),t.stroke()}if(g&&g.length>0){t.font="12px Inter, sans-serif",t.textAlign="center",t.textBaseline="bottom";for(const e of g){const n=b+(e.start_time-d)*j;if(n<f-50||n>i+50)continue;const x=e.start_time<=d;t.fillStyle=x?s.tabFretPlayed:"rgba(139, 92, 246, 0.5)",t.fillText(e.chord_name,n,o-8)}}const L=u.events||[];t.textAlign="center",t.textBaseline="middle",t.font="bold 14px Inter, monospace";for(const e of L){const n=b+(e.time-d)*j;if(n<f-30||n>i+30)continue;const x=e.time<=d,W=x&&e.time+e.duration>d;for(let y=0;y<r;y++){const S=e.frets[y];if(S==null)continue;const _=r-1-y,R=o+_*p,w=t.measureText(String(S)).width,A=Math.max(w+8,18),T=18;W&&(t.fillStyle="rgba(139, 92, 246, 0.3)",t.beginPath(),t.roundRect(n-A/2,R-T/2,A,T,4),t.fill()),t.fillStyle=s.tabBg,t.fillRect(n-A/2,R-T/2,A,T),t.fillStyle=x?s.tabFretPlayed:s.tabFret,t.fillText(String(S),n,R)}}t.strokeStyle=s.tabProgressLine,t.lineWidth=2,t.shadowColor="rgba(139, 92, 246, 0.5)",t.shadowBlur=8,t.beginPath(),t.moveTo(b,o-10),t.lineTo(b,o+(r-1)*p+10),t.stroke(),t.shadowBlur=0,t.fillStyle=s.textMuted,t.font="bold 16px Inter, sans-serif",t.textAlign="center",t.textBaseline="middle";const B=o+F/2;t.fillText("T",15,B-12),t.fillText("A",15,B),t.fillText("B",15,B+12),m.current=requestAnimationFrame(h)},[u,E,i,a,g]);return v.useEffect(()=>(m.current=requestAnimationFrame(h),()=>{m.current&&cancelAnimationFrame(m.current)}),[h]),u?P.jsx("canvas",{ref:M,style:{display:"block",width:"100%",height:"100%"}}):P.jsx("div",{className:"tab-empty",children:P.jsx("p",{children:"No guitar tab data available."})})}export{N as default};
api/static/assets/{SheetMusic-DJu-kILP.js β†’ SheetMusic-DAPl-kkp.js} RENAMED
The diff for this file is too large to render. See raw diff
 
api/static/assets/{index-BtDnEmF3.js β†’ index-8qL2GjUN.js} RENAMED
The diff for this file is too large to render. See raw diff
 
api/static/index.html CHANGED
@@ -9,7 +9,7 @@
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
11
  <title>Mr. Octopus</title>
12
- <script type="module" crossorigin src="/assets/index-BtDnEmF3.js"></script>
13
  <link rel="stylesheet" crossorigin href="/assets/index-1_DPe19x.css">
14
  </head>
15
  <body>
 
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
11
  <title>Mr. Octopus</title>
12
+ <script type="module" crossorigin src="/assets/index-8qL2GjUN.js"></script>
13
  <link rel="stylesheet" crossorigin href="/assets/index-1_DPe19x.css">
14
  </head>
15
  <body>
app/src/App.jsx CHANGED
@@ -402,7 +402,7 @@ export default function App() {
402
  className={`view-tab ${activeTab === 'sheet' ? 'active' : ''}`}
403
  onClick={() => setActiveTab('sheet')}
404
  >
405
- {songMode === 'full' ? 'Piano Arrangement' : 'Sheet Music'}
406
  </button>
407
  {songMode === 'full' && (
408
  <>
 
402
  className={`view-tab ${activeTab === 'sheet' ? 'active' : ''}`}
403
  onClick={() => setActiveTab('sheet')}
404
  >
405
+ Sheet Music
406
  </button>
407
  {songMode === 'full' && (
408
  <>