- Files
- 15
- Added
- +1490
- Removed
- −857
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
- Files
- 0
- Added
- +0
- Removed
- −0
Add /embed-s3 endpoint for direct R2 audio embedding
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
Add CLAP semantic audio search (text -> audio via pgvector)
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
- Files
- 0
- Added
- +0
- Removed
- −0
Stop flagging tonal one-shots as percussive
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
Consolidate sample classification into a single rules engine
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
- Files
- 2
- Added
- +1
- Removed
- −7
Merge pull request #368 from arminnaimi/claude/relaxed-pasteur-efl7ib
Commit notes
Add AI sonic search with natural-language query parsing
- Files
- 0
- Added
- +0
- Removed
- −0
Add AI sonic search over sample embeddings
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
- Files
- 1
- Added
- +38
- Removed
- −6
- Files
- 1
- Added
- +11
- Removed
- −179
- Files
- 8
- Added
- +436
- Removed
- −56
- Files
- 17
- Added
- +514
- Removed
- −331
Merge pull request #367 from arminnaimi/claude/nifty-cannon-j31l2a
Commit notes
Fix timing-safe comparison and prevent accidental worker/server startup
- Files
- 0
- Added
- +0
- Removed
- −0
Fix Bun usage gotchas: password verify, safeCompare, entrypoint guards
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
- Files
- 0
- Added
- +0
- Removed
- −0
Make Batch Edit "Add to existing" tags actually merge
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
Fix pack batch rename not persisting visibly
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
- Files
- 0
- Added
- +0
- Removed
- −0
Add Linear-style quick filters to the library
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
- Files
- 0
- Added
- +0
- Removed
- −0
Apply Biome formatting fixes to pre-existing files
Commit notes
No behavior change; brings these files in line with Biome's formatting rules (import order, line wrapping). [private session redacted]
- Files
- 5
- Added
- +51
- Removed
- −37
Add Pro-gated sample reanalysis (single + batch/all)
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
- Files
- 7
- Added
- +530
- Removed
- −227
- Files
- 9
- Added
- +702
- Removed
- −236
- Files
- 3
- Added
- +153
- Removed
- −49
- Files
- 28
- Added
- +1880
- Removed
- −381
- Files
- 2
- Added
- +93
- Removed
- −4
- Files
- 5
- Added
- +28
- Removed
- −20
- Files
- 2
- Added
- +2
- Removed
- −2
- Files
- 0
- Added
- +0
- Removed
- −0
- Files
- 4
- Added
- +49
- Removed
- −48
- Files
- 2
- Added
- +14
- Removed
- −14
Merge pull request #363 from arminnaimi/advisor/006-stripe-sdk-upgrade
Commit notes
Upgrade Stripe SDK to v22 and align billing handlers
- Files
- 0
- Added
- +0
- Removed
- −0
- Files
- 12
- Added
- +129
- Removed
- −81
Merge pull request #362 from arminnaimi/advisor/005-money-route-integration-tests
Commit notes
Add integration coverage for money routes and Stripe Connect
- Files
- 0
- Added
- +0
- Removed
- −0
- Files
- 1
- Added
- +1
- Removed
- −1
- Files
- 2
- Added
- +46
- Removed
- −29
- Files
- 1
- Added
- +165
- Removed
- −0
- Files
- 1
- Added
- +337
- Removed
- −0
- Files
- 1
- Added
- +8
- Removed
- −4
Merge pull request #361 from arminnaimi/advisor/004-upload-completion-auth-retry
Commit notes
Add 401 refresh-and-retry for critical upload and download fetches
- Files
- 0
- Added
- +0
- Removed
- −0
- Files
- 1
- Added
- +1
- Removed
- −1
- Files
- 4
- Added
- +117
- Removed
- −11
Merge pull request #360 from arminnaimi/advisor/003-upload-link-transactional-upload
Commit notes
Make upload-link uploads transactional and enforce atomic file limits
- Files
- 0
- Added
- +0
- Removed
- −0
- Files
- 1
- Added
- +1
- Removed
- −1
- Files
- 2
- Added
- +322
- Removed
- −80
Merge pull request #359 from arminnaimi/advisor/002-download-zip-integrity
Commit notes
Fail downloads loudly and record credits only after successful ZIP assembly
- Files
- 0
- Added
- +0
- Removed
- −0
- Files
- 1
- Added
- +1
- Removed
- −1