1const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/storage-a3z5XoZ2.js","assets/index-BEP3oK3s.js","assets/vendor-react-DK8sSDTI.js","assets/vendor-radix-DfM_gqlA.js","assets/vendor-supabase-D-CR3b6K.js","assets/index-BqlnJi7n.css","assets/stepTracker-B4ek-6Qn.js","assets/audioAnalyzer-DTLoHK3z.js","assets/spectrum-CCqQdEIt.js","assets/fft-CCXY2iZ_.js","assets/chords-Dok_oWmJ.js","assets/chordDetection-WsC-5qLd.js","assets/analysisRegistry-DqI6aFZx.js","assets/effectRegistry-CBru4A4t.js","assets/gauge-ClDGkEAO.js","assets/shield-bPDytHEZ.js","assets/audio-waveform-CVsn2mAk.js","assets/sliders-horizontal-hTT1lzX0.js","assets/scissors-B3KjSIRB.js","assets/trending-up-D8SN2rG8.js","assets/guitar-Clv98-T7.js","assets/users-CZRz652T.js","assets/audio-lines-BihZ6G_V.js","assets/mp3Encoder-CvKbdzTf.js","assets/wavEncoder-D91sCXBd.js"])))=>i.map(i=>d[i]); 2import{s as H,b$ as se,aq as v,bY as I,c0 as K,ef as x,cS as g,b1 as ne,bZ as q,aQ as ee,bX as z,c8 as de,cV as E,eg as M,cA as Y,cZ as L,eh as le,ei as ce,ej as ue}from"./index-BEP3oK3s.js";import{h as G,d as he,i as J,c as pe}from"./stepTracker-B4ek-6Qn.js";import{r as me}from"./trackRecreation-DObJ8ZIO.js";import{g as ge}from"./audioAnalyzer-DTLoHK3z.js";import{M as fe}from"./toolResultCap-B8NfIXWV.js";import{D as ye,a as ke}from"./stemTypes-DnLveQvC.js";import"./vendor-react-DK8sSDTI.js";import"./vendor-radix-DfM_gqlA.js";import"./vendor-supabase-D-CR3b6K.js";import"./chords-Dok_oWmJ.js";import"./chordDetection-WsC-5qLd.js";import"./spectrum-CCqQdEIt.js";import"./fft-CCXY2iZ_.js";import"./analysisRegistry-DqI6aFZx.js";import"./effectRegistry-CBru4A4t.js";import"./gauge-ClDGkEAO.js";import"./shield-bPDytHEZ.js";import"./audio-waveform-CVsn2mAk.js";import"./sliders-horizontal-hTT1lzX0.js";import"./scissors-B3KjSIRB.js";import"./trending-up-D8SN2rG8.js";import"./guitar-Clv98-T7.js";import"./users-CZRz652T.js";import"./audio-lines-BihZ6G_V.js";import"./mp3Encoder-CvKbdzTf.js";import"./wavEncoder-D91sCXBd.js";import"./storage-a3z5XoZ2.js";import"./audioRender-C7ZNV-72.js";import"./fades-pLcBddM3.js";const m=H;function p(e){return e.user?null:(e.navigate("/auth"),{ok:!1,message:"Please sign in first."})}function h(e,t){return e instanceof Error?e.message:t}function we(e){const t=/^(?:\d+(?:bpm|hz|khz|s|ms|k)?|\d+\/\d+|tr-?\d+|\d{3}s?|[a-g][#b]?m?)$/i,a=e.replace(/["'`\u201C\u201D\u2018\u2019][^"'`\u201C\u201D\u2018\u2019]{2,}["'`\u201C\u201D\u2018\u2019]/g,"").replace(/\b(?:like|inspired by|reminiscent of|in the style of|Ã la|a la)\s+[A-Z][\p{L}0-9'.-]*(?:\s+[A-Z][\p{L}0-9'.-]*)*/gu,"").split(/\s+/);return a.filter((i,n)=>{const s=i.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"");if(!s||t.test(s))return!0;if(/[^\x00-\x7F]/.test(s)&&/\p{L}/u.test(s)||/\p{L}/u.test(s)&&/\d/.test(s))return!1;const d=n===0||/[.!?:\n]$/.test(a[n-1]??"");return!(/^\p{Lu}/u.test(s)&&!d&&!/^(?:BPM|EDM|DNB|DnB|UK|US|LFO|FX|EQ|VST|ADSR|MIDI|FM|AM|LA)$/.test(s))}).join(" ").replace(/\b(?:of|with|and|by|from)\s+(?=[,.;]|$)/gi,"").replace(/\s+([,.;])/g,"$1").replace(/([,.;])\1+/g,"$1").replace(/\s{2,}/g," ").trim()}const be=[/^create (a )?new track (inspired by|in the style of)[^.]*\.?/i,/^(create|generate|make|produce|compose|write)( me)?( an?| some)? (track|song|piece|beat|loop|sound)( of| that| which)?/i,/^(create|generate|make|produce|compose|write)( me)?/i],ve=/^(reference profile|stylistic direction|match the)/i,Z=48;function T(e){return e.replace(/\s+/g," ").replace(/^[\s,.\-ââ:;]+|[\s,.\-ââ:;]+$/g,"").trim()}function B(e){if(e.length<=Z)return e;const t=e.slice(0,Z),r=t.lastIndexOf(" ");return T(r>16?t.slice(0,r):t)}function F(e){const t=T(e.displayTitle??"");if(t)return B(t);const r=T(e.prompt??"");if(r){const o=r.split(/(?<=[.!?])\s+/).map(T).filter(Boolean);for(const i of o){if(ve.test(i))continue;let n=i;for(const s of be)n=T(n.replace(s,""));if(n.length>=3)return B(n)}}const a=T([e.genre,e.style].filter(Boolean).join(" "));return a?B(`${a} track`):"Generated Track"}const Se=32;function Ae(e){const t=[];for(const r of e){if(!r.label||r.label==="N"||r.root<0)continue;const a=t[t.length-1];a&&a.label===r.label&&Math.abs(a.end-r.start)<.05?t[t.length-1]={...a,end:r.end}:t.push({...r})}return t}function Ie(e,t){const r=e*t/60;if(r>=3.5){const o=r/4,i=Math.round(o*2)/2;return`${i} bar${i===1?"":"s"}`}const a=Math.max(1,Math.round(r));return`${a} beat${a===1?"":"s"}`}function Te(e,t){const r=Ae(e.segments);if(r.length===0)return null;const a=t>
20?t:120,o=r.slice(0,Se),i=o.map(l=>`${l.label} (${Ie(l.end-l.start,a)})`).join(", "),n=r.length>o.length?`, then ${r.length-o.length} more changes`:"",s=[];for(const l of r)s[s.length-1]!==l.label&&s.push(l.label);const d=s.length>16?`${s.slice(0,16).join(" â ")} â¦`:s.join(" â "),c=`Follow this exact chord progression in order, in ${e.musicalKey}, holding each chord for the length given: ${i}${n}. Keep every chord quality as written â do not simplify diminished, augmented or seventh chords to plain triads, and do not substitute a diatonic progression.`;return{summary:d,brief:c,changes:r.length}}function Pe(e,t){return t?`brief included the reference's progression (${t.summary}).`:e?"no stable chord progression could be read from the reference, so the brief carried tempo, key and energy only.":"the reference audio could not be read (fetch, decode or measurement failed), so the brief carried only the style direction â nothing was measured from the reference itself."}async function C(e){if(e<=0)return null;const t=await z(e);return t.ok?null:{ok:!1,message:t.error??"Insufficient credits."}}const Me=new Set(["runAIProcess","separateStems","recreateTrack","generateMusic","generateSimilarTrack","generateWithModel","transcribeDrums","transcribePitched","listGenerations","listSounds","setSoundVisibility","setGenerationVisibility"]),te=async(e,t)=>{if(!Me.has(e.type))return null;switch(e.type){case"runAIProcess":return xe(e,t);case"separateStems":return Ce(e,t);case"recreateTrack":return je(e,t);case"generateMusic":return $e(e,t);case"generateSimilarTrack":return Ee(e,t);case"generateWithModel":return De(e,t);case"transcribeDrums":return Re(e,t);case"transcribePitched":return _e(e,t);case"listGenerations":return qe(e,t);case"listSounds":return Le(e,t);case"setSoundVisibility":return Ge(e,t);case"setGenerationVisibility":return ze(e,t);default:return null}};async function xe(e,t){const r=p(t);if(r)return r;if(!e.action)return{ok:!1,message:"Missing AI action"};const a=se(e.action),o=await C(a);if(o)return o;try{const{data:i,error:n}=await v("ai-audio",{body:{action:e.action,audioUrl:e.audioUrl,prompt:e.prompt,referenceUrl:e.referenceUrl,bpm:e.bpm,key:e.key,genre:e.genre,duration:e.duration}});if(n)throw n;const s=i;if(s?.error)throw new Error(s.error);s?.isDemo||await I(a,`Agent: ${e.action}`);const d=s?.settings,c=d?`Returned SUGGESTED SETTINGS only â no audio was produced and nothing was applied to the project: ${JSON.stringify(d).slice(0,400)}. `+(e.action==="vocal-tuning"?"To actually tune the voice, apply the vocal-tuning track effect with key (0-11), scale (1 major, 2 minor, 3 maj penta, 4 min penta), pitchCorrection and retune.":"Apply them with the matching track effect if the user wants."):"";return{ok:!0,message:s?.result_url?`Done â result: ${s.result_url}`:s?.result_content?`Done â ${String(s.result_content).slice(0,240)}`:c||`Ran ${e.action} â no audio or settings were returned.`}}catch(i){return{ok:!1,message:h(i,"AI process failed")}}}async function Ce(e,t){const r=p(t);if(r)return r;if(!e.audioUrl)return{ok:!1,message:"audioUrl is required"};const a=e.deep??!1,o=await C(x(a,a));if(o)return o;try{const{data:i,error:n}=await v("stem-separation",{timeoutMs:K,body:{audioUrl:e.audioUrl,stems:e.stems??[...a?ye:ke],quality:e.quality??"standard",deep:a}});if(n)throw n;const s=i;if(s?.error)throw new Error(s.error);const d=Object.keys(s?.stems??{});if(!s?.isDemo&&d.length>0){const l=s?.deepCascadeApplied??a;await I(x(a,l),`Agent: stem separation${a?" (deep)":""}`)}const c=s?.isDemo?" (demo mode â set REPLICATE_API_KEY for real separation)":"";return{ok:!0,message:`Separated stems${d.length?`: ${d.join(", ")}`:""}${c}`,output:{stems:s?.stems??{},isDemo:s?.isDemo===!0,deep:a}}}catch(i){return{ok:!1,message:h(i,"Stem separation failed")}}}async function je(e,t){const r=p(t);if(r)return r;if(!e.audioUrl)return{ok:!1,message:"audioUrl is required"};const a=await C(g.recreateTrack);if(a)return a;const o=G(["Separating stems","Detecting tempo & key","Transcribing parts","Rebuilding & mastering"],e.onProgress),i=n=>{switch(n.kind){case"stem-separation":n.status==="start"?o.start("Separating stems"):o.done("Separating stems");break;case"tempo-key":n.status==="start"?o.start("Detecting tempo & key"):o.done("Detecting tempo & key");break;case"pitch-transcription":case"drum-transcription":n.status==="start"&&o.start("Transcribing parts");break;case"assemble":n.status==="start"?(o.done("Transcribing parts"),o.start("Rebuilding & mastering")):o.finish();break;case"error":o.failCurrent();break}};try{const n=await me(e.audioUrl,{deep:e.deep,keepStems:e.keepStems,transcribePitched:e.transcribePitched,transcribeDrums:e.transcribeDrums,skipMasterMix:e.skipMasterMix,genre:e.genre,bpm:e.bpm,musicalKey:e.musicalKey},i);return n.isDemo?.stemSeparation||await I(g.recreateTrack,"Agent: AI cover / track recreation"),{ok:!0,message:`${n.summary} â ${n.tracks.length} tracks built (BPM ${Math.round(n.bpm)} ⢠${n.musicalKey}).`,output:{recreated:n,trackNames:n.tracks.map(s=>s.name),bpm:n.bpm,musicalKey:n.musicalKey}}}catch(n){return o.failCurrent(),{ok:!1,message:h(n,"recreateTrack failed")}}}async function N(e,t,r){const a=e.lyrics;try{r.start("Composing & rendering");const{data:o,error:i}=await v("elevenlabs-music",{body:{prompt:e.prompt,duration:e.duration??30,lyrics:a,instrumental:e.instrumental,bpm
2:e.bpm,key:e.key,genre:e.genre,metadata:{source:"agent",...e.genre?{genre:e.genre}:{},...e.key?{key:e.key}:{},...typeof e.bpm=="number"&&Number.isFinite(e.bpm)?{bpm:e.bpm}:{}}},timeoutMs:K});if(i)throw i;if(o?.error)throw new Error(o.error);r.done("Composing & rendering"),r.start("Finalizing track"),o?.audioContent&&!(o.creditsCharged&&o.creditsCharged>0)&&await I(q(e.duration??30),`Agent: music generation "${e.prompt?.slice(0,40)??"similar"}"`);let n=o?.audioUrl,s=o?.generationId;if(!n&&o?.audioContent)try{const l=atob(o.audioContent),f=new Uint8Array(l.length);for(let u=0;u<l.length;u++)f[u]=l.charCodeAt(u);const w=new Blob([f],{type:o.mimeType||"audio/mpeg"}),{uploadToBucket:k}=await ee(async()=>{const{uploadToBucket:u}=await import("./storage-a3z5XoZ2.js").then(b=>b.s);return{uploadToBucket:u}},__vite__mapDeps([0,1,2,3,4,5])),{publicUrl:y}=await k(w,{bucket:"audio",userId:`generated/${t.user.id}`,prefix:`song-${Date.now()}-`,ext:"mp3",contentType:w.type});n=y}catch(l){console.warn("[runMusicGeneration] upload failed, returning without URL",l)}if(n&&!s)try{const{data:l}=await m.from("ai_generations").insert({user_id:t.user.id,type:"music",model:"elevenlabs-music",prompt:e.prompt,status:"completed",result_url:n,credits_used:o?.audioContent?q(e.duration??30):0,metadata:{duration:e.duration??30,hasLyrics:!!a,instrumental:!!e.instrumental&&!a,source:"agent",title:F(e),...e.genre?{genre:e.genre}:{},...e.key?{key:e.key}:{},...typeof e.bpm=="number"&&Number.isFinite(e.bpm)?{bpm:e.bpm}:{}}}).select("id").maybeSingle();s=l?.id}catch(l){console.warn("[runMusicGeneration] ai_generations insert failed",l)}let d;if(s&&!o?.generationId)try{const{data:l,error:f}=await v("generate-cover-art",{body:{prompt:e.prompt,title:F(e),generationId:s}});!f&&l?.url&&(d=l.url)}catch(l){console.warn("[runMusicGeneration] cover-art generation failed",l)}r.finish();const c=n||(o?.audioContent?`data:${o.mimeType||"audio/mpeg"};base64,${o.audioContent}`:void 0);return{ok:!0,message:n?`Generated song â ${n}`:c?"Song generated (inline preview only â upload to library from the chat card)":"Song generated (no URL available)",output:{audioUrl:c,thumbnailUrl:d,title:F(e),generationId:s}}}catch(o){r.failCurrent();const i=h(o,"Music generation failed");if(o?.code==="safety_rejection"||/safety_rejection|terms of service|violated|bad_prompt|safety|content policy|moderation/i.test(i)){const s=i.replace(/^SAFETY_REJECTION:\s*/,""),d=s.match(/Suggested rewrite:\s*"([^"]+)"/i);return{ok:!1,message:`ð« Prompt blocked by safety filter: ${s} No credits were charged.`,output:{safetyRejection:!0,suggestedPrompt:d?.[1],reason:s}}}return{ok:!1,message:i}}}async function $e(e,t){const r=p(t);if(r)return r;if(!e.prompt)return{ok:!1,message:"prompt is required"};const a=G(["Analyzing prompt","Composing & rendering","Finalizing track"],e.onProgress);a.start("Analyzing prompt");const o=await C(g.generation);if(o)return a.failCurrent(),o;a.done("Analyzing prompt");const i=await N(e,t,a),n=i.output;if(i.ok||!n?.safetyRejection||e._safetyRetry)return i;const s=we(n.suggestedPrompt?.trim()||e.prompt);if(!s||s===e.prompt)return i;const d=G(["Retrying with copyright-safe prompt","Composing & rendering","Finalizing track"],e.onProgress);d.start("Retrying with copyright-safe prompt"),d.done("Retrying with copyright-safe prompt");const c={...e,prompt:s,_safetyRetry:!0},l=await N(c,t,d);return l.ok?{...l,message:`${l.message} 3 4(Auto-retried after safety rejection with sanitized prompt: "${s}")`}:l}async function Ee(e,t){const r=p(t);if(r)return r;if(!e.referenceAudioUrl)return{ok:!1,message:"referenceAudioUrl is required"};const a=G(["Analyzing reference","Building similar prompt","Composing & rendering","Finalizing track"],e.onProgress);a.start("Analyzing reference");const o=await C(g.generation);if(o)return a.failCurrent(),o;try{let i=null,n=null;try{const b=await ne(e.referenceAudioUrl);i=await ge(b);try{const j=Array.from({length:b.numberOfChannels},(Q,U)=>b.getChannelData(U));n=he(j,b.sampleRate,{bpm:i.bpm>0?i.bpm:void 0})}catch(j){console.warn("[generateSimilarTrack] chord detection failed",j)}}catch(b){console.warn("[generateSimilarTrack] audio analysis failed",b)}a.done("Analyzing reference"),a.start("Building similar prompt");
4const s=[e.style,e.prompt].filter(Boolean).join(" â "),d=n?.segments.length?n.musicalKey:i?.key,c=n&&i?Te(n,i.bpm):null,l=i?`Create a new track inspired by the uploaded reference. Reference profile: ${Math.round(i.bpm)} BPM, key of ${d}, energy ${Math.round(i.energy)}/100, spectral brightness ${Math.round(i.spectralCentroid)}Hz, duration ${Math.round(i.duration)}s. ${s?"Stylistic direction: "+s+".":"Match the instrumentation, density, and overall vibe of the reference closely."}`:`Create a new track in the style of the uploaded reference. ${s?"Stylistic direction: "+s+".":"Match the vibe, instrumentation, and production of the reference."}`,f=c?`${l} ${c.brief}`:l,w=i?Math.min(Math.max(Math.round(i.duration),30),120):30,k=e.duration??w;a.done("Building similar prompt");const y={...e,prompt:f,duration:k,lyrics:e.lyrics,instrumental:e.instrumental,displayTitle:e.displayTitle||(s?`${s} (similar)`:"Similar to reference"),...i&&Number.isFinite(i.bpm)&&i.bpm>0?{bpm:Math.round(i.bpm)}:{},...d?{key:d}:{}},u=await N(y,t,a);return u.ok?{...u,message:`${u.message} â ${Pe(i!==null,c)}`,output:{...u.output,referenceAudioUrl:e.referenceAudioUrl,...c?{referenceProgression:c.summary,referenceKey:d}:{}}}:u}catch(i){return a.failCurrent(),{ok:!1,message:h(i,"Similar track generation failed")}}}async function De(e,t){const r=p(t);if(r)return r;if(!e.modelId||!e.prompt)return{ok:!1,message:"modelId and prompt are required"};const a=await C(q(e.duration??30));if(a)return a;try{const{data:o,error:i}=await v("generate-with-ai-model",{timeoutMs:K,body:{modelId:e.modelId,prompt:e.prompt,parameters:e.parameters,referenceAudio:e.referenceAudio,duration:e.duration,format:e.format}});if(i)throw i;if(o?.error)throw new Error(o.error);o?.audioContent&&!(o.creditsCharged&&o.creditsCharged>0)&&await I(q(e.duration??30),`Agent: model generation "${e.prompt.slice(0,40)}"`);const n=o?.audioUrl||o?.result_url;return{ok:!0,message:n?`Generated â ${n}`:"Generation complete",output:{audioUrl:n}}}catch(o){return{ok:!1,message:h(o,"Model inference failed")}}}async function Re(e,t){const r=p(t);if(r)return r;if(!e.audioUrl)return{ok:!1,message:"audioUrl is required"};try{const{data:a,error:o}=await v("drum-transcription",{body:{drumStemUrl:e.audioUrl,audioUrl:e.audioUrl,bpm:e.bpm}});if(o)throw o;if(a?.error)throw new Error(a.error);return{ok:!0,message:`Transcribed ${Array.isArray(a?.notes)?a.notes.length:0} drum hits`,output:a}}catch(a){return{ok:!1,message:h(a,"Drum transcription failed")}}}async function _e(e,t){const r=p(t);if(r)return r;if(!e.audioUrl)return{ok:!1,message:"audioUrl is required"};try{const{transcribePitch:a}=await ee(async()=>{const{transcribePitch:n}=await import("./stepTracker-B4ek-6Qn.js").then(s=>s.n);return{transcribePitch:n}},__vite__mapDeps([6,7,8,9,10,1,2,3,4,5,11,12,13,14,15,16,17,18,19,20,21,22,23,24])),o=await a({audioUrl:e.audioUrl,onProgress:void 0}),i=o.notes.length;return i===0&&o.isDemo?{ok:!1,message:`Transcription did not run: ${o.message??"not configured"}`}:{ok:!0,message:`Transcribed ${i} notes (${o.engine??"server"} engine)`,output:{notes:o.notes,engine:o.engine,instrument:o.instrument}}}catch(a){return{ok:!1,message:h(a,"Pitch transcription failed")}}}async function qe(e,t){const r=p(t);if(r)return r;try{const{data:a,error:o}=await m.from("ai_generations").select("id,prompt,type,status,result_url,created_at,is_public").eq("user_id",t.user.id).order("created_at",{ascending:!1}).limit(12);if(o)throw o;const i=a||[];if(!i.length)return{ok:!0,message:"No generations yet."};const n=`Recent generations (pass an "audio" URL to addClip to drop it on the timeline). 5NOTE: the text on each row is the prompt the generation was made FROM. It is not a description of the resulting audio, and none of it has been listened to or analysed. Do not characterise how any of these sound, or their instrumentation, tempo, key, arrangement or production quality â call analyzeAudio on a result_url if you need to know. 6`,s=fe-n.length-200,d=[];let c=0,l=0;for(const y of i){const u=`⢠[${y.status}] ${y.type}: ${(y.prompt||"(no prompt)").slice(0,160)} â ${y.is_public?"public":"private"} â ${y.id}`,b=y.result_url?`${u} 7 audio: ${y.result_url}`:u;if(c+b.length+1<=s){d.push(b),c+=b.length+1;continue}if(y.result_url&&c+u.length+1<=s){d.push(u),c+=u.length+1,l++;continue}break}const f=i.length-d.length,w=[f>0&&`${f} older one${f===1?"":"s"} omitted to fit`,l>0&&`${l} audio link${l===1?"":"s"} too long to include â ask again for a single id to get it`].filter(
7Boolean),k=w.length?` 8(${d.length} of ${i.length} shown; ${w.join("; ")}.)`:"";return{ok:!0,message:`${n}${d.join(` 9`)}${k}`}}catch(a){return{ok:!1,message:h(a,"Failed to list generations")}}}async function Le(e,t){const r=p(t);if(r)return r;try{let a=m.from("sounds").select("id,title,genre,bpm,key,is_public").eq("created_by",t.user.id);e.query&&(a=a.ilike("title",`%${e.query}%`)),e.genre&&(a=a.eq("genre",e.genre));const{data:o,error:i}=await a.order("created_at",{ascending:!1}).limit(20);if(i)throw i;const n=o||[];return n.length?{ok:!0,message:`Sounds (titles and stored metadata â none of this audio has been listened to or analysed, so do not describe how any of it sounds; call analyzeAudio if you need to know): 10`+n.map(d=>`⢠${d.title}${d.genre?` [${d.genre}]`:""}${d.bpm?` ${d.bpm}bpm`:""} â ${d.is_public?"public":"private"} â ${d.id}`).join(` 11`)}:{ok:!0,message:"No sounds match."}}catch(a){return{ok:!1,message:h(a,"Failed to list sounds")}}}async function Ge(e,t){const r=p(t);if(r)return r;if(!e.soundId)return{ok:!1,message:"soundId is required."};if(typeof e.isPublic!="boolean")return{ok:!1,message:"isPublic must be true or false."};try{const{data:a,error:o}=await m.from("sounds").select("id,title,is_public").eq("id",e.soundId).eq("created_by",t.user.id).maybeSingle();if(o)throw o;if(!a)return{ok:!1,message:`No sound ${e.soundId} owned by you.`};const i=a.is_public;if(i===e.isPublic)return{ok:!0,message:`"${a.title}" is already ${e.isPublic?"public":"private"}.`};const{error:n}=await m.from("sounds").update({is_public:e.isPublic}).eq("id",e.soundId);if(n)throw n;return{ok:!0,message:e.isPublic?`Published "${a.title}" â it now appears in Discover for everyone.`:`"${a.title}" is now private and no longer listed in Discover.`,output:{soundId:e.soundId,isPublic:e.isPublic},undoCommand:{type:"setSoundVisibility",soundId:e.soundId,isPublic:i}}}catch(a){return{ok:!1,message:h(a,"Failed to update sound visibility")}}}async function ze(e,t){const r=p(t);if(r)return r;if(!e.generationId)return{ok:!1,message:"generationId is required."};if(typeof e.isPublic!="boolean")return{ok:!1,message:"isPublic must be true or false."};try{const{data:a,error:o}=await m.from("ai_generations").select("id,prompt,is_public").eq("id",e.generationId).eq("user_id",t.user.id).maybeSingle();if(o)throw o;if(!a)return{ok:!1,message:`No generation ${e.generationId} owned by you.`};const i=a.is_public;if(i===e.isPublic)return{ok:!0,message:`That generation is already ${e.isPublic?"public":"private"}.`};const{error:n}=await m.from("ai_generations").update({is_public:e.isPublic}).eq("id",e.generationId);if(n)throw n;const s=(a.prompt||"").slice(0,50)||e.generationId;return{ok:!0,message:e.isPublic?`Published "${s}" â anyone with /s/${e.generationId} can now play it.`:`"${s}" is private again; the share link no longer resolves.`,output:{generationId:e.generationId,isPublic:e.isPublic},undoCommand:{type:"setGenerationVisibility",generationId:e.generationId,isPublic:i}}}catch(a){return{ok:!1,message:h(a,"Failed to update generation visibility")}}}const Ue=new Set(["navigate","openStudio","searchLibrary","generateSound","createProject","listProjects","openProject","deleteProject"]);function Be(){return typeof window<"u"&&window.location?.pathname?.startsWith("/studio")===!0}const Fe=async(e,t)=>{if(!Ue.has(e.type))return null;const{user:r,navigate:a}=t;switch(e.type){case"navigate":return e.path?(a(e.path),{ok:!0,message:`Opened ${e.path}`}):{ok:!1,message:"No path provided"};case"openStudio":return a(e.projectId?`/studio?project=${e.projectId}`:"/studio"),{ok:!0,message:"Opened Studio"};case"searchLibrary":{const o=e.query||e.prompt||"";return a(`/discover?q=${encodeURIComponent(o)}`),{ok:!0,message:`Searching for "${o}"`}}case"generateSound":{if(Be()){const i=await te({...e,type:"generateMusic"},t);if(i)return i}const o=new URLSearchParams;return e.prompt&&o.set("prompt",e.prompt),e.soundType&&o.set("type",e.soundType),a(`/generate?${o.toString()}`),{ok:!0,message:"Opened Generate with your prompt â this does not produce audio by itself. Use generateMusic when you need an audio URL back."}}case"createProject":return Ve(e,t);case"listProjects":return Ne(e,t);case"openProject":return Oe(e,t);case"deleteProject":return We(e,t);default:return null}};async function Ve(e,t){const r=p(t);if(r)return r;try{const a={user_id:t.user.id,name:e.name||"Untitled Project",bpm:e.bpm??120,key:e.key??"C",duration:30,tracks:[],generations:[],settings:{autoSave:!0,masterVolume:75,qualityPreset:"standard"},tags:e.genre?[e.genre]:[],is_public:!1,version:1},{data:o,error:i}=await m.from("projects").insert(a).select().single();if(i)throw i;return de({id:o.id,name:o.name,updatedAt:new Date().toISOString()}),t.navigate(`/studio?project=${o.id}`),{ok:!0,message:`Created "${o.name}" and opened it in the Studio`,undo:async()=>
11{await m.from("projects").delete().eq("id",o.id)}}}catch(a){return{ok:!1,message:h(a,"Failed to create project")}}}async function Ne(e,t){const r=p(t);if(r)return r;try{const{data:a,error:o}=await m.from("projects").select("id,name,bpm,updated_at").eq("user_id",t.user.id).order("updated_at",{ascending:!1}).limit(20);if(o)throw o;const i=a||[];return i.length?{ok:!0,message:`Your projects: 12${i.map(s=>`⢠${s.name}${s.bpm?` (${s.bpm} BPM)`:""} â ${s.id}`).join(` 13`)}`}:{ok:!0,message:"You have no projects yet."}}catch(a){return{ok:!1,message:h(a,"Failed to list projects")}}}async function Oe(e,t){const r=p(t);if(r)return r;try{let a=e.projectId;if(!a&&e.name){const{data:o}=await m.from("projects").select("id,name").eq("user_id",t.user.id).ilike("name",`%${e.name}%`).limit(1).maybeSingle();a=o?.id}return a?(t.navigate(`/studio?project=${a}`),{ok:!0,message:"Opened project in the Studio"}):{ok:!1,message:`Couldn't find a project matching "${e.name??""}"`}}catch(a){return{ok:!1,message:h(a,"Failed to open project")}}}async function We(e,t){const r=p(t);if(r)return r;try{let a=e.projectId,o=e.name;if(!a&&o){const{data:s}=await m.from("projects").select("id,name").eq("user_id",t.user.id).ilike("name",`%${o}%`).limit(1).maybeSingle();a=s?.id,o=s?.name}if(!a)return{ok:!1,message:"Project not found"};const{data:i}=await m.from("projects").select("*").eq("id",a).single(),{error:n}=await m.from("projects").delete().eq("id",a);if(n)throw n;return{ok:!0,message:`Deleted "${o||a}"`,undo:async()=>{i&&await m.from("projects").insert(i)}}}catch(a){return{ok:!1,message:h(a,"Failed to delete project")}}}const He=new Set(["cloneVoice","listVoices","synthesizeVoice"]),Ke=async(e,t)=>{if(!He.has(e.type))return null;switch(e.type){case"cloneVoice":return Qe(e,t);case"listVoices":return Ye(e,t);case"synthesizeVoice":return Je(e,t);default:return null}};async function Qe(e,t){const r=p(t);if(r)return r;if(!e.name||!e.audioUrls?.length)return{ok:!1,message:"name and audioUrls are required"};const a=await z(g.voiceClone);if(!a.ok)return{ok:!1,message:a.error??"Insufficient credits."};try{const{data:o,error:i}=await v("voice-clone?action=clone",{body:{name:e.name,description:e.description,audioUrls:e.audioUrls}});if(i)throw i;if(o?.error)throw new Error(o.error);return await I(g.voiceClone,`Agent: voice clone "${e.name}"`),{ok:!0,message:`Cloned voice "${e.name}"${o?.voiceId?` (id: ${o.voiceId})`:""}`}}catch(o){return{ok:!1,message:h(o,"Voice cloning failed")}}}async function Ye(e,t){const r=p(t);if(r)return r;try{const{data:a,error:o}=await v("voice-clone?action=list",{body:{}});if(o)throw o;const i=a?.voices??[];return i.length?{ok:!0,message:`Voices: 14${i.slice(0,20).map(s=>`⢠${s.name??"Untitled"} â ${s.voice_id??""}`).join(` 15`)}`}:{ok:!0,message:"No voices found."}}catch(a){return{ok:!1,message:h(a,"Failed to list voices")}}}async function Je(e,t){const r=p(t);if(r)return r;if(!e.voiceId||!e.text)return{ok:!1,message:"voiceId and text are required"};const a=await z(g.voiceSynthesis);if(!a.ok)return{ok:!1,message:a.error??"Insufficient credits."};try{const{data:o,error:i}=await v("voice-clone?action=synthesize",{body:{voiceId:e.voiceId,text:e.text}});if(i)throw i;if(o?.error)throw new Error(o.error);return await I(g.voiceSynthesis,"Agent: voice synthesis"),{ok:!0,message:o?.audioUrl?`Synthesized â ${o.audioUrl}`:"Synthesis complete",output:o?.audioUrl?{audioUrl:o.audioUrl}:void 0}}catch(o){return{ok:!1,message:h(o,"Synthesis failed")}}}const Ze=new Set(["startTraining","listTrainingJobs","cancelTraining"]),Xe=async(e,t)=>{if(!Ze.has(e.type))return null;switch(e.type){case"startTraining":return et(e,t);case"listTrainingJobs":return tt(e,t);case"cancelTraining":return at(e,t);default:return null}};async function et(e,t){const r=p(t);if(r)return r;if(!e.name||!e.datasetUrl||!e.modelType)return{ok:!1,message:"name, datasetUrl, and modelType are required"};const a=await z(g.modelTraining);if(!a.ok)return{ok:!1,message:a.error??"Insufficient credits."};try{const{data:o,error:i}=await v("model-training?action=create",{body:{name:e.name,datasetUrl:e.datasetUrl,modelType:e.modelType,parameters:e.parameters}});if(i)throw i;if(o?.error)throw new Error(o.error);return await I(g.modelTraining,`Agent: model training "${e.name}"`),{ok:!0,message:`Started training job${o?.job?.id?` (${o.job.id})`:""}`}}catch(o){return{ok:!1,message:h(o,"Training start failed")}}}async function tt(e,t){const r=p(t);if(r)return r;try{const{data:a,error:o}=await v("model-training?action=list",{body:{}});if(o)throw o;const i=a?.jobs??[];return i.length?{ok:!0,message:`Training jobs: 16${i.slice(0,20).map(s=>`⢠${s.name??"Untitled"} [${s.status??"?"}] â ${s.id}`).join(` 17`)}`}:{ok:!0,message:"No training jobs."}}catch(a){return{ok:!1,message:h(a,"Failed to list training jobs")}}}async function at(e,t){const r=p(t);if(r)return r;if(!e.jobId)return{ok:!1,message:"jobId is required"};try{const{data:a,error:o}=await v("model-training?action=cancel",{body:{jobId:e.jobId}});if(o)throw o;if(a?.error)throw new Error(a.error);return{ok:!0,message:`Cancelled training job ${e.jobId}`}}catch(a){return{ok:!1,message:h(a,"Failed to cancel training job")}}}const ot=new Set(["toggleTheme","showCredits","rememberFact","createAIMusician","listAIMusicians","updateAIMusician","deleteAIMusician","webSearch"]),rt=async(e,t)=>{if(!ot.has(e.type))return null;switch(e.type){case"toggleTheme":return st(e);case"showCredits":return nt(e,t);case"rememberFact":return dt(e,t);case"createAIMusician":return lt(e,t);case"listAIMusicians":return ct(e,t);case"updateAIMusician":return ut(e,t);case"deleteAIMusician":return ht(e,t);case"webSearch":return it(e,t);default:return null}};async function it(e,t){const r=p(t);if(r)return r;const a=(e.query??"").trim();if(!a)return{ok:!1,message:"query is required"};try{const{data:o,error:i}=await H.functions.invoke("web-search",{body:{query:a,recency:e.recency,mode:e.mode==="academic"?"academic":"web"}});if(i)throw i;if(o?.error)return{ok:!1,message:o.error};const n=(o?.answer??"").trim();if(!n)return{ok:!1,message:"No results returned."};const s=(o?.citations??[]).slice(0,5),d=s.length?` 18 19Sources: 20${s.map((c,l)=>`${l+1}. ${c}`).join(` 21`)}`:"";return{ok:!0,message:`${n}${d}`,output:{answer:n,citations:s,query:a}}}catch(o){return{ok:!1,message:h(o,"Web search failed")}}}function st(e){try{const t=document.documentElement,r=t.classList.contains("light")?"light":"dark",a=e.mode??(r==="dark"?"light":"dark");return localStorage.setItem("theme",a),t.classList.toggle("dark",a==="dark"),t.classList.toggle("light",a==="light"),window.dispatchEvent(new StorageEvent("storage",{key:"theme",newValue:a})),setTimeout(()=>window.location.reload(),150),{ok:!0,message:`Theme set to ${a}`}}catch(t){return{ok:!1,message:h(t,"Failed to toggle theme")}}}async function nt(e,t){const r=p(t);if(r)return r;try{const{data:a,error:o}=await m.from("user_credits").select("balance,lifetime_used").eq("user_id",t.user.id).maybeSingle();if(o)throw o;return a?{ok:!0,message:`Credits: ${a.balance} available (${a.lifetime_used} used lifetime).`}:{ok:!0,message:"No credit record yet â generate something to initialize your balance."}}catch(a){return{ok:!1,message:h(a,"Failed to read credits")}}}async function dt(e,t){const r=p(t);if(r)return r;if(!e.fact)return{ok:!1,message:"fact required"};const a=e.fact.slice(0,280),o=e.scope==="project"?"project":"user",i=o==="project"?(typeof window<"u"?new URLSearchParams(window.location.search).get("project"):null)??null:null;try{const{data:n,error:s}=await m.from("agent_memories").insert({user_id:t.user.id,scope:o,project_id:i,fact:a}).select("id").single();if(s)throw s;return{ok:!0,message:`Remembered (${o}): "${a.slice(0,60)}${a.length>60?"â¦":""}"`,output:{id:n.id,scope:o,projectId:i},undo:async()=>{await m.from("agent_memories").delete().eq("id",n.id)}}}catch(n){return{ok:!1,message:h(n,"Failed to persist memory")}}}async function lt(e,t){const r=p(t);if(r)return r;if(!e.name||!e.role)return{ok:!1,message:"Name and role are required"};try{const{data:a,error:o}=await m.from("ai_musicians").insert({user_id:t.user.id,name:e.name,role:e.role,style:e.style,personality:e.personality,description:e.description,skills:e.skills,is_active:!0}).select().single();if(o)throw o;return{ok:!0,message:`Created AI musician "${a.name}" (${a.role})`,undo:async()=>{await m.from("ai_musicians").delete().eq("id",a.id)}}}catch(a){return{ok:!1,message:h(a,"Failed to create musician")}}}async function ct(e,t){const r=p(t);if(r)return r;try{const{data:a,error:o}=await m.from("ai_musicians").select("id,name,role,style,is_active").eq("user_id",t.user.id).order("created_at",{ascending:!1});if(o)throw o;const i=a||[];return i.length?{ok:!0,message:`AI musicians: 22${i.map(s=>`⢠${s.name} â ${s.role}${s.style?` (${s.style})`:""}${s.is_active?"":" [inactive]"} â ${s.id}`).join(` 23`)}`}:{ok:!0,message:"No AI musicians yet."}}catch(a){return{ok:!1,message:h(a,"Failed to list musicians")}}}async function ut(e,t){const r=p(t);if(r)return r;if(!e.musicianId)return{ok:!1,message:"musicianId is required"};try{const a={};e.name!==void 0&&(a.name=e.name),e.role!==void 0&&(a.role=e.role),e.style!==void 0&&(a.style=e.style),e.personality!==void 0&&(a.personality=e.personality),e.description!==void 0&&(a.description=e.description),e.skills!==void 0&&(a.skills=e.skills),e.isActive!==void 0&&(a.is_active=e.isActive);const{data:o}=await m.from("ai_musicians").select("*").eq("id",e.musicianId).single(),{data:i,error:n}=await m.from("ai_musicians").update(a).eq("id",e.musicianId).select().single();if(n)throw n;return{ok:!0,message:`Updated AI musician "${i.name}"`,undo:async()=>{o&&await m.from("ai_musicians").update(o).eq("id",e.musicianId)}}}catch(a){return{ok:!1,message:h(a,"Failed to update musician")}}}async function ht(e,t){const r=p(t);if(r)return r;if(!e.musicianId)return{ok:!1,message:"musicianId is required"};try{const{data:a}=await m.from("ai_musicians").select("*").eq("id",e.musicianId).single(),{error:o}=await m.from("ai_musicians").delete().eq("id",e.musicianId);if(o)throw o;return{ok:!0,message:"Deleted AI musician",undo:async()=>{a&&await m.from("ai_musicians").insert(a)}}}catch(a){return{ok:!1,message:h(a,"Failed to delete musician")}}}const pt=[{id:"app-overview",kind:"area",title:"What SoundGen is and how it fits together",summary:"SoundGen is a browser DAW with AI generation built in: generate or upload audio, arrange and mix it in the Studio, then export or share it.",route:"/dashboard",details:'Four areas cover almost everything. **Generate** (/generate) turns a text prompt into audio. **Studio** (/studio) is the full multitrack DAW â timeline, mixer, MIDI, effects, automation, export. **Library** is Discover (/discover, public sounds) plus My Sounds (/sounds, your own). **AI Agent** (/agent, or the "Ask AI" sidebar on any page) is this assistant, which can both advise and operate the app for you. Everything that spends compute spends credits; the balance lives in Settings â Credits.',keywords:["overview","what is soundgen","getting started","tour","app","sections","areas"],related:["page-generate","page-studio","page-discover","page-my-sounds","page-agent","credits-system"]},{id:"nav-and-shortcuts",kind:"reference",title:"Getting around: sidebar, go-to shortcuts, command palette",summary:'The left sidebar groups pages into Create / Library / Account; two-key "G" shortcuts jump straight to any of them.',uiPath:"Left sidebar (toggle with Cmd+\\)",details:"Sidebar sections: **Create** â AI Agent, Generate, Studio, AI Musicians. **Library** â Home, Discover, My Sounds. **Account** â Settings. Inside the Studio, Cmd+K opens the command palette, which searches navigation and Studio actions together.",shortcuts:["nav.studio","nav.generate","nav.discover","nav.mysounds","nav.settings","G A â AI Agent","ui.toggleSidebar","global.search","global.help"],tools:["navigate"],keywords:["navigation","sidebar","menu","keyboard shortcuts","hotkeys","command palette","cmd k","where do i find"],related:["studio-command-palette","settings-shortcuts"]},{id:"page-dashboard",kind:"page",title:"Home / Dashboard",summary:"Your landing page after signing in: recent projects, recent generations, and quick entry points.",route:"/dashboard",requiresAuth:!0,details:"Projects you open from here load straight into the Studio. New projects can be created here or from the Studio header.",tools:["navigate","listProjects","createProject","openProject"],keywords:["home","dashboard","my projects","recent","start"],related:["page-studio","howto-first-track"]},{id:"page-generate",kind:"page",title:"Generate â text to audio",summary:"Type a prompt, pick a mode (Music, Loop, or SFX), and generate audio you can play, save, or send to the Studio.",route:"/generate",requiresAuth:!0,steps:["Open Generate (sidebar â Generate, or press G then G).","Choose a mode: **Music** (up to 120s, default 30), **Loop** (up to 30s, default 8), or **SFX** (up to 22s, default 5).","Write the prompt, or start from a starter prompt / the prompt builder tags (mood, instrument, tempo).","Optionally set type (drums, vocals, instrument, fx, synth, other), genre, and duration in Generation Settings.","Press Generate (Cmd+Enter), wait for the progress steps, then audition the result.","Save it to your library, or send it to a Studio project."],credits:`Billed by length â ${E()} credits per minute, so ${g.generation} for the ${M}s default.`,shortcuts:["generation.generate","generation.regenerate","generation.export"],tools:["generateSound","generateMusic","navigate"],keywords:["generate","text to audio","text to music","prompt","ai music","sfx","sound effect","loop","make a song"],related:["ai-generate-music","howto-make-loop","credits-system","page-my-sounds","trouble-generation-failed"]},{id:"page-discover",kind:"page",title:"Discover â the public sound library",summary:"Browse and search sounds other people have published, then like, download, or pull them into a project.",route:"/discover",details:"Search accepts plain text; results can be shown as a grid, a list, or a compact power-browse list. Only sounds explicitly published as public appear here.",tools:["searchLibrary","listSounds","navigate"],keywords:["discover","browse","search sounds","public library","samples","find sounds","community"],related:["page-my-sounds","concept-visibility-sharing","howto-publish-discover"]},{id:"page-my-sounds",kind:"page",title:"My Sounds â your library",summary:"Everything of yours in one place, split into Uploaded, Liked, Downloaded, and Generated tabs.",route:"/sounds",requiresAuth:!0,details:'From a sound card you can play it, edit its metadata, change whether it is public, or delete it. Uploading new audio is done here too â the upload dialog asks for a title and an explicit public/private choice. The Generated tab lists AI generations, which are stored separately from the sound library: each card can be played, retitled, published, downloaded, or sent straight to the Studio timeline with "Add to Studio".',tools:["listSounds","listGenerations","navigate"],keywords:["my sounds","library","uploads","liked","downloaded","generated","my files","upload audio"],related:["page-discover","concept-visibility-sharing","howto-import-audio"]},{id:"page-studio",kind:"page",title:"Studio â the DAW",summary:"The full multitrack
23workspace: arrange, sequence MIDI, mix, run AI processing, and export.",route:"/studio",requiresAuth:!0,details:"A project is opened via /studio?project=<id>, from the Dashboard, or by asking the agent. Top to bottom the Studio is: header (project name, save, undo/redo, export and import menus), transport bar (play/stop/record, BPM, add track), the six view tabs, the active panel, and a status bar showing length, track count, BPM, time signature, and snap grid.",shortcuts:["nav.studio","global.save","audio.playPause","timeline.addTrack"],tools:["openStudio","openProject","createProject","setStudioView","queryProject"],keywords:["studio","daw","timeline","multitrack","project","workspace","arrange"],related:["studio-layout","studio-view-arrangement","studio-view-mixer","howto-first-track"]},{id:"page-ai-musicians",kind:"page",title:"AI Musicians",summary:"Named AI performer profiles â role, style, personality, and skills â you can reuse across projects.",route:"/ai-musicians",requiresAuth:!0,details:'A musician is a saved creative brief (e.g. "Ivy â session bassist, motown feel, plays behind the beat") that shapes generation requests instead of retyping the same style prompt.',tools:["createAIMusician","listAIMusicians","updateAIMusician","deleteAIMusician"],keywords:["ai musicians","band","performer","session player","persona","character"],related:["ai-generate-music","page-agent"]},{id:"page-agent",kind:"page",title:"AI Agent â chat that can operate the app",summary:'A full-page chat at /agent, plus an "Ask AI" sidebar available on every other page; it can run real actions and undo them.',route:"/agent",requiresAuth:!0,details:"Conversations are saved and listed on the left; each action the agent takes appears as a card with its status and an Undo button. Low-risk edits run automatically; destructive ones ask first. In the Studio, the agent can drive every panel â transport, tracks, plugins, automation, mixing, export â and can measure the mix before and after a change.",shortcuts:["G A â AI Agent"],tools:["navigate"],keywords:["agent","ai assistant","chat","copilot","ask ai","automation","bot"],related:["ai-agent-copilot","howto-use-agent","app-overview"]},{id:"report-bug-or-request",kind:"howto",title:"Reporting a bug or requesting a feature",summary:"Tell the AI agent what broke or what you wish existed and it files the report for you, with the page, project and recent errors attached.",route:"/agent",uiPath:'AI Agent (/agent) or the "Ask AI" sidebar on any page',steps:['Open the agent â /agent, or the "Ask AI" sidebar on any signed-in page.',"Describe the problem, or type /bug <what went wrong> â /feature <what you want> for a request.","Answer its follow-up questions: what you expected, and the steps that trigger it.","Check the summary it shows you, then click Run on the confirmation card to send it.",'Ask "what have I reported?" any time to see your reports and their triage status.'],details:"The agent attaches the session context automatically â the page you were on, the open project, your browser, and the most recent runtime errors, with any credentials stripped out â so you do not have to gather any of it. Reports go to the SoundGen team by email and are recorded for triage; you can see your own with listMyReports. Replies go to your account email unless you give the agent another address. The agent needs you signed in, so the Contact page (/contact) is the route when you are signed out or the report cannot be filed.",requiresAuth:!0,tools:["reportIssue","listMyReports","searchHelp"],keywords:["report a bug","bug report","feature request","broken","crash","not working","something is wrong","suggestion","feedback","complain","support","file an issue"],related:["page-agent","ai-agent-copilot","page-contact"]},{id:"page-contact",kind:"page",title:"Contact page",summary:"A plain form at /contact that emails the SoundGen team â the fallback when you would rather not go through the agent.",route:"/contact",details:"Name, email, subject and message; submissions reach the support inbox and are recorded. For bugs and feature requests the agent is the better route â it attaches the page, project and recent errors automatically.",tools:["navigate"],keywords:["contact","support","email us","get in touch","help desk"],related:["report-bug-or-request"]},{id:"page-settings",kind:"page",title:"Settings",summary:"Seven tabs: Profile, Preferences, Shortcuts, Audio, Credits, Account, Integrations.",route:"/settings",requiresAuth:!0,shortcuts:["nav.settings"],details:"**Profile** â display name and public profile. **Preferences** â notifications, theme, general behaviour. **Shortcuts** â view and rebind every keyboard shortcut. **Audio** â engine settings such as sample rate and buffer size. **Credits** â balance, usage charts, receipts, spend cap, renewal/rollover. **Account** â email, password, danger zone. **Integrations** â API keys and connected services.",tools:["navigate","showCredits"],keywords:["settings","preferences","account","profile","options","configuration"],related:["settings-shortcuts","credits-system","api-access","
23studio-audio-engine-settings"]},{id:"settings-shortcuts",kind:"panel",title:"Rebinding keyboard shortcuts",summary:"Settings â Shortcuts lists every shortcut by category and lets you record a new binding for any of them.",route:"/settings",uiPath:"Settings â Shortcuts tab",details:"Categories are Global, Audio, Generation, Timeline, Navigation, UI, and Project. Shortcuts marked global work anywhere; the rest only fire in their context (most audio and timeline ones are Studio-only). Press ? anywhere for the quick reference overlay.",shortcuts:["global.help"],keywords:["shortcut","keybinding","rebind","hotkey","keyboard","remap"],related:["nav-and-shortcuts","page-settings"]},{id:"account-auth",kind:"page",title:"Signing in and account recovery",summary:"Sign up or sign in at /auth; password resets go through an emailed link.",route:"/auth",details:"Most of the app requires an account â Generate, Studio, My Sounds, AI Musicians, the agent, and Settings are all behind sign-in. New accounts receive a welcome credit grant. Password reset lands on /reset-password from the emailed link; email confirmations land on /auth/confirm.",keywords:["sign in","log in","sign up","register","password","reset password","account","auth","email confirmation"],related:["credits-system","page-settings"]},{id:"credits-system",kind:"concept",title:"Credits: what they cost and when they are spent",summary:`Credits pay for AI compute. New accounts get ${Y} free; music generation is priced by length (${E()} credits per minute, so ${g.generation} for the ${M}-second default), stem separation ${x(!1)} (${L()} deep), voice cloning ${g.voiceClone}, an AI Cover ${g.recreateTrack}, and most small AI actions 1.`,route:"/settings",uiPath:"Settings â Credits tab (balance is also in the header)",details:`Full table: textâaudio generation is per-second â ${E()} credits per minute of output, so ${g.generation} credits for the ${M}s default · voice clone ${g.voiceClone} (one-off per voice) · voice synthesis ${g.voiceSynthesis} · stem separation ${x(!1)} standard, ${L()} deep · AI Cover / recreate track ${g.recreateTrack} · prompt-to-loop and text-to-sfx 5 each · vocal harmonizer 2 · an agent chat turn is billed on what the turn actually cost in tokens · most other ai-audio actions (auto-mix, mastering, chord suggestions, melody, arrangement, lyrics, fills, sound design, reference match, tempo/key detect) 1. Model training is free because there is no training backend behind it. Beat-match, vocal tuning, noise removal, and audio upscale are zero-rated. Charges land on success â a failed or demo result is not billed.`,credits:`Welcome bonus: ${Y} credits.`,tools:["showCredits"],keywords:["credits","cost","price","billing","how much","balance","free","charge","spend"],related:["buy-credits","page-settings","trouble-credits","ai-features-panel"]},{id:"buy-credits",kind:"howto",title:"Buying more credits",summary:"Buy from Settings â Credits or the Buy Credits dialog; bundles and prices are shown live at checkout.",route:"/settings",uiPath:"Settings â Credits tab â Buy Credits",steps:["Open Settings â Credits (or click the credit balance in the header).","Choose a bundle â the available tiers and prices are fetched live, not hard-coded in the app.","Complete checkout; the balance updates when the payment webhook lands.","Receipts and past purchases are listed on the same tab."],details:"A spend cap and renewal/rollover settings live on the same tab if you want to bound usage.",keywords:["buy credits","purchase","top up","payment","checkout","pricing","subscription"],related:["credits-system","trouble-credits","page-pricing"]},{id:"page-pricing",kind:"page",title:"Pricing page",summary:"Public page describing plans and credit bundles.",route:"/pricing",keywords:["pricing","plans","cost","tiers","how much does it cost"],related:["credits-system","buy-credits"]},{id:"api-access",kind:"reference",title:"REST API",summary:"A documented HTTP API covering sounds, generation, stems, voices, projects (read-only), AI processing, musicians, and account.",route:"/api",details:"The docs page (/api) is sectioned: Overview, Authentication, Sounds, AI Generation, Stem Separation, Voice Cloning, Projects, AI Audio Processing, AI Musicians, Account & Credits, Webhooks, Error Handling, MCP Server, SDKs, and Rate Limits. API keys are created in Settings â Integrations. Calls spend the same credits as the in-app equivalents.\n\nProjects are **read-only over REST** â `GET /projects` and `GET /projects/{id}` list and fetch public projects, and there is no REST endpoint that creates or edits one. Building or editing an arrangement programmatically is what the MCP server is for. Audio is referenced by `audio_url`, so the API takes a URL rather than a file upload; unlike the app, it has no video-to-audio step, so point it at an audio file.",keywords:["api","rest","http","developer","integration","api key","endpoint","webhook","sdk"],related:["mcp-server","page-settings","credits-system"]},{id:"mcp-server",kind:"reference",title:"MCP server",summary:"SoundGen exposes an MCP server so external AI clients can call its tools directly.",route:"/api",uiPath:"/api â MCP Server section",details:"Not a repackaging of the REST API â it is wider. Alongside sounds, generation, stem separation, voices and AI audio processing, the MCP tools can **build and edit an arrangement**: read it back, add/update/delete tracks and clips, set automation, track effects and sends, warp markers, tempo, markers, notes and takes, plus project versions and collaborators. The REST API is read-only for projects by comparison. Authentication uses an API key from Settings â Integrations, sent on every request as `Authorization: Bearer sgk_â¦` (the same key works for the REST API). The only official endpoint is the one shown on /api under MCP Server (Streamable HTTP); there is no npm client package and no other host â never invent URLs or packages. OAuth login is not supported, so clients like Codex must be configured with the bearer header (Codex: `bearer_token_env_var` in config.toml). Keys work regardless of when they were created or credits purchased. This is separate from the in-app agent, which runs against the live Studio session in the browser rather than over HTTP â so the agent can do things MCP cannot (transport, live metering, anything needing the audio engine), and vice versa.",keywords:["mcp","model context protocol","claude desktop","external agent","tools","integration"],related:["api-access","ai-agent-copilot"]},{id:"page-changelog",kind:"page",title:"Changelog",summary:"Running list of what shipped and when.",route:"/changelog",keywords:["changelog","updates","release notes","new features","what changed"],related:["app-overview"]},{id:"app-desktop",kind:"page",title:"SoundGen for desktop (macOS & Windows)",summary:"A native desktop build of the full studio, free to install, at /download.",route:"/download",details:"Same app and same account as the browser â sign in and your projects are where you left them, because everything is stored server-side rather than
23in the app. The macOS build is a universal binary (Apple silicon and Intel), signed and notarized by Apple, so it opens without a Gatekeeper warning; install it by dragging SoundGen from the .dmg into Applications. The Windows build runs on 64-bit Windows 10 and 11 and installs per-user, so it needs no admin password, and the installer fetches the WebView2 runtime automatically if it is missing.",keywords:["desktop","desktop app","native app","download","install","mac","macos","osx","windows","pc","dmg","exe","installer","offline","standalone","app store"],related:["app-overview","account-auth"]},{id:"app-public-links",kind:"feature",title:"Public links â shared tracks and the project player",summary:"A published track gets a /s/ link and a published project a /play/ link, both listenable with no account.",details:"Two separate surfaces, both read-only and both gated on the item being published. A generation you make public in My Sounds â Generated gets a share link at /s/<id> â its Copy link button â showing the cover art, the prompt, the model and a player. A project you make public gets /play/<id>, which streams the mixed project under its name. Neither viewer can edit, and neither requires signing in; turning the item private again breaks the link immediately, because the page only ever loads rows that are still public. Inviting a collaborator to edit is a different thing â that is the Studio Share dialog.",keywords:["share link","public link","send someone a track","listen without an account","copy link","shared track","project player","play link","anonymous listener","demo link"],related:["howto-share-track","concept-visibility-sharing","page-my-sounds","ai-cover-art"]},{id:"page-teams",kind:"page",title:"Teams â shared workspaces",summary:"A team owns projects on behalf of everyone on it, so members reach them by membership instead of a per-project invite.",route:"/teams",requiresAuth:!0,steps:["Open Teams and press New team, then give it a name.","Invite people by email address and pick their role: admin, member, or viewer.","They accept from the invitation link, which lands on /invite/<token>; it expires after 14 days.","Move a project into the team to share it with everyone at once.","Owners and admins can fund a shared credit pool and cap how much any one member spends per month."],details:"Roles: the **owner** is the single person who can hand the team over or delete it; **admins** rename the team, invite and remove people, and manage team projects; **members** open and edit team projects; **viewers** only look. Billing is per team â **personal** means each person spends their own credits as usual, **pooled** draws from the team balance â and can be overridden for one person. Per-project collaborators still exist for the guest-on-one-track case; that is the Studio Share dialog, not a team.",keywords:["team","teams","workspace","organisation","invite someone","add a member","shared projects","roles","permissions","shared credits","credit pool","budget"],related:["credits-system","concept-visibility-sharing","app-public-links"]}],mt=[{id:"studio-layout",kind:"panel",title:"How the Studio is laid out",summary:"A control bar across the top, a view rail down the left, the arrangement in the middle with an editor pane below it, and a browser on the right â every pane resizable and hideable.",route:"/studio",details:`The Studio is laid out like a desktop DAW rather than a stack of tabs. 24 25**Top bar** â one breadcrumb, \`Projects ⺠project name ⺠branch ⺠Studio ⺠view\`, with a dot showing save state; the Projects crumb is a dropdown of recent projects and the Studio crumb jumps back to Arrange. The project actions sit in this same bar: AI, Connect, Branches, collaborators, Share, undo/redo, Save, Export, and a More menu holding templates, project info, activity, version and undo history, and fullscreen. 26 27**Control bar** â return to zero, play/pause (Space), stop, record (R), then punch I/O, input monitor and metronome. The LCD beside them shows Position / Bar / Beat / Bpm / Meter as one block: click the position to cycle between m:ss, bars.beats and m:ss.ms, and type or scroll on Bpm to change tempo. A setup menu holds count-in, tap tempo and playback rate; a Mstr chip opens the master fader; the right end carries CPU and an add-track split button (audio, MIDI, or an FX bus recipe). 28 29**Left rail** â a narrow icon strip that switches views. Arrange and Session take over the middle pane. Mix, Devices, MIDI and Audio Editor open in the **editor pane below the arrangement**, so the timeline stays visible while you work in them. AI Generate is its own mode. The foot of the rail toggles the inspector, the editor pane, and the browser. 30 31**Inspector** (left, off by default) â the selected track's colour, arm/mute/solo, volume, pan, bus routing and MIDI voice, plus project tempo, meter, length and track count. 32 33**Browser** (right) â Sounds, Media, Locations and Notes as tabs, with buttons to open the AI agent column and the Video & Post panel. 34 35**Status strip** â sample rate and bit depth, output latency, frame rate, snap grid, and the current clip selection. 36 37The inspector, browser, editor pane and minimap each show or hide independently, and every pane is drag-resizable from its edge (double-click an edge to reset it). Focus mode (ââ§F) hides the header, navigation, minimap, browser and status strip, leaving the arrangement. Lane density runs tiny â small â medium â large â huge, plus **fit**, which sizes rows so the whole arrangement is on screen at once; â¥â/â¥â step it and â¥F fits.`,tools:["setStudioView","setStudioLayout","setTrackRowHeight","o
37penStudioDialog","queryProject"],keywords:["studio layout","interface","where is","panels","tabs","ui","screen","header","toolbar","inspector","browser","minimap","focus mode","row height","track height","hide panel"],related:["page-studio","studio-transport","studio-view-arrangement","studio-command-palette"]},{id:"studio-track-height",kind:"panel",title:"Track row height â fitting more of the arrangement on screen",summary:'Lane density runs tiny to huge, plus "fit", which sizes every track so the whole arrangement is visible at once.',route:"/studio",studioView:"arrangement",uiPath:"Studio â left rail â Arrange â timeline toolbar â Rows",details:`The Rows menu on the timeline toolbar sets lane height: **tiny**, **small**, **medium**, **large**, **huge**, or a free slider. **Fit all tracks** sizes the rows to the visible pane so a whole session is on screen â useful before an arrangement pass, or when hunting for a clip in a large project. 38 39Keyboard: **â¥â / â¥â** step the density, **â¥F** fits everything. A single lane can also be resized on its own by dragging its bottom edge; the named steps and "fit" clear those per-track overrides so the setting applies evenly. 40 41Row height is about how much you can *see*; the timeline's zoom (â/+, or Z to fit) is about how much *time* is on screen. They are independent â a dense session usually wants small rows and a wide zoom.`,tools:["setTrackRowHeight","setStudioLayout"],keywords:["track height","row height","rows","lane height","taller","shorter","smaller","bigger","more tracks on screen","fit all tracks","see everything","density","compact tracks","shrink tracks","zoom out tracks","too much scrolling"],related:["studio-layout","studio-timeline-toolbar","studio-view-arrangement","studio-track-header"]},{id:"studio-view-arrangement",kind:"panel",title:"Arrange view â the timeline",summary:"The main timeline: track lanes, clips, markers, automation lanes, and the ruler.",route:"/studio",studioView:"arrangement",uiPath:"Studio â left rail â Arrange",details:"Each track is a lane; audio and MIDI clips sit on it and can be dragged, trimmed by their edges, split, duplicated, and faded. The ruler above carries markers and the loop region; a minimap gives an overview of long projects. Automation lanes open underneath a track.",shortcuts:["ui.zoomIn","ui.zoomOut","ui.zoomFit","timeline.addTrack","timeline.duplicateTrack"],tools:["setStudioView","addTrack","createClip","moveClip","splitClip","trimClip"],keywords:["arrange","arrangement","timeline","tracks","clips","lanes","main view"],related:["studio-timeline-toolbar","studio-track-header","studio-clips","studio-automation","studio-markers"]},{id:"studio-view-session",kind:"panel",title:"Session view â clip launcher",summary:"A grid of clip slots and scenes you trigger live, Ableton-style, instead of playing a fixed timeline.",route:"/studio",studioView:"session",uiPath:"Studio â left rail â Session",details:"Columns are tracks, rows are scenes. Launching a slot starts that clip at the next launch-quantize boundary; launching a scene fires the whole row. Follow actions can chain one slot into another automatically.",tools:["setStudioView","createScene","setSessionClip","launchSessionClip","launchScene","stopSessionTrack","setLaunchQuantize","setFollowAction","updateScene","deleteScene","clearSessionClip"],keywords:["session","clip launcher","scenes","live","grid","ableton","trigger clips"],related:["studio-view-arrangement","concept-project-structure"]},{id:"studio-chord-detection",kind:"feature",title:"Chord detection â reading the harmony off a recording",summary:"Right-click any audio clip and choose Detect chords to get its progression, its key and its roman numerals written onto the clip's chord track.",route:"/studio",uiPath:"Studio â Arrange â right-click an audio clip â Detect chords",details:`Detect chords listens to an audio clip and works out what it is playing: the chord at each beat, the key the progression sits in, and each chord's degree in that key (I, vi, bVII, V7). The result is written to the clip's **chord track**, and the chord symbols appear along the top of the clip on the timeline so you can see the changes against the waveform. 42 43It runs **locally in your browser and costs no credits** â there is no model call and nothing is uploaded. A three-minute track takes a fraction of a second, so it is worth running on anything you are unsure about. 44 45It needs harmony to find: a full mix, keys, guitar, strings or pads all work. A drum loop has no chords, and a single melodic line only implies them â for a melody use Convert to MIDI instead. A **MIDI** clip already contains its notes, so there is nothing to detect; read them in the piano roll. 46 47What it is for, beyond the answer itself: once the chord track is populated, **Render to MIDI** turns the progression into playable chord voicings on a MIDI track, and **Harmonize melody** voices a melody against it. Detect the chords in a generated track, then write a bass or pad that actually follows them. 48 49Chord times are stored in **beats** from the start of the clip, so they move with the music if you change the tempo. A trimmed clip is analysed over the part you can hear, not the whole source file. 50 51Limits worth knowing: the detector reports triads, sixths/sevenths, sus and diminished shapes â not extended jazz voicings (9ths, 11ths, 13ths), which it reports as the underlying seventh. Passages with no clear harmony come back as gaps rather than guesses. Heavily distorted or very dense mixes are harder than a clean one, and inversions are reported by the bass note the mix actually has under them.`,tools:["detectChords","setClipChords","convertClipToMidi","generateMIDI"],keywords:["chord detection","detect chords","chords","chord recognition","harmonic","harmony","progression","chord progression","what chords","what key","key detection","roman numerals","degrees","chart","changes","transcribe chords","analyse harmony","analyze harmony"],related:["studio-view-midi","studio-view-arrangement","howto-change-tempo-key"]},{id:"studio-view-midi",kind:"panel",title:"MIDI view â piano roll",summary:"Draw, edit, and quantize notes on a piano roll; also where the step sequencer and chord track live.",route:"/studio",studioView:"midi",uiPath:"Studio â left rail â MIDI (opens in the editor pane below the arrangement)",details:"Select a MIDI clip to edit its notes: pitch, start, length, velocity. Notes can be quantized to a grid at a chosen strength, and per-note MPE expression (pressure, slide, glide) can be written on top. A MIDI track plays through either a built-in synth voice (fourteen, listed by listInstruments; left unset the voice is guessed from the track name) or a plugin instrument â the track's instrument picker lists both, with a **Plugin instruments** group that includes a sampled-instrument Soundfont Player, Synth-101, Spectrum Modal and a DRM-16 drum sampler that load on first pick, plus any WAM instrument added in the plugins panel. Drum-style parts can be programmed as a step pattern instead of individual notes.",tools:["setStudioView","addMidiNote","editMidiNote","removeMidiNote","listMidiNotes","quantizeRegion","generateMIDI","setStepPattern","setClipChords","detectChords","setNoteExpression","listInstruments"],keywords:["midi","piano roll","notes","sequencer","compose","melody","chords","velocity","quantize"],related:["howto-quantize-midi","studio-drum-machine","ai-features-panel","studio-chord-detection"]},{id:"studio-view-mixer",kind:"panel",title:"Mix view â console",summary:"Channel strips for every track plus the master section, sends, scenes, cue mixes, and control room.",route:"/studio",studioView:"mixer",uiPath:"Studio â left rail â Mix (opens in the editor pane below the arrangement)",tools:["setStudioView","setTrackVolume","setTrackPan","setTrackEq","addPlugin","createSend","setMasterVolume"],keywords:["mixer","mix","console","channel strip","faders","levels","balance"],related:["studio-channel-strip","studio-master-section","studio-sends-buses","studio-mixer-scenes","howto-master-track"]},{id:"studio-view-devices",kind:"panel",title:"Devices view â the selected trackâs effect chain",summary:"The insert chain for whichever track is selected, laid out left to right, with each deviceâs parameters inline.",route:"/studio",studioView:"fx",uiPath:"Studio â left rail â Devices (opens in the editor pane below the arrangement)",details:"Select a track, then open Devices to see its inserts in signal order and edit them without going through the mixer â previously this rack was reachable only by opening Mix and expanding the right channel strip. Devices can be added, reordered by dragging, bypassed, and removed; each has a wet/dry balance and an A/B pair for comparing two settings. The master chain lives on the master strip in Mix. `listEffe
51ctTypes` enumerates every available device and its parameter names.",tools:["setStudioView","listEffectTypes","addPlugin","setPluginParam","removePlugin","reorderPlugins","setEffectEnabled","setEffectMix"],keywords:["devices","fx","effects","insert","chain","plugins","rack","signal chain","processing"],related:["studio-effects-rack","studio-channel-strip","studio-view-mixer","studio-reverb"]},{id:"studio-view-editor",kind:"panel",title:"Audio Editor view â waveform editing",summary:"Zoomed waveform editing on a single clip: trim, select regions, and apply destructive audio operations.",route:"/studio",studioView:"editor",uiPath:"Studio â left rail â Audio Editor (opens in the editor pane below the arrangement)",details:"Use this for surgical work on one file â tightening a start, silencing a breath, normalizing, reversing â rather than arrangement-level moves, which belong in the Arrange view.",tools:["setStudioView","normalizeClip","reverseClip","trimClipStart","setClipGainEnvelope"],keywords:["audio editor","waveform","edit audio","trim","crop","silence","destructive edit"],related:["studio-clips","studio-view-arrangement"]},{id:"studio-view-generate",kind:"panel",title:"AI Generate view â the Studio AI panel",summary:"The AI toolbox inside the Studio: mixing, mastering, stems, repair, transcription, and generation, applied to the open project.",route:"/studio",studioView:"generate",uiPath:"Studio â left rail â AI Generate",details:'The features are listed on the left with a search box and a count per category; the AI Co-Pilot chat sits on the right of the same tab, with starter prompts ("How should I EQ my vocals?", "How to sidechain compress") and a badge saying it can apply changes. That Co-Pilot is scoped to the open project; the "Ask AI" agent in the bottom-right corner is the platform-wide one that can also navigate, generate, and manage projects.',tools:["setStudioView","runAIProcess"],keywords:["ai panel","ai generate","ai tools","ai features","studio ai","co-pilot","copilot","chat"],related:["ai-features-panel","ai-auto-mix","ai-mastering","ai-stem-separation","ai-agent-copilot"]},{id:"studio-transport",kind:"panel",title:"Transport bar â play, record, tempo",summary:"Play/pause, stop, return to zero, record, punch, input monitoring, metronome, BPM, and add-track buttons.",route:"/studio",uiPath:"Studio â control bar",details:'Space toggles play/pause; Escape stops; R arms recording. BPM can be typed, nudged, or tapped in with the Tap button. The count-in is a dropdown (default "No count-in"), and there is a playback-rate control next to it. The buttons to add an Audio Track, MIDI Track, or Bus live at the right end of this bar.',shortcuts:["audio.playPause","audio.stop","audio.rewind","audio.fastForward","timeline.recordTrack"],tools:["play","pause","stop","seekTo","setBPM","toggleMetronome","setTimeSignature","setCountIn","setClickPattern"],keywords:["transport","play","stop","record","bpm","tempo","metronome","click","playhead"],related:["studio-recording","studio-tempo-meter","howto-change-tempo-key"]},{id:"studio-timeline-toolbar",kind:"panel",title:"Timeline toolbar â zoom, snap, marquee, clip edits",summary:"Zoom controls, snap-to-grid toggle, marquee selection, split/duplicate/copy/delete, scrub, and the loop region.",route:"/studio",studioView:"arrangement",uiPath:"Studio â left rail â Arrange â timeline toolbar",details:'Marquee (M) drags a rectangle across tracks and time to select a region â that region is what "trim to selection", "clear", and "bounce in place" act on. Snap (S) constrains dragging to the current grid. J/K/L scrub backwards, pause, and forwards. The loop region can be set by hand or from quick presets, then enabled/disabled independently.',shortcuts:["ui.zoomIn","ui.zoomOut","ui.zoomFit","Shift+Z â zoom to selection","M â marquee","S â snap toggle","J/K/L â scrub","timeline.duplicateTrack","timeline.deleteTrack"],tools:["setSnapGrid","setLoopRegion","setLoopEnabled","trimToMarquee","clearMarquee","bounceMarquee","copyClips","pasteClips"],keywords:["zoom","snap","grid","marquee","selection","select region","loop","cycle","scrub","split","duplicate"],related:["studio-view-arrangement","concept-snap-grid","studio-clips"]},{id:"studio-track-header",kind:"panel",title:"Track header â arm, mute, solo, volume, pan",summary:"The controls at the left of each timeline lane: name, colour, record-arm, mute, solo, volume, pan, and freeze.",route:"/studio",studioView:"arrangement",uiPath:"Studio â left rail â Arrange â left of each track lane",details:`Tracks are audio or MIDI. A MIDI track header (and the Inspector's Voice row) carries the instrument picker â a built-in voice, "Auto (from name)" to guess from the track name, or a plugin instrument from the **Plugin instruments** group (Soundfont Player for sampled piano/guitar/strings, Synth-101, Spectrum Modal, DRM-16 drums). Picking a plugin puts it at the head of the track's insert rack; picking a built-in voice takes it off. Beyond the basics a track carries input trim, stereo width, mid/side handling, monitoring mode, timebase, notes, and direct outs; those live on the channel strip in the Mix view. Freezing renders a track to audio so its plugins stop costing CPU.`,shortcuts:["timeline.soloTrack","timeline.muteTrack","timeline.recordTrack","timeline.addTrack","timeline.deleteTrack"],tools:["addTrack","removeTrack","renameTrack","setTrackColor","duplicateTrack","freezeTrack","bounceTrackToAudio","muteTrack","soloTrack","setTrackVolume","setTrackPan","armTrack","reorderTracks","listInstruments","setTrackProperty"],keywords:["track","mute","solo","arm","volume","pan","fader","rename track","track colour","freeze","
51instrument","voice","synth"],related:["studio-channel-strip","howto-freeze-track","concept-buses-groups-vca"]},{id:"studio-clips",kind:"concept",title:"Working with clips",summary:"Clips are the blocks of audio or MIDI on a track; they can be moved, trimmed, split, faded, pitched, stretched, and looped.",route:"/studio",studioView:"arrangement",details:"Per-clip properties: name, colour, lock, gain, loop, warp, and take. Edits available without leaving the timeline: split at the playhead, trim either edge, duplicate, reverse, normalize, mute, fade in/out (with curve shapes), pitch shift in semitones, time-stretch by ratio, draw a clip-gain envelope, and bounce the clip through its own effect chain to print the result.",tools:["createClip","placeClips","moveClip","splitClip","trimClip","trimClipStart","duplicateClip","deleteClip","reverseClip","normalizeClip","setClipMute","setClipFades","setClipPitchShift","setClipTimeStretch","setClipProperty","setClipGainEnvelope","bounceClip","loopClip","convertClipToMidi"],keywords:["clip","region","split","cut","trim","fade","crossfade","pitch","stretch","loop clip","reverse"],related:["howto-fades-crossfade","studio-comping-takes","concept-timebase-warp","studio-view-editor"]},{id:"studio-channel-strip",kind:"panel",title:"Channel strip",summary:"Per-track console channel: input trim, 3-band EQ, insert FX rack, sends, pan, fader, and metering.",route:"/studio",studioView:"mixer",uiPath:"Studio â left rail â Mix (editor pane) â one strip per track",details:"Signal order is gain â EQ â insert effects â pan â output. The three EQ bands are low, mid, and high. Insert effects added here are heard live during playback and are also rendered on export. Sends feed bus tracks in parallel.",tools:["setTrackVolume","setTrackPan","setTrackEq","addPlugin","setPluginParam","createSend","setTrackProperty","setTrackSpatial"],keywords:["channel strip","strip","eq","trim","gain","insert","fx slot","meter","width","mid side"],related:["studio-effects-rack","studio-sends-buses","studio-view-mixer","howto-add-effect"]},{id:"studio-master-section",kind:"panel",title:"Master section",summary:"The master bus: master fader, master effect chain, level and LUFS readouts.",route:"/studio",studioView:"mixer",uiPath:"Studio â left rail â Mix (editor pane) â master strip",details:"The master chain is where a mastering compressor, EQ, and limiter belong. Keep pre-master peaks near â6 dBFS so the chain has headroom to work with. The LUFS readout is the loudness reference for delivery.",tools:["setMasterVolume","addMasterEffect","setMasterEffectParam","removeMasterEffect","setMasterEffects","measureMix","readMeters"],keywords:["master","master bus","master fader","lufs","loudness","output","mastering chain","headroom"],related:["howto-master-track","ai-mastering","concept-headroom-lufs"]},{id:"studio-effects-rack",kind:"panel",title:"Effects and plugins",summary:"Built-in effects: reverb (algorithmic, four characters), delay, EQ, compressor, limiter, noise gate, distortion, filter, chorus, phaser, flanger, large hall, gated and shimmer reverb, dynamic EQ, pitch shift, noise removal, vocal tuning.",route:"/studio",studioView:"mixer",uiPath:"Studio â left rail â Mix (editor pane) â channel strip FX slot (or the master strip); the Devices pane shows the selected trackâs chain on its own",details:"Effects are grouped as dynamics, EQ, time, modulation, distortion, utility, and repair. Each has real parameters (a compressor has threshold, ratio, attack, release, knee, makeup gain) with sensible defaults on insert. Any plugin can be bypassed, wet/dry mixed, reordered, sidechained from another track, or removed. Track and master edits are heard immediately during playback.",tools:["listEffectTypes","addPlugin","applyTrackEffect","setPluginParam","removePlugin","reorderPlugins","setEffectEnabled","setEffectMix","setSidechainSource","setDynamicEqBand","addMasterEffect","setMasterEffectParam","removeMasterEffect"],keywords:["effect","plugin","reverb","delay","compressor","limiter","eq","gate","distortion","chorus","phaser","flanger","saturation","fx","insert","bypass","wet dry"],related:["howto-add-effect","howto-sidechain","studio-channel-strip","studio-plugin-marketplace"]},{id:"studio-reverb",kind:"panel",title:"Reverb",summary:"Algorithmic reverb with four characters (room, hall, plate, spring), full tone and modulation control, and a freeze.",route:"/studio",studioView:"mixer",uiPath:"Studio â left rail â Mix (editor pane) â channel strip FX slot â Reverb",details:`The reverb is a modulated feedback delay network, not a convolver â so it can damp, move and freeze, and every control ramps live instead of rebuilding the effect. 52 53**Space** â Size is the physical size of the room (0â1, not seconds). Decay is RT60: how long until the tail is 60 dB down. Pre-Delay separates the source from its tail and is what keeps a vocal in front of its reverb rather than smeared into it. Diffusion goes from discrete echoes to a fully smeared wash. Early Reflections sets the level of the first-arrival cluster, which is most of what tells you how big a room is. 54 55**Tone** â Damping makes highs die faster than mids, as air does; it is the single biggest control over whether a tail sounds like a room or like noise. Low Cut sits inside the feedback loop so low end cannot accumulate. High Cut shapes the output. 56 57**Movement** â Mod Depth and Mod Rate wobble the delay lines to break up metallic ringing. Width goes from a mono tail to an exaggerated stereo one. 58 59**Freeze** holds the tail forever: feedback goes to unity and the input mutes, so whatever is ringing stays ringing. 60 61For several tracks sharing one space, use a reverb send rather than one insert per track â the Bus menu builds it wired up.`,tools:["addPlugin","setPluginParam","setEffectMix","createFxBus","listEffe
61ctTypes"],keywords:["reverb","verb","room","hall","plate","spring","tail","decay","rt60","predelay","pre-delay","damping","diffusion","early reflections","freeze","shimmer","gated reverb","ambience","space","wet dry"],related:["studio-effects-rack","studio-sends-buses","howto-add-effect"]},{id:"studio-sends-buses",kind:"panel",title:"Sends, buses, groups, and VCAs",summary:"Route tracks together: a bus for shared processing, a folder for organisation, a VCA for linked levels, a send for parallel effects.",route:"/studio",studioView:"mixer",details:`A **send** taps a track and feeds a bus in parallel â the standard way to share one reverb across several tracks. A **bus** sums its inputs and processes them together. A **folder** groups lanes visually. A **VCA** rides the faders of its members without passing audio. **Track groups** link controls across a named set of tracks. 62 63The **Bus menu** in the transport bar builds the common setups in one click, wiring the effect and the sends together: Reverb Send, Parallel Compression, Delay Throw, and a Drum Glue Bus. An effect on a send return is forced to 100% wet â if a return passes dry signal it arrives a few milliseconds after the source track's own dry path and combs rather than adding depth.`,tools:["createFxBus","createSend","setSendLevel","removeSend","createBusTrack","setBusInput","addVcaTrack","addFolderTrack","setTrackParent","setTrackGroup","createTrackGroup","updateTrackGroup","deleteTrackGroup"],keywords:["send","bus","aux","group","vca","folder","routing","submix","parallel","reverb bus","parallel compression","new york compression","fx bus","delay throw","drum bus"],related:["concept-buses-groups-vca","howto-add-effect","studio-channel-strip"]},{id:"studio-mixer-scenes",kind:"panel",title:"Mixer scenes and A/B",summary:"Snapshot the whole console and recall it later; A/B compares two versions of a mix.",route:"/studio",studioView:"mixer",uiPath:"Studio â left rail â Mix (editor pane) â Scenes panel",details:'Save a scene before a big mix pass so the previous balance is always recallable. A/B stores one snapshot as "A" and toggles between it and the live state for quick comparison.',tools:["saveMixerScene","recallMixerScene","deleteMixerScene","storeABSnapshot","toggleAB"],keywords:["scene","snapshot","recall","ab","a/b","compare mixes","save mix","revert mix"],related:["studio-view-mixer","howto-master-track","studio-version-history"]},{id:"studio-cue-mixes",kind:"panel",title:"Cue mixes and control room",summary:"Separate headphone mixes for performers, plus control-room monitoring options.",route:"/studio",studioView:"mixer",details:"A cue mix is an independent set of send levels â the singer can hear more of themselves without changing the main balance. Control room settings cover monitor level, dim, mono fold-down checks, and solo behaviour (including whether solo defeats mute).",tools:["createCueMix","updateCueMix","setCueSend","deleteCueMix","setControlRoom","setSoloDefeatsMute"],keywords:["cue mix","headphone mix","foldback","control room","monitor","dim","mono check","solo"],related:["studio-view-mixer","studio-recording"]},{id:"studio-automation",kind:"panel",title:"Automation lanes",summary:"Draw parameter changes over time â volume, pan, sends, plugin parameters â on lanes under each track.",route:"/studio",studioView:"arrangement",uiPath:"Studio â left rail â Arrange â automation lane under a track",details:"Add a lane for a parameter, then write points along the timeline. Lanes can be shown, hidden, bypassed, or cleared. The automation mode controls whether moving a control writes new points (write/touch/latch) or just plays back (read/off). Fader and knob gestures in the mixer can be recorded into lanes.",tools:["writeAutomation","addAutomationLane","removeAutomationLane","clearAutomation","setAutomationLaneState","setAutomationMode"],keywords:["automation","automate","envelope","volume ride","fade over time","parameter","lane","write mode"],related:["howto-automate-volume","concept-automation-modes","studio-view-arrangement"]},{id:"studio-markers",kind:"panel",title:"Markers and memory locations",summary:"Name points in the timeline (intro, chorus, drop) and jump to them; memory locations are numbered recall slots.",route:"/studio",studioView:"arrangement",uiPath:"Studio â left rail â Arrange â marker ruler above the tracks",tools:["addMarker","removeMarker","listMarkers","addMemoryLocation","recallMemoryLocation","removeMemoryLocation"],keywords:["marker","section","chorus","verse","locate","memory location","cue point","song structure"],related:["studio-view-arrangement","ai-features-panel"]},{id:"studio-tempo-meter",kind:"panel",title:"Tempo, time signature, and sync",summary:"Project BPM and meter, plus tempo/meter changes at points in the timeline and external
63clock sync.",route:"/studio",uiPath:"Studio â control bar LCD (Bpm cell) and the tempo panel",details:"A single project BPM covers most work; a tempo map adds changes at specific times for ritardandos or section shifts. Tap tempo sets BPM by feel. Tempo sync can follow an external clock source instead of the internal one.",tools:["setBPM","setTimeSignature","addTempoChange","removeTempoChange","addTimeSignatureChange","removeTimeSignatureChange","setTempoSyncSource"],keywords:["tempo","bpm","time signature","meter","tap tempo","tempo map","sync","clock","midi clock"],related:["howto-change-tempo-key","studio-transport","concept-timebase-warp"]},{id:"studio-recording",kind:"panel",title:"Recording audio and MIDI",summary:"Arm a track, choose an input, optionally set a count-in and punch region, then hit record.",route:"/studio",steps:["Add or select the track to record onto and arm it (R). Arming opens the input and shows its level in the track header.","Pick the input device and enable input monitoring if you need to hear yourself â it plays through the armed track's channel strip.","Optionally set a count-in and a punch region so recording only starts and stops inside a range; both land on the exact beat.","Press record: the count-in plays, then the song rolls from the playhead with the take. Record again ends the take and keeps playing; stop ends both.","With a loop set, each pass becomes a take on the clip (the last is active). Right-click the clip â Takes â Show take lanes to comp: drag across a lane to use that take for that stretch, double-click to split a section, drag a seam to move it, click a section to give it that lane's take, click a lane's name to use it everywhere.","MIDI tracks record in the same pass â notes, sustain pedal, pitch bend and aftertouch â and share the take's start. Audio Engine â MIDI Recording sets merge or replace, and input quantise."],tools:["armTrack","startRecording","stopRecording","setPunchRegion","setInputMonitoring","setCountIn","setClickPattern","setInputDevice","setLoopRecording"],keywords:["record","recording","microphone","mic","input","punch","count in","monitor","take","overdub","cycle recording","loop recording","record multiple passes","comp","midi recording","merge","replace","input quantise","input quantize","sustain pedal","pitch bend","aftertouch","latency","calibrate","calibration","drift","two devices","audio interface"],related:["studio-comping-takes","trouble-mic-not-working","studio-transport","studio-cue-mixes"]},{id:"studio-comping-takes",kind:"feature",title:"Takes and comping",summary:"Multiple takes live under one clip; switching takes swaps which recording that clip plays.",route:"/studio",tools:["listTakes","addTake","selectTake","deleteTake"],keywords:["take","comp","comping","alternate take","best take","playlist","take lanes","swipe comping","swipe","cycle recording","loop recording","split take","seam","crossfade takes"],related:["studio-recording","studio-clips"]},{id:"studio-media-browser",kind:"panel",title:"Media browser and sound library",summary:"Browse your sounds, the shared library and your AI generations from inside the Studio, and pull them onto the timeline.",route:"/studio",details:"Three tabs: **Local** (files imported this session), **Library** (your sounds plus published ones) and **AI** (everything SoundGen generated for you, whether or not it was ever saved to the library). The + on a row drops it on the selected track at the playhead, creating a track if the project has none. Files can also be dragged from the desktop straight onto the timeline to create a clip.",tools:["listSounds","listGenerations","searchLibrary","addClip"],keywords:["media browser","library","samples","drag and drop","import sound","soundbase","browse","generated","ai tab"],related:["howto-import-audio","page-my-sounds"]},{id:"studio-plugin-marketplace",kind:"panel",title:"Plugin marketplace",summary:"Browse and activate additional plugins beyond the built-in effect set.",route:"/studio",details:"Audio has to be initialized before a marketplace plugin can be activated â start playback once if activation reports the engine is not ready.",keywords:["plugin marketplace","more plugins","install plugin"],related:["studio-effects-rack","trouble-no-sound","studio-external-plugins"]},{id:"studio-external-plugins",kind:"panel",title:"Third-party plugins: CLAP, VST, AU and Web Audio Modules",summary:"Load plugins you already own, or WebAssembly plugins from a URL.",route:"/studio",details:`Two kinds, with different rules. 64 65CLAP plugins installed on your computer are found by Studio â Connect â Integrations â Plugins â Scan. This needs the SoundGen desktop app: a browser tab cannot load native code, so the panel says so rather than showing an empty list. Scanned plugins appear in the effects rack under "Plugins" and are applied when you export or bounce â not during live playback, so you will not hear them while the transport is running. 66 67Web Audio Modules are plugins compiled to WebAssembly. Add one by pasting its URL in the same panel. These do run live, in the browser and on the desktop. A WAM runs as part of SoundGen with the same access to your account that SoundGen has, so the first plugin from a given developer asks for approval before loading, and plugins served over plain HTTP are refused. 68 69VST3 is not supported yet â it needs a licence agreement with Steinberg, which is in progress. If a plugin also ships a CLAP build, that one loads today. Audio Units are not supported yet either. 70 71A plugin that crashes while being scanned is skipped on later scans so one bad plugin does not slow every scan down; use "Try again" next to it after updating the plugin. 72 73The AI agent can drive a plugin it has never seen before. The scan reads each plugin's own parameter list â names, ranges, and what the plugin prints for a value ("1.20 kHz", "Lowpass") â so you can say "put my Fuzz Face on the guitar and back the drive off" and it will find the plugin, find the parameter, and set it in the plugin's own units. Ask it to list your plugins or describe one to see what it can see. 74 75When a plugin's knobs are named things like "Colour" or "Character", ask the agent to learn the plugin. It renders a test signal through it at several settings of every control and reports what each one actually did â output level, brightness, saturation, decay, dynamics, stereo width, or nothing at all. That takes a while (one render per setting) and is remembered per plugin version, so it happens once rather than every time you ask.`,tools:["listExternalPlugins","describePlugin","learnPlugin","addPlugin","setPluginParam","removePlugin","reorderPlugins"],keywords:["vst","vst3","clap","audio unit","au plugin","third party plugin","plugin scan","install plugin","wam","web audio module","my plugins","serum","kontakt","fabfilter","plugin not showing","agent plugin control","describe plugin","plugin parameters"],related:["studio-effects-rack","studio-plugin-marketplace","studio-export-dialog","trouble-export-silent"]},{id:"studio-drum-machine",kind:"panel",title:"Drum machine and step sequencer",summary:"Program drums on a step grid instead of drawing individual MIDI notes.",route:"/studio",studioView:"midi",tools:["setStepPattern","generateMIDI"],keywords:["drum machine","step sequencer","beat","drum pattern","grid","808","hi hat"],related:["studio-view-midi","ai-features-panel"]},{id:"studio-hardware",kind:"panel",title:"Hardware: control surfaces, MIDI input, grid controllers",summary:"Map physical controllers, take MIDI input from a keyboard, use a footswitch for punch, and drive multi-device audio.",route:"/studio",details:"Grid controllers (Push/Launchpad-style layouts) map to the Session view. Hardware inserts route a track out to an external processor and back.",tools:["addGridController","removeGridController","setHardwareInsert","setInputDevice","setAudioSettings"],keywords:["controller","midi keyboard","control surface","launchpad","push","footswitch","hardware","external gear"],related:["studio-audio-engine-settings","studio-view-session","studio-recording"]},{id:"studio-audio-engine-settings",kind:"dialog",title:"Audio engine settings",summary:"Sample rate, buffer size, and device selection for the Web Audio engine.",route:"/studio",details:"A smaller buffer lowers latency for recording but costs CPU; raise it if playback crackles. The same audio preferences are mirrored in Settings â Audio.",tools:["setAudioSettings","setInputDevice"],keywords:["audio settings","buffer","latency","sample rate","device","asio","crackle","dropout"],related:["trouble-no-sound","page-settings"]},{id:"studio-command-palette",kind:"dialog",title:"Command palette",summary:"Cmd+K opens a searchable list of navigation targets and Studio actions.",route:"/studio",shortcuts:["global.search"],keywords:["command palette","cmd k","quick actions","search commands","launcher"],related:["nav-and-shortcuts","studio-layout"]},{id:"studio-notes-panel",kind:"panel",title:"Project notes",summary:"A scratchpad attached to the project for lyrics, to-dos, and session notes.",route:"/studio",tools:["setProjectNotes"],keywords:["notes","lyrics","todo","scratchpad","session notes"],related:["studio-metadata"]},{id:"studio-export-dialog",kind:"dialog",title:"Export dialog â bouncing audio out",summary:"Render a mixdown, stems, or both to WAV, MP3, FLAC, OGG, or AIFF.",route:"/studio",studioDialog:"exportDialog",uiPath:"Studio â top bar â Export",steps:["Open the Export dialog from the Studio header. It has five tabs: Format, Batch, Naming, Tracks, Metadata.","On the Format tab pick the audio format (WAV, MP3, FLAC, OGG, AIFF) and the sample rate.","Choose the export type on the same tab: Mixdown (single stereo file), Stems (individual tracks), or Both. An estimated file size updates as you change these.","On the Tracks tab pick which tracks are included when exp
75orting stems.","Optionally set a filename template on the Naming tab and title/artist on the Metadata tab.","Use the Batch tab to render several formats in one pass.","Press Export and download the results when they appear."],details:"If an encoder is unavailable the render falls back to WAV and the result is flagged as a fallback rather than failing.",shortcuts:["project.export","generation.export"],tools:["exportProject","renderExport","openStudioDialog"],keywords:["export","bounce","render","download","wav","mp3","flac","stems","mixdown","save audio"],related:["howto-export-stems","studio-interchange","trouble-export-silent"]},{id:"studio-interchange",kind:"dialog",title:"Interchange: AAF/OMF, EDL, consolidate, session import",summary:"Move projects between SoundGen and other DAWs or post-production tools.",route:"/studio",studioDialog:"aafOmf",uiPath:"Studio â top bar â import/export menus",details:"**AAF/OMF and FCP XML** hand a session to a video editor â they carry audio clips and timing, and no MIDI. **.dawproject** is the cross-DAW format Bitwig and Studio One read: it carries tracks, clips, MIDI notes, gain and pan, and embeds the audio so the archive opens standalone. Reach for the first when the session is going to picture, the second when it is going to another DAW. **EDL** exports an edit list. **Consolidate** copies every referenced media file into the project so nothing is missing. **Import session** and **DAW import** bring outside projects in â Ableton, DAWproject, and REAPER import in full, while Logic, GarageBand, and FL are salvage-only because of what their formats expose.",tools:["exportInterchange","exportDawProject","exportEdl","consolidateProject","importProjectJSON","exportProjectJSON","openStudioDialog"],keywords:["aaf","omf","edl","fcp xml","dawproject","bitwig","studio one","cross-daw","interchange","import project","ableton","reaper","logic","consolidate","migrate"],related:["studio-export-dialog","concept-project-structure"]},{id:"studio-templates",kind:"dialog",title:"Project templates",summary:"Start from a prepared track layout instead of an empty project.",route:"/studio",studioDialog:"templates",uiPath:"Studio â top bar â Templates",tools:["openStudioDialog"],keywords:["template","starter","preset project","new project layout"],related:["howto-first-track","page-studio"]},{id:"studio-metadata",kind:"dialog",title:"Project metadata",summary:"Title, artist, and release information carried into exports.",route:"/studio",studioDialog:"metadata",uiPath:"Studio â top bar â Metadata",tools:["openStudioDialog","renameProject"],keywords:["metadata","title","artist","tags","id3","credits"],related:["studio-export-dialog","studio-notes-panel"]},{id:"studio-version-history",kind:"panel",title:"Undo history, version history, and branches",summary:"Step back through edits, jump to any point in the history, restore a saved version, or branch the project.",route:"/studio",uiPath:"Studio â top bar â undo/redo and version controls",details:"Undo/redo is the normal Cmd+Z stack, and the history panel lets you jump several edits at once instead of tapping undo repeatedly. Branches keep parallel versions of the same project so an experiment does not overwrite the safe take.",shortcuts:["global.undo","global.redo"],tools:["undoEdit","redoEdit","listEditHistory","jumpToHistory","createBranch","switchBranch"],keywords:["undo","redo","history","version","revert","branch","restore","mistake","go back"],related:["concept-undo-model","studio-mixer-scenes"]},{id:"studio-collaboration",kind:"feature",title:"Collaboration: sharing, invites, and comments",summary:"Invite collaborators by email with a role, or turn on a public link so anyone can listen.",route:"/studio",uiPath:"Studio â top bar â Share",details:"The Share dialog has an invite tab (email + role, plus the current collaborator list) and a link tab with a public-access toggle and copyable URL. Public link viewers can listen but cannot edit unless invited. Timeline comments pin feedback to a moment in the track and can be resolved.",tools:["addComment","resolve
75Comment","deleteComment"],keywords:["share","collaborate","invite","permissions","comment","feedback","team","public link"],related:["howto-share-track","concept-visibility-sharing"]},{id:"studio-video-post",kind:"panel",title:"Video and post-production panel",summary:"Score to picture: load a video source, spot cues against it, and record ADR.",route:"/studio",studioDialog:"videoPost",uiPath:"Studio â top bar â Video & Post",tools:["openStudioDialog"],keywords:["video","film","score to picture","adr","spotting","post production","sync to video"],related:["studio-markers","studio-recording"]}],gt=[{id:"ai-features-panel",kind:"panel",title:"The AI feature list (Studio â AI Generate)",summary:"Twenty-one AI features grouped as Mix & Master, Creative, Repair, and Analysis, each shown with its credit cost.",route:"/studio",studioView:"generate",uiPath:"Studio â AI Generate tab",details:"**Mix & Master** â AI Auto-Mix, AI Mastering, Reference Match, Vocal Harmonizer, Beat Matcher. **Creative** â Suggest Chords, Generate Melody, AI Cover, Arrangement AI, Lyrics Generator, Fill Generator, Voice Cloning, Sound Designer, Prompt to Loop, AI Search. **Repair** â Isolate Vocals, Vocal Tuning, Noise Removal, Audio Upscale. **Analysis** â Tempo/Key Detect, Stem Separation. Features marked as needing a track require you to pick one first; others need a prompt. Some (auto-mix, mastering, vocal tuning, noise removal) show a preview you can accept or discard before it is committed.",credits:`Most actions cost 1 credit. Music generation is billed by length at ${E()} credits per minute (${g.generation} for a ${M}-second clip). Stem separation and vocal isolation ${x(!1)} (${L()} deep), prompt-to-loop and text-to-sfx 5, vocal harmonizer 2, voice clone ${g.voiceClone}, AI Cover ${g.recreateTrack}. Beat-match, vocal tuning, noise removal, and audio upscale are free.`,tools:["runAIProcess","setStudioView"],keywords:["ai features","ai tools","auto mix","mastering","stems","repair","analysis","what can ai do"],related:["ai-auto-mix","ai-mastering","ai-stem-separation","ai-cover","credits-system","studio-view-generate"]},{id:"ai-generate-music",kind:"feature",title:"Generating music from a prompt",summary:"Describe the music you want â style, instrumentation, mood, tempo â and get back a finished audio track.",route:"/generate",steps:["Go to Generate, or ask the agent for a track directly in chat.",'Write a style brief rather than a title: "uplifting West Coast pop, warm electric guitar, live drums, ~110 BPM, male lead vocal".',"Use the Vocals control under the prompt: paste lyrics to have those exact words sung, leave the box empty to let the model write its own, or switch to Instrumental for no vocals. The prompt describes the sound, not the words â a lyric typed there is read as a style brief.","Set duration â 60â180 seconds reads as a full song; the default 30 is a sketch.","Generate, audition, then save to your library or drop it into a project."],details:'Prompts naming real artists, bands, or songs are rejected for copyright reasons; describe the sound instead ("late-90s trip-hop with dusty breakbeats") and it will pass.',credits:`ElevenLabs (the default model) is billed by length â ${E()} credits per minute, so ${g.generation} for the ${M}s default. Stable Audio is a flat ${g.stableAudioGeneration} credits per generation at any length up to 190s. Pick the model with the Model control on the Generate page.`,tools:["generateMusic","generateSound","generateSimilarTrack","generateWithModel"],keywords:["generate music","make a song","text to music","ai song","full track","lyrics","suno","compose"],related:["page-generate","trouble-generation-failed","ai-reference-track","credits-system"]},{id:"ai-reference-track",kind:"feature",title:"Making something that sounds like a reference",summary:'Upload a track and generate a new one in its style, optionally with a twist ("same vibe but disco").',details:"Attach the audio in the agent chat or supply its URL; the reference is analysed and its style drives the generation. The output is a new piece, not a copy.",credits:`Billed by length â ${g.generation} credits for the ${M}s default.`,tools:["generateSimilarTrack"],keywords:["reference","sounds like","similar","in the style of","match vibe","remake"],related:["ai-generate-music","ai-cover"]},{id:"ai-stem-separation",kind:"feature",title:"Stem separation",summary:"Split a mixed track into vocals, drums, bass, and other â each landing on its own Studio track.",route:"/studio",studioView:"generate",uiPath:"Studio â AI Generate tab â Stem Separation (or Isolate Vocals)",steps:["Put the mixed track in the project and select it.","Open the AI Generate tab and choose Stem Separation.","Pick which stems you want, then run it.","The separated parts arrive as new tracks you can mix independently."],details:'Deep mode runs the 6-source model instead, which additionally pulls out piano and guitar; the drums come back as one stem. Separation runs server-side, so the source must be a real hosted URL â a local blob that has not been uploaded cannot be processed. "Isolate Vocals" is the same engine keeping only the vocal.',credits:`${x(!1)} credits for a standard job; ${L()} for deep, which runs the larger 6-source model.`,tools:["separateStems","runAIProcess"],keywords:["stems","stem separation","split","
75isolate","acapella","remove vocals","karaoke","demucs","extract"],related:["ai-cover","ai-features-panel","howto-export-stems"]},{id:"ai-cover",kind:"feature",title:"AI Cover â deconstruct and rebuild a track",summary:"Takes a finished track apart (stems, pitch transcription, drum onsets) and rebuilds it as separate tracks with MIDI, an auto-mix, and a master.",route:"/studio",studioDialog:"aiCover",uiPath:"Studio â AI Generate tab â AI Cover",details:`The heaviest pipeline in the app: separation, per-stem analysis, MIDI transcription, then mixing and mastering the reconstruction. Expect it to take a while and to produce a full project rather than a single file. 76 77**Getting a song in:** the dialog takes an upload (drag a file in or pick one), anything already in My Sounds or your generations, or a pasted URL. A clip that only exists locally in the browser can be uploaded from the dialog in one click â server-side separation cannot reach a blob: URL. 78 79**What comes back** is controlled by three independent options: *Keep original stems* adds the separated stems as muted A/B reference tracks; *Render MIDI parts to audio* bounces each transcribed part through the built-in instruments into an audio stem (free, follows the original notes); *AI-generate each stem* composes a brand-new part per instrument from the detected tempo and key (one generation charged per part, and it does NOT follow the original arrangement).`,credits:"45 credits, plus one generation per part if AI-generated stems are enabled.",tools:["recreateTrack","openStudioDialog"],keywords:["ai cover","recreate","remake track","deconstruct","rebuild","transcribe song","cover version","reference track","reproduce song","remake with midi"],related:["ai-stem-separation","ai-transcription","credits-system"]},{id:"ai-transcription",kind:"feature",title:"Audio to MIDI transcription",summary:"Turn a recorded performance into MIDI notes â pitched instruments or drums.",details:'Pitched transcription follows the melody or chords of a monophonic or lightly polyphonic part; drum transcription detects onsets and maps them to drum lanes. Both produce editable MIDI, not audio. Note that "Suggest Chords" and "Generate Melody" in the AI panel *write* new material from tempo/key/genre â they do not analyse your take; transcription is the one that listens.',tools:["transcribePitched","transcribeDrums"],keywords:["transcribe","audio to midi","midi from audio","notes from recording","drum transcription","basic pitch"],related:["studio-view-midi","ai-features-panel","ai-cover"]},{id:"ai-auto-mix",kind:"feature",title:"AI Auto-Mix",summary:"Sets levels, EQ, and panning across the project to get a balanced starting mix.",route:"/studio",studioView:"generate",credits:"1 credit.",details:"Shows a preview of what it will change so you can accept or discard before it is applied.",tools:["runAIProcess","suggestMixMove","measureMix"],keywords:["auto mix","automix","balance","mix for me","levels","quick mix"],related:["ai-mastering","howto-master-track","ai-features-panel"]},{id:"ai-mastering",kind:"feature",title:"AI Mastering",summary:"Builds a genre-aware mastering chain on the master bus â compression, EQ, limiting.",route:"/studio",studioView:"generate",credits:"1 credit.",details:"Master before you master: leave roughly â6 dBFS of headroom on the mix so the chain has room. Reference Match is the related tool that shapes your master toward the spectral balance of a track y
79ou supply.",tools:["runAIProcess","addMasterEffect","measureMix"],keywords:["mastering","master","loudness","limiter","final polish","make it loud","reference match"],related:["howto-master-track","studio-master-section","concept-headroom-lufs"]},{id:"ai-voice-cloning",kind:"feature",title:"Voice cloning and speech synthesis",summary:"Clone a voice from a sample, then synthesize speech with it and drop the result on the timeline.",route:"/studio",studioDialog:"voiceCloning",uiPath:"Studio â AI Generate tab â Voice Cloning",steps:["Open the Voice Cloning dialog and either record a sample with your microphone or upload one.","Name the voice and create it (this is the one-off clone charge).","Type the text you want spoken and synthesize it.","Add the result to the timeline as a clip."],credits:"30 credits to clone a voice (once per voice), 6 per synthesis.",tools:["cloneVoice","listVoices","synthesizeVoice"],keywords:["voice clone","clone my voice","tts","text to speech","narration","vocal","speak","elevenlabs"],related:["ai-features-panel","credits-system","studio-recording"]},{id:"ai-model-manager",kind:"dialog",title:"AI model manager and training",summary:"Pick which generation model a project uses; the training panel accepts jobs but there is no fine-tuning backend yet.",route:"/studio",studioDialog:"aiModelManager",uiPath:"Studio â header â AI Models",details:"Be straight with users about this one: submitting a training job runs a timed progress simulation and produces no usable model, which is why it is free. Model selection for generation does work.",credits:"Model training: 0 credits (deliberately â it does not produce a model).",tools:["startTraining","listTrainingJobs","cancelTraining","openStudioDialog"],keywords:["train model","fine tune","custom model","model manager","training job","my own model"],related:["ai-generate-music","credits-system"]},{id:"ai-agent-copilot",kind:"feature",title:"What the AI agent can do for you",summary:"The agent both advises and operates the app: it can create projects, generate audio, edit every Studio surface, measure the mix, and undo what it did.",route:"/agent",details:"It has direct control of transport, tracks, clips, MIDI, plugins, sends, automation, the console, the session grid, tempo and markers, recording, export and interchange, and the Studio's own tabs and dialogs. It can also *measure* â offline-render a range and report peak, true peak, RMS, LUFS estimate, crest factor, DC offset, clipped samples, stereo correlation, width, and a band-by-band energy split â so a mix change can be verified in numbers rather than asserted. Studio tools only work while a project is open; ask it to open or create one first. Every action appears as a card with an Undo button, and it can remember preferences across sessions.",credits:"1 credit per agent chat turn; anything it triggers costs that action's own credits.",tools:["produceArrangement","measureMix","readMeters","queryProject","rememberFact","webSearch"],keywords:["agent","copilot","assistant","what can you do","capabilities","ai control","do it for me"],related:["page-agent","howto-use-agent","credits-system"]},{id:"ai-cover-art",kind:"feature",title:"Cover art on generated tracks",summary:"Every AI-generated track gets square album artwork composed from its own prompt, at no extra cost.",route:"/sounds",uiPath:"My Sounds â Generated (the artwork on each card)",details:`This happens on its own â there is nothing to turn on and nothing to ask for. When a track finishes generating, its prompt is turned into a square 1:1 image (abstract and cinematic, deliberately with no text, logos or watermarks) and attached to the generation, so it appears on the card in the Generated tab, behind the player bar, and on the track's public share page. A track whose art failed or was never made shows a placeholder instead â the track itself is unaffected. 80 81Not to be confused with **AI Cover**, which is an entirely different feature that rebuilds a song as a new project.`,credits:"None â the artwork is bundled with the generation that produced the track.",keywords:["cover art","album art","artwork","album cover","thumbnail","image","picture","no artwork","missing cover","broken thumbnail","track image","art for my track"],related:["ai-generate-music","page-my-sounds","app-public-links","ai-cover"]}],ft=[{id:"howto-first-track",kind:"howto",title:"Start a project and get sound on the timeline",summary:"Create a project, add a track, put audio or MIDI on it, and press play.",route:"/studio",steps:["From the Dashboard or the Studio header, create a project (Cmd+N) and name it.","Set the BPM and time signature in the transport bar before you record anything â changing them later moves everything around.","Add a track (Cmd+T): audio for recordings and samples, MIDI for parts you play in or draw.","Get material onto it â drag a file in, drag a sound from the media browser, record, or generate.","Press Space to play. Adjust level and pan on the track header.","Save with Cmd+S (autosave also runs; the header shows the save state)."],tools:["createProject","addTrack","setBPM","addClip","play","saveProject"],keywords:["get started","first project","new project","beginner","how do i start","basics","add track"],related:["page-studio","studio-templates","howto-import-audio","studio-transport"]},{id:"howto-import-audio",kind:"howto",title:"Get audio into a project",summary:"Drag a file onto the timeline, pull one from the media browser, or upload it to your library first.",route:"/studio",steps:["Drag an audio file from your desktop straight onto a track lane â it becomes a clip where you drop it.","Or open the media browser inside the Studio and pull a sound in from the Library tab, or a previous generation from the AI tab.",'Or, from My Sounds â Generated, hit "Add to Studio" on a generated track to open the Studio with it already on the timeline.',"Or upload to My Sounds first (useful when you want it saved and reusable), then browse to it in the Studio.","A video works too â drop a .mov or .mp4 and the audio is taken out of it, which is what you want for anything recorded on a phone."],details:"AI generations do not need to be saved to your library first â they live in their own list and the media browser's AI tab reads
81it directly. Server-side AI actions (stem separation, AI Cover) need audio that actually lives on the server â a file that was only dropped in locally has to be uploaded before those will run on it. Accepted: WAV, MP3, FLAC, OGG, AIFF, M4A, and video containers (.mov, .mp4, .m4v, .3gp) that we keep only the audio from.",tools:["addClip","listSounds","listGenerations","createClip"],keywords:["import","upload","add audio","drag and drop","load file","insert sample","bring in","phone","iphone","android","photos","camera roll","voice memo","video","mov","mp4","recorded on my phone"],related:["studio-media-browser","page-my-sounds","ai-stem-separation","trouble-phone-recording"]},{id:"howto-record-vocals",kind:"howto",title:"Record a vocal or instrument",summary:"Arm an audio track, pick the input, monitor, count in, record.",route:"/studio",steps:["Add an audio track and arm it (R). Its input meter goes live in the track header â set your gain so it stays out of the red.","Choose your input device (Editor â Audio Engine) and turn on input monitoring if you need to hear yourself; monitoring goes through the track's fader, effects and sends.","Set a count-in (one or two bars) and turn on the metronome.","Optionally set a punch region so recording only engages over the section you are fixing.","Press record: after the count-in the song plays from the playhead and the take starts on the same beat. Press record again to end the take while the song keeps playing, or stop to end both.","For multiple passes, set a loop and record â every pass over the loop becomes a take on the clip. Right-click the clip â Takes â Show take lanes, then drag across a lane to use that take for that stretch (a comp), or click a lane's name to use it everywhere.","If takes land slightly late, run Calibrate under Latency Compensation once with the microphone near the speakers; recordings are then moved earlier by the measured round trip."],tools:["addTrack","armTrack","setInputDevice","setInputMonitoring","setCountIn","startRecording","stopRecording"],keywords:["record vocals","record guitar","microphone","tracking","overdub","punch in","take"],related:["studio-recording","trouble-mic-not-working","studio-comping-takes","studio-cue-mixes"]},{id:"howto-add-effect",kind:"howto",title:"Add reverb, compression, or any effect to a track",summary:"Insert it on the track's FX slot in the Mix view, or send to a bus when several tracks should share one.",route:"/studio",studioView:"mixer",steps:["Go to the Mix tab and find the track's channel strip.","Add the effect to its insert rack â it loads with sensible defaults.","Adjust the real parameters (a compressor: threshold, ratio, attack, release, knee, makeup gain).","Use the wet/dry mix for parallel-style processing, and bypass to A/B the effect against nothing.","To share one reverb across several tracks, use the transport bar's Bus menu â Reverb Send: it creates the bus, puts a 100%-wet reverb on it, and wires a send from each selected track in one step. The same menu builds a parallel compressor, a delay throw, and a drum glue bus.","Building a send return by hand instead? Set the effect on the bus to 100% wet. The source track already reaches the master through its own fader, so a return that passes dry signal combs against it rather than adding depth."],details:"Insert effects are heard live during playback and are included in exports. Order matters â the rack processes top to bottom, so an EQ before a compressor is a different sound than after.",tools:["addPlugin","setPluginParam","setEffectEnabled","setEffectMix","reorderPlugins","createFxBus","createSend","setSendLevel","listEffectTypes"],keywords:["add reverb","add compressor","effect","plugin","insert","eq a track","delay","how do i add","reverb send","parallel compression","share a reverb"],related:["studio-effects-rack","studio-sends-buses","studio-channel-strip","howto-sidechain"]},{id:"howto-sidechain",kind:"howto",title:"Sidechain a compressor (ducking)",summary:"Put a compressor on the track that should duck, then point its sidechain input at the track that triggers it.",route:"/studio",studioView:"mixer",steps:["Add a compressor to the track you want to duck â usually the bass or a pad.",`Open that compressor's parameters and set the **Sidechain Source** dropdown to the trigger track, usually the kick. The list holds every track except the one you are on; "None (internal)" turns sidechaining off.`,"A compressor with a source set shows an **SC** badge on its card in the rack.","Use the **Key Listen** button next to the dropdown to audition the trigger in isolation while you set the threshold.","Dial the threshold down until the meter shows movement on each hit, then set the ratio for how hard it ducks.","Use a fast attack and set the release so the level recovers just before the next hit."],details:"The source is a dropdown on the plugin itself â there is no right-click menu for it. Kick-versus-bass is also worth attacking with EQ first: high-pass the bass below the kick's fundamental, or use a dynamic EQ band, and only then reach for the ducking.",tools:["addPlugin","setSidechainSource","setPluginParam","setDynamicEqBand","measureMix"],keywords:["sidechain","ducking","pumping","kick and bass","compressor trigger","side chain","key listen","fighting","masking","muddy low end","clashing"],related:["studio-effects-rack","howto-add-effect","concept-headroom-lufs","howto-master-track"]},{id:"howto-automate-volume",kind:"howto",title:"Automate a fader or plugin parameter over time",summary:"Open an automation lane for the parameter and draw points, or record your fader moves live.",route:"/studio",studioView:"arrangement",steps:["In the Arrange view, open the automation lane under the track and pick the parameter (volume, pan, a send, a plugin control).","Draw points along the lane â each point is a value at a time, and the line between them is the ride.","Or set the automation mode to write/touch/latch and move the control during playback to record the gesture.","Switch back to read mode so playback follows the lane instead of overwriting it.","A lane can be hidden, bypassed, or cleared without deleting the track."],tools:["addAutomationLane","writeAutomation","setAutomationMode","clearAutomation","setAutomationLaneState"],keywords:["automation","automate volume","fade out","ride the fader","filter sweep","envelope","over time"],related:["studio-automation","concept-automation-modes"]},{id:"howto-fades-crossfade",kind:"howto",title:"Fade a clip in or out, and crossfade between two",summary:"Set fade lengths on the clip itself; overlap two clips and fade one out as the other fades in.",route:"/studio",steps:["Select the clip and set its fade-in and fade-out lengths (a fade curve shape can be chosen too).","For a crossfade, overlap the two clips and give the outgoing one a fade-out and the incoming one a fade-in across the overlap.","A short fade (a few milliseconds) on a hard edit removes the click at the splice."],tools:["setClipFades","splitClip","moveClip"],keywords:["fade","fade in","fade out","crossfade","click at edit","pop","smooth transition"],related:["studio-clips","studio-view-editor"]},{id:"howto-quantize-midi",kind:"howto",title:"Quantize a performance to the grid",summary:"Select the region and quantize to a grid value at a strength â 100% is locked, less keeps some feel.",route:"/studio",studioView:"midi",steps:["Open the MIDI view and select the clip or note range.","Choose the grid (1/8, 1/16, triplets) and apply quantize.","Use a strength below 100% to pull the timing toward the grid without flattening the groove."],details:"Audio timing is a different job â that is warp markers and transients, not quantize.",tools:["quantizeRegion","
81setSnapGrid","setWarpMarkers","setTransients"],keywords:["quantize","timing","snap notes","grid","tighten","groove","off beat"],related:["studio-view-midi","concept-snap-grid","concept-timebase-warp"]},{id:"howto-change-tempo-key",kind:"howto",title:"Change the tempo or key of a project",summary:"BPM lives in the transport bar; key is a project setting; tempo changes mid-song go in the tempo map.",route:"/studio",steps:["Set BPM in the transport bar (type it, nudge it, or tap it in).","Set the project key so generation and chord tools stay in the right tonality.","For a tempo change partway through, add a tempo change at that time rather than editing the global BPM.","Clips set to a musical timebase follow tempo changes; clips on an absolute timebase stay put."],tools:["setBPM","setKey","addTempoChange","setTimeSignature","setTrackProperty"],keywords:["change tempo","bpm","key","transpose","speed up","slow down","time signature"],related:["studio-tempo-meter","concept-timebase-warp"]},{id:"howto-master-track",kind:"howto",title:"Mix and master a finished track",summary:"Balance first, leave headroom, then build a master chain â and measure before and after instead of guessing.",route:"/studio",steps:["Save a mixer scene or store an A snapshot first so the current balance is recallable.","Measure the mix to see where you actually are â peak, crest factor, stereo correlation, and the band-by-band energy split.","Fix gain staging before adding processing; aim for roughly â6 dBFS peaks going into the master.","Balance levels and panning, then carve masking with EQ where the band split shows two parts fighting.","Build the master chain on the master strip: compression, EQ, then a limiter last.","Re-measure the same range and compare the numbers; A/B against the stored snapshot."],details:"Warning signs to check: a very low crest factor means it is over-compressed, and negative stereo correlation means phase problems that will hurt in mono.",tools:["measureMix","storeABSnapshot","saveMixerScene","addMasterEffect","setMasterEffectParam","toggleAB"],keywords:["master","mastering","mix","loudness","limiter","polish","finish track","make it louder","lufs"],related:["studio-master-section","ai-mastering","concept-headroom-lufs","studio-mixer-scenes"]},{id:"howto-export-stems",kind:"howto",title:"Export stems (one file per track)",summary:"Open Export, set the type to Stems, pick the tracks, choose a format, render.",route:"/studio",studioDialog:"exportDialog",steps:["Open the Export dialog from the Studio header.","On the Format tab set the export type to Stems (individual tracks) â or Both, to get the mixdown as well.","On the Tracks tab, tick the tracks to include.","Pick the format and sample rate â WAV is the usual deliverable.","Export, then download the files."],details:`Exported stems include each track's own effects. "Stems" here means your project's tracks; splitting a *mixed* file into parts is stem separation, which is a different feature.`,tools:["exportProject","renderExport"],keywords:["export stems","stem export","individual tracks","send to mixer","deliverables","multitrack export"],related:["studio-export-dialog","ai-stem-separation","studio-interchange"]},{id:"howto-share-track",kind:"howto",title:"Share a project or a finished track",summary:"Publish it, then send the link â where you do that depends on whether it is a generated track or a whole project.",route:"/studio",steps:["For a single AI-generated track: open My Sounds â Generated, switch the card from Private to Public, and use Copy link. That link is /s/<id> and plays the track, its artwork and its prompt for anyone, signed in or not.","For a whole project: open Share from the Studio header, and on the link tab switch on public access and copy the URL. That one is /play/<id> and streams the mixed project.","To let someone actually work on it rather than listen, invite them by email on the Share dialog's invite tab and give them a role.","Turn public access back off at any time to revoke either link â the pages only load items that are still published."],details:"Publishing and inviting are independent: a public link is listen-only no matter who opens it, and a collaborator does not need the item to be public. Publishing a sound also lists it in Discover, which a share link on its own does not.",tools:["exportProject","setGenerationVisibility"],keywords:["share","send to
81someone","link","collaborate","invite","public","client review","copy link","share a track","send a demo","listen without an account"],related:["app-public-links","studio-collaboration","concept-visibility-sharing","howto-publish-discover"]},{id:"howto-publish-discover",kind:"howto",title:"Publish a sound to Discover",summary:"Sounds are private by default; set one to public and it appears in the public library.",route:"/sounds",steps:["Open My Sounds and find the sound (or set the choice while uploading).","Edit it and switch its visibility from private to public.","It then shows up in Discover and through the public API; switch it back to private to pull it."],details:"The upload dialog makes this an explicit choice rather than a default, because public means world-readable.",tools:["listSounds","setSoundVisibility"],keywords:["publish","make public","share sound","discover","private","visibility","unlist"],related:["concept-visibility-sharing","page-my-sounds","page-discover"]},{id:"howto-make-loop",kind:"howto",title:"Make a loop that plays in time",summary:"Generate or trim to an exact bar length, then set the clip to loop and match the project tempo.",route:"/generate",steps:["On the Generate page, choose Loop mode (up to 30 seconds) and describe the groove.","Bring it into the project and trim it to an exact bar boundary with snap on.","Turn on the clip's loop property so it repeats for as long as you drag it.","If it was generated at a different tempo, time-stretch it (or warp it) to the project BPM."],credits:`5 credits for prompt-to-loop; a Generate-page loop is billed by length (${g.generation} for the default clip).`,tools:["generateSound","setClipProperty","setClipTimeStretch","setSnapGrid"],keywords:["loop","looping","bar","repeat","seamless","beat loop","in time"],related:["page-generate","concept-snap-grid","concept-timebase-warp"]},{id:"howto-freeze-track",kind:"howto",title:"Free up CPU by freezing a track",summary:"Freezing renders a track with its effects to audio so the plugins stop running live.",route:"/studio",details:"Use it on finished tracks with heavy chains. Unfreeze to get the plugins back for editing. Bouncing a clip through its chain is the permanent version of the same idea.",tools:["freezeTrack","bounceClip"],keywords:["freeze","cpu","performance","slow","glitching","render track","bounce in place"],related:["studio-track-header","trouble-no-sound","studio-audio-engine-settings"]},{id:"howto-use-agent",kind:"howto",title:"Getting good results from the AI agent",summary:"Ask for the outcome, not the button â it will open the project, make the change, and show an Undo card.",route:"/agent",steps:['Open the agent (G then A) or the "Ask AI" sidebar on any page.',"For Studio work, have the project open â or just ask it to open one by name.",'Describe the result you want: "the vocal is buried in the chorus, fix it" beats "add a compressor".',"It will usually measure first, make the change, then re-measure and report the numbers.","Every action gets a card with an Undo button; ask it to store an A snapshot before a big pass if you want a fast revert."],credits:"1 credit per turn, plus whatever the actions themselves cost.",keywords:["agent","ask ai","prompt the agent","how to use","assistant tips","copilot"],related:["ai-agent-copilot","page-agent","studio-version-history"]}],yt=[{id:"concept-project-structure",kind:"concept",title:"Projects, tracks, and clips",summary:"A project holds tracks; a track holds clips; a clip points at audio or MIDI and carries its own edits.",details:"Project-level settings: name, BPM, time signature, key, length, master volume, master effects, snap grid, markers, and notes. A track carries its own volume, pan, mute, solo, EQ, insert effects, sends, and automation lanes. A clip has a position, a length, fades, gain, pitch and stretch settings, and either an audio reference or MIDI notes. Editing a clip never alters the underlying file â trims and fades are non-destructive until you bounce.",tools:["queryProject","exportProjectJSON","importProjectJSON","deleteProject"],keywords:["project","track","clip","structure","model","how it works","non destructive"],related:["studio-clips","studio-track-header","page-studio"]},{id:"concept-buses-groups-vca",kind:"concept",title:"Bus vs group vs VCA vs send",summary:"A bus sums audio, a folder organises lanes, a VCA links faders without carrying audio, a send taps a copy in parallel.",details:"Reach for a **bus** when several tracks should be processed together (all drums through one compressor). A **send** when several tracks should share one effect but keep their own dry signal (one reverb for the whole mix). A **VCA** when you want one fader to ride a group's levels while keeping their relative balance and their individual routing. A **folder** purely to tidy the arrangement.",tools:["createBusTrack","addVcaTrack","addFolderTrack","createSend","setTrackParent"],keywords:["bus","group","vca","send","aux","routing","difference between","submix"],related:["studio-sends-buses","howto-add-effect"]},{id:"concept-automation-modes",kind:"concept",title:"Automation modes: read, write, touch, latch",summary:"The mode decides whether moving a control plays back existing automation or records new points over it.",details:"**Read** plays the lane and ignores your moves. **Write** overwrites continuously while the transport rolls. **Touch** records only while you are holding the control and snaps back after. **Latch** starts recording when you touch and keeps writing until you stop. Leave things in read once the ride is written, or the next playback pass will over
81write it.",tools:["setAutomationMode"],keywords:["automation mode","read","write","touch","latch","overwriting automation"],related:["studio-automation","howto-automate-volume"]},{id:"concept-timebase-warp",kind:"concept",title:"Musical vs absolute timebase, and warping",summary:"A musical-timebase clip follows tempo changes; an absolute one stays at its wall-clock position.",details:"Set dialogue, sound effects, and anything locked to picture to an absolute timebase so a tempo change does not move it. Musical parts belong on the musical timebase. Warp markers pin points in a piece of audio to points in the timeline so the audio bends to the grid; transients mark the hits the warping and slicing work from.",tools:["setTrackProperty","setWarpMarkers","setTransients","setClipTimeStretch"],keywords:["timebase","warp","stretch","follows tempo","time lock","elastic audio","flex"],related:["studio-tempo-meter","howto-quantize-midi","studio-video-post"]},{id:"concept-snap-grid",kind:"concept",title:"Snap and the grid",summary:"Snap constrains drags to the current grid division so edits land on the beat.",details:"The grid value (1/4, 1/8, 1/16, tripletsâ¦) is shown in the status bar and set from the timeline toolbar. Turn snap off (S) for fine, off-grid placement, and back on for structural moves.",shortcuts:["S â snap toggle"],tools:["setSnapGrid"],keywords:["snap","grid","magnetic","quantize drag","on the beat","alignment"],related:["studio-timeline-toolbar","howto-quantize-midi"]},{id:"concept-headroom-lufs",kind:"concept",title:"Headroom, crest factor, LUFS, and phase",summary:"The numbers worth watching when mixing: peak level, crest factor, loudness, and stereo correlation.",details:"**Headroom** â keep pre-master peaks around â6 dBFS so the master chain has room to work. **Crest factor** (peak minus RMS) shows how much dynamic range survives; a very low crest is the tell for over-compression. **LUFS** is perceived loudness and is what streaming platforms normalize to. **Stereo correlation** near +1 is mono-compatible, near 0 is wide, and negative means phase cancellation that will hurt when the mix folds to mono. A band-by-band energy split shows which instruments are masking each other before you reach for EQ.",tools:["measureMix","readMeters","analyzeAudio"],keywords:["headroom","lufs","loudness","crest factor","phase","correlation","clipping","dbfs","metering","true peak"],related:["howto-master-track","studio-master-section","ai-mastering"]},{id:"concept-harmony-detection",kind:"concept",title:"Chord detection: reading the harmony of a track",summary:"detectHarmony returns the key plus the chord progression with timings and roman numerals.",details:"Harmony analysis sums a track to mono, takes a chroma (12 pitch-class) profile per beat, and matches each frame against chord templates that include the overtone series â without that, a plain C major triad reads as Em7, because the third harmonic of each note really is present in the audio. Chords are smoothed over neighbouring frames so a passing tone cannot invent a chord, and the key is estimated from the detected chord tones (Krumhansl correlation, weighted toward where the phrase starts and lands) rather than from the raw spectrum. MIDI tracks are rendered through their instrument first, so the answer describes what is heard. Percussive, monophonic or near-silent material returns nothing rather than a guess.",tools:["detectHarmony","analyzeAudio"],keywords:["chords","chord chart","harmony","progression","what chords","roman numerals","key detection","harmonic analysis"],related:["howto-quantize-midi","concept-headroom-lufs"]},{id:"concept-visibility-sharing",kind:"concept",title:"Private by default: who can see your audio",summary:"Sounds and projects are private unless you explicitly publish them; public means world-readable.",details:"A sound set to public appears in Discover and through the public API. A project with public access on can be listened to by anyone holding the link, but not edited unless they are invited as a collaborator. Both are reversible â switch back to private and the public route closes.",tools:["setSoundVisibility","setGenerationVisibility"],keywords:["private","public","visibility","who can see","permissions","security","share","unlisted"],related:["howto-publish-discover","howto-s
81hare-track","studio-collaboration"]},{id:"concept-undo-model",kind:"concept",title:"Undo: the project stack vs agent action cards",summary:"Two separate undo paths â the Cmd+Z edit stack for your own edits, and per-action Undo buttons for agent actions.",details:"The project edit history is the normal stack, and the history panel can jump several steps at once. Actions the agent takes each get their own card with an Undo button; that undo is live for the session, so after a page reload use the project history (or a mixer scene / A snapshot) instead.",shortcuts:["global.undo","global.redo"],tools:["undoEdit","redoEdit","listEditHistory","jumpToHistory"],keywords:["undo","redo","revert","ctrl z","mistake","history","restore"],related:["studio-version-history","ai-agent-copilot","studio-mixer-scenes"]}],kt=[{id:"trouble-no-sound",kind:"troubleshooting",title:"I press play and hear nothing",summary:"Usually a muted or soloed track, a zeroed fader, the playhead past the end, or the browser audio context not started.",steps:["Check whether another track is soloed â solo anywhere mutes everything else.","Check the track is not muted and its fader is not at zero.","Check the master fader and that the playhead is actually over a clip.","Browsers only start audio after a user gesture: click play once on the page before expecting sound (this is also why activating a marketplace plugin can report the engine is not ready).","Check the output device in the audio settings, and that the system volume is up."],tools:["readMeters","queryProject","muteTrack","soloTrack"],keywords:["no sound","silent","cant hear","no audio","nothing plays","muted","not working"],related:["studio-audio-engine-settings","studio-track-header","trouble-export-silent"]},{id:"trouble-mic-not-working",kind:"troubleshooting",title:"My microphone is not picking up",summary:"Check browser permission, the selected input device, that the track is armed, and that monitoring is on.",steps:["Grant the browser microphone permission â the prompt only appears once, so check the site settings if you dismissed it.","Select the right input device in the audio settings.","Arm the track you are recording onto (R) â an unarmed track records nothing.","Turn on input monitoring if you want to hear yourself while recording.","If you hear yourself doubled or echoing, turn monitoring off in one place â either the app or your interface, not both."],tools:["setInputDevice","armTrack","setInputMonitoring"],keywords:["microphone","mic not working","no input","cant record","permission","input device","monitoring"],related:["studio-recording","howto-record-vocals","studio-audio-engine-settings"]},{id:"trouble-export-silent",kind:"troubleshooting",title:"My export is silent, short, or missing tracks",summary:"Check the export type and track selection, solo/mute state, and the project length.",steps:["If tracks are missing, check the Tracks tab â a stems export only renders the tracks that are ticked.","Solo and mute are respected by the render, so an accidental solo will export one track.","The render covers the project length; if that is shorter than your material, extend it.","If the file came out as WAV when you asked for another format, the encoder fell back â the result is flagged as a fallback, and the audio itself is fine."],tools:["exportProject","renderExport","setProjectLength","measureMix"],keywords:["export silent","export empty","missing tracks","export wrong","bounce problem","short export"],related:["studio-export-dialog","howto-export-stems","trouble-no-sound"]},{id:"trouble-generation-failed",kind:"troubleshooting",title:"Generation failed or was rejected",summary:"Most rejections are the safety filter reacting to artist names, song titles, or explicit content in the prompt.",steps:["Strip proper nouns â artist names, band names, and song titles â from the prompt.","Describe the sound instead: genre, instrumentation, tempo, mood, era.","Retry; a failed generation is not charged.","If it fails again without a safety message, shorten the prompt and try a shorter duration."],keywords:["generation failed","rejected","safety","copyright","error generating","blocked prompt","wont generate"],related:["ai-generate-music","page-generate","credits-system"]},{id:"trouble-credits",kind:"troubleshooting",title:"My credit balance looks wrong",summary:"Check Settings â Credits for the ledger; charges land on success, and failed or demo results are not billed.",steps:["Open Settings â Credits and compare the balance against the usage chart and receipts.","Remember that only successful actions are charged â a failed generation should not have cost anything.","If a purchase has not landed, the payment webhook may still be in flight; receipts on the same tab show what was recorded.","A spend cap set on that tab can also block an action that would otherwise run."],tools:["showCredits"],keywords:["credits missing","balance wrong","charged twice","not credited","purchase","refund","spend cap"],related:["credits-system","buy-credits","page-settings"]},{id:"trouble-performance",kind:"troubleshooting",title:"Playback stutters, crackles, or the tab gets slow",summary:"Raise the audio buffer, freeze finished tracks, and close other heavy tabs.",steps:["Raise the buffer size in the audio settings â a bigger buffer trades latency for stability.","Freeze tracks whose plugin chains are done; that stops them processing live.","Bounce clips through their chains where the processing is settled.","Reduce the number of simultaneously running effects, especially convolution reverbs."],tools:["setAudioSettings","freezeTrack","bounceClip"],keywords:["crackle","stutter","glitch","dropout","slow","cpu","lag","performance","freezing"],related:["studio-audio-engine-settings","howto-freeze-track"]},{id:"trouble-ai-needs-hosted-audio",kind:"troubleshooting",title:"An AI action says it cannot read my audio",summary:"Server-side AI runs on hosted files â audio that only exists locally in the browser has to be uploaded first.",details:"Stem separation, AI Cover, and similar pipelines fetch the audio server-side, so a local blob or data URL cannot be processed. Save the sound to your library (or let the upload finish) and run the action against that.",tools:["listSounds","
81separateStems"],keywords:["cannot read audio","blob url","invalid audio","upload first","ai error","no audio url"],related:["ai-stem-separation","howto-import-audio","ai-cover"]},{id:"trouble-phone-recording",kind:"troubleshooting",title:"My recording from my phone will not upload",summary:"A clip recorded in the Camera or Photos app is a video â we take the audio out of it, and there is a simpler route next time.",steps:["Upload the .mov or .mp4 as it is. The audio track is taken out of it in the browser and the video is discarded â the picker, the timeline drop and the agent attachments all accept video for this.","If nothing appears in the file dialog at all, the file is probably in the Photos app rather than Files. On iPhone, open the clip in Photos, Share â Save to Files, and pick it from there.",`If it is refused with "no audio track, or this browser may not decode that video's format", the browser will not read that codec. Record with Voice Memos instead, which gives you an audio file directly, or export the audio on the device and upload that.`,"Videos over 500MB are refused before they are read â a phone can fill that in a minute or two of 4K. Trim the clip, or export just its audio."],details:"The audio comes out as a 16-bit WAV, which is uncompressed: a long video can decode to something over the 50MB upload limit even though the video itself was well under the video limit. If that happens, trim it or export a compressed audio file on the device.",tools:["listSounds"],keywords:["phone recording","voice memo","iphone","android","mov","mp4","video","camera roll","photos app","cant upload","file not showing","wont upload","record on phone","get song off my phone"],related:["howto-import-audio","trouble-ai-needs-hosted-audio","page-my-sounds"]},{id:"trouble-lost-work",kind:"troubleshooting",title:"I lost changes / want to go back",summary:"Try undo history first, then a mixer scene or A snapshot, then version history or a branch.",steps:["Cmd+Z steps back; the history panel can jump several edits at once.","If it was a mix change, recall a saved mixer scene or toggle back to the A snapshot.","Version history can restore an earlier saved state of the project.","Branches keep parallel versions, so experiments do not have to overwrite the safe take."],shortcuts:["global.undo","global.redo"],tools:["undoEdit","jumpToHistory","recallMixerScene","toggleAB","switchBranch"],keywords:["lost work","undo","restore","went wrong","recover","previous version","revert"],related:["studio-version-history","concept-undo-model","studio-mixer-scenes"]}],wt=new Set(["a","an","the","i","my","me","you","it","is","are","was","be","do","does","did","how","what","where","when","why","which","who","can","could","should","would","to","of","in","on","at","for","with","from","and","or","but","if","this","that","these","those","there","here","get","got","use","using","want","need","please","help","about","into","out","up","down","now","not","no"]),bt={title:8,keywords:6,id:4,summary:3,uiPath:3,steps:1.5,details:1};function A(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(t=>t.length>1&&!wt.has(t))}function vt(e,t){let r=0;for(const a of t){if(a===e)return 1;a.length>=4&&e.length>=4&&(a.startsWith(e)||e.startsWith(a))&&(r=Math.max(r,.6))}return r}function St(e){const t={title:A(e.title),keywords:A((e.keywords??[]).join(" ")),id:A(e.id.replace(/-/g," ")),summary:A(e.summary),uiPath:A(e.uiPath??""),steps:A((e.steps??[]).join(" ")),details:A(e.details??"")},r=[e.title,e.summary,e.uiPath??"",(e.keywords??[]).join(" ")].join(" ").toLowerCase();return{node:e,fields:t,haystack:r}}let R=null;function At(e){return(!R||R.length!==e.length)&&(R=e.map(St)),R}function It(e,t,r={}){const a=A(t);if(!a.length)return[];const o=t.trim().toLowerCase(),i=r.limit??5,n=r.minScore??1.5,s=[];for(const d of At(e)){if(r.kinds?.length&&!r.kinds.includes(d.node.kind))continue;let c=0,l=0;for(const f of a){let w=0;for(const[k,y]of Object.entries(bt)){const u=vt(f,d.fields[k]);u>0&&(w=Math.max(w,u*y))}w>0&&(l+=1),c+=w}l&&(o.length>4&&d.haystack.includes(o)&&(c+=12),c*=l/a.length,c>=n&&s.push({node:d.node,score:Math.round(c*100)/100}))}return s.sort((d,c)=>c.score-d.score||d.node.id.localeCompare(c.node.id)).slice(0,i)}const D=[...pt,...mt,...gt,...ft,...yt,...kt],ae=new Map(D.map(e=>[e.id,e]));function Tt(e){return ae.get(e)}function oe(e,t){return It(D,e,t)}function Pt(e){return D.filter(t=>t.related?.includes(e))}
81function Mt(e){const t=new Set([e.id]),r=[];for(const a of e.related??[]){const o=ae.get(a);o&&!t.has(a)&&(t.add(a),r.push(o))}for(const a of Pt(e.id))t.has(a.id)||(t.add(a.id),r.push(a));return r}const xt=new Map(le.map(e=>[e.id,e]));function Ct(e){if(!e.includes("."))return e;const t=xt.get(e);return t?`${t.keys.join(t.keys.length>1&&!t.keys[0].includes("+")?" then ":" / ")} â ${t.name}`:e}function re(e){const t=[];return e.uiPath?t.push(e.uiPath):e.route&&t.push(e.route),e.route&&e.uiPath&&!e.uiPath.startsWith(e.route)&&t.push(`(${e.route})`),t.length?t.join(" "):null}function jt(e){const t=re(e);return`- **${e.title}** \`${e.id}\` (${e.kind})${t?` â ${t}`:""} 82 ${e.summary}`}function ie(e,t={}){const r=[`## ${e.title} \`${e.id}\``,e.summary],a=re(e);a&&r.push(`**Where:** ${a}`);const o=[];if(e.requiresAuth&&o.push("requires sign-in"),e.requiresProject&&o.push("requires an open project"),o.length&&r.push(`**Requires:** ${o.join("; ")}`),e.steps?.length&&(r.push("**Steps:**"),e.steps.forEach((i,n)=>r.push(`${n+1}. ${i}`))),e.details&&r.push(e.details),e.credits&&r.push(`**Credits:** ${e.credits}`),e.shortcuts?.length&&r.push(`**Shortcuts:** ${e.shortcuts.map(Ct).join(" · ")}`),e.tools?.length&&r.push(`**I can do this with:** ${e.tools.join(", ")}`),t.withNeighbours){const i=Mt(e);i.length&&r.push(`**Related topics:** ${i.map(n=>`${n.title} (\`${n.id}\`)`).join(" · ")}`)}else e.related?.length&&r.push(`**Related topics:** ${e.related.join(", ")}`);return r.join(` 83`)}function $t(e,t){if(!t.length)return`No help topic matched "${e}". Try different words, or call listHelpTopics to see what the knowledge base covers.`;const[r,...a]=t,o=[ie(r.node)];return a.length&&(o.push(` 84### Other matches`),o.push(a.map(i=>jt(i.node)).join(` 85`)),o.push(` 86Call getHelpTopic with one of those ids for the full entry.`)),o.join(` 87`)}const Et=["area","page","panel","dialog","feature","howto","concept","troubleshooting","reference"];function Dt(e,t="summary"){const r=new Map;for(const o of e)r.has(o.kind)||r.set(o.kind,[]),r.get(o.kind).push(o);const a=[];for(const o of Et){const i=r.get(o);if(!i?.length)continue;const n=t==="id"?i.map(s=>`\`${s.id}\``).join(", "):i.map(s=>`- \`${s.id}\` â ${s.title}${t==="summary"?`: ${s.summary}`:""}`).join(` 88`);a.push(`### ${o} 89${n}`)}return a.join(` 90 91`)}function Rt(e,t=3400){const r=["summary","title","id"];for(const a of r){const o=Dt(e,a);if(o.length<=t||a==="id"){const i=a==="summary"?"":` 92 93(${e.length} topics â ${a==="title"?"titles":"ids"} only to fit; call getHelpTopic for any of them.)`;return o.length<=t?o+i:`${o.slice(0,t)}â¦${i}`}}return""}const _t=new Set(["searchHelp","getHelpTopic","listHelpTopics"]),qt=async e=>{if(!_t.has(e.type))return null;switch(e.type){case"searchHelp":return Lt(e);case"getHelpTopic":return Gt(e);case"listHelpTopics":return zt(e);default:return null}};function Lt(e){const t=(e.query??"").trim();if(!t)return{ok:!1,message:"query is required"};const r=oe(t,{limit:e.limit??4,kinds:e.topicKind?[e.topicKind]:void 0});return{ok:!0,message:$t(t,r)}}function Gt(e){const t=(e.topicId??"").trim();if(!t)return{ok:!1,message:"topicId is required"};const r=Tt(t);if(!r){const a=oe(t.replace(/-/g," "),{limit:3}),o=a.length?` Closest topics: ${a.map(i=>i.node.id).join(", ")}.`:" Call listHelpTopics to see the available ids.";return{ok:!1,message:`No help topic with id "${t}".${o}`}}return{ok:!0,message:ie(r,{withNeighbours:!0})}}function zt(e){const t=e.topicKind,r=t?D.filter(a=>a.kind===t):D;return r.length?{ok:!0,message:Rt(r)}:{ok:!1,message:`No topics of kind "${t}".`}}const Ut=/^(access_token|refresh_token|id_token|token|apikey|api_key|key|secret|password|passwd|pwd|signature|sig|code|credential|authorization|session)$/i,Bt=/eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*/g,Ft=/(bearer\s+)[A-Za-z0-9._~+/-]{8,}=*/gi,Vt=/((?:^|[?&#;\s"'])(?:access_token|refresh_token|id_token|token|apikey|api_key|key|secret|password|passwd|pwd|signature|sig|code|credential|authorization|session)\s*[=:]\s*)([^&\s"'#,}]+)/gi;function O(e){return e&&e.replace(Bt,"[redacted-jwt]").replace(Ft,"$1[redacted]").replace(Vt,"$1[redacted]")}function Nt(e){const[t,r]=e.split("?");if(!r)return O(t);const a=new URLSearchParams(r);for(const i of[...a.keys()])Ut.test(i)&&a.set(i,"[redacted]");const o=a.toString();return O(o?`${t}?${decodeURIComponent(o)}`:t)}const Ot=700;function Wt(e){const t={};return e.route&&(t.route=Nt(e.route).slice(0,300)),e.projectId&&(t.projectId=String(e.projectId).slice(0,64)),e.viewport&&(t.viewport=e.viewport),e.userAgent&&(t.userAgent=e.userAgent.
93slice(0,300)),e.language&&(t.language=e.language),typeof e.online=="boolean"&&(t.online=e.online),e.mode&&(t.mode=e.mode),(e.errors??[]).forEach((r,a)=>{const o=`[${r.source} ${r.at}] ${r.message}${r.stack?` 94${r.stack}`:""}`;t[`recentError${a+1}`]=O(o).slice(0,Ot)}),t}function Ht(){if(typeof window>"u")return{};try{const e=window.location.search;return Wt({route:`${window.location.pathname}${e}`,projectId:new URLSearchParams(e).get("project"),viewport:`${window.innerWidth}Ã${window.innerHeight}`,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,language:typeof navigator<"u"?navigator.language:void 0,online:typeof navigator<"u"?navigator.onLine:void 0,mode:"production",errors:ce(2)})}catch{return{}}}const P=new Map,Kt=5*6e4;function W(e,t){return`${e}|${t.trim().toLowerCase().replace(/\s+/g," ")}`}function Qt(e,t,r=Date.now()){const a=P.get(W(e,t));return a?r-a.at>Kt?(P.delete(W(e,t)),null):a:null}function Yt(e,t,r,a=Date.now()){if(P.set(W(e,t),{id:r,at:a}),P.size>50){const o=[...P.entries()].sort((i,n)=>i[1].at-n[1].at)[0];o&&P.delete(o[0])}}const Jt=new Set(["reportIssue","listMyReports"]),Zt=async(e,t)=>{if(!Jt.has(e.type))return null;switch(e.type){case"reportIssue":return ia(e,t);case"listMyReports":return sa(e,t);default:return null}},_={bug:"Bug report",feature:"Feature request",other:"Feedback"},Xt=new Set(["bug","feature","other"]),ea=new Set(["low","medium","high","critical"]),ta=/^[^\s@]+@[^\s@.]+\.[^\s@]+$/;function aa(e){const t=String(e??"").trim().toLowerCase();return Xt.has(t)?t:"other"}function oa(e){const t=String(e??"").trim().toLowerCase();return ea.has(t)?t:null}async function ra(e){let t=h(e,"Could not reach the feedback service");const r=e?.context,a=r&&typeof r.status=="number"?r.status:null;if(r&&typeof r.clone=="function")try{const o=await r.clone().json();o?.error&&(t=String(o.error))}catch{}return{status:a,message:t}}function V(e){const t=e.trim();return/[.!?]$/.test(t)?t:`${t}.`}function X(){return typeof navigator<"u"&&navigator.onLine===!1?" You appear to be offline â reconnect and ask me again.":""}async function ia(e,t){const r=aa(e.reportKind),a=oa(e.severity),o=(e.title??"").trim().replace(/\s+/g," ").slice(0,200),i=(e.details??"").trim();if(o.length<3)return{ok:!1,message:"title is required (at least 3 characters)"};if(i.length<10)return{ok:!1,message:"details must describe the problem in at least 10 characters"};const n=Qt(r,o);if(n)return{ok:!0,message:`Already filed that ${_[r].toLowerCase()} a moment ago${n.id?` (ref ${n.id.slice(0,8)})`:""} â not sending it twice. Tell me what to add and I can file a follow-up.`,output:{reportId:n.id,kind:r,title:o,duplicate:!0}};const s=(e.email??"").trim().toLowerCase(),d=s&&ta.test(s)?s.slice(0,255):null,c=!!s&&!d,l=Ht(),f=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():null,w={kind:r,title:o,body:i.slice(0,8e3),stepsToReproduce:e.stepsToReproduce?.trim().slice(0,4e3)||void 0,severity:a??void 0,email:d??void 0,clientReportId:f??void 0,context:l};let k=null,y=!1,u=null;try{const{data:S,error:$}=await H.functions.invoke("submit-feedback",{body:w});if($)throw $;if(S?.error)throw new Error(S.error);k=S?.reportId??null,y=!!S?.emailed}catch(S){u=await ra(S)}if(u&&u.status!==null&&u.status>=400&&u.status<500)return{ok:!1,message:`Couldn't file that report â ${V(u.message)} Nothing was sent.`};if(u&&t.user)try{const{data:S,error:$}=await m.from("feedback_reports").insert({...f?{id:f}:null,user_id:t.user.id,kind:r,title:o,body:i.slice(0,8e3),steps_to_reproduce:e.stepsToReproduce?.trim().slice(0,4e3)??null,severity:a,contact_email:d??t.user.email??null,context:l}).select("id").single();if($)throw $;k=S?.id??null,u=null}catch(S){if(S?.code==="23505")k=f,u=null;else return{ok:!1,message:`Couldn't file that ${_[r].toLowerCase()} â ${V(h(S,"the report was not saved"))} Nothing was sent. You can send it through the Contact page (/contact) instead.${X()}`}}if(u)return{ok:!1,message:`Couldn't file that report â ${V(u.message)} Nothing was sent. Try again, or use the Contact page (/contact).${X()}`};Yt(r,o,k);const b=k?` (ref ${k.slice(0,8)})`:"",j=y?"It has been sent to the SoundGen team":"It has been recorded for the SoundGen team",Q=d||t.user?"":" Add an email address if you want a reply â the report is anonymous otherwise.",
94U=c?` (I left the reply address off â "${s.slice(0,40)}" isn't a valid email.)`:"";return{ok:!0,message:`${_[r]} filed: "${o}"${b}. ${j}.${U}${Q}`,output:{reportId:k,kind:r,title:o,emailed:y,contextAttached:Object.keys(l)}}}async function sa(e,t){const r=p(t);if(r)return r;const a=Number(e.limit),o=Number.isFinite(a)?Math.min(Math.max(Math.trunc(a),1),50):10;try{const{data:i,error:n}=await m.from("feedback_reports").select("id,kind,title,severity,status,created_at").eq("user_id",t.user.id).order("created_at",{ascending:!1}).limit(o);if(n)throw n;const s=i||[];return s.length?{ok:!0,message:`Your reports: 95${s.map(c=>{const l=(c.title??"").length>80?`${c.title.slice(0,80)}â¦`:c.title,f=Number.isNaN(Date.parse(c.created_at))?c.created_at:new Date(c.created_at).toLocaleDateString();return`⢠[${c.status}] ${_[c.kind]??c.kind}: ${l}${c.severity?` (${c.severity})`:""} â ${f} â ${c.id.slice(0,8)}`}).join(` 96`)}`}:{ok:!0,message:"You haven't filed any bug reports or feature requests yet."}}catch(i){return{ok:!1,message:h(i,"Failed to read your reports")}}}const na=[qt,Fe,te,Ke,Xe,Zt,rt];async function La(e,t){for(const r of na){const a=await r(e,t);if(a)return a}if(e.type==="analyzeAudio"){const r=J(e.audioUrl)?e.audioUrl:J(e.clipId)?e.clipId:null;return r?pe(r):{ok:!1,message:"analyzeAudio needs audio it can fetch. Pass `audioUrl` with the attachment or generation URL (that works with no project open). A `clipId` only resolves once a Studio project holding that clip is open."}}return ue.has(e.type)?{ok:!1,message:`"${e.type}" needs an open Studio project. Call createProject (or openProject) first, then retry ${e.type} â do not ask the user to do it manually.`}:{ok:!1,message:`Unknown action: ${e.type}`}}export{La as runGlobalCommand};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.