LOADING…
0%
Build history

Every step,
from the first commit.

A complete build timeline of Grainstash taking shape—from early sequencer experiments to the product it is today.

Commits
1,543
Since
2024
Through
2026

Showing 101–150 of 1,543

Page 3 of 31

24 commits
  1. Add /embed-s3 endpoint for direct R2 audio embedding

    Claude 01c527d7

    Commit notes

    Lets the CLAP service pull sample audio straight from object storage in production instead of having the worker re-upload bytes over HTTP. - clap-service: new POST /embed-s3 endpoint + storage.py (boto3 R2 client, R2_* config), mirroring the Essentia service's S3 path. - backend: embedAudioFromS3Key() client fn; the worker prefers S3 when STORAGE_PROVIDER=r2 and falls back to upload on failure or in MinIO dev. - docker-compose: pass R2_* env to the clap service; README/env updates.

    Files
    8
    Added
    +207
    Removed
    −9
  2. Add CLAP semantic audio search (text -> audio via pgvector)

    Claude bedd5376

    Commit notes

    Layer a CLAP (Contrastive Language-Audio Pretraining) model on top of the existing pgvector setup so users can search audio with natural language (e.g. "dark 808s", "crispy hi-hats"). - services/clap-service: new Python FastAPI sidecar running laion/clap-htsat-fused, exposing /embed-text and /embed-audio in a shared 512-dim space (L2-normalized for cosine search). - DB: migration 0024 adds samples.clap_embedding vector(512) + HNSW cosine index (+ scoped partial indexes), guarded on pgvector availability. - Worker: new clap-embedding queue + processor that streams sample audio to the CLAP service and writes the vector; analysis fans out a job on completion. - Search: clap-search service runs the pgvector <=> query with hybrid relational filters (BPM range, key, type, ...), exposed via POST /api/v1/library/clap-search. - Batch backfill script (bun run clap:backfill) for existing samples. - Config, docker-compose service, npm scripts, env example, docs, and a unit test for the CLAP client.

    Files
    24
    Added
    +1176
    Removed
    −1
  3. Stop flagging tonal one-shots as percussive

    Claude ccdf0299

    Commit notes

    analyze_classification's catch-all marked any short, fast-attack sound is_percussive=True even with no drum_type — but a pitched pluck, synth stab, or 808 is also short and fast-attack. That false percussive flag then forced the key-reliability rules to demand overwhelming (0.9/0.45) evidence, stripping the detected key off legitimately tonal one-shots. The catch-all now requires actual inharmonic evidence (no tonal hint and percussive-dominant HPSS energy) before calling an unclassified sound percussive. The decision lives in the pure, unit-tested classification_rules module alongside the key gating. [private session redacted]

    Files
    3
    Added
    +90
    Removed
    −5
  4. Consolidate sample classification into a single rules engine

    Claude d0a921fd

    Commit notes

    The worker, Elysia API mapper, and Essentia service each ran their own classification rules that overrode or cancelled each other: - Elysia re-applied half/double-time octave correction on top of Essentia's tempo decision, using only the final number plus a hardcoded 80-170 prior, so it could un-do Essentia's (better-informed) octave choice. - Elysia re-gated keys with thresholds that differed from Essentia's own key nulling, so a sample could be tonal in one layer and stripped in the other depending on which boundary it fell between. - Elysia re-classified/fabricated drum types, producing rows where isLoop, drumType and category disagreed and where drumTypeConfidence described a different label than the one stored. - The worker's dedup cache treated 'kick/perc + no bpm/key' as a stale legacy analysis, but that is the correct, normal Essentia output for a drum one-shot, so every percussive one-shot was re-analyzed on each dedup hit. Now each layer does what it is strongest at: - Essentia owns all audio-informed decisions (tempo + octave, key gating, loop, drum type). Key gating moves into a pure, unit-tested classification_rules module so there is one source of truth for the thresholds. - The Elysia mapper becomes thin: unit/Camelot conversion, null-coalescing, and a value-preserving policy for whether to surface a one-shot's tempo. - The worker re-analyzes only rows that lack Essentia-only feature columns. [private session redacted]

    Files
    6
    Added
    +328
    Removed
    −203
  5. Add AI sonic search over sample embeddings

    Claude c8b8c077

    Commit notes

    Lets producers search their library with natural language ("dark punchy 808 around 140 BPM in F minor") instead of exact filters. The query is turned into a synthesized 64-dim target embedding and matched against the pgvector audio embeddings already stored per sample. Backend (services/sonic-search.ts + POST /api/v1/library/ai-search): - parseSonicQuery: uses the existing OpenAI/AI-SDK integration (gpt-5-nano, gated by ENABLE_LLM_ANALYSIS + OPENAI_API_KEY) to extract a structured sonic intent, with a deterministic keyword-lexicon fallback so the feature works with no API key. - synthesizeQueryEmbedding: maps timbral targets (brightness, energy, punchiness, noisiness) onto the known embedding dimensions, encoding only "presence" directions (cosine can't express absence under normalization). - pgvector cosine search (with JS cosine fallback) scoped to the user and refined by exact bpm/key/mode/type/loop filters; relaxes filters or falls back to metadata browsing when needed. - Unit tests for synthesis, key normalization and heuristic parsing. Frontend: - aiSonicSearch API client + createAiSonicSearchQuery hook + query key. - AI search input above the sample grid (LibraryAiSearch). When active it drives the grid with ranked results and shows an interpretation banner. [private session redacted]

    Files
    10
    Added
    +1328
    Removed
    −7
  6. Fix Bun usage gotchas: password verify, safeCompare, entrypoint guards

    Claude 51c19eca

    Commit notes

    Three correctness/robustness issues found while reviewing Bun API usage: - auth/password.ts: Bun.password.verify() THROWS on malformed/legacy/corrupt hashes (UnsupportedAlgorithm/InvalidEncoding) rather than returning false. comparePassword now catches and returns false, so a bad stored hash surfaces as "invalid credentials" instead of a 500 and the constant-time login path stays uniform. Added a regression test. - utils/crypto.ts: safeCompare hand-rolled an XOR loop and, on length mismatch, called Bun.hash() (a fast NON-cryptographic hash) and discarded the result — a no-op that did nothing for timing safety. Replaced with SHA-256 digests compared via node:crypto timingSafeEqual (native in Bun): genuinely constant-time regardless of input length. - index.ts / worker.ts: the server and worker booted unconditionally at module load. Since index.ts also exports the `App` type for Eden inference, a runtime import would accidentally boot the server/DB/cron. Guarded both with `if (import.meta.main)` (true for `bun run` and compiled binaries, false on import) — the idiomatic Bun entrypoint pattern. [private session redacted]

    Files
    5
    Added
    +57
    Removed
    −36
  7. Make Batch Edit "Add to existing" tags actually merge

    Claude bef2f35c

    Commit notes

    The Tags step in BatchEditModal offered a "Replace all" / "Add to existing" toggle, but "add" still sent the tag list as a replacement — it silently clobbered each sample's existing tags. The comment in the handler even noted the merge was unimplemented. Thread a `tagsMode` ('replace' | 'add') through the batch update path. The PATCH /samples/batch handler now, in "add" mode, merges the provided tags into each sample's current tags (de-duplicated, existing order preserved) instead of overwriting; "replace" keeps the prior set-or-clear behaviour. The modal no longer clears tags when "add" is selected with an empty input. Covered by a DB-backed integration test for the add (merge + dedupe), replace, and clear cases.

    Files
    5
    Added
    +207
    Removed
    −13
  8. Fix pack batch rename not persisting visibly

    Claude 47f23b7b

    Commit notes

    Batch "Find & Replace" inside a pack returned 200 but the rename never appeared. The shared BatchEditModal routed pack renames to PATCH /samples/batch/rename, which rewrites the underlying samples.name — yet the pack organizer displays and inline-edits the pack-specific pack_samples.display_name (packDisplayName ?? name). So when a display-name override existed the change was masked, and even the preview was computed from the wrong field. Add a pack-scoped PATCH /sample-packs/:id/samples/batch-rename that applies find/replace to the display name (falling back to the sample name when no override is set), leaving samples.name untouched. The modal now calls this in the pack context and PackOrganizer previews against the displayed names. Covered by a DB-backed integration test for both the override and fallback cases plus pack-ownership authorization.

    Files
    7
    Added
    +393
    Removed
    −11
  9. Add Linear-style quick filters to the library

    Claude f2f279e8

    Commit notes

    Introduce a reusable quick-filter API plus a chip row above the sample grid in the library. The first quick filter is "Favourited". Backend: - New services/quick-filters.ts defining quick-filter shortcuts (id, label, library query params) with live per-user counts. Adding a filter is a single registry entry plus a count condition. - New GET /api/v1/library/quick-filters endpoint returning the definitions with counts, so the same shortcuts can be reused anywhere. Frontend: - getQuickFilters() API client + createQuickFiltersQuery() hook + query key. - LibraryQuickFilters.svelte renders Linear-style toggle chips (icon, label, count) above the grid. - library-state tracks active quick filters generically and merges their query params into getFilters(); counts as active filters and clears with the rest. - Favourite toggle invalidates the quick-filter counts. [private session redacted]

    Files
    10
    Added
    +209
    Removed
    −2
  10. Add Pro-gated sample reanalysis (single + batch/all)

    Claude 1735ac28

    Commit notes

    Adds POST /samples/:id/reanalyse and POST /samples/batch/reanalyse endpoints that re-enqueue the audio analysis job for owned samples, gated to Pro/Team subscription plans. Wires up matching API client functions, TanStack Query mutations, and library UI controls (per-row reanalyse button, "Reanalyse" for selection, "Reanalyse All"). [private session redacted]

    Files
    6
    Added
    +346
    Removed
    −5
6 commits
20 commits