Hasli4

Untitled

Feb 18th, 2026
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 7.63 KB | None | 0 0
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using System;
  4. using System.Linq;
  5.  
  6. [RequireComponent(typeof(AudioSource))]
  7. public class TunerController : MonoBehaviour
  8. {
  9.     [Header("UI")]
  10.     public Text freqText;
  11.     public Text noteText;
  12.     public Text centsText;
  13.     public Image tuningNeedle; // (опционально) стрелка для визуального индикатора
  14.  
  15.     [Header("Microphone settings")]
  16.     public int sampleRate = 16000;        // рекомендовано: 16000 или 44100 (меньше - легче вычисления)
  17.     public int clipLengthSec = 1;        // сколько сек в цикле записи (1..2)
  18.     public string microphoneDevice = ""; // если пусто — берётся первый
  19.  
  20.     [Header("Detection settings")]
  21.     public float minFreq = 80f;          // минимальная детектируемая частота (Hz) — низкая E ~82Hz
  22.     public float maxFreq = 1000f;        // максимальная частота для поиска (Hz)
  23.     public float confidenceThreshold = 0.8f; // порог надежности автокорреляции (0..1)
  24.  
  25.     AudioSource audioSource;
  26.     AudioClip micClip;
  27.     string micDevice;
  28.     int sampleWindow = 2048; // окно выборки для анализа (стремиться к степеням 2)
  29.  
  30.     void Start()
  31.     {
  32.         audioSource = GetComponent<AudioSource>();
  33.         // найдем устройство
  34.         if (Microphone.devices.Length == 0)
  35.         {
  36.             Debug.LogWarning("Microphone not found.");
  37.             return;
  38.         }
  39.  
  40.         micDevice = string.IsNullOrEmpty(microphoneDevice) ? Microphone.devices[0] : microphoneDevice;
  41.         micClip = Microphone.Start(micDevice, true, clipLengthSec, sampleRate);
  42.         // опционально подключаем микрофон к audioclip, чтобы слышать (обычно не нужно)
  43.         audioSource.loop = true;
  44.         while (!(Microphone.GetPosition(micDevice) > 0)) { } // ждём, пока начнётся запись
  45.         audioSource.clip = micClip;
  46.         audioSource.Play();
  47.     }
  48.  
  49.     void Update()
  50.     {
  51.         if (micClip == null) return;
  52.  
  53.         float freq = DetectPitch();
  54.         if (freq > 0)
  55.         {
  56.             float midi = FrequencyToMidi(freq);
  57.             string note = MidiToNoteName(midi, out int octave);
  58.             float cents = FrequencyToCents(freq, midi);
  59.  
  60.             if (freqText) freqText.text = $"{freq:F1} Hz";
  61.             if (noteText) noteText.text = $"{note}{octave}";
  62.             if (centsText) centsText.text = $"{cents:+0.0;-0.0}¢";
  63.  
  64.             if (tuningNeedle)
  65.             {
  66.                 // визуальный индикатор: -50..+50 центов -> -45..+45 градусов
  67.                 float angle = Mathf.Clamp(cents, -50f, 50f) / 50f * 45f;
  68.                 tuningNeedle.rectTransform.localRotation = Quaternion.Euler(0, 0, -angle);
  69.             }
  70.         }
  71.         else
  72.         {
  73.             if (freqText) freqText.text = $"-- Hz";
  74.             if (noteText) noteText.text = $"-";
  75.             if (centsText) centsText.text = $"";
  76.             if (tuningNeedle) tuningNeedle.rectTransform.localRotation = Quaternion.Euler(0, 0, 0);
  77.         }
  78.     }
  79.  
  80.     void OnDestroy()
  81.     {
  82.         if (micClip != null && Microphone.IsRecording(micDevice))
  83.             Microphone.End(micDevice);
  84.     }
  85.  
  86.     float DetectPitch()
  87.     {
  88.         // Берём окно samples
  89.         float[] samples = new float[sampleWindow];
  90.         int micPos = Microphone.GetPosition(micDevice);
  91.         if (micPos < sampleWindow) return -1f;
  92.         micClip.GetData(samples, micPos - sampleWindow);
  93.  
  94.         // Применим оконную функцию (Hamming)
  95.         ApplyHammingWindow(samples);
  96.  
  97.         // Автокорреляция
  98.         int maxLag = Mathf.FloorToInt(sampleRate / minFreq);
  99.         int minLag = Mathf.CeilToInt(sampleRate / maxFreq);
  100.  
  101.         if (maxLag >= samples.Length) maxLag = samples.Length - 1;
  102.         if (minLag < 1) minLag = 1;
  103.  
  104.         float[] acf = new float[maxLag + 1]; // acf[lag]
  105.         for (int lag = minLag; lag <= maxLag; lag++)
  106.         {
  107.             float sum = 0f;
  108.             for (int i = 0; i < samples.Length - lag; i++)
  109.                 sum += samples[i] * samples[i + lag];
  110.             acf[lag] = sum;
  111.         }
  112.  
  113.         // Найдём максимум автокорреляции (за исключением lag=0)
  114.         int bestLag = -1;
  115.         float bestVal = 0f;
  116.         for (int lag = minLag; lag <= maxLag; lag++)
  117.         {
  118.             if (acf[lag] > bestVal)
  119.             {
  120.                 bestVal = acf[lag];
  121.                 bestLag = lag;
  122.             }
  123.         }
  124.  
  125.         if (bestLag <= 0) return -1f;
  126.  
  127.         // Оценка надежности: сравнить максимум с значениями на соседних лагах
  128.         float normalizedConfidence = bestVal / acf[minLag]; // грубая оценка (можно улучшить)
  129.         if (normalizedConfidence < (1f - confidenceThreshold)) // если слишком слабый сигнал
  130.         {
  131.             // всё ещё можно вернуть частоту, но пометим как ненадёжную
  132.             // здесь — возвращаем -1 для простоты
  133.             // можно убрать это условие, если нужно всегда показывать результат
  134.             // return -1f;
  135.         }
  136.  
  137.         // Параболическая интерполяция для уточнения пика (sub-sample)
  138.         float refinedLag = ParabolicInterpolation(acf, bestLag);
  139.  
  140.         float frequency = sampleRate / refinedLag;
  141.         return frequency;
  142.     }
  143.  
  144.     static void ApplyHammingWindow(float[] data)
  145.     {
  146.         int N = data.Length;
  147.         for (int i = 0; i < N; i++)
  148.         {
  149.             float w = 0.54f - 0.46f * Mathf.Cos(2f * Mathf.PI * i / (N - 1));
  150.             data[i] *= w;
  151.         }
  152.     }
  153.  
  154.     // Возвращает скорректированный (может быть дробный) лаг
  155.     static float ParabolicInterpolation(float[] acf, int lag)
  156.     {
  157.         if (lag <= 0 || lag >= acf.Length - 1) return lag;
  158.         float y0 = acf[lag - 1];
  159.         float y1 = acf[lag];
  160.         float y2 = acf[lag + 1];
  161.         float denom = (y0 - 2f * y1 + y2);
  162.         if (Mathf.Abs(denom) < 1e-6f) return lag;
  163.         float shift = 0.5f * (y0 - y2) / denom;
  164.         return lag + shift;
  165.     }
  166.  
  167.     // Перевод частоты в MIDI номер (float), где A4 = 440Hz -> MIDI 69
  168.     static float FrequencyToMidi(float freq)
  169.     {
  170.         return 69f + 12f * Mathf.Log(freq / 440f, 2f);
  171.     }
  172.  
  173.     // Возвращает название ноты (C,C#,D...) и octave
  174.     static string MidiToNoteName(float midiFloat, out int octave)
  175.     {
  176.         int midi = Mathf.RoundToInt(midiFloat);
  177.         string[] names = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
  178.         int noteIndex = midi % 12;
  179.         octave = (midi / 12) - 1;
  180.         return names[(noteIndex + 12) % 12];
  181.     }
  182.  
  183.     // Разница в центах между частотой и целым MIDI номером
  184.     static float FrequencyToCents(float freq, float midiFloat)
  185.     {
  186.         float nearestMidi = Mathf.Round(midiFloat);
  187.         float cents = 1200f * Mathf.Log(freq / MidiToFrequency(nearestMidi), 2f);
  188.         return cents;
  189.     }
  190.  
  191.     static float MidiToFrequency(float midi)
  192.     {
  193.         return 440f * Mathf.Pow(2f, (midi - 69f) / 12f);
  194.     }
  195. }
  196.  
Advertisement
Add Comment
Please, Sign In to add comment