Files
SonicForgeStudio/app/static/js/services/ghostNoteExtractor.js
T
3dtours 4a9cbcdef1 fix: per-track instrument + ghost note visibility
soundfontPlayer.js:
- selectInstrument: respect passed channel (don't allocate new one)
- _playNoteFluid: only use _engineChMap when channel is undefined

ghostNoteExtractor.js:
- Include ALL notes from overlapping items (not clipped)
- relative_start_beat can be negative (notes before window)
- Keep original duration instead of clamping
2026-07-27 20:04:56 +07:00

72 lines
2.4 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) 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',
notes: trackGhostNotes
});
}
}
return ghostLayers;
}
window.SonicGhost = { extractGhostLayers: extractGhostLayers };
})();