Sound logo

Sound

Community
illodev
sound

Music, sound effects and mixing for drawn-by-code videos. Use it when choosing or cutting music, syncing the animation to the beat, placing sound effects (sfx) or generating new ones with ElevenLabs, and when adding the audio to the MP4.

Overview

Publisherillodev
Repositorydrawn-by-code
Skill namesound
Stars
31
Forks
3
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by illodev on GitHub. Read the source before you install it.

Installation

Install the Sound AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/illodev/drawn-by-code/tree/main/.claude/skills/sound
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/illodev/drawn-by-code.git /tmp/drawn-by-code
mkdir -p .claude/skills
cp -r /tmp/drawn-by-code/.claude/skills/sound .claude/skills/sound
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Sound in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Sound on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Sound is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Sound

The video is cut to the music, not the other way round: tempo first, cuts after.

Music

  • Find the BPM and the first beat (beatOffset). At 120 BPM one beat = 0.5 s and a 4-beat bar = 2 s. Declare bpm and beatOffset in the scene.
  • If the track doesn't fit the duration, cut it by bars with ffmpeg: soft intro → strong entry → groove → the intro returns → final hit.
  • Leave 1.5–2.5 s at the end to read the closing line.
  • Licenses: Pixabay allows commercial use without attribution, but not redistributing the track on its own; don't commit it to the repo (do commit the cutting script). If YouTube claims it via Content ID, dispute it with the license.

Synthesized music (license-free)

If there is no track, it can be generated with code: sandbox/2026-09-24-exquisite-corpse/music.mjs writes a WAV with kick, snare, hi-hat, bass, sitar and a filtered pad, and changes texture at the same seconds as the scene's segments. It is deterministic, so the .wav is not committed: it is regenerated. Without listening to it, check it with the waveform (ffmpeg -i mix.wav -filter_complex showwavespic=s=1600x240 -frames:v 1 wave.png) and volumedetect: no segment should end up nearly flat.

Generated music (ElevenLabs Music)

POST https://api.elevenlabs.io/v1/music (key in ELEVENLABS_API_KEY, never in the repo; commercial use = paid plan; the track is third-party audio: keep it in the experiment's private/ or out/, never committed).

  • A plain prompt ignores tempo and timings («120 BPM, drop at 10 s» gave ~96 BPM with the drop at 24 s). A composition_plan (global styles with «120 bpm», sections with duration_ms, respect_sections_durations: true) gets the tempo right, but sections still don't land where asked.
  • So: generate with a plan, measure it (node engine/tempo.mjs track.mp3: BPM, first beat, dB per half second → where the build, the drop and the ending really are), then cut it by bars with ffmpeg (atrim + 30 ms acrossfade) so each section lands on the edit. Example: sandbox/2026-09-24-saas-promo/cut-music.sh.

Effects

  • Library in assets/sfx/ (33 paper, office and cartoon effects). The prompts they were generated with are in assets/sfx/generate-sfx.mjs: for a new one, add it to SFX and run ELEVENLABS_API_KEY=… node assets/sfx/generate-sfx.mjs name. The key never goes into the repo. Commercial use of ElevenLabs = paid plan.
  • Every visible action with weight gets its sound (impacts, appearances, writing), but not everything: background effects at -13/-17 dB, hits at -4/-6 dB.
  • Place them on the frame of contact, not on the frame where the movement starts.

Mixing

audio.json in the experiment folder:

json
{ "music": "music-cut.mp3", "musicDb": -2, "duration": 6.5, "sfxDir": "../../assets/sfx",
  "cues": [["pop", 1.0, -10], ["stamp", 2.5, -4], ["scribble", 3.0, -15]] }

node engine/mix.mjs sandbox/x/audio.json → mix.wav; with audio: { mix: 'mix.wav' } in the scene, render.mjs adds it automatically. If you move a shot, move its cues.

  • "align": "onset" (recommended): a cue's second is when its hit is heard. Many effects start with a breath of air (page_turn 0.16 s, tick 0.8 s into the file); without it they land a drawing or more late. Then write the contact second from the block's code (its landing/beat constants) as is.
  • A track that stops dead before the end card holds: throw its last half second through a delay in time (aecho at eighth notes, decaying) and fade by the end; it rings out in its own key without you having to listen (see sandbox/2026-09-24-saas-promo/cut-music.sh).
  • Private music: point audio.mix at a file in private/; render.mjs skips a missing mix, so the scene still renders without the private folder.

Lessons

  • 2026-09-23 · paper-short · A silence mid-video looks like a technical fault: if the music stops, make it on a clear, brief hit.
  • 2026-09-24 · exquisite-corpse · With the kick at full and the pad low, the quiet segments were nearly mute after normalizing: balance per segment by looking at the waveform.
  • 2026-09-25 · physics-history v2 · ElevenLabs with a composition_plan of one section per scene (duration_ms = the scene's length, respect_sections_durations: true) gave the film's exact length (58 s) and a fade-out; a dark beat in the picture (the fall into a black hole) is scored by ducking the track with an ffmpeg volume=…:eval=frame envelope, never by cutting it to silence (see sandbox/2026-09-25-physics-history/gen-music.mjs, cut-music.sh).
  • 2026-09-29 · external trailer · A generated trailer track ignored its sections (no quiet drop, hits every 4 s wherever they fell): measure its hit onsets (an energy rise of more than 9 dB between 20 ms windows), find the one delay that lands most hits on the cuts (0.5 s fitted five of eight), and duck the track with a volume=…:eval=frame envelope where the picture asks for near-silence.
  • 2026-09-29 · external trailer · Speech-to-text is not deterministic: a TTS line that passed one transcription was heard with an extra syllable by another pass over the same file (and the other way round on a later call). Check every line with two requests in different modes (plain and character timestamps) and flag it if either differs from the script.
  • 2026-09-29 · external trailer · The user heard the volume "going up and down, it sounds odd": a voice-keyed sidechaincompress (ratio 6, release 350 ms) cut the music 15–20 dB on every phrase and gave it all back in every gap, and a single-pass loudnorm (dynamic mode) rode the gain too. Plot momentary loudness (ebur128, M every 0.25 s) of the music before and after ducking to see it. Fix: precompute the music envelope from the line timings (about −7 dB under speech, 0.25 s down, 0.6 s up, gaps under 1.2 s bridged) and multiply it in (amultiply with an envelope rendered to a raw f32 file); normalise with one measured static gain plus a peak limiter, not dynamic loudnorm; keep scripted quiet sections near −10 dB, not −18. Title cards in a trailer want a real impact: half a second of music suck-out before the cut, a reverse riser whose peak lands on the cut, and a long-tailed hit about 10 dB above the dialogue.
  • 2026-09-29 · external trailer · Correction to the line above: the user rejected that recipe ("the title sounds are horrible, redo the whole sound"). Pasted title effects (a generated hit plus a riser on top of a playing track, with a suck-out before each) sound cheap, and the peaks drove the limiter into audible crushing. What worked: build the score from separate generated segments, one per section, each title card opening a new segment on its own orchestral impact (so the hit belongs to the music), quiet sections being genuinely quiet music, and no volume automation beyond a plain duck under speech. Generated short pieces fade out on their own, so request each one several seconds longer than needed and use only the strong part; a model that will not write a crescendo can give a decrescendo to reverse into a riser. Balance by measurement: title hits 4–6 dB (momentary) above the speech median, limiter under ~3 dB on isolated peaks, a light compressor on the narrator, and every line still transcribed correctly in the final mix.
  • 2026-09-30 · external product promo · A weak fricative at the start of a brand name (an «f») disappears under a kick that lands on it: the final mix was transcribed as a different word twice while the dry voice read right. Say the name in a gap of the music (place the track so a beat-less bar or a break covers it and the groove returns right after), duck the music a further ~6 dB around every occurrence of the name, and transcribe the final mix, not the voice alone.
  • 2026-09-30 · external product promo · A limiter at 44.1 kHz holds the samples, not the true peak (between samples): mixes measured +0.6 to +1.8 dBTP. Limit at 4× (aresample=176400,alimiter=…,aresample=44100), then measure ebur128=peak=true and trim the gain only if it still exceeds −1 dBTP; trimming alone cost 3 dB of loudness.
  • 2026-09-30 · external product promo · A generated track that ends before a longer voice: repeat whole bars of its groove before its natural ending (cut on bar boundaries measured from the first beat, 10 ms fades at each joint, concat), so the music still resolves just after the last line instead of fading under it.
  • 2026-09-30 · external product promo · Sound effects matched by their peak (−6 to −15 dBFS) said nothing about how far under the narration they sat: measured with stems, some were 26 dB above the voice, and the user heard them «very very loud», covering words. Put the effects on their own bus about −10 dB, duck that bus a further ~8 dB under each word (merged across gaps under 0.3 s, so hits between phrases keep their punch), lower any single effect that still masks a word by exactly what it lacks (computed per 50 ms window against the voice as it sounds in the mix, compressor included), and measure voice minus effects bus in every window with speech: at least 10 dB.
  • 2026-10-02 · product videos (four styles) · Measure voice against effects at full band with K-weighting, never resampled to 8 kHz: a resampled mixer cannot hear anything above 4 kHz, so clicks, sparkles, crackle and keyboard clacks passed the check while sitting 3–10 dB above the narration. Use the same measure for the automatic per-effect gain and for the check (high-pass 38 Hz + high-shelf +4 dB from 1.5 kHz, at 44.1/48 kHz), and confirm it against ebur128 on the stems before trusting it. Rules that held: with voice, effects ≥ 10–12 dB under it per 50 ms window; without voice, momentary effects ≥ 8 LU under the typical voice level (the per-word duck does nothing between phrases). Windows at a phrase edge (the voice's tail plus an effect right behind it) are the ones that fail. Put a −6 dB shelf above 6 kHz on the effects bus when they are mostly treble.
  • 2026-10-02 · product videos · ebur128 prints M: nan in silence. An analyser that skips those lines leaves exactly the voiceless stretches unchecked: parse nan as silence (very low) and pair windows by their timestamp, not by position in the list.
  • 2026-10-02 · product videos · eleven_v3 sometimes cuts a take in the middle of the phrase's last sound, and speech-to-text fills in the missing letter from context (a word ending in «l» cut before the «l» is transcribed whole), so the transcription check passes it. Detect it by level: the last 40 ms of each take against the take's peak, cut if within 30 dB. Fix only the affected phrases with a non-spoken tag after them ([breath], [beat], [short breath]); on every phrase it makes several takes much slower. Each tag is a new take: compare spoken duration (first word to last), the last 40 ms, fricatives («s», «z») and what follows the last letter (some tags add 1–3 s of silence or a click), and keep each good take's cache before generating the next. When aligning, trim trailing spaces and the tag's gap so it does not count as part of the phrase; a clean ending is also longer (0.26–0.35 s natural decay), so re-tune the gaps to keep the rhythm.
  • 2026-10-02 · product videos · A voice-heavy mix is checked by transcribing the final mix phrase by phrase, too, and by comparing against the whole track, not the isolated take: the spelling of a name is decided across the full text, and Scribe does not always return the same.
  • 2026-10-02 · product videos · A limiter at 4× followed by a return to 44.1 kHz raised the true peak by ~1.2 dB with a hot voice, and trimming afterwards cost 1.8 LU: lower the limiter's ceiling and drive it harder instead. And an afade inside a graph with adelay inputs and amix=duration=longest did not land where expected (−39 dBFS on the last frame): fade in the normalisation step, on a file that starts at 0, with the duration in whole frames.
  • 2026-10-02 · product videos · afir (ffmpeg 6.1): dry is the input's gain (dry=0 gives silence), the reverb tail is cut where the input ends unless the input is padded with apad, and gtype=none leaves the level to the mixer.
  • 2026-10-02 · product videos · Generated music (ElevenLabs Music) ignored the section contents again (the bass came in 2 s late, an «intro of 3.5 s» came out at 7 s, a section asked «without drums» came out as silence, and the ending was cut dead): always measure bass onsets and pulse and mount by measurement; sections under 3000 ms are rejected (422). A closing chord that has to ring until the logo is more reliable synthesised in the measured key. For retro/8-bit pieces, synthesise the effects (pulse with duty cycle, triangle, 15-bit LFSR noise): zero quota, deterministic and in the music's key.

Frequently asked questions

What does the Sound AI skill do?

Music, sound effects and mixing for drawn-by-code videos. Use it when choosing or cutting music, syncing the animation to the beat, placing sound effects (sfx) or generating new ones with ElevenLabs, and when adding the audio to the MP4.

Why use Sound on TypingMind?

Because you install it once and use it with any model. Sound is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Sound in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/illodev/drawn-by-code/tree/main/.claude/skills/sound. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Sound?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Sound?

As many as you like. As long as a model supports skills, you can use Sound with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Sound AI skill free?

It is published on GitHub by illodev. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇