# 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 ```text [ 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 ```html ``` --- ### 5.2 Auto-Populate Logic (`autoFillPresetModal`) ```javascript // 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") ```javascript // 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) ```javascript 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 $\rightarrow$ values 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.