4a9cbcdef1
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
72 lines
2.4 KiB
JavaScript
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 };
|
|
})();
|