16 KiB
TECHNICAL SPECIFICATION: AUTOMATED DATA TRANSFER FROM MIDI PROMPT GENERATOR TO AI PRESET MANAGER
This document details the software implementation plan to automatically extract calculated parameters (Bars, BPM, Category, Keywords, Prompt Content) and auto-populate the AI Prompt Preset Manager form whenever a user clicks the "Save as Preset" button on the MIDI AI Prompt Generator tool, adhering strictly to the designated interface layout.
1. TECHNICAL GOALS
-
Zero Re-typing: Eliminates manual copy-pasting of generated prompt text or re-entering parameters such as Bars, Role, and Category.
-
Smart Data Mapping:
-
Auto-suggests a Preset Name based on Section Type and Bar Count.
-
Auto-extracts Trigger Keywords from Role, Section Type, and Action Descriptions.
-
Auto-calculates Default Bars using the formula
(\text{EndBar} - \text{StartBar} + 1). -
Overwrites the complete generated prompt text directly into the SYSTEM PROMPT TEMPLATE area.
-
Structured Preset Generation ("Create Structured Preset" Green Button): Enables converting static prompt text into a structured variable template containing dynamic placeholders (e.g.,
{{start_bar}},{{end_bar}},{{total_beats}}).
2. DATA MAPPING MATRIX
Upon clicking "Save as Preset", the system reads the active State from the Prompt Generator and maps it to the Modal fields:
| AI Prompt Preset Manager Field | Source Data from Prompt Generator | Processing Rules / Logic | Example Auto-Populated Data |
|---|---|---|---|
| PRESET NAME | inputSectionType + barCount |
${sectionType} (${barCount} Bars) |
"Dramatic Build-Up (8 Bars)" |
| CATEGORY | inputRole |
Evaluates role keywords to assign category (Piano, Orchestral, EDM, Cinematic, etc.) | "Piano Solo" or "Orchestral / Film Score" |
| TRIGGER KEYWORDS | sectionType + role + trackLabels |
Extracts key nouns, formatted as a comma-separated string | "build-up, dramatic, piano solo, 8 bars" |
| DEFAULT BARS | inputStartBar & inputEndBar |
\max(1, (\text{EndBar} - \text{StartBar} + 1)) |
8 |
| DEFAULT BPM | Form / State Context | Inherits active project BPM or defaults to 120 | 120 |
| DEFAULT SCALE | Form / State Context | Inherits active project key signature or defaults to C Minor | "C Minor" |
| SYSTEM PROMPT TEMPLATE | promptOutputText.innerText |
Extracts the compiled output text directly from the Live Preview component | (Full generated prompt text) |
3. DATA FLOW LIFECYCLE
[ USER GENERATES PROMPT ON GENERATOR FORM ]
│
▼
[ CLICK BUTTON: "Save as Preset" ]
│
├─► 1. Call `computeTimeMetrics()` (Extract StartBar, EndBar, TotalBeats)
├─► 2. Extract `generatePromptText()`
├─► 3. Invoke `autoFillPresetModal(stateData)`
│
▼
[ POPUP MODAL: "AI PROMPT PRESET MANAGER" OPENS ]
- Preset Name <-- Auto-populated
- Category <-- Auto-selected via dropdown
- Trigger Keywords <-- Auto-generated tag string
- Bars / BPM <-- Auto-populated numbers
- System Prompt <-- Full prompt text inserted
│
├──► [ CLICK "Create Structured Preset" (Green Button) ]
│ └─► Converts static text to template variable placeholders (`{{total_beats}}`)
│
└──► [ CLICK "Save Preset" (Purple Button) ]
└─► Saves JSON object to `localStorage('daw_ai_prompt_presets')` & closes Modal
4. STEP-BY-STEP IMPLEMENTATION PLAN
Step 1: Update Modal UI
Position the green "Create Structured Preset" button at the bottom-left of the modal action bar (adjacent to the Back, Save Preset, and Close button group).
Step 2: Build autoFillPresetModal() Function
Implement a JavaScript function responsible for parsing active form control values and injecting them into the target modal input elements when triggered.
Step 3: Implement "Create Structured Preset" Logic (Green Button Feature)
Program the handler to parse static numeric values within the prompt text (e.g., 8 bars, beat 64.0, Bars 9 to 16) and replace them with dynamic template placeholders ({{bars}}, {{total_beats}}, {{start_bar}}, {{end_bar}}). This allows saved presets to adapt dynamically to variable bar lengths when reused.
Step 4: LocalStorage & Event Bus Integration
Save the compiled preset object to localStorage under the key daw_ai_prompt_presets and dispatch a PRESET_CREATED CustomEvent so that the AI Copilot Chat Bar updates its preset options instantly without requiring a page refresh.
5. CODE IMPLEMENTATION SNIPPETS
5.1 Modal HTML Markup
<!-- MODAL: AI PROMPT PRESET MANAGER -->
<div id="presetManagerModal" class="fixed inset-0 bg-black/80 backdrop-blur-md z-50 flex items-center justify-center hidden p-4">
<div class="w-full max-w-3xl bg-[#13141a] border border-slate-800 rounded-2xl shadow-2xl p-6 space-y-4 text-slate-200 relative overflow-hidden">
<!-- Header -->
<div class="flex items-center justify-between border-b border-slate-800/80 pb-3">
<h2 class="text-sm font-bold tracking-widest text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-pink-400 to-indigo-400 font-mono uppercase">
AI PROMPT PRESET MANAGER
</h2>
<button id="btnCloseModalX" class="w-7 h-7 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white flex items-center justify-center transition-colors">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<!-- Section Sub-Title -->
<div class="text-xs font-bold text-slate-300 uppercase tracking-wider font-mono flex items-center gap-2">
<i class="fa-solid fa-wand-magic-sparkles text-purple-400"></i> CREATE NEW PRESET
</div>
<!-- Form Input Grid -->
<div class="space-y-3 text-xs">
<!-- Row 1: Name & Category -->
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">PRESET NAME</label>
<input type="text" id="presetNameInput" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-purple-500 font-sans" placeholder="Enter preset name...">
</div>
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">CATEGORY</label>
<select id="presetCategorySelect" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-purple-500 font-sans">
<option value="Orchestral / Film Score">Orchestral / Film Score</option>
<option value="Piano Solo">Piano Solo</option>
<option value="EDM / Pop">EDM / Pop</option>
<option value="Cinematic">Cinematic</option>
<option value="Jazz & Swing">Jazz & Swing</option>
<option value="Lo-fi Chillhop">Lo-fi Chillhop</option>
</select>
</div>
</div>
<!-- Row 2: Trigger Keywords -->
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">TRIGGER KEYWORDS (COMMA-SEPARATED)</label>
<input type="text" id="presetKeywordsInput" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-purple-500 font-sans" placeholder="e.g. epic orchestra, dramatic, film score">
</div>
<!-- Row 3: Bars, BPM, Scale -->
<div class="grid grid-cols-3 gap-3">
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">DEFAULT BARS</label>
<input type="number" id="presetBarsInput" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-purple-500">
</div>
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">DEFAULT BPM</label>
<input type="number" id="presetBpmInput" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-purple-500">
</div>
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">DEFAULT SCALE</label>
<input type="text" id="presetScaleInput" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-purple-500">
</div>
</div>
<!-- Row 4: Template Textarea -->
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">SYSTEM PROMPT TEMPLATE</label>
<textarea id="presetTemplateTextarea" rows="6" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg p-3 text-white font-mono text-xs focus:outline-none focus:border-purple-500 leading-relaxed custom-scrollbar"></textarea>
</div>
</div>
<!-- Bottom Action Row -->
<div class="space-y-3 pt-2 border-t border-slate-800/80">
<div class="flex items-center justify-between">
<!-- Green Button: Create Structured Preset -->
<button id="btnCreateStructuredPreset" class="px-4 py-2 bg-emerald-700 hover:bg-emerald-600 text-white rounded-lg text-xs font-bold transition-all shadow-md flex items-center gap-1.5">
Create Structured Preset
</button>
<!-- Right Button Group: Back + Save Preset -->
<div class="flex items-center gap-2">
<button id="btnModalBack" class="px-4 py-2 bg-[#252836] hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-semibold transition-colors">
Back
</button>
<button id="btnSavePresetSubmit" class="px-5 py-2 bg-purple-600 hover:bg-purple-500 text-white rounded-lg text-xs font-bold transition-all shadow-lg shadow-purple-950/80 flex items-center gap-1.5">
Save Preset
</button>
</div>
</div>
<!-- Bottom Close Button -->
<div class="flex justify-end">
<button id="btnCloseModalBottom" class="px-4 py-1.5 bg-[#1f222e] hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-semibold transition-colors">
Close
</button>
</div>
</div>
</div>
</div>
5.2 Auto-Populate Logic (autoFillPresetModal)
// Function: Auto-populate Modal fields when "Save as Preset" is clicked
function handleOpenSavePresetModal() {
// 1. Calculate time metrics from the Form Engine
const timeData = computeTimeMetrics(); // returns { barCount, totalBeats, start, end }
const currentGeneratedPrompt = promptOutputText.innerText.trim();
// 2. Read values from form controls
const sectionType = inputSectionType.value.trim() || "Building Section";
const role = inputRole.value.trim() || "Composer";
const actionDesc = inputActionDesc.value.trim();
// 3. Auto-infer Category based on Role or Section
let category = "Orchestral / Film Score";
const roleLower = role.toLowerCase();
if (roleLower.includes("piano")) category = "Piano Solo";
else if (roleLower.includes("edm") || roleLower.includes("electro")) category = "EDM / Pop";
else if (roleLower.includes("jazz")) category = "Jazz & Swing";
else if (roleLower.includes("lo-fi") || roleLower.includes("chillhop")) category = "Lo-fi Chillhop";
// 4. Auto-generate Keyword Tags
const keywordList = [
sectionType.toLowerCase(),
roleLower.split(' ')[0],
`${timeData.barCount} bars`
];
const keywordsStr = keywordList.filter(Boolean).join(', ');
// 5. Populate Form Fields
presetNameInput.value = `${sectionType} (${timeData.barCount} Bars)`;
presetCategorySelect.value = category;
presetKeywordsInput.value = keywordsStr;
presetBarsInput.value = timeData.barCount;
presetBpmInput.value = 120; // Default BPM or read from session context
presetScaleInput.value = "C Minor";
presetTemplateTextarea.value = currentGeneratedPrompt;
// 6. Display Modal
presetManagerModal.classList.remove('hidden');
}
// Event Listener for the main button
btnSavePresetModal.addEventListener('click', handleOpenSavePresetModal);
5.3 Structured Preset Variable Replacement ("Create Structured Preset")
// Function: Converts static numbers in prompt into dynamic placeholders
btnCreateStructuredPreset.addEventListener('click', () => {
let promptText = presetTemplateTextarea.value;
const bars = presetBarsInput.value || "8";
// Replace specific numbers with template variables
promptText = promptText
.replace(new RegExp(`dài ${bars} ô nhịp`, 'g'), 'dài {{bars}} ô nhịp')
.replace(/Từ Ô nhịp \d+ đến \d+/g, 'Từ Ô nhịp {{start_bar}} đến {{end_bar}}')
.replace(/Phách 0\.0 đến \d+\.0/g, 'Phách 0.0 đến {{total_beats}}')
.replace(/phách \d+\.0/g, 'phách {{total_beats}}');
presetTemplateTextarea.value = promptText;
showToast("Converted Prompt to Structured Variable Template!");
});
5.4 Save Preset Handler (localStorage & Event Dispatch)
btnSavePresetSubmit.addEventListener('click', () => {
const name = presetNameInput.value.trim();
const category = presetCategorySelect.value;
const keywordsRaw = presetKeywordsInput.value.trim();
const defaultBars = parseInt(presetBarsInput.value) || 8;
const defaultBpm = parseInt(presetBpmInput.value) || 120;
const defaultScale = presetScaleInput.value.trim() || "C Minor";
const template = presetTemplateTextarea.value.trim();
if (!name) {
showToast("Please enter a Preset Name!", "error");
presetNameInput.focus();
return;
}
if (!template) {
showToast("System Prompt content cannot be empty!", "error");
return;
}
const newPresetObj = {
id: `preset_${Date.now()}`,
name: name,
category: category,
keywords: keywordsRaw ? keywordsRaw.split(',').map(k => k.trim()) : [name.toLowerCase()],
default_bars: defaultBars,
default_bpm: defaultBpm,
default_scale: defaultScale,
system_instruction_template: template,
is_user_defined: true,
created_at: new Date().toISOString()
};
// Read existing presets and prepend new one
let savedPresets = [];
try {
const stored = localStorage.getItem('daw_ai_prompt_presets');
savedPresets = stored ? JSON.parse(stored) : [];
} catch (e) {
savedPresets = [];
}
savedPresets.unshift(newPresetObj);
localStorage.setItem('daw_ai_prompt_presets', JSON.stringify(savedPresets));
// Dispatch Custom Event so Copilot Chat UI updates dynamically
window.dispatchEvent(new CustomEvent('AI_PRESET_SAVED', { detail: newPresetObj }));
showToast(`Preset "${name}" saved successfully!`);
presetManagerModal.classList.add('hidden');
});
6. OPERATIONAL VERIFICATION CHECKLIST
-
Open Form Generator and set parameters (e.g., Bars 9 to 16, Role: Pianist).
-
Click "Save as Preset":
-
Modal opens.
-
PRESET NAME displays:
Dramatic Build-Up (8 Bars). -
CATEGORY selects:
Piano Solo. -
DEFAULT BARS populates:
8. -
SYSTEM PROMPT TEMPLATE contains the generated prompt text.
-
Test the "Create Structured Preset" green button:
-
Click button
\rightarrowvalues such as 8 bars or beat 64.0 are converted to variables like{{bars}}and{{total_beats}}. -
Click "Save Preset" (Purple Button):
-
Toast notification confirms save.
-
localStorage.getItem('daw_ai_prompt_presets')contains the new preset object at the beginning of the array.