flutter_sequencer 0.4.4 copy "flutter_sequencer: ^0.4.4" to clipboard
flutter_sequencer: ^0.4.4 copied to clipboard

A Flutter plugin for sequencing audio. Use it to build sequences of notes and play them back using SFZ or SF2 sound fonts.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_sequencer/global_state.dart';
import 'package:flutter_sequencer/models/sfz.dart';
import 'package:flutter_sequencer/models/instrument.dart';
import 'package:flutter_sequencer/sequence.dart';
import 'package:flutter_sequencer/track.dart';

import 'components/drum_machine/drum_machine.dart';
import 'components/position_view.dart';
import 'components/step_count_selector.dart';
import 'components/tempo_selector.dart';
import 'components/track_selector.dart';
import 'components/transport.dart';
import 'models/project_state.dart';
import 'models/step_sequencer_state.dart';
import 'constants.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  final sequence =
      Sequence(tempo: INITIAL_TEMPO, endBeat: INITIAL_STEP_COUNT.toDouble());
  Map<int, StepSequencerState?> trackStepSequencerStates = {};
  List<Track> tracks = [];
  Map<int, double> trackVolumes = {};
  Track? selectedTrack;
  late Ticker ticker;
  double tempo = INITIAL_TEMPO;
  int stepCount = INITIAL_STEP_COUNT;
  double position = 0.0;
  bool isPlaying = false;
  bool isLooping = INITIAL_IS_LOOPING;

  @override
  void initState() {
    super.initState();

    GlobalState().setKeepEngineRunning(true);

    final instruments = [
      Sf2Instrument(path: "assets/sf2/TR-808.sf2", isAsset: true),
      SfzInstrument(
        path: "assets/sfz/GMPiano.sfz",
        isAsset: true,
        tuningPath: "assets/sfz/meanquar.scl",
      ),
      RuntimeSfzInstrument(
          id: "Sampled Synth",
          sampleRoot: "assets/wav",
          isAsset: true,
          sfz: Sfz(groups: [
            SfzGroup(regions: [
              SfzRegion(sample: "D3.wav", key: 62),
              SfzRegion(sample: "F3.wav", key: 65),
              SfzRegion(sample: "Gsharp3.wav", key: 68),
            ])
          ])),
      RuntimeSfzInstrument(
          id: "Generated Synth",
          // This SFZ doesn't use any sample files, so just put "/" as a placeholder.
          sampleRoot: "/",
          isAsset: false,
          // Based on the Unison Oscillator example here:
          // https://sfz.tools/sfizz/quick_reference#unison-oscillator
          sfz: Sfz(groups: [
            SfzGroup(regions: [
              SfzRegion(sample: "*saw", otherOpcodes: {
                "oscillator_multi": "5",
                "oscillator_detune": "50",
              })
            ])
          ])),
    ];

    sequence.createTracks(instruments).then((tracks) {
      this.tracks = tracks;
      tracks.forEach((track) {
        trackVolumes[track.id] = 0.0;
        trackStepSequencerStates[track.id] = StepSequencerState();
      });

      setState(() {
        this.selectedTrack = tracks[0];
      });
    });

    ticker = this.createTicker((Duration elapsed) {
      setState(() {
        tempo = sequence.getTempo();
        position = sequence.getBeat();
        isPlaying = sequence.getIsPlaying();

        tracks.forEach((track) {
          trackVolumes[track.id] = track.getVolume();
        });
      });
    });
    ticker.start();
  }

  handleTogglePlayPause() {
    if (isPlaying) {
      sequence.pause();
    } else {
      sequence.play();
    }
  }

  handleStop() {
    sequence.stop();
  }

  handleSetLoop(bool nextIsLooping) {
    if (nextIsLooping) {
      sequence.setLoop(0, stepCount.toDouble());
    } else {
      sequence.unsetLoop();
    }

    setState(() {
      isLooping = nextIsLooping;
    });
  }

  handleToggleLoop() {
    final nextIsLooping = !isLooping;

    handleSetLoop(nextIsLooping);
  }

  handleStepCountChange(int nextStepCount) {
    if (nextStepCount < 1) return;

    sequence.setEndBeat(nextStepCount.toDouble());

    if (isLooping) {
      final nextLoopEndBeat = nextStepCount.toDouble();

      sequence.setLoop(0, nextLoopEndBeat);
    }

    setState(() {
      stepCount = nextStepCount;
      tracks.forEach((track) => syncTrack(track));
    });
  }

  handleTempoChange(double nextTempo) {
    if (nextTempo <= 0) return;
    sequence.setTempo(nextTempo);
  }

  handleTrackChange(Track? nextTrack) {
    setState(() {
      selectedTrack = nextTrack;
    });
  }

  handleVolumeChange(double nextVolume) {
    if (selectedTrack != null) {
      selectedTrack!.changeVolumeNow(volume: nextVolume);
    }
  }

  handleVelocitiesChange(
      int trackId, int step, int noteNumber, double velocity) {
    final track = tracks.firstWhere((track) => track.id == trackId);

    trackStepSequencerStates[trackId]!.setVelocity(step, noteNumber, velocity);

    syncTrack(track);
  }

  syncTrack(track) {
    track.clearEvents();
    trackStepSequencerStates[track.id]!
        .iterateEvents((step, noteNumber, velocity) {
      if (step < stepCount) {
        track.addNote(
            noteNumber: noteNumber,
            velocity: velocity,
            startBeat: step.toDouble(),
            durationBeats: 1.0);
      }
    });
    track.syncBuffer();
  }

  loadProjectState(ProjectState projectState) {
    handleStop();

    trackStepSequencerStates[tracks[0].id] = projectState.drumState;
    trackStepSequencerStates[tracks[1].id] = projectState.pianoState;
    trackStepSequencerStates[tracks[2].id] = projectState.bassState;
    trackStepSequencerStates[tracks[3].id] = projectState.synthState;

    handleStepCountChange(projectState.stepCount);
    handleTempoChange(projectState.tempo);
    handleSetLoop(projectState.isLooping);

    tracks.forEach(syncTrack);
  }

  handleReset() {
    loadProjectState(ProjectState.empty());
  }

  handleLoadDemo() {
    loadProjectState(ProjectState.demo());
  }

  Widget _getMainView() {
    if (selectedTrack == null) return Text('Loading...');

    final isDrumTrackSelected = selectedTrack == tracks[0];

    return Center(
      child: Column(children: [
        Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
          Transport(
            isPlaying: isPlaying,
            isLooping: isLooping,
            onTogglePlayPause: handleTogglePlayPause,
            onStop: handleStop,
            onToggleLoop: handleToggleLoop,
          ),
          PositionView(position: position),
        ]),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            StepCountSelector(
                stepCount: stepCount, onChange: handleStepCountChange),
            TempoSelector(
              selectedTempo: tempo,
              handleChange: handleTempoChange,
            ),
          ],
        ),
        TrackSelector(
          tracks: tracks,
          selectedTrack: selectedTrack,
          handleChange: handleTrackChange,
        ),
        Row(mainAxisAlignment: MainAxisAlignment.center, children: [
          MaterialButton(
            child: Text('Reset'),
            onPressed: handleReset,
          ),
          MaterialButton(
            child: Text('Load Demo'),
            onPressed: handleLoadDemo,
          ),
        ]),
        DrumMachineWidget(
          track: selectedTrack!,
          stepCount: stepCount,
          currentStep: position.floor(),
          rowLabels: isDrumTrackSelected ? ROW_LABELS_DRUMS : ROW_LABELS_PIANO,
          columnPitches:
              isDrumTrackSelected ? ROW_PITCHES_DRUMS : ROW_PITCHES_PIANO,
          volume: trackVolumes[selectedTrack!.id] ?? 0.0,
          stepSequencerState: trackStepSequencerStates[selectedTrack!.id],
          handleVolumeChange: handleVolumeChange,
          handleVelocitiesChange: handleVelocitiesChange,
        ),
      ]),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
          colorScheme: ColorScheme.dark(),
          textTheme:
              Theme.of(context).textTheme.apply(bodyColor: Colors.white)),
      home: Scaffold(
        appBar: AppBar(title: const Text('Drum machine example')),
        body: _getMainView(),
      ),
    );
  }
}
62
likes
140
points
37
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for sequencing audio. Use it to build sequences of notes and play them back using SFZ or SF2 sound fonts.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

ffi, flutter, path

More

Packages that depend on flutter_sequencer