forProfile static method

SoundRecipe forProfile(
  1. SoundRecipe base,
  2. FeedbackProfile profile
)

Derives the profile-specific recipe for base:

  • mechanical is a little crisper — quicker attack (never under 2ms, so still click-free), shorter decay and a faint 2nd harmonic — but its brightness (harmonics <= 0.15) and gain (<= 0.4) are capped so it never turns harsh.
  • subtle is softer still — slower attack, no harmonics, lower gain — keeping attack + decay unchanged so the fade-out stays smooth.

Implementation

static SoundRecipe forProfile(SoundRecipe base, FeedbackProfile profile) {
  final isNoise = base.waveform == SoundWaveform.noise;
  switch (profile) {
    case FeedbackProfile.mechanical:
      final attack = (base.envelope.attackMs * 0.6).clamp(2.0, 1000.0);
      return base.copyWith(
        envelope: base.envelope.copyWith(
          attackMs: attack,
          decayMs: base.envelope.decayMs * 0.8,
        ),
        harmonics: isNoise
            ? base.harmonics
            : [
                for (final h in base.harmonics) h.clamp(0.0, 0.15),
                if (base.harmonics.isEmpty) 0.12,
              ],
        gain: (base.gain * 1.1).clamp(0.0, 0.4),
      );
    case FeedbackProfile.subtle:
      final attack = base.envelope.attackMs * 1.6;
      final decay = (base.envelope.decayMs - (attack - base.envelope.attackMs))
          .clamp(1.0, 10000.0);
      return base.copyWith(
        envelope: base.envelope.copyWith(attackMs: attack, decayMs: decay),
        harmonics: const [],
        gain: base.gain * 0.75,
      );
    case FeedbackProfile.tactile:
    case FeedbackProfile.silent:
      return base;
  }
}