✨ Features

A modern and easy-to-use Flutter package for real-time chord and note detection straight from the device microphone, with a pure Dart DSP core:

  • 🎸 Note Detection – Detect individual notes (E, A, D, G, B, â€Ļ) with the YIN pitch-detection algorithm for accurate, octave-error-resistant results.
  • đŸŽļ Chord Detection – Recognize chords such as Am, Bb, C, Em7, and Asus2 using chroma analysis and template matching.
  • đŸŽšī¸ Tuning in Cents – Every detected note reports how flat or sharp it is, ready to drive a tuner needle.
  • đŸŽģ Multi-Instrument Ready – Built-in InstrumentProfile presets (Guitar, Ukulele, Bass), or supply your own custom tuning.
  • 🔇 Noise Handling – DC-offset removal, a high-pass filter, an energy gate, and temporal smoothing reduce background interference.
  • ⚡ Real-Time & Efficient – A from-scratch FFT and Stream-based pipeline, with a bounded pitch-detection search, keep detection fast and responsive.
  • 🚀 Flutter Widget Support – A single widget handles the microphone permission (including the permanently-denied → Settings flow) and streams results back to you.

🚀 Getting started

Requirements

Requirement Version
Dart SDK â‰Ĩ 3.0.0
Flutter SDK â‰Ĩ 3.10.0
Android — min SDK 28
Android — target SDK 37
iOS — min deployment 15.0

Tested on Flutter 3.47.9 / Dart 3.13.7.

Add the package to your pubspec.yaml:

dependencies:
  pitch_kit: ^0.0.2

Or install it from the command line:

flutter pub add pitch_kit

This package depends on record for microphone capture (and the iOS permission check) and permission_handler for the Android permission flow and opening Settings. Both are pulled in automatically.

📱 Platform Setup

pitch_kit needs microphone access. Setup differs by platform.

Android

No action needed. pitch_kit already declares android.permission.RECORD_AUDIO in its manifest, so it merges into your app automatically.

iOS

Add the usage description to ios/Runner/Info.plist, at the top level of the <dict>. Without it, iOS terminates the app the instant it accesses the microphone:

<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to detect notes and chords from your instrument.</string>

No Podfile changes, build flags or environment variables are needed. It works the same with Swift Package Manager and CocoaPods.

💡 Usage

The example below shows a minimal integration. See the example project for the full app.

🎸 PitchKitListener

Wrap any widget with PitchKitListener. It requests the microphone permission, starts detection, and streams a typed TuningResult back through the onResult callback. Detection stops automatically when the widget is disposed.

Parameters Description
profile This parameter is the InstrumentProfile to tune for, with default value InstrumentProfile.guitar (built-in: .guitar, .ukulele, .bass)
titleText This parameter is the title text of the permission popup with default value "Microphone needed"
rationaleText This parameter is the text shown when the permission can still be requested
permanentlyDeniedText This parameter is the text shown when the permission is permanently denied (the button then opens Settings)
allowText This parameter is the confirm button label when the permission can still be requested with default value "Allow"
openSettingsText This parameter is the confirm button label when the permission is permanently denied with default value "Open Settings"
dismissText This parameter is the dismiss button label with default value "Not now"
onResult This parameter is required and it's the callback invoked with each detection result (NoteTuningResult, ChordTuningResult, or SilenceTuningResult)
onError This parameter is the optional callback invoked when the microphone can't be captured (e.g. in use by another app); detection restarts when the app returns to the foreground
child This parameter is required and it's the widget rendered while the tuner runs in the background

đŸŽļ TuningResult

TuningResult is a sealed class with three cases you can handle exhaustively:

Type Description
NoteTuningResult A single detected note: name (e.g. "E", "A#"), freq (Hz), and cents (deviation, -50..+50)
ChordTuningResult A detected chord: name (e.g. "Am", "Cmaj7")
SilenceTuningResult No sound / below the detection threshold

đŸŽģ InstrumentProfile

Parameters Description
name This parameter is required and it's the human-readable label, e.g. "Guitar"
minFreq This parameter is required and it's the lowest frequency (Hz) analysis considers
maxFreq This parameter is required and it's the highest frequency (Hz) considered for chroma
bassCeiling This parameter is required and it's the ceiling (Hz) for the chord bass-note scan
harmonicPivot This parameter is required and it's the frequency (Hz) around which harmonic down-weighting is centred
openStrings This parameter is required and it's the list of OpenString values for the instrument's standard tuning
rmsGate This parameter is the loudness gate (RMS); frames quieter than this are treated as silence, default value 0.01
useFlats This parameter is the option to display flats (Bb) instead of sharps (A#), default value false
import 'package:flutter/material.dart';
import 'package:pitch_kit/pitch_kit.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pitch Kit',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6200EE)),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _resultNote = '-';
  double _resultFreq = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Pitch Kit',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        centerTitle: true,
      ),
      // ==========================================
      // 1. Start the tuner
      // ==========================================
      body: PitchKitListener(
        profile: InstrumentProfile.guitar,
        onResult: (result) {
          setState(() {
            _resultFreq = result is NoteTuningResult ? result.freq : 0.0;
            _resultNote = switch (result) {
              NoteTuningResult() => result.name,
              ChordTuningResult() => result.name,
              SilenceTuningResult() => '-',
            };
          });
        },
        // ==========================================
        // 2. Render your UI
        // ==========================================
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                _resultNote,
                style: const TextStyle(
                  fontSize: 96,
                  fontWeight: FontWeight.bold,
                ),
              ),
              if (_resultFreq > 0)
                Text(
                  '${_resultFreq.toStringAsFixed(1)} Hz',
                  style: const TextStyle(fontSize: 20, color: Colors.grey),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

đŸŽŧ Custom Tuning

You can supply your own InstrumentProfile for non-standard tunings:

const dropD = InstrumentProfile(
  name: 'Drop D',
  minFreq: 60.0,
  maxFreq: 5000.0,
  bassCeiling: 400.0,
  harmonicPivot: 200.0,
  openStrings: [
    OpenString('D2', 73.42),
    OpenString('A2', 110.00),
    OpenString('D3', 146.83),
    OpenString('G3', 196.00),
    OpenString('B3', 246.94),
    OpenString('E4', 329.63),
  ],
);

PitchKitListener(
  profile: dropD,
  onResult: (result) {
    //...your code here
  },
  child: const SizedBox(),
);

🎹 Supported Chords

Each of the 12 roots is combined with the following qualities: major, minor (m), dominant 7th (7), minor 7th (m7), major 7th (maj7), sus2, sus4, diminished (dim), and augmented (aug).

Examples: C, Am, G7, Em7, Dmaj7, Asus2, Bdim, Faug.

â„šī¸ Additional information

Thank you for using pitch_kit! Your feedback helps make this package better. If you encounter any bugs or unexpected behavior, please open an issue on the GitHub repository.