Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

71 行
1.9KB

  1. package dsp
  2. import "math"
  3. // MPXLimiter is a look-ahead peak limiter for the composite MPX signal.
  4. // It prevents overmodulation by keeping the composite output within
  5. // the configured ceiling. The limiter applies gain reduction smoothly
  6. // to avoid introducing audible artifacts.
  7. type MPXLimiter struct {
  8. ceiling float64 // maximum absolute output level (e.g. 1.0)
  9. attackCoeff float64 // attack smoothing coefficient
  10. releaseCoeff float64 // release smoothing coefficient
  11. gainReduction float64 // current gain reduction in linear scale (0 = no reduction)
  12. }
  13. // NewMPXLimiter creates a limiter with the given ceiling, attack and release
  14. // times in milliseconds, and sample rate.
  15. func NewMPXLimiter(ceiling, attackMs, releaseMs, sampleRate float64) *MPXLimiter {
  16. if ceiling <= 0 {
  17. ceiling = 1.0
  18. }
  19. if attackMs <= 0 {
  20. attackMs = 0.1 // fast attack for MPX
  21. }
  22. if releaseMs <= 0 {
  23. releaseMs = 50 // moderate release
  24. }
  25. attackSamples := attackMs * sampleRate / 1000
  26. releaseSamples := releaseMs * sampleRate / 1000
  27. return &MPXLimiter{
  28. ceiling: ceiling,
  29. attackCoeff: 1.0 - math.Exp(-1.0/attackSamples),
  30. releaseCoeff: 1.0 - math.Exp(-1.0/releaseSamples),
  31. }
  32. }
  33. // Process applies limiting to a single composite sample.
  34. func (l *MPXLimiter) Process(in float64) float64 {
  35. absIn := math.Abs(in)
  36. targetReduction := 0.0
  37. if absIn > l.ceiling {
  38. targetReduction = 1.0 - l.ceiling/absIn
  39. }
  40. if targetReduction > l.gainReduction {
  41. l.gainReduction += l.attackCoeff * (targetReduction - l.gainReduction)
  42. } else {
  43. l.gainReduction += l.releaseCoeff * (targetReduction - l.gainReduction)
  44. }
  45. gain := 1.0 - l.gainReduction
  46. return in * gain
  47. }
  48. // Reset clears the limiter state.
  49. func (l *MPXLimiter) Reset() {
  50. l.gainReduction = 0
  51. }
  52. // HardClip provides a simple hard clipper as a safety net after the limiter.
  53. func HardClip(sample, ceiling float64) float64 {
  54. if sample > ceiling {
  55. return ceiling
  56. }
  57. if sample < -ceiling {
  58. return -ceiling
  59. }
  60. return sample
  61. }