<?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: Nikolai Stepanov</title>
    <description>The latest articles on DEV Community by Nikolai Stepanov (@nikolai_stepanov_d02669aa).</description>
    <link>https://dev.to/nikolai_stepanov_d02669aa</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%2F4060120%2F31c69bc7-3bb1-4a95-a774-eab0a154a3e9.jpg</url>
      <title>DEV Community: Nikolai Stepanov</title>
      <link>https://dev.to/nikolai_stepanov_d02669aa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikolai_stepanov_d02669aa"/>
    <language>en</language>
    <item>
      <title>My macOS virtual mic driver is Apple's sample code. The hard part was a slider that did 0.00 dB</title>
      <dc:creator>Nikolai Stepanov</dc:creator>
      <pubDate>Wed, 07 Oct 2026 12:30:57 +0000</pubDate>
      <link>https://dev.to/nikolai_stepanov_d02669aa/my-macos-virtual-mic-driver-is-apples-sample-code-the-hard-part-was-a-slider-that-did-000-db-fcc</link>
      <guid>https://dev.to/nikolai_stepanov_d02669aa/my-macos-virtual-mic-driver-is-apples-sample-code-the-hard-part-was-a-slider-that-did-000-db-fcc</guid>
      <description>&lt;p&gt;The Compression slider in my app moved the output by exactly 0.00 dB. Not "a bit too little". Zero, at two of three input levels, on real presets, and none of my existing checks caught it.&lt;/p&gt;

&lt;p&gt;It took five rounds to fix. The first four times I declared it done, the person listening in headphones heard nothing change. This post is about that bug and a few of its relatives. I hit all of them while building a virtual microphone for macOS: an app that takes your real mic, processes it in real time, and shows up in Zoom, OBS or Discord as one more input device.&lt;/p&gt;

&lt;p&gt;The surprising part is where the difficulty sat. The driver, the bit everyone warns you about, was the easy half.&lt;/p&gt;

&lt;h2&gt;
  
  
  The virtual device is a loopback, and Core Audio is the transport
&lt;/h2&gt;

&lt;p&gt;A virtual mic on macOS is an AudioServerPlugIn: a bundle in &lt;code&gt;/Library/Audio/Plug-Ins/HAL&lt;/code&gt; that &lt;code&gt;coreaudiod&lt;/code&gt; loads and publishes as a device. No kernel extension, no DriverKit.&lt;/p&gt;

&lt;p&gt;I did not write it from scratch. Apple publishes a sample called NullAudio in &lt;a href="https://developer.apple.com/documentation/coreaudio/creating-an-audio-server-driver-plug-in" rel="noopener noreferrer"&gt;Creating an Audio Server Driver Plug-in&lt;/a&gt;. My build script downloads the pinned zip, checks its SHA-256, applies a 134-line patch and compiles one C file with clang. The repo never commits the generated driver source. You edit the patch.&lt;/p&gt;

&lt;p&gt;The patch does three things: renames the bundle and the device, sets stable UIDs, and adds a ring buffer of 16,384 stereo Float32 frames, indexed by sample time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WriteMix: what a client plays into the device's output side&lt;/span&gt;
&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;sampleTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ReadInput: what Zoom records from the device's input side&lt;/span&gt;
&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;sampleTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IO callback does no allocation and takes no locks. The whole trick is the loopback. The device has an output side and an input side. My processing engine plays the finished voice into the output side as an ordinary Core Audio client, and Zoom records from the input side. There is no shared memory, socket or XPC between the engine and the driver. The HAL itself carries the audio.&lt;/p&gt;

&lt;p&gt;That keeps the code running inside a system daemon tiny and dumb. The plug-in loads no models, touches no network and reads no user files. Everything interesting happens in a normal user process, where a crash is my problem and not the system audio daemon's.&lt;/p&gt;

&lt;p&gt;The measured loopback latency, write to read, is 512 frames, or 10.667 ms. A small verifier writes an impulse at frame 4800 and a 997 Hz tone into the output scope, then reads them back from the input scope. The acceptance run fails if latency reaches 20 ms.&lt;/p&gt;

&lt;p&gt;One thing I would tell anyone starting out: freeze the device UID on day one. When the product was renamed, the UID stayed. Change it and Zoom forgets the selected mic. Worse, if the renamed bundle installs under a new file name, the old one keeps loading too, and you publish two devices with the same UID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three processes, not one
&lt;/h2&gt;

&lt;p&gt;The app is split into three processes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The GUI, a SwiftUI app that owns no audio at all.&lt;/li&gt;
&lt;li&gt;An engine agent under launchd (&lt;code&gt;RunAtLoad&lt;/code&gt;, &lt;code&gt;KeepAlive&lt;/code&gt;). It holds the physical mic, runs the realtime DSP and writes to the virtual device. It keeps running when the window closes, because your call does not end when you close a settings window.&lt;/li&gt;
&lt;li&gt;The HAL plug-in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The GUI talks to the agent over a narrow XPC protocol of 13 methods: ping, configure, start, stop, apply a preset, snapshot, plus a few monitor, playback and calibration calls. Presets travel as JSON.&lt;/p&gt;

&lt;p&gt;Realtime code is C++20. Offline code (the voice analyzer and the preset generator) is Swift: pure functions, easy to test. Inside the agent the format is 48 kHz Float32 mono, with a preferred HAL buffer of 128 frames. Every buffer is allocated up front. That includes the delay line for the headphone monitor, sized for the worst-case latency of every stage, because growing it later would mean allocating on the render thread.&lt;/p&gt;

&lt;p&gt;The split hides a trap. A buffer bound lives in C++, while the UI that sets it lives in Swift. Raise the calibration recording from 20 to 60 seconds in Swift alone, and it compiles fine and then fails at runtime. When a limit sizes a preallocated array, the C++ side has to move with the Swift side.&lt;/p&gt;

&lt;p&gt;Between the agent and the device sits a lock-free single-producer, single-consumer ring of 262,144 frames. The mic and the virtual device run on different clocks, so they drift. The reader keeps a reserve of 256 frames with a tolerance of ¬±64. When the fill drifts outside that window, it reads one frame more or one fewer and linearly stretches the block back to the requested length. A one-frame stretch now and then is cheap. A ring that slowly runs dry is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: a mic that was silent, with no error anywhere
&lt;/h2&gt;

&lt;p&gt;Symptom: with some mics the level meter sat at minus infinity. The engine looked like it was running. Nothing in the logs.&lt;/p&gt;

&lt;p&gt;I first filed it as "Bluetooth headsets don't work". Wrong. The real cause: I opened the input audio unit at 48 kHz regardless of the hardware rate and let it convert. Every property call returned &lt;code&gt;noErr&lt;/code&gt;. Then every single &lt;code&gt;AudioUnitRender&lt;/code&gt; failed with &lt;code&gt;kAudioUnitErr_CannotDoInCurrentContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The unit's internal converter wants an exact client frame count, but the input callback receives device frames. For 16 kHz you can scale, since it is an integer ratio. For 44.1 kHz you cannot: one frame short returned -10863, one frame long returned -10874, alternating.&lt;/p&gt;

&lt;p&gt;The fix: open the unit at the hardware rate and resample with an &lt;code&gt;AudioConverter&lt;/code&gt; inside the callback, skipping it when the rates already match. To prove the cause was the rate and not Bluetooth, I forced the MacBook's built-in mic to 44.1 kHz. Before the fix it was silent too. After, it read -52.0 dB.&lt;/p&gt;

&lt;p&gt;Lesson: when a Core Audio setup call "succeeds", believe the render callback, not the setter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: the slider that did 0.00 dB
&lt;/h2&gt;

&lt;p&gt;This is the expensive one.&lt;/p&gt;

&lt;p&gt;A compressor has an absolute threshold in dBFS. The design assumed speech would reach the compressor at around -20.2 dBFS. I measured where it actually arrived on the built-in mic: -37.8 dBFS. That is 17.6 dB below the level the whole stage was designed around. A compressor whose input never crosses the threshold does nothing, whatever ratio you set.&lt;/p&gt;

&lt;p&gt;Where did the level go? The per-voice EQ alone took 8.8 dB, filters 2.4 dB, room handling 1.1 dB. And the only stage that raised the signal back to the working level sat at the end of the chain, after everything that depended on it. The dynamic EQ, pop cut and a parallel density stage had the same disease.&lt;/p&gt;

&lt;p&gt;The fix was structural, not a new constant. The working point became its own stage, right after the noise gate. Everything with a threshold relative to the raw mic sits above it. Everything with an absolute threshold sits below it. Gain became a pure volume control. Later a broadcast-style leveler replaced the static trim: it decides on speech before the gate, freezes during pauses, and moves at most 6 dB per second.&lt;/p&gt;

&lt;p&gt;So why five rounds? Because I kept declaring it fixed on numbers, not on what the listener heard. Round one moved the output by 0.00 dB on real presets. Round two managed -0.8 dB, round three "half a notch", round four 0.01 dB. Only round five produced something a person could hear: Ratio pulled the level down by 4.0 to 6.3 dB, and sibilants dropped 12 dB.&lt;/p&gt;

&lt;p&gt;The check that finally held is a sweep. It drives the real C++ stages at three input levels, moves the control across its range, and requires a monotonic change with a real step size. On the old code it is red. That last part became a rule: a new check has to fail on the old code before I trust it to pass on the new one. I swap the old file in with &lt;code&gt;git show &amp;lt;sha&amp;gt;:path&lt;/code&gt;, build, run, and restore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 3: nine green tests and a de-esser that never fired
&lt;/h2&gt;

&lt;p&gt;Same disease, different organ. The de-esser's output was bit-identical at every slider position. Its threshold (target minus 7, so -22 dBFS) assumed auto-level had already lifted the signal. On a real minute of speech, the 7.2 kHz band peaked at -37 dBFS. Fifteen dB short.&lt;/p&gt;

&lt;p&gt;Nine sibilance tests were green the whole time. All nine checked what the preset generator wrote. None checked that the stage fired.&lt;/p&gt;

&lt;p&gt;After moving the stage, a second bug showed up in the reduction curve itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before: a ceiling the signal never reached&lt;/span&gt;
&lt;span class="n"&gt;gr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;excess&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// above ~3 dB of depth, every setting gave the same 1.20 dB&lt;/span&gt;

&lt;span class="c1"&gt;// after: keeps growing with depth&lt;/span&gt;
&lt;span class="n"&gt;gr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;excess&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// 0 / 1.20 / 2.27 / 3.20 dB at depth 0 / 3 / 6 / 9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pop filter had the same dead shape. The threshold also became relative to a slow level envelope, so the volume knob stopped secretly driving the de-esser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 4: the numbers passed and the ear did not
&lt;/h2&gt;

&lt;p&gt;I had a room-reverb reduction stage with acceptance criteria declared before measuring, which is good practice. It passed: the tail 200 ms after a word dropped by 7.5 to 9 dB, and loudness moved by no more than 0.6 dB. The person listening heard no difference at all.&lt;/p&gt;

&lt;p&gt;They were right. On a laptop mic, what you hear as "room" is not the late tail. It is early reflections in the first 50 to 100 ms, inside the words. My metric measured the tail. The point 50 ms after a word barely moved.&lt;/p&gt;

&lt;p&gt;The underlying physics is distance. The direct-to-reverberant ratio is around +19 dB at the mouth and around -1 dB at 50 cm. A single-channel classic DSP stage cannot put that distance back. What did help was a neural voice-isolation stage, picked from six candidates in a loudness-matched A/B test by ear.&lt;/p&gt;

&lt;p&gt;Two rules came out of it. Numbers only screen candidates; a loudness-matched listening test is the final gate. And test on the worst mic first (the MacBook's built-in one) with at least two different recordings, because fixtures lie. A sine "vowel" has no formants, and white-noise "room tone" reads as a fricative.&lt;/p&gt;

&lt;h2&gt;
  
  
  The latency budget I planned and the one I chose
&lt;/h2&gt;

&lt;p&gt;The plan said under 20 ms at P95, mic to virtual output. The honest status: I never measured that number end to end, and the product does not meet it.&lt;/p&gt;

&lt;p&gt;What I do have is per-stage numbers. The mic gets a requested 128-frame HAL buffer (2.67 ms). The driver loopback measures 10.67 ms. The drift reserve adds about 5 ms. The whole default DSP chain costs 1.33 ms, and that is just the limiter's lookahead. Every shaping stage is a filter or a gain, so none of them buffer.&lt;/p&gt;

&lt;p&gt;Then comes the voice-isolation stage that won the listening test. On its own it costs several tens of milliseconds, and I took that trade on purpose. What I did not want was a hidden cost. So the app shows each stage's latency on screen, and three screens had to be fixed because they kept charging 10 ms for a stage that was switched off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small bugs worth stealing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;std::vector&amp;lt;float&amp;gt;{1024, 0.0f}&lt;/code&gt; is a two-element initializer list, not 1024 zeros. An envelope was being estimated from heap garbage. AddressSanitizer found it in one run, after three measurements had blamed working code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;std::clamp&lt;/code&gt; does nothing to NaN. A non-finite knob stayed on and inert.&lt;/li&gt;
&lt;li&gt;There was no limiter at first, only &lt;code&gt;std::clamp&lt;/code&gt; on the output. Real true peaks on four mics were 6.9 to 9.3 dB above the frame-peak figure the headroom math used. A proper limiter (1.33 ms lookahead) plus a corrected headroom formula took the worst overshoot from +8.34 dB to +0.33 dB.&lt;/li&gt;
&lt;li&gt;A USB headset delivered a DC offset that was 36% of the recording's energy. It dragged the measured spectral centroid down to 90 Hz. Subtracting the mean fixed it. A 20 Hz one-pole high-pass would have cost a low-frequency test its margin.&lt;/li&gt;
&lt;li&gt;Loudness follows ITU-R BS.1770. Checking it against the EBU Tech 3341 anchor (a 1 kHz sine at -23 dBFS should read -23 LUFS) caught a 3 dB error. Published targets assume two channels, so a mono signal counted once lands every voice 3 dB loud.&lt;/li&gt;
&lt;li&gt;Polling a meter at 20 Hz through an &lt;code&gt;ObservableObject&lt;/code&gt; re-rendered unrelated SwiftUI screens. Fixing that took idle CPU from 86% to 1%.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;launchctl bootout&lt;/code&gt; returns before launchd has actually removed the job, so an immediate &lt;code&gt;bootstrap&lt;/code&gt; fails with "5: Input/output error" and leaves no agent. Retry every 100 ms for up to 5 seconds.&lt;/li&gt;
&lt;li&gt;After an app update, &lt;code&gt;KeepAlive&lt;/code&gt; kept the old engine agent running, so users heard last version's DSP. The agent now reports its build in &lt;code&gt;ping&lt;/code&gt;, and the GUI kickstarts it on a mismatch. Read that build string at init: a lazy static reads the new Info.plist from disk and lies.&lt;/li&gt;
&lt;li&gt;The Swift test suite takes 643 s in debug and 11 s in release with identical results. DSP tests run with &lt;code&gt;-c release&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Put gain staging on paper before writing a single stage. The two worst "this control does nothing" bugs in this project were the same bug: a stage with an absolute threshold sitting where the signal never reached it.&lt;/p&gt;

&lt;p&gt;Write the sweep before the feature. A control that cannot show a monotonic, audible step across its range on a real recording is not done, however green the unit tests are.&lt;/p&gt;

&lt;p&gt;And let a human ear veto the metric, early. Five rounds of confident numbers lost to one listener who could not hear a difference.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: this is the engine behind &lt;a href="https://tunedmic.com" rel="noopener noreferrer"&gt;TunedMic&lt;/a&gt;, a Mac app I sell. You read a passage aloud for a minute, it measures your voice, mic and room, and builds the processing chain described above for that combination. If you want the non-engineering version of what a virtual mic is for, there is a plain-language explainer on &lt;a href="https://tunedmic.com/blog/virtual-microphone-mac" rel="noopener noreferrer"&gt;the TunedMic blog&lt;/a&gt;. Happy to answer Core Audio questions in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>audio</category>
      <category>cpp</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Best Suno prompt generators in 2026, compared</title>
      <dc:creator>Nikolai Stepanov</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:02:12 +0000</pubDate>
      <link>https://dev.to/nikolai_stepanov_d02669aa/best-suno-prompt-generators-in-2026-compared-51d9</link>
      <guid>https://dev.to/nikolai_stepanov_d02669aa/best-suno-prompt-generators-in-2026-compared-51d9</guid>
      <description>&lt;p&gt;You wrote forty careful words. &lt;code&gt;dream pop, shoegaze guitars, female vocal, reverb-heavy, slow tempo, melancholic, 90s tape warmth&lt;/code&gt;. Suno handed back a mid-tempo indie rock track with a male vocal and a drum fill at 0:12 that belongs in somebody else's song.&lt;/p&gt;

&lt;p&gt;So you go looking for a prompt generator. Most of them give you a longer prompt. Sixty words where you had forty, with &lt;code&gt;ethereal&lt;/code&gt; and &lt;code&gt;atmospheric&lt;/code&gt; stapled on the end. The track does not move any closer to what you heard in your head.&lt;/p&gt;

&lt;p&gt;Length is not the variable. Suno acts on the gaps you leave, not on the words you add. Every decision you skip gets filled with the statistical middle of everything the model was trained on, and the statistical middle of dream pop is a mid-tempo indie rock track. A prompt tool is worth your time only if it closes those gaps.&lt;/p&gt;

&lt;p&gt;That is the lens I used on the five tools below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three failures these tools are supposed to fix
&lt;/h2&gt;

&lt;p&gt;They get lumped together as "bad prompts". They are not the same problem, and most tools only touch the first one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt gets ignored.&lt;/strong&gt; You asked for brushed drums and got a trap hi-hat. Usually there is a contradiction sitting inside your own prompt, or you leaned on something Suno treats as decoration. A numeric BPM is the classic case: writing &lt;code&gt;128 BPM&lt;/code&gt; reads as a mood word, not a tempo instruction, and when it collides with a genre that lives somewhere else the model picks the genre.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The vocal drifts.&lt;/strong&gt; Verse one is breathy and close to the mic. By the second chorus the same singer is belting into a stadium. Nothing in the prompt pinned the register, so nothing held it there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The artist name gets blocked.&lt;/strong&gt; You typed a name because a name is the fastest way to describe a sound. Suno refused it, or quietly dropped it, and now you are guessing which four adjectives add up to that particular voice.&lt;/p&gt;

&lt;p&gt;A tool that hands you dropdowns of genres and instruments helps with the first problem, partly. It does nothing about the other two.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I compared them
&lt;/h2&gt;

&lt;p&gt;Every claim below was checked against each tool's own live site on 3 August 2026, plus whatever its public interface exposes without payment. This is not a deep hands-on bake-off of paid tiers. Prices and limits in this niche change monthly, so treat the numbers as a starting point and read the pricing page before you spend anything.&lt;/p&gt;

&lt;p&gt;Five questions, one per column:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does it write the &lt;strong&gt;exclude side&lt;/strong&gt;, or only the positive one?&lt;/li&gt;
&lt;li&gt;Do you start from a &lt;strong&gt;blank box&lt;/strong&gt; or from something that already works?&lt;/li&gt;
&lt;li&gt;Does it have an answer for &lt;strong&gt;blocked artist names&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What does the first usable prompt cost?&lt;/li&gt;
&lt;li&gt;Can you &lt;strong&gt;reuse&lt;/strong&gt; the output, or does every session start from zero?&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Free to start&lt;/th&gt;
&lt;th&gt;Signup&lt;/th&gt;
&lt;th&gt;Writes an exclude field&lt;/th&gt;
&lt;th&gt;Starting point&lt;/th&gt;
&lt;th&gt;Also makes audio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sunomarket&lt;/td&gt;
&lt;td&gt;Yes, limited preview&lt;/td&gt;
&lt;td&gt;For saving&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;1,960 style recipes, 1,003 artist recipes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SunoPrompt&lt;/td&gt;
&lt;td&gt;Yes, free credits&lt;/td&gt;
&lt;td&gt;Not for prompts&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Blank box, plus image/video/audio input&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suno Prompt Generator Pro&lt;/td&gt;
&lt;td&gt;Free credits&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Blank box plus a metatag library&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SunoPrompter&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Constraints field&lt;/td&gt;
&lt;td&gt;Blank box&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suno Prompt Builder&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Dropdowns: 50+ genres, 200+ instruments&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Sunomarket
&lt;/h2&gt;

&lt;p&gt;The thing that separates this one is that you do not start from an empty field. The catalog holds 1,960 style recipes and 1,003 artist-style recipes, and each one is a finished prompt somebody already balanced: genre anchor, sub-genres, instruments, production notes, vocal treatment, and the exclusions that keep the model off the obvious path.&lt;/p&gt;

&lt;p&gt;The artist recipes are the part worth explaining. Instead of a name Suno will refuse, each one describes the sound in descriptors the model reads without complaint: the vocal register and its grain, the era of the drum treatment, the specific reverb behaviour, the arrangement habits. You get the sound without the name, which is the whole point, because the name was never what you wanted.&lt;/p&gt;

&lt;p&gt;The constructor at &lt;a href="https://sunomarket.com/app" rel="noopener noreferrer"&gt;/app&lt;/a&gt; builds the prompt rather than generating it. There is no language model in that loop, which sounds like a downside until the third time an LLM-based tool hands you a prompt containing two mutually exclusive instructions. It validates as you go and flags contradictions between slots before you paste anything. It also rewrites negative phrasings into positive ones Suno obeys more reliably. What comes out is four separate blocks: a Style field, an exclude list, a lyrics scaffold with section tags, and a vocal block. Atmosphere goes out in Suno's asterisk format rather than being smuggled in as an instrument, which is a small detail that stops ambience turning into a synth pad.&lt;/p&gt;

&lt;p&gt;For instrumentals there is a scene and melody-rules block that pushes against the flat, looping melodies Suno defaults to when nothing tells it where the piece is going.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short:&lt;/strong&gt; it does not make music. It produces text you paste into Suno, nothing more. Most of the catalog sits behind an account, with a small free preview to see the format first. Pricing runs a free tier, then a monthly plan or a one-off lifetime option, listed on &lt;a href="https://sunomarket.com/pricing" rel="noopener noreferrer"&gt;the pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone whose complaint is "Suno ignores what I write" or "the name got blocked".&lt;/p&gt;

&lt;h2&gt;
  
  
  2. SunoPrompt
&lt;/h2&gt;

&lt;p&gt;The most complete workspace of the five, and the only one that closes the loop from prompt to finished audio. Its site describes four tools live today, seven AI models, generation from text, images, video or audio, and export to as many as eight stems plus MIDI. There is a free tier with credits for new accounts and no subscription required to start.&lt;/p&gt;

&lt;p&gt;That breadth is the pitch and also the caveat. It is an AI music platform that includes a prompt generator, not a prompt tool that got ambitious. If you already pay for Suno and only want better instructions to feed it, you are walking past most of what you are being offered. If you want stems and a DAW handoff without leaving one tab, nothing else here competes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short:&lt;/strong&gt; the prompt side is a blank box with assistance. Nothing on the site indicates it writes an exclude list or checks your prompt for internal contradictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; producers who want the whole chain in one place, especially stem separation and MIDI export.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Suno Prompt Generator Pro
&lt;/h2&gt;

&lt;p&gt;Sold as a metatag workbench, and that is the honest description. You drag metatags directly onto lyrics and style text, lay the song out on an interactive structure timeline, and generate lyrics in several languages. The extras run wide: transcription in 13 languages, four video visualisation styles, an audio compliance check. There is a Chrome extension and a premium tier through the Chrome Web Store, with free credits to begin.&lt;/p&gt;

&lt;p&gt;The timeline is genuinely useful if your problem is arrangement. Seeing &lt;code&gt;[Verse]&lt;/code&gt;, &lt;code&gt;[Pre-Chorus]&lt;/code&gt;, &lt;code&gt;[Chorus]&lt;/code&gt; laid out in order, with tags attached to each, makes it obvious when you have written a chorus that arrives ninety seconds late.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short:&lt;/strong&gt; an account is required before you can do much, and the feature list has drifted well past prompting into video and subtitles. If tags are not your bottleneck, most of it is weight you carry for nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; people who work tag-first and think in song sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. SunoPrompter
&lt;/h2&gt;

&lt;p&gt;The cleanest of the free ones. You hand it a loose idea and it returns a structured prompt covering style, arrangement, vocals, lyrics, production notes and constraints, and it will generate variants so you can compare two framings of the same song instead of committing to the first.&lt;/p&gt;

&lt;p&gt;The constraints field matters more than its placement suggests. It is the only free tool here that publicly commits to writing the negative side of a prompt, which is exactly the half most people never fill in. No signup, no credits, no upsell in the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short:&lt;/strong&gt; it starts from a blank box every time. There is no library to work from and nothing to save, so session three begins exactly where session one did. And no amount of structure solves the blocked-name problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; a fast, free second opinion on a prompt you already drafted.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Suno Prompt Builder
&lt;/h2&gt;

&lt;p&gt;The lowest-friction option on this list. No signup, no credits, no limit, per the site. It offers 50+ genres and 200+ instruments as pickable options, a lyric builder for theme and emotion and perspective, and it will compress your lyrics into the roughly 200-character summary Suno's simple mode wants.&lt;/p&gt;

&lt;p&gt;That last feature is more thoughtful than it sounds. Suno's simple mode truncates aggressively, and most people find out by having their best line cut. Building to the limit on purpose beats discovering it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short:&lt;/strong&gt; it is a picker, not a checker. Choose &lt;code&gt;ambient&lt;/code&gt; and &lt;code&gt;double-time breakbeat&lt;/code&gt; together and it will happily concatenate them for you. The page carries ads, which is the trade for it being free and unlimited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; a first prompt, in under a minute, with nothing to sign up for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would actually use
&lt;/h2&gt;

&lt;p&gt;If your prompts are being ignored or your artist names are being blocked, none of the blank-box tools will fix it, because the problem is not that your prompt is too short. Start from a recipe that already balances, edit it, and let something check the result. That is the case for putting Sunomarket first, and I have an obvious interest in you agreeing, so weigh it accordingly.&lt;/p&gt;

&lt;p&gt;If you want stems and MIDI out the other end, SunoPrompt is the only one on this list that gets you there.&lt;/p&gt;

&lt;p&gt;If you want to spend nothing and sign up for nothing, SunoPrompter has the better output and Suno Prompt Builder has the faster path to it.&lt;/p&gt;

&lt;p&gt;And if none of them are helping, the problem may be upstream of the tool. Suno's newer models read plain descriptive sentences far better than they read stacked keywords, so a prompt that would have needed a generator in 2024 may just need rewriting as a sentence in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;I build Sunomarket, which is ranked first here. Every factual claim about the other four tools comes from their own live sites as of 3 August 2026, the links to them are unpaid, and nobody in this list paid to be included or excluded. Check each tool yourself before paying for any of them, including mine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>music</category>
      <category>prompts</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
