using UnityEngine; using Concentus.Structs; using Concentus.Enums; public class OpusMicRecorder : MonoBehaviour { [Header("Audio Settings")] public int sampleRate = 16000; public int frameSizeMs = 20; // 20 میلی‌ثانیه [Header("Bandpass Filter Frequencies (Hz)")] [Range(50f, 1000f)] public float lowFreq = 300f; [Range(1000f, 8000f)] public float highFreq = 3400f; private AudioClip micClip; private int frameSizeSamples; private int lastSamplePos = 0; private OpusEncoder encoder; private BandpassFilter voiceFilter; public delegate void EncodedAudioReady(byte[] data); public event EncodedAudioReady OnEncodedAudio; void Start() { frameSizeSamples = (sampleRate / 1000) * frameSizeMs; encoder = new OpusEncoder(sampleRate, 1, OpusApplication.OPUS_APPLICATION_VOIP); encoder.Bitrate = 16000; micClip = Microphone.Start(null, true, 1, sampleRate); // اعتبارسنجی ورودی فرکانس ها if (lowFreq >= highFreq) { Debug.LogWarning("lowFreq باید کمتر از highFreq باشد. تنظیم به مقادیر پیش‌فرض."); lowFreq = 300f; highFreq = 3400f; } voiceFilter = new BandpassFilter(sampleRate, lowFreq, highFreq); } void Update() { int micPos = Microphone.GetPosition(null); int available = micPos - lastSamplePos; if (available < 0) available += micClip.samples; if (available < frameSizeSamples) return; float[] samples = new float[frameSizeSamples]; micClip.GetData(samples, lastSamplePos); lastSamplePos = (lastSamplePos + frameSizeSamples) % micClip.samples; // آپدیت فیلتر اگر کاربر در Inspector مقادیر را تغییر داد if (voiceFilter.LowFreq != lowFreq || voiceFilter.HighFreq != highFreq) { voiceFilter.SetFrequencies(lowFreq, highFreq); } voiceFilter.Process(samples); short[] pcm = new short[frameSizeSamples]; for (int i = 0; i < frameSizeSamples; i++) pcm[i] = (short)Mathf.Clamp(samples[i] * short.MaxValue, short.MinValue, short.MaxValue); byte[] encoded = new byte[1275]; int encodedLength = encoder.Encode(pcm, 0, frameSizeSamples, encoded, 0, encoded.Length); byte[] packet = new byte[encodedLength]; System.Buffer.BlockCopy(encoded, 0, packet, 0, encodedLength); OnEncodedAudio?.Invoke(packet); } } // کلاس فیلتر میان‌گذر (Bandpass) با قابلیت تنظیم داینامیک فرکانس‌ها public class BandpassFilter { private float a0, a1, a2, b1, b2; private float z1, z2; private float sampleRate; public float LowFreq { get; private set; } public float HighFreq { get; private set; } public BandpassFilter(float sampleRate, float lowFreq, float highFreq) { this.sampleRate = sampleRate; SetFrequencies(lowFreq, highFreq); z1 = 0; z2 = 0; } public void SetFrequencies(float lowFreq, float highFreq) { // اعتبارسنجی ساده if (lowFreq <= 0) lowFreq = 50f; if (highFreq >= sampleRate / 2f) highFreq = sampleRate / 2f - 100; if (lowFreq >= highFreq) { Debug.LogWarning("BandpassFilter: lowFreq must be less than highFreq"); return; } LowFreq = lowFreq; HighFreq = highFreq; float omegaL = 2.0f * Mathf.PI * LowFreq / sampleRate; float omegaH = 2.0f * Mathf.PI * HighFreq / sampleRate; float centerFreq = (omegaL + omegaH) / 2.0f; float bandwidth = omegaH - omegaL; float Q = centerFreq / bandwidth; float alpha = Mathf.Sin(centerFreq) / (2.0f * Q); float cosW0 = Mathf.Cos(centerFreq); float norm = 1.0f + alpha; a0 = alpha / norm; a1 = 0; a2 = -alpha / norm; b1 = -2.0f * cosW0 / norm; b2 = (1.0f - alpha) / norm; // ریست فیلتر برای جلوگیری از artifact بعد تغییر فرکانس z1 = 0; z2 = 0; } public void Process(float[] samples) { for (int i = 0; i < samples.Length; i++) { float input = samples[i]; float output = a0 * input + a1 * z1 + a2 * z2 - b1 * z1 - b2 * z2; z2 = z1; z1 = output; samples[i] = output; } } }