buildEvents method

List<FaustEventData> buildEvents({
  1. double autoDecayMs = 10.0,
})

Renders the sequence into a list of FaustEventData ready for the native bridge. autoDecayMs is the micro-gap between staccato notes to ensure clean re-strikes.

Implementation

List<FaustEventData> buildEvents({double autoDecayMs = 10.0}) {
  final List<FaustEventData> events = <FaustEventData>[];
  final double samplesPerBeat = (60.0 / tempoBpm) * sampleRate;
  final int autoDecaySamples = (autoDecayMs * sampleRate / 1000.0).toInt();

  // Group notes by instrument to handle overlaps/slurs
  final Map<int, List<FaustNote>> instrumentTracks = {};
  for (var note in _notes) {
    instrumentTracks.putIfAbsent(note.instrumentId, () => []).add(note);
  }

  for (var entry in instrumentTracks.entries) {
    final int instId = entry.key;
    final List<FaustNote> trackNotes = entry.value;
    trackNotes.sort((a, b) => a.beat.compareTo(b.beat));

    for (int i = 0; i < trackNotes.length; i++) {
      final note = trackNotes[i];
      final int startSample = (note.beat * samplesPerBeat).toInt();
      final int durationSamples = (note.duration * samplesPerBeat).toInt();
      final int endSample = startSample + durationSamples;

      // 1. Set Frequency
      events.add(FaustEventData(
        sampleOffset: startSample,
        instrumentId: instId,
        eventType: 1, // SetFreq
        value: note.pitch,
      ));

      // 2. Start Note (Strike/Breath)
      events.add(FaustEventData(
        sampleOffset: startSample,
        instrumentId: instId,
        eventType: 0, // Strike
        value: note.velocity,
        paramId: note.paramId,
      ));

      // 3. Handle Note Off or Slur
      bool isSlurred = false;
      if (i < trackNotes.length - 1) {
        final nextNote = trackNotes[i + 1];
        final int nextStart = (nextNote.beat * samplesPerBeat).toInt();

        // If the next note starts exactly at or before our end, and it's legato
        if (nextStart <= endSample && (note.legato || nextNote.legato)) {
          isSlurred = true;
        }
      }

      if (!isSlurred) {
        // Normal note: Insert a micro-decay before the end to ensure separation
        final int stopSample = max(startSample + 100, endSample - autoDecaySamples);
        events.add(FaustEventData(
          sampleOffset: stopSample,
          instrumentId: instId,
          eventType: 0, // Stop Breath/Strike
          value: 0.0,
        ));
      }
    }
  }

  events.sort((FaustEventData a, FaustEventData b) => a.sampleOffset.compareTo(b.sampleOffset));
  return events;
}