Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ export default function App() {
<div className="space-y-1">
<p className="text-[10px] font-mono uppercase tracking-wider">STEM PITCH/NOTE TRANSLATION</p>
<p className="text-[10px] font-mono uppercase tracking-wide opacity-80">
Optional. Slower and heavier. Turns on the extra stem-based note draft. The separate melody guide may still appear when measurement can produce one.
Optional. Slower and heavier. Turns on the stem-aware note draft (Demucs + torchcrepe on bass and lead). When off, the measurement-layer melody contour and Gemini stem listening notes can still appear when those stages run.
</p>
</div>
</div>
Expand Down
164 changes: 21 additions & 143 deletions apps/ui/src/components/AnalysisResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { downloadFile, generateMarkdown } from '../utils/exportUtils';
import { INTERPRETATION_LABEL } from '../services/phaseLabels';
import { MeasurementDashboard } from './MeasurementDashboard';
import { SessionMusicianPanel } from './SessionMusicianPanel';
import { StemListeningNotesPanel } from './StemListeningNotesPanel';
import { hasStemListeningNotesContent } from '../services/sessionMusician';
import {
AccentMetricCard,
MetricBar,
Expand Down Expand Up @@ -485,9 +487,7 @@ export function AnalysisResults({
: phase2StatusMessage
? 'disabled'
: 'pending';
const stemSummaryStems = Array.isArray(stemSummary?.stems) ? stemSummary.stems : [];
const stemSummaryFlags = Array.isArray(stemSummary?.uncertaintyFlags) ? stemSummary.uncertaintyFlags : [];
const hasStemSummaryContent = stemSummaryStems.length > 0;
const hasStemSummaryContent = hasStemListeningNotesContent(stemSummary);
const warpTargets = warpGuide
? [
{ label: 'Full Track', target: warpGuide.fullTrack },
Expand Down Expand Up @@ -537,7 +537,7 @@ export function AnalysisResults({
audioObservations ? { id: 'section-audio-observations', label: 'Audio' } : null,
arrangement ? { id: 'section-arrangement', label: 'Arrangement' } : null,
{ id: 'section-session', label: 'Session' },
hasStemSummaryContent ? { id: 'section-stem-summary', label: 'Stem Summary' } : null,
hasStemSummaryContent ? { id: 'section-stem-summary', label: 'Stem Notes' } : null,
sonicCards.length > 0 ? { id: 'section-sonic-elements', label: 'Sonic' } : null,
mixGroups.length > 0 ? { id: 'section-mix-chain', label: 'Mix Chain' } : null,
patchCards.length > 0 ? { id: 'section-patches', label: 'Patches' } : null,
Expand Down Expand Up @@ -1540,148 +1540,26 @@ export function AnalysisResults({
</section>
)}

<div id="section-session" className="scroll-mt-24">
<SessionMusicianPanel phase1={phase1} sourceFileName={sourceFileName} pitchNoteMode={pitchNoteMode} />
</div>

{hasStemSummaryContent && (
<section id="section-stem-summary" className="space-y-6 scroll-mt-24">
<ResultsSectionHeader
title="AI stem summary for musical understanding"
rightSlot={
<span className="text-[10px] font-mono bg-bg-panel border border-accent/30 text-accent px-2 py-1 rounded font-bold">
BEST EFFORT
</span>
}
{/* Pair the note-draft panel and the Gemini stem listening notes visually.
The parent motion container uses space-y-12; this local space-y-4 wrapper
tightens the gap between Session Musician and Stem Notes so they read
as a paired suite. The StickyNav anchor IDs stay on the inner divs. */}
<div id="section-musician-suite" className="space-y-4">
<div id="section-session" className="scroll-mt-24">
<SessionMusicianPanel
phase1={phase1}
sourceFileName={sourceFileName}
pitchNoteMode={pitchNoteMode}
hasStemListeningNotes={hasStemSummaryContent}
/>
</div>

<div className="rounded-sm border border-accent/20 bg-accent/5 p-4 space-y-2">
<p className="text-[10px] font-mono uppercase tracking-[0.18em] text-accent">
What this is for
</p>
<p className="text-xs font-mono text-text-secondary leading-relaxed">
This is a plain-language musical summary of the separated stems. It is useful for understanding the role of the bass and upper material, not for exact MIDI truth.
</p>
{stemSummary?.summary && (
<p className="text-xs font-mono text-text-secondary leading-relaxed">
{truncateAtSentenceBoundary(stemSummary.summary, 320)}
</p>
)}
</div>

{stemSummaryFlags.length > 0 && (
<div className="flex flex-wrap gap-2">
{stemSummaryFlags.map((flag, index) => (
<span
key={`${flag}-${index}`}
className="text-[10px] font-mono rounded-sm border border-warning/30 bg-warning/10 px-2 py-1 text-warning"
>
{flag}
</span>
))}
</div>
)}

<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{stemSummaryStems.map((stem) => (
<div
key={stem.stem}
className="rounded-sm border border-border bg-bg-card p-4 space-y-4"
>
<div className="flex items-start justify-between gap-3">
<div>
<h3
data-text-role="item-title"
className={textRoleClassName('item-title')}
>
{stem.label}
</h3>
<p data-text-role="body" className={textRoleClassName('body', 'mt-2')}>
{truncateAtSentenceBoundary(stem.summary, 220)}
</p>
</div>
<span className="text-[9px] font-mono uppercase px-1.5 py-0.5 rounded border border-accent/30 bg-accent/5 text-accent">
{stem.stem}
</span>
</div>

<div className="grid grid-cols-1 gap-3">
{stem.bars.map((bar, index) => (
<div
key={`${stem.stem}-bar-${bar.barStart}-${index}`}
className="rounded-sm border border-border/80 bg-bg-panel/50 p-3 space-y-2"
>
<div className="flex items-center justify-between gap-3">
<span className="text-[10px] font-mono uppercase tracking-[0.18em] text-text-secondary">
Bars {bar.barStart}-{bar.barEnd}
</span>
<span className="text-[9px] font-mono uppercase px-1.5 py-0.5 rounded border border-border text-text-secondary">
{bar.uncertaintyLevel} certainty
</span>
</div>
{bar.noteHypotheses.length > 0 && (
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Notes: {bar.noteHypotheses.join(', ')}
</p>
)}
{bar.scaleDegreeHypotheses.length > 0 && (
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Scale degrees: {bar.scaleDegreeHypotheses.join(', ')}
</p>
)}
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Rhythm: {truncateAtSentenceBoundary(bar.rhythmicPattern, 180)}
</p>
<p className="text-xs font-mono text-warning leading-relaxed">
Uncertainty: {truncateAtSentenceBoundary(bar.uncertaintyReason, 180)}
</p>
</div>
))}
</div>

<div className="space-y-2">
<p className="text-[10px] font-mono uppercase tracking-[0.18em] text-text-secondary">
Global pattern
</p>
<div className="space-y-1">
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Bass role: {truncateAtSentenceBoundary(stem.globalPatterns.bassRole, 180)}
</p>
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Musical role: {truncateAtSentenceBoundary(stem.globalPatterns.melodicRole, 180)}
</p>
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Movement: {truncateAtSentenceBoundary(stem.globalPatterns.pumpingOrModulation, 180)}
</p>
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Synthesis: {truncateAtSentenceBoundary(stem.globalPatterns.synthesisCharacter, 180)}
</p>
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Vocal presence: {truncateAtSentenceBoundary(stem.globalPatterns.vocalPresence, 180)}
</p>
<p className="text-xs font-mono text-text-secondary leading-relaxed">
Bass character: {truncateAtSentenceBoundary(stem.globalPatterns.bassCharacter, 180)}
</p>
</div>
</div>

{stem.uncertaintyFlags.length > 0 && (
<div className="flex flex-wrap gap-2">
{stem.uncertaintyFlags.map((flag, index) => (
<span
key={`${stem.stem}-flag-${index}`}
className="text-[10px] font-mono rounded-sm border border-warning/30 bg-warning/10 px-2 py-1 text-warning"
>
{flag}
</span>
))}
</div>
)}
</div>
))}
{hasStemSummaryContent && (
<div id="section-stem-summary" className="scroll-mt-24">
<StemListeningNotesPanel stemSummary={stemSummary} />
</div>
</section>
)}
)}
</div>

{sonicCards.length > 0 && (
<section id="section-sonic-elements" className="space-y-6 scroll-mt-24">
Expand Down
Loading