77 lines
2.6 KiB
JavaScript
77 lines
2.6 KiB
JavaScript
// 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 };
|
|
})();
|