← back to kbarnoski__melody-memo

Function bodies 245 total

All specs Real LLM only Function bodies
PopoverTitle function · typescript · L58-L66 (9 LOC)
src/components/ui/popover.tsx
function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
  return (
    <div
      data-slot="popover-title"
      className={cn("font-medium", className)}
      {...props}
    />
  )
}
PopoverDescription function · typescript · L68-L79 (12 LOC)
src/components/ui/popover.tsx
function PopoverDescription({
  className,
  ...props
}: React.ComponentProps<"p">) {
  return (
    <p
      data-slot="popover-description"
      className={cn("text-muted-foreground", className)}
      {...props}
    />
  )
}
ScrollArea function · typescript · L8-L29 (22 LOC)
src/components/ui/scroll-area.tsx
function ScrollArea({
  className,
  children,
  ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
  return (
    <ScrollAreaPrimitive.Root
      data-slot="scroll-area"
      className={cn("relative", className)}
      {...props}
    >
      <ScrollAreaPrimitive.Viewport
        data-slot="scroll-area-viewport"
        className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
      >
        {children}
      </ScrollAreaPrimitive.Viewport>
      <ScrollBar />
      <ScrollAreaPrimitive.Corner />
    </ScrollAreaPrimitive.Root>
  )
}
ScrollBar function · typescript · L31-L56 (26 LOC)
src/components/ui/scroll-area.tsx
function ScrollBar({
  className,
  orientation = "vertical",
  ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
  return (
    <ScrollAreaPrimitive.ScrollAreaScrollbar
      data-slot="scroll-area-scrollbar"
      orientation={orientation}
      className={cn(
        "flex touch-none p-px transition-colors select-none",
        orientation === "vertical" &&
          "h-full w-2.5 border-l border-l-transparent",
        orientation === "horizontal" &&
          "h-2.5 flex-col border-t border-t-transparent",
        className
      )}
      {...props}
    >
      <ScrollAreaPrimitive.ScrollAreaThumb
        data-slot="scroll-area-thumb"
        className="bg-border relative flex-1 rounded-full"
      />
    </ScrollAreaPrimitive.ScrollAreaScrollbar>
  )
}
Separator function · typescript · L8-L26 (19 LOC)
src/components/ui/separator.tsx
function Separator({
  className,
  orientation = "horizontal",
  decorative = true,
  ...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
  return (
    <SeparatorPrimitive.Root
      data-slot="separator"
      decorative={decorative}
      orientation={orientation}
      className={cn(
        "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
        className
      )}
      {...props}
    />
  )
}
Slider function · typescript · L8-L61 (54 LOC)
src/components/ui/slider.tsx
function Slider({
  className,
  defaultValue,
  value,
  min = 0,
  max = 100,
  ...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
  const _values = React.useMemo(
    () =>
      Array.isArray(value)
        ? value
        : Array.isArray(defaultValue)
          ? defaultValue
          : [min, max],
    [value, defaultValue, min, max]
  )

  return (
    <SliderPrimitive.Root
      data-slot="slider"
      defaultValue={defaultValue}
      value={value}
      min={min}
      max={max}
      className={cn(
        "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
        className
      )}
      {...props}
    >
      <SliderPrimitive.Track
        data-slot="slider-track"
        className={cn(
          "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1
Tabs function · typescript · L9-L26 (18 LOC)
src/components/ui/tabs.tsx
function Tabs({
  className,
  orientation = "horizontal",
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
  return (
    <TabsPrimitive.Root
      data-slot="tabs"
      data-orientation={orientation}
      orientation={orientation}
      className={cn(
        "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
        className
      )}
      {...props}
    />
  )
}
Repobility · code-quality intelligence · https://repobility.com
TabsList function · typescript · L43-L57 (15 LOC)
src/components/ui/tabs.tsx
function TabsList({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<typeof TabsPrimitive.List> &
  VariantProps<typeof tabsListVariants>) {
  return (
    <TabsPrimitive.List
      data-slot="tabs-list"
      data-variant={variant}
      className={cn(tabsListVariants({ variant }), className)}
      {...props}
    />
  )
}
TabsTrigger function · typescript · L59-L76 (18 LOC)
src/components/ui/tabs.tsx
function TabsTrigger({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
  return (
    <TabsPrimitive.Trigger
      data-slot="tabs-trigger"
      className={cn(
        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        "group-data-[
TabsContent function · typescript · L78-L89 (12 LOC)
src/components/ui/tabs.tsx
function TabsContent({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
  return (
    <TabsPrimitive.Content
      data-slot="tabs-content"
      className={cn("flex-1 outline-none", className)}
      {...props}
    />
  )
}
midiToNote function · typescript · L16-L18 (3 LOC)
src/lib/ai/build-system-prompt.ts
function midiToNote(midi: number): string {
  return `${NOTE_NAMES[midi % 12]}${Math.floor(midi / 12) - 1}`;
}
buildRecordingSystemPrompt function · typescript · L20-L106 (87 LOC)
src/lib/ai/build-system-prompt.ts
export function buildRecordingSystemPrompt(analysis: Analysis): string {
  if (!analysis) {
    return "You are a music theory expert. The recording has not been analyzed yet. Let the user know they need to run the analysis first.";
  }

  // Top chords by frequency
  const chordCounts = new Map<string, number>();
  for (const c of analysis.chords) {
    chordCounts.set(c.chord, (chordCounts.get(c.chord) || 0) + 1);
  }
  const sortedChords = [...chordCounts.entries()]
    .sort((a, b) => b[1] - a[1])
    .slice(0, 15);

  // Chord progression (limited to first 30 to avoid token waste)
  const chordProgression = analysis.chords
    .slice(0, 30)
    .map((c) => c.chord)
    .join(" | ");
  const progressionSuffix = analysis.chords.length > 30
    ? ` ... (${analysis.chords.length - 30} more chord changes)`
    : "";

  // Note range
  const midiValues = analysis.notes.map((n) => n.midi);
  const minNote = midiValues.length > 0 ? midiToNote(Math.min(...midiValues)) : "N/A";
  const maxN
buildCompareSystemPrompt function · typescript · L108-L177 (70 LOC)
src/lib/ai/build-system-prompt.ts
export function buildCompareSystemPrompt(
  titleA: string,
  analysisA: Analysis,
  titleB: string,
  analysisB: Analysis
): string {
  function summarizeAnalysis(title: string, a: Analysis): string {
    const chordCounts = new Map<string, number>();
    for (const c of a.chords) {
      chordCounts.set(c.chord, (chordCounts.get(c.chord) || 0) + 1);
    }
    const topChords = [...chordCounts.entries()]
      .sort((a, b) => b[1] - a[1])
      .slice(0, 12);

    const progression = a.chords
      .slice(0, 25)
      .map((c) => c.chord)
      .join(" | ");

    const midiValues = a.notes.map((n) => n.midi);
    const minNote = midiValues.length > 0 ? midiToNote(Math.min(...midiValues)) : "N/A";
    const maxNote = midiValues.length > 0 ? midiToNote(Math.max(...midiValues)) : "N/A";

    const melodySummary = a.melody && a.melody.length > 0
      ? a.melody.slice(0, 15).map((n) => midiToNote(n.midi)).join(" ")
      : "Not extracted";

    const bassSummary = a.bass_line && a.bass_li
summarizeAnalysis function · typescript · L114-L155 (42 LOC)
src/lib/ai/build-system-prompt.ts
  function summarizeAnalysis(title: string, a: Analysis): string {
    const chordCounts = new Map<string, number>();
    for (const c of a.chords) {
      chordCounts.set(c.chord, (chordCounts.get(c.chord) || 0) + 1);
    }
    const topChords = [...chordCounts.entries()]
      .sort((a, b) => b[1] - a[1])
      .slice(0, 12);

    const progression = a.chords
      .slice(0, 25)
      .map((c) => c.chord)
      .join(" | ");

    const midiValues = a.notes.map((n) => n.midi);
    const minNote = midiValues.length > 0 ? midiToNote(Math.min(...midiValues)) : "N/A";
    const maxNote = midiValues.length > 0 ? midiToNote(Math.max(...midiValues)) : "N/A";

    const melodySummary = a.melody && a.melody.length > 0
      ? a.melody.slice(0, 15).map((n) => midiToNote(n.midi)).join(" ")
      : "Not extracted";

    const bassSummary = a.bass_line && a.bass_line.length > 0
      ? a.bass_line.slice(0, 10).map((n) => NOTE_NAMES[n.midi % 12]).join(" ")
      : "Not extracted";

    const progre
buildInsightsSystemPrompt function · typescript · L179-L209 (31 LOC)
src/lib/ai/build-system-prompt.ts
export function buildInsightsSystemPrompt(
  analyses: { recording_title: string; key_signature: string | null; tempo: number | null; chords: { chord: string }[] }[]
): string {
  const keyDistribution = new Map<string, number>();
  const allChords = new Map<string, number>();

  for (const a of analyses) {
    if (a.key_signature) {
      keyDistribution.set(a.key_signature, (keyDistribution.get(a.key_signature) || 0) + 1);
    }
    for (const c of a.chords) {
      allChords.set(c.chord, (allChords.get(c.chord) || 0) + 1);
    }
  }

  const topKeys = [...keyDistribution.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
  const topChords = [...allChords.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20);

  return `You are a music theory expert and composition coach analyzing a library of ${analyses.length} piano voice memos recorded over several years.

**Key Distribution:**
${topKeys.map(([key, count]) => `- ${key}: ${count} recordings`).join("\n")}

**Most Used Chords:**
${to
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
findCommonProgressions function · typescript · L9-L12 (4 LOC)
src/lib/analysis/cross-recording.ts
export function findCommonProgressions(
  analyses: RecordingAnalysis[],
  minLength: number = 3
): { progression: string[]; count: number; recordings: string[] }[] {
findSimilarRecordings function · typescript · L42-L44 (3 LOC)
src/lib/analysis/cross-recording.ts
export function findSimilarRecordings(
  analyses: RecordingAnalysis[]
): { pair: [string, string]; similarity: number; reasons: string[] }[] {
getKeyDistribution function · typescript · L94-L96 (3 LOC)
src/lib/analysis/cross-recording.ts
export function getKeyDistribution(
  analyses: RecordingAnalysis[]
): { key: string; count: number }[] {
getHarmonicTendencies function · typescript · L108-L110 (3 LOC)
src/lib/analysis/cross-recording.ts
export function getHarmonicTendencies(
  analyses: RecordingAnalysis[]
): { tendencies: string[]; dominantStyle: string } {
getChordFrequency function · typescript · L156-L158 (3 LOC)
src/lib/analysis/cross-recording.ts
export function getChordFrequency(
  analyses: RecordingAnalysis[]
): { chord: string; count: number }[] {
autoAnalyzeRecording function · typescript · L5-L33 (29 LOC)
src/lib/audio/analysis-queue.ts
export async function autoAnalyzeRecording(recordingId: string, audioUrl: string) {
  try {
    const { transcribeAudio } = await import("./transcribe");
    const { analyzeNotes } = await import("./analyze");

    const notes = await transcribeAudio(audioUrl);
    const result = analyzeNotes(notes);

    const supabase = createClient();
    await supabase.from("analyses").upsert(
      {
        recording_id: recordingId,
        status: result.status,
        key_signature: result.key_signature,
        tempo: result.tempo,
        time_signature: result.time_signature,
        chords: result.chords,
        notes: result.notes,
        midi_data: result.midi_data,
      },
      { onConflict: "recording_id" }
    );

    return result;
  } catch (error) {
    console.error(`Auto-analysis failed for ${recordingId}:`, error);
    return null;
  }
}
midiToNoteName function · typescript · L6-L8 (3 LOC)
src/lib/audio/analyze.ts
function midiToNoteName(midi: number): string {
  return PITCH_CLASSES[midi % 12];
}
midiToFullName function · typescript · L10-L13 (4 LOC)
src/lib/audio/analyze.ts
function midiToFullName(midi: number): string {
  const octave = Math.floor(midi / 12) - 1;
  return `${PITCH_CLASSES[midi % 12]}${octave}`;
}
Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
detectChords function · typescript · L19-L79 (61 LOC)
src/lib/audio/analyze.ts
function detectChords(notes: NoteEvent[], baseTempo: number | null): ChordEvent[] {
  if (notes.length === 0) return [];

  // Adaptive window: use beat duration if tempo known, else 0.5s
  const windowSize = baseTempo ? 60 / baseTempo : 0.5;
  const maxTime = Math.max(...notes.map((n) => n.time + n.duration));
  const chords: ChordEvent[] = [];

  for (let t = 0; t < maxTime; t += windowSize) {
    const windowEnd = t + windowSize;

    const activeNotes = notes.filter(
      (n) => n.time < windowEnd && n.time + n.duration > t
    );

    if (activeNotes.length === 0) continue;

    // Sort by pitch — bass note matters for inversions
    const notesByPitch = [...activeNotes].sort((a, b) => a.midi - b.midi);
    const pitchClasses = [...new Set(notesByPitch.map((n) => midiToNoteName(n.midi)))];
    const bassNote = midiToNoteName(notesByPitch[0].midi);

    if (pitchClasses.length < 2) continue;

    let chordName: string | null = null;

    // Try with bass note first for better inve
correlate function · typescript · L103-L115 (13 LOC)
src/lib/audio/analyze.ts
  function correlate(profile: number[]): number {
    const meanP = profile.reduce((a, b) => a + b) / 12;
    const meanH = normalized.reduce((a: number, b: number) => a + b) / 12;
    let num = 0, denP = 0, denH = 0;
    for (let i = 0; i < 12; i++) {
      const dp = profile[i] - meanP;
      const dh = normalized[i] - meanH;
      num += dp * dh;
      denP += dp * dp;
      denH += dh * dh;
    }
    return denP * denH > 0 ? num / Math.sqrt(denP * denH) : 0;
  }
estimateTempo function · typescript · L143-L189 (47 LOC)
src/lib/audio/analyze.ts
function estimateTempo(notes: NoteEvent[]): number | null {
  if (notes.length < 4) return null;

  const onsets = notes.map((n) => n.time).sort((a, b) => a - b);
  const intervals: number[] = [];
  for (let i = 1; i < onsets.length; i++) {
    const interval = onsets[i] - onsets[i - 1];
    if (interval > 0.05 && interval < 2.0) {
      intervals.push(interval);
    }
  }

  if (intervals.length === 0) return null;

  const minBeatDuration = 60 / 240;
  const maxBeatDuration = 60 / 40;
  const step = 0.005;

  let bestBeat = 0;
  let bestScore = -Infinity;

  for (let beat = minBeatDuration; beat <= maxBeatDuration; beat += step) {
    let score = 0;
    for (const interval of intervals) {
      for (let mult = 0.5; mult <= 4; mult *= 2) {
        const ratio = interval / (beat * mult);
        const nearestInt = Math.round(ratio);
        if (nearestInt > 0 && nearestInt <= 8) {
          const deviation = Math.abs(ratio - nearestInt);
          score += Math.exp(-deviation * deviati
detectTimeSignature function · typescript · L194-L218 (25 LOC)
src/lib/audio/analyze.ts
function detectTimeSignature(notes: NoteEvent[], tempo: number | null): string {
  if (!tempo || notes.length < 8) return "4/4";

  const beatDuration = 60 / tempo;
  const onsets = notes.map((n) => n.time).sort((a, b) => a - b);
  const totalDuration = Math.max(...onsets) - Math.min(...onsets);

  let score3 = 0;
  let score4 = 0;

  for (const note of notes) {
    const beatPos3 = (note.time / beatDuration) % 3;
    const beatPos4 = (note.time / beatDuration) % 4;
    const dist3 = Math.min(beatPos3, 3 - beatPos3);
    const dist4 = Math.min(beatPos4, 4 - beatPos4);
    score3 += note.velocity * Math.exp(-dist3 * dist3 * 10);
    score4 += note.velocity * Math.exp(-dist4 * dist4 * 10);
  }

  score3 /= Math.max(1, totalDuration / (beatDuration * 3));
  score4 /= Math.max(1, totalDuration / (beatDuration * 4));

  if (score3 > score4 * 1.15) return "3/4";
  return "4/4";
}
extractMelody function · typescript · L223-L248 (26 LOC)
src/lib/audio/analyze.ts
function extractMelody(notes: NoteEvent[]): NoteEvent[] {
  if (notes.length === 0) return [];

  const sorted = [...notes].sort((a, b) => a.time - b.time);
  const melody: NoteEvent[] = [];
  const windowSize = 0.1;
  const maxTime = Math.max(...notes.map((n) => n.time + n.duration));

  for (let t = 0; t < maxTime; t += windowSize) {
    const active = sorted.filter(
      (n) => n.time <= t + windowSize && n.time + n.duration > t
    );
    if (active.length === 0) continue;

    const highest = active.reduce((a, b) => (a.midi > b.midi ? a : b));

    const last = melody[melody.length - 1];
    if (last && last.midi === highest.midi && Math.abs(last.time + last.duration - t) < 0.01) {
      last.duration += windowSize;
    } else {
      melody.push({ ...highest, time: t, duration: windowSize });
    }
  }

  return melody;
}
extractBassLine function · typescript · L253-L278 (26 LOC)
src/lib/audio/analyze.ts
function extractBassLine(notes: NoteEvent[]): NoteEvent[] {
  if (notes.length === 0) return [];

  const sorted = [...notes].sort((a, b) => a.time - b.time);
  const bass: NoteEvent[] = [];
  const windowSize = 0.25;
  const maxTime = Math.max(...notes.map((n) => n.time + n.duration));

  for (let t = 0; t < maxTime; t += windowSize) {
    const active = sorted.filter(
      (n) => n.time <= t + windowSize && n.time + n.duration > t
    );
    if (active.length === 0) continue;

    const lowest = active.reduce((a, b) => (a.midi < b.midi ? a : b));

    const last = bass[bass.length - 1];
    if (last && last.midi === lowest.midi && Math.abs(last.time + last.duration - t) < 0.01) {
      last.duration += windowSize;
    } else {
      bass.push({ ...lowest, time: t, duration: windowSize });
    }
  }

  return bass;
}
analyzeHarmonicRhythm function · typescript · L283-L293 (11 LOC)
src/lib/audio/analyze.ts
function analyzeHarmonicRhythm(chords: ChordEvent[]): string {
  if (chords.length < 2) return "static";

  const durations = chords.map((c) => c.duration);
  const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;

  if (avgDuration < 0.75) return "fast (chord per beat or faster)";
  if (avgDuration < 1.5) return "moderate (1-2 beats per chord)";
  if (avgDuration < 3) return "slow (1-2 bars per chord)";
  return "very slow (multi-bar)";
}
detectProgressions function · typescript · L298-L321 (24 LOC)
src/lib/audio/analyze.ts
function detectProgressions(chords: ChordEvent[]): string[] {
  if (chords.length < 3) return [];

  const patterns: string[] = [];
  for (let i = 0; i < chords.length - 2; i++) {
    const seq3 = [chords[i].chord, chords[i + 1].chord, chords[i + 2].chord].join(" → ");
    patterns.push(seq3);
    if (i < chords.length - 3) {
      const seq4 = [chords[i].chord, chords[i + 1].chord, chords[i + 2].chord, chords[i + 3].chord].join(" → ");
      patterns.push(seq4);
    }
  }

  const counts = new Map<string, number>();
  for (const p of patterns) {
    counts.set(p, (counts.get(p) || 0) + 1);
  }

  return [...counts.entries()]
    .filter(([, count]) => count > 1)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 5)
    .map(([pattern, count]) => `${pattern} (×${count})`);
}
Repobility · code-quality intelligence platform · https://repobility.com
analyzeNotes function · typescript · L323-L348 (26 LOC)
src/lib/audio/analyze.ts
export function analyzeNotes(notes: NoteEvent[]): AnalysisResult {
  const tempo = estimateTempo(notes);
  const keyResult = detectKey(notes);
  const key_signature = keyResult?.key ?? null;
  const chords = detectChords(notes, tempo);
  const time_signature = detectTimeSignature(notes, tempo);
  const melody = extractMelody(notes);
  const bassLine = extractBassLine(notes);
  const harmonicRhythm = analyzeHarmonicRhythm(chords);
  const progressions = detectProgressions(chords);

  return {
    status: "completed",
    key_signature,
    key_confidence: keyResult?.confidence ?? 0,
    tempo,
    time_signature,
    chords,
    notes,
    melody,
    bass_line: bassLine,
    harmonic_rhythm: harmonicRhythm,
    progressions,
    midi_data: null,
  };
}
detectAudioCodec function · typescript · L6-L39 (34 LOC)
src/lib/audio/detect-codec.ts
export async function detectAudioCodec(file: File): Promise<string | null> {
  try {
    const readSize = Math.min(file.size, 500000);
    const buffer = await file.slice(0, readSize).arrayBuffer();
    const bytes = new Uint8Array(buffer);

    // Scan for known codec FourCC codes in the MP4 container
    // "alac" = 0x616c6163, "mp4a" (AAC) = 0x6d703461
    for (let i = 0; i < bytes.length - 3; i++) {
      if (
        bytes[i] === 0x61 && bytes[i + 1] === 0x6c &&
        bytes[i + 2] === 0x61 && bytes[i + 3] === 0x63
      ) {
        return "alac";
      }
    }

    // If it's an M4A/MP4 but not ALAC, it's likely AAC
    const ext = file.name.toLowerCase();
    if (ext.endsWith(".m4a") || ext.endsWith(".mp4") || ext.endsWith(".aac")) {
      return "aac";
    }
    if (ext.endsWith(".mp3")) {
      return "mp3";
    }
    if (ext.endsWith(".wav")) {
      return "wav";
    }

    return null;
  } catch {
    return null;
  }
}
midiToNoteName function · typescript · L6-L9 (4 LOC)
src/lib/audio/midi-utils.ts
export function midiToNoteName(midi: number): string {
  const octave = Math.floor(midi / 12) - 1;
  return `${NOTE_NAMES[midi % 12]}${octave}`;
}
noteNameToMidi function · typescript · L11-L17 (7 LOC)
src/lib/audio/midi-utils.ts
export function noteNameToMidi(name: string): number {
  const match = name.match(/^([A-G]#?)(-?\d+)$/);
  if (!match) return 60;
  const [, note, octave] = match;
  const noteIndex = NOTE_NAMES.indexOf(note);
  return (parseInt(octave) + 1) * 12 + noteIndex;
}
createMidiFile function · typescript · L19-L34 (16 LOC)
src/lib/audio/midi-utils.ts
export function createMidiFile(notes: NoteEvent[], name: string = "Transcription"): Uint8Array {
  const midi = new Midi();
  const track = midi.addTrack();
  track.name = name;

  for (const note of notes) {
    track.addNote({
      midi: note.midi,
      time: note.time,
      duration: note.duration,
      velocity: note.velocity / 127,
    });
  }

  return midi.toArray();
}
downloadMidi function · typescript · L36-L45 (10 LOC)
src/lib/audio/midi-utils.ts
export function downloadMidi(notes: NoteEvent[], filename: string = "transcription.mid") {
  const midiData = createMidiFile(notes, filename.replace(".mid", ""));
  const blob = new Blob([midiData.buffer as ArrayBuffer], { type: "audio/midi" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}
getMp4CreationDate function · typescript · L8-L88 (81 LOC)
src/lib/audio/mp4-creation-date.ts
export async function getMp4CreationDate(file: File): Promise<Date | null> {
  try {
    // Step 1: Scan top-level atoms to find where `moov` lives.
    let offset = 0;
    let moovOffset = -1;
    let moovSize = 0;
    let moovHeaderSize = 8;

    while (offset + 8 <= file.size) {
      const headerBuf = await file.slice(offset, offset + 16).arrayBuffer();
      const hv = new DataView(headerBuf);
      let size = hv.getUint32(0);
      const type = String.fromCharCode(
        hv.getUint8(4), hv.getUint8(5), hv.getUint8(6), hv.getUint8(7)
      );

      let hdrSize = 8;

      // 64-bit extended size: size field = 1 means real size is in next 8 bytes
      if (size === 1 && headerBuf.byteLength >= 16) {
        const hi = hv.getUint32(8);
        const lo = hv.getUint32(12);
        size = hi * 4294967296 + lo;
        hdrSize = 16;
      }

      if (size < 8) break;

      if (type === "moov") {
        moovOffset = offset;
        moovSize = size;
        moovHeaderSize = hdrSize
transcribeAudio function · typescript · L3-L91 (89 LOC)
src/lib/audio/transcribe.ts
export async function transcribeAudio(
  audioUrl: string,
  onProgress?: (stage: string, progress: number) => void
): Promise<NoteEvent[]> {
  onProgress?.("Loading audio...", 10);

  // Resolve signed URL if needed (the audio API now returns JSON with a URL)
  let finalUrl = audioUrl;
  if (audioUrl.startsWith("/api/")) {
    const res = await fetch(audioUrl);
    const data = await res.json();
    if (data.url) {
      finalUrl = data.url;
    }
  }

  const audioContext = new AudioContext({ sampleRate: 22050 });
  let audioBuffer: AudioBuffer;

  // Try decoding the signed URL first; if it fails (ALAC on Chrome), use server transcoding
  try {
    const response = await fetch(finalUrl);
    const arrayBuffer = await response.arrayBuffer();
    onProgress?.("Decoding audio...", 20);
    audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
  } catch {
    // ALAC decode failed — fall back to server-side transcoding
    onProgress?.("Transcoding audio (this may take a minute
Repobility · code-quality intelligence · https://repobility.com
createClient function · typescript · L3-L8 (6 LOC)
src/lib/supabase/client.ts
export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}
createClient function · typescript · L4-L28 (25 LOC)
src/lib/supabase/server.ts
export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing sessions.
          }
        },
      },
    }
  );
}
resolveColor function · typescript · L5-L15 (11 LOC)
src/lib/use-theme-colors.ts
function resolveColor(varName: string): string {
  const value = getComputedStyle(document.documentElement)
    .getPropertyValue(varName)
    .trim();
  const el = document.createElement("div");
  el.style.color = value;
  document.body.appendChild(el);
  const resolved = getComputedStyle(el).color;
  el.remove();
  return resolved;
}
resolveAll function · typescript · L17-L23 (7 LOC)
src/lib/use-theme-colors.ts
function resolveAll() {
  return {
    primary: resolveColor("--primary"),
    mutedForeground: resolveColor("--muted-foreground"),
    chart1: resolveColor("--chart-1"),
  };
}
useThemeColors function · typescript · L25-L52 (28 LOC)
src/lib/use-theme-colors.ts
export function useThemeColors() {
  const [colors, setColors] = useState({
    primary: "rgb(80, 60, 200)",
    mutedForeground: "rgb(150, 150, 150)",
    chart1: "rgb(80, 60, 200)",
  });

  useEffect(() => {
    // Resolve on mount
    const id = requestAnimationFrame(() => setColors(resolveAll()));

    // Re-resolve when theme class changes on <html>
    const observer = new MutationObserver(() => {
      requestAnimationFrame(() => setColors(resolveAll()));
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"],
    });

    return () => {
      cancelAnimationFrame(id);
      observer.disconnect();
    };
  }, []);

  return colors;
}
cn function · typescript · L4-L6 (3 LOC)
src/lib/utils.ts
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
‹ prevpage 5 / 5