pitch_kit 0.0.1 copy "pitch_kit: ^0.0.1" to clipboard
pitch_kit: ^0.0.1 copied to clipboard

Real-time chord and note detection from the device microphone, built on a pure Dart DSP core.

example/lib/main.dart

import 'package:flutter/material.dart';

import 'dart:async';

import 'package:pitch_kit/pitch_kit.dart';

import 'helpers/peg_helpers.dart';

import 'package:flutter/services.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  final _pitchKitPlugin = PitchKit();

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

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion =
          await _pitchKitPlugin.getPlatformVersion() ??
          'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

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

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

  @override
  State<TunerScreen> createState() => _TunerScreenState();
}

class _TunerScreenState extends State<TunerScreen> {
  String _resultNote = '-';
  double _resultFreq = 0.0;

  void _onResult(TuningResult result) {
    setState(() {
      if (result is NoteTuningResult) {
        _resultFreq = result.freq;
      }
      _resultNote = switch (result) {
        // "${result.name} ${result.freq} (${result.cents}ยข)" if you want detail
        NoteTuningResult() => result.name,
        ChordTuningResult() => result.name,
        SilenceTuningResult() => '-',
      };
    });
  }

  @override
  Widget build(BuildContext context) {
    // GuitarTunerListener handles mic permission and streams results; it
    // renders [child] underneath while running in the background.
    return Scaffold(
      body: SafeArea(
        child: GuitarTunerListener(
          onResult: _onResult,
          child: ExpressiveTunerUI(
            resultNote: _resultNote,
            resultFreq: _resultFreq,
          ),
        ),
      ),
    );
  }
}

class ExpressiveTunerUI extends StatelessWidget {
  final String resultNote;
  final double resultFreq;

  const ExpressiveTunerUI({
    super.key,
    required this.resultNote,
    required this.resultFreq,
  });

  // Standard guitar tuning targets.
  static const List<String> _standardStrings = ['E', 'A', 'D', 'G', 'B', 'e'];

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final textTheme = Theme.of(context).textTheme;

    // Check if the detected note matches a standard string. isPegActive uses
    // the frequency gate to separate low E from high e.
    final isStandardNote = _standardStrings.any(
      (peg) =>
          isPegActive(resultFreq: resultFreq, resultNote: resultNote, peg: peg),
    );

    // Smoothly animate the main note color to green when a target is detected.
    final animatedNoteColor = isStandardNote
        ? const Color(0xFF4CAF50)
        : colorScheme.onSurface;

    return Container(
      color: colorScheme.surface,
      padding: const EdgeInsets.all(32.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            'Pitch Kit',
            style: textTheme.headlineMedium?.copyWith(
              color: colorScheme.primary,
              fontWeight: FontWeight.bold,
            ),
          ),

          // Main expressive note display.
          Container(
            width: 240,
            height: 240,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
            ),
            alignment: Alignment.center,
            child: FittedBox(
              // Mirrors Compose's autoSize: scales the glyph to fit the circle.
              fit: BoxFit.scaleDown,
              child: Padding(
                padding: const EdgeInsets.all(24.0),
                child: AnimatedDefaultTextStyle(
                  duration: const Duration(milliseconds: 300),
                  style: textTheme.displayLarge!.copyWith(
                    fontWeight: FontWeight.w800,
                    color: animatedNoteColor,
                  ),
                  child: Text(
                    resultNote,
                    maxLines: 1,
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
            ),
          ),

          const SizedBox(height: 16),

          // Standard strings indicator row.
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: _standardStrings.map((stringNote) {
              final isCurrentTarget = isPegActive(
                resultFreq: resultFreq,
                resultNote: resultNote,
                peg: stringNote,
              );
              return _PegIndicator(
                label: stringNote,
                isActive: isCurrentTarget,
              );
            }).toList(),
          ),
        ],
      ),
    );
  }
}

/// A single tuning-peg circle that pops and turns green when it's the detected
/// target. Bundles the three Compose animations (bg color, text color, scale).
class _PegIndicator extends StatelessWidget {
  final String label;
  final bool isActive;

  const _PegIndicator({required this.label, required this.isActive});

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final textTheme = Theme.of(context).textTheme;

    final backgroundColor = isActive
        ? const Color(0xFF4CAF50).withValues(alpha: 0.2)
        : Colors.transparent;
    final textColor = isActive
        ? const Color(0xFF2E7D32)
        : colorScheme.onSurfaceVariant;

    // A slight pop when the string goes active (1.0 โ†’ 1.2), animated.
    return TweenAnimationBuilder<double>(
      tween: Tween(begin: 1.0, end: isActive ? 1.2 : 1.0),
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeOut,
      builder: (context, scale, child) {
        return Transform.scale(scale: scale, child: child);
      },
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        width: 48,
        height: 48,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: backgroundColor,
        ),
        alignment: Alignment.center,
        child: AnimatedDefaultTextStyle(
          duration: const Duration(milliseconds: 300),
          style: textTheme.titleLarge!.copyWith(
            color: textColor,
            fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
          ),
          child: Text(label),
        ),
      ),
    );
  }
}
1
likes
160
points
87
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Real-time chord and note detection from the device microphone, built on a pure Dart DSP core.

Repository (GitHub)
View/report issues

Topics

#chord-detector #tuner #tuning #frequency-analysis #music

License

Apache-2.0 (license)

Dependencies

flutter, permission_handler, plugin_platform_interface, record

More

Packages that depend on pitch_kit

Packages that implement pitch_kit