diff --git a/md/50_APPLY_GENERATOR.md b/md/50_APPLY_GENERATOR.md new file mode 100644 index 0000000..63b9141 --- /dev/null +++ b/md/50_APPLY_GENERATOR.md @@ -0,0 +1,346 @@ +# 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. \ No newline at end of file