<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jeswin jaison</title>
    <description>The latest articles on DEV Community by jeswin jaison (@jeswin_jaison_4537e4ccbf1).</description>
    <link>https://dev.to/jeswin_jaison_4537e4ccbf1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1571443%2Fdb7ed9ff-453f-4558-ba2a-eeda0c34e1bf.jpg</url>
      <title>DEV Community: jeswin jaison</title>
      <link>https://dev.to/jeswin_jaison_4537e4ccbf1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeswin_jaison_4537e4ccbf1"/>
    <language>en</language>
    <item>
      <title>How I built a riff vault for Sam with open-source AI</title>
      <dc:creator>jeswin jaison</dc:creator>
      <pubDate>Sat, 03 Oct 2026 19:25:11 +0000</pubDate>
      <link>https://dev.to/jeswin_jaison_4537e4ccbf1/how-i-built-a-riff-vault-for-sam-with-open-source-ai-5f5l</link>
      <guid>https://dev.to/jeswin_jaison_4537e4ccbf1/how-i-built-a-riff-vault-for-sam-with-open-source-ai-5f5l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Somewhere on Sam's laptop, there's a riff Sam swears is the best thing he has written all year.&lt;/p&gt;

&lt;p&gt;It's in a file called &lt;code&gt;recording_027_final_v2.wav&lt;/code&gt;. Or maybe &lt;code&gt;heavyyy_enough.wav&lt;/code&gt;. Or &lt;code&gt;idea_actually_good.m4a&lt;/code&gt;. He has 100s of these, and not many filenames say what's inside.&lt;/p&gt;

&lt;p&gt;Here's the cruel part: you don't remember a riff by its name. You remember how it &lt;em&gt;felt&lt;/em&gt;. Ask Sam about the lost one and you get something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"heavy, Drop C, around 145, I think it was a chorus idea"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a perfectly good search query. It's also one that no folder on earth can answer.&lt;/p&gt;

&lt;p&gt;So I built something that can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RiffSalad&lt;/strong&gt; (yes, that's what the recordings folder looks like) is a local AI vault for guitarists. You drop riffs in, or record them straight from the browser, and it &lt;em&gt;listens&lt;/em&gt;: tempo, key, energy, how busy the playing is, even a MIDI transcription. It writes a short description and a few tags for each one. Then you search the way you actually think:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;heavy drop C riff around 140 BPM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clean mellow fingerpicked thing in E minor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;something like my last blues riff&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can talk to your riffs, too. Record a voice note ("this was the bridge, needs a key change") and it's transcribed on your own machine and becomes searchable. There's also a waveform player, inline editing, and a "find similar riffs" button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who I built it for
&lt;/h3&gt;

&lt;p&gt;Sam is an intermediate guitarist whose head is constantly popping off with new ideas. The last time I saw him, he was sitting in front of his laptop, humming a tune at it, while I sat there being the overachieving automator. I wanted to build the thing that lets him stop scrolling and just type what he remembers. Mostly because I wanted him to stop humming. lol!&lt;/p&gt;

&lt;h3&gt;
  
  
  What Sam said
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Finallyyy, I can have something organized in my life and easy to find.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/jqUX-qTd9Tg" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jesi318" rel="noopener noreferrer"&gt;
        jesi318
      &lt;/a&gt; / &lt;a href="https://github.com/jesi318/Riff-Salad" rel="noopener noreferrer"&gt;
        Riff-Salad
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/jesi318/Riff-Salad/frontend/public/riff-salad-logo.svg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjesi318%2FRiff-Salad%2FHEAD%2Ffrontend%2Fpublic%2Friff-salad-logo.svg" alt="Riff Salad" width="700"&gt;&lt;/a&gt;
&lt;p&gt;&lt;strong&gt;Your guitar ideas, searchable.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A local-first AI vault for guitarists. Record a riff, import the file — and find it later by describing it in plain English.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://python.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7656ab9e8d32e5be4a1b6c1ae3377fbe93e62e29ca567993546f4c0a07a4cda9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507974686f6e2d332e31312d3337373661623f7374796c653d666c61742d737175617265266c6f676f3d707974686f6e266c6f676f436f6c6f723d7768697465" alt="Python"&gt;&lt;/a&gt;
&lt;a href="https://fastapi.tiangolo.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6344f1c3458d637b82551f4b5aa9324f6948a70bb0d6ef9ab16b6f70e5652f3e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f466173744150492d302e3132382d3030393638383f7374796c653d666c61742d737175617265266c6f676f3d66617374617069266c6f676f436f6c6f723d7768697465" alt="FastAPI"&gt;&lt;/a&gt;
&lt;a href="https://react.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e7f85ea0665f170728de8b991339d2c9fa17a7a9e01aa32e81147ad99e087c1e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f52656163742d31392d3631646166623f7374796c653d666c61742d737175617265266c6f676f3d7265616374266c6f676f436f6c6f723d626c61636b" alt="React"&gt;&lt;/a&gt;
&lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a85b7ec09874fe000b81b8008068531bb7801ee9234c55b71377daca716d66b0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f6c6c616d612d6c6f63616c5f41492d3030303030303f7374796c653d666c61742d737175617265" alt="Ollama"&gt;&lt;/a&gt;
&lt;a href="https://docs.docker.com/compose/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/166ae7e1aae9a4c4a00de86287c47d293de016649f4a4cb7dea6e852b67bcec1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f636b65722d436f6d706f73652d3234393665643f7374796c653d666c61742d737175617265266c6f676f3d646f636b6572266c6f676f436f6c6f723d7768697465" alt="Docker"&gt;&lt;/a&gt;
&lt;a href="https://github.com/jesi318/Riff-Salad/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/152aa2a37725b9fd554b28ff24d270f6071c67927a63e6d635a55c8e188e20c7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e3f7374796c653d666c61742d737175617265" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every guitarist knows this feeling: you recorded a cool riff a few months ago, but you can't find it because you saved it as &lt;code&gt;recording_027_final_v2.wav&lt;/code&gt;. You remember it was a heavy, slow, drop-tuned thing around 80 BPM — but searching a folder of audio files for that is impossible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RiffSalad solves this.&lt;/strong&gt; Import your recordings and search them like you'd describe them to a bandmate.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;"heavy drop C riffs around 140 BPM"
"that mellow fingerpicking thing in E minor"
"fast aggressive shredding, lots of notes"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How It Works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────┐
│  Import WAV / MP3 / M4A                                          │
│         │                                                        │
│         ▼                                                        │
│  librosa analyzes: BPM, Key, Energy, Note Density, Duration      │
│         │                                                        │
│         ├──► Ollama LLM&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jesi318/Riff-Salad" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Everything runs locally and needs no API keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/jesi318/Riff-Salad
&lt;span class="nb"&gt;cd &lt;/span&gt;Riff-Salad
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;localhost:3000&lt;/code&gt;. The first run pulls the models. After that, it works with no internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;One decision shaped everything: &lt;strong&gt;the small model does as little as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted this to run on a normal machine, so the language model is a 1.5B-parameter &lt;code&gt;qwen2.5&lt;/code&gt; served by Ollama. That's tiny. A model that size is good at turning "heavy drop C riff around 140" into structured filters, and bad at deciding whether a riff actually &lt;em&gt;is&lt;/em&gt; heavy. So I stopped asking it to decide.&lt;/p&gt;

&lt;p&gt;The rest of the stack is deliberately plain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ears:&lt;/strong&gt; &lt;code&gt;librosa&lt;/code&gt; measures BPM (harmonic/percussive separation, then tempo tracking), key (matching against tonal profiles), energy, and note density. A Basic Pitch-style ONNX model extracts MIDI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice:&lt;/strong&gt; &lt;code&gt;faster-whisper&lt;/code&gt; (the &lt;code&gt;tiny&lt;/code&gt; model) transcribes voice notes locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; &lt;code&gt;nomic-embed-text&lt;/code&gt; embeddings via Ollama, stored alongside a SQLite database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plumbing:&lt;/strong&gt; FastAPI and SQLAlchemy on the back end; React 19, TypeScript, Vite, Tailwind, and WaveSurfer.js on the front.&lt;/li&gt;
&lt;li&gt;On an M4 mac air, a riff takes less than 15 seconds to analyze.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search runs in four stages, and the chat model only gets the first one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse.&lt;/strong&gt; The LLM turns your sentence into structured filters: BPM range, key, energy, mood words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prune.&lt;/strong&gt; Plain code throws out anything that breaks the measurable facts. "Around 100 BPM" still lets 96 through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-rank.&lt;/strong&gt; Embedding similarity sorts what's left.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fall back.&lt;/strong&gt; If the LLM or embeddings are unavailable, it still matches text across titles, notes, tags, transcripts, and descriptions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rule I'm proudest of: the tagger isn't allowed to say "heavy," "metal," or "doom" unless the numbers back it up. Small models love to over-claim, and "heavy" is the easiest word in music to overuse. So the measured facts get the first vote, and the model just gets to phrase things nicely. "Find similar" follows the same philosophy, blending embedding similarity with plain feature distance (BPM, key, energy), so "sounds like this" is something you can sanity-check.&lt;/p&gt;

&lt;p&gt;It isn't magic. Key and BPM detection are heuristics, and sometimes they're wrong, which is why both can be overridden inline. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;A riff is a diary entry you can hum. It's half-finished, sometimes embarrassing, often unreleased. I didn't want Sam to upload his unfinished ideas to someone else's server just to find them again later. With open models running locally, they don't have to: the audio, the transcripts, and the database all live in a &lt;code&gt;data/&lt;/code&gt; folder on his own machine.&lt;/p&gt;

&lt;p&gt;Open also gave me things a closed API wouldn't have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No meter running.&lt;/strong&gt; Every import triggers analysis, tagging, and embeddings. Sam can throw 200 riffs at it without a bill, a quota, or an API key to babysit. After the first run, it works in a rehearsal room with no wifi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Models are a config line.&lt;/strong&gt; &lt;code&gt;LLM_MODEL&lt;/code&gt;, &lt;code&gt;EMBEDDING_MODEL&lt;/code&gt;, and &lt;code&gt;WHISPER_MODEL&lt;/code&gt; are environment variables, so swapping a model never means touching code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints made the design better.&lt;/strong&gt; Because I committed to a tiny local model, I had to build the hybrid pipeline: the LLM for language, plain code for facts. That's why search still works when the model is down, and why I can explain every result. A giant hosted model would have let me be lazy, and I suspect the app would be less predictable for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sam still has 103 files called recording-something. He just doesn't need the filenames anymore.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
