Files
SonicForgeStudio/app/static/js/services/ghostNoteExtractor.js
T
3dtours 1628a34ea0 fix: ghost overlap use inclusive boundary for adjacent items
Changed itemEndBeat <= windowStartBeat → < and
noteAbsEnd <= windowStartBeat → < so abutting items
(Item 1 ends at bar 1, Item 2 starts at bar 1) show
ghost notes from the adjacent item.
2026-07-27 19:31:36 +07:00

76 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 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;
if (itemStartBeat >= windowEndBeat || itemEndBeat < windowStartBeat) 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 || noteAbsEnd < windowStartBeat) continue;
var clampedStart = Math.max(noteAbsStart, windowStartBeat);
var clampedEnd = Math.min(noteAbsEnd, windowEndBeat);
var clampedDur = clampedEnd - clampedStart;
trackGhostNotes.push({
id: 'ghost_' + (note.id || Math.random().toString(36).substr(2, 9)),
pitch: note.pitch,
relative_start_beat: clampedStart - windowStartBeat,
duration_beats: clampedDur,
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',
notes: trackGhostNotes
});
}
}
return ghostLayers;
}
window.SonicGhost = { extractGhostLayers: extractGhostLayers };
})();