// SonicForge Studio Ghost Note Extractor Service (function() { function extractGhostLayers(activeTracks, targetTrackId, targetItemId, bpm) { if (!activeTracks || !targetItemId) return []; const secondsPerBeat = 60.0 / (parseInt(bpm) || 120); let targetItem = null; for (var i = 0; i < activeTracks.length; i++) { var t = activeTracks[i]; var found = (t.midiItems || []).find(function(m) { return m.id === targetItemId; }); if (found) { targetItem = found; break; } } if (!targetItem) return []; const windowStartBeat = targetItem.startTime / secondsPerBeat; const windowEndBeat = (targetItem.startTime + targetItem.duration) / secondsPerBeat; const ghostLayers = []; for (var i = 0; i < activeTracks.length; i++) { var track = activeTracks[i]; if (!track.midiItems || !track.midiItems.length) continue; if (track.muted) continue; var isSameTrack = track.id === targetTrackId; var trackGhostNotes = []; for (var j = 0; j < track.midiItems.length; j++) { var item = track.midiItems[j]; if (item.id === targetItemId) continue; var itemStartBeat = item.startTime / secondsPerBeat; var itemEndBeat = (item.startTime + item.duration) / secondsPerBeat; // Same-track items always contribute notes (show whole track); cross-track only when overlapping the window if (itemStartBeat >= windowEndBeat || itemEndBeat < windowStartBeat) { if (!isSameTrack) continue; } var notes = item.notes || []; for (var k = 0; k < notes.length; k++) { var note = notes[k]; var noteAbsStart = itemStartBeat + (note.start_beat || 0); var noteAbsEnd = noteAbsStart + (note.duration_beats || 1); if (noteAbsStart >= windowEndBeat && !isSameTrack) continue; trackGhostNotes.push({ id: 'ghost_' + (note.id || Math.random().toString(36).substr(2, 9)), pitch: note.pitch, relative_start_beat: noteAbsStart - windowStartBeat, duration_beats: (note.duration_beats || 1), velocity: note.velocity, original_track_name: track.name, original_track_color: track.color || '#888888' }); } } if (trackGhostNotes.length > 0) { ghostLayers.push({ track_id: track.id, track_name: track.name, track_color: track.color || '#6b7280', isSameTrack: isSameTrack, notes: trackGhostNotes }); } } return ghostLayers; } window.SonicGhost = { extractGhostLayers: extractGhostLayers }; })();