๐ŸŽ™๏ธ Unity์—์„œ ๋งˆ์ดํฌ ์ž…๋ ฅ ๋ฐ›์•„ ์‹œ๊ฐํ™”ํ•˜๊ธฐ

๊น€๋ณด๊ทผยท2025๋…„ 7์›” 10์ผ

Unity

๋ชฉ๋ก ๋ณด๊ธฐ
111/113

๐ŸŽ™๏ธ Unity์—์„œ ๋งˆ์ดํฌ ์ž…๋ ฅ ๋ฐ›์•„ ์‹œ๊ฐํ™”ํ•˜๊ธฐ

์˜ค๋Š˜์€ Unity์—์„œ Microphone API๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งˆ์ดํฌ ์ž…๋ ฅ์„ ๋ฐ›์•„ UI๋กœ ์‹œ๊ฐํ™”ํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ดค๋‹ค.

โœ… ๋ชฉํ‘œ

  • ๋งˆ์ดํฌ๋กœ ์ž…๋ ฅ๋œ ์†Œ๋ฆฌ์˜ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค.

  • ๊ฐ์ง€๋œ ๋ณผ๋ฅจ์„ Image.fillAmount๋กœ ์‹œ๊ฐํ™”ํ•œ๋‹ค.

  • ์กฐ์šฉํ•  ๋• ๋ฐ”๊ฐ€ 0์— ๊ฐ€๊น๊ณ , ์†Œ๋ฆฌ๋ฅผ ๋‚ด๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์˜ฌ๋ผ๊ฐ€๋„๋ก ํ•œ๋‹ค.

๐Ÿ”ง ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

Unity์—๋Š” Microphone ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์˜ค๋””์˜ค ์ž…๋ ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
AudioSource์— ๋งˆ์ดํฌ ์ž…๋ ฅ์„ ์—ฐ๊ฒฐํ•˜๊ณ , GetOutputData()๋กœ ์‹ค์‹œ๊ฐ„ ์˜ค๋””์˜ค ์ƒ˜ํ”Œ์„ ๊ฐ€์ ธ์™€ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋‹ค.

audioSource.clip = Microphone.Start(mic, true, 10, 44100);
audioSource.loop = true;
audioSource.Play();

GetOutputData()๋กœ ๋ฐ›์€ ์ƒ˜ํ”Œ์„ ํ†ตํ•ด RMS(Root Mean Square)๋ฅผ ๊ตฌํ•˜๊ณ , ์ด๋ฅผ ๋ฐ์‹œ๋ฒจ(dB)๋กœ ๋ณ€ํ™˜ํ–ˆ๋‹ค.

float sum = 0f;
for (int i = 0; i < samples.Length; i++)
{
    sum += samples[i] * samples[i];
}

float rms = Mathf.Sqrt(sum / samples.Length);
float db = 20 * Mathf.Log10(rms / 0.1f);

๐ŸŽš๏ธ ๊ฐ๋„ ์กฐ์ ˆ

์ดˆ๊ธฐ์—๋Š” ์•„๋ฌด ๋ง๋„ ์•ˆ ํ•ด๋„ fillAmount๊ฐ€ 0.3 ๊ทผ์ฒ˜์—์„œ ๊ณ„์† ์›€์ง์—ฌ์„œ ๋„ˆ๋ฌด ๋ฏผ๊ฐํ•˜๊ฒŒ ๋А๊ปด์กŒ๋‹ค.
์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ž„๊ณ„๊ฐ’(threshold dB)๋ฅผ ์„ค์ •ํ•ด์„œ ์ผ์ • ๋ณผ๋ฅจ ์ดํ•˜์˜ ์ž…๋ ฅ์€ ๋ฌด์‹œํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

float thresholdDb = -20f;
db = Mathf.Max(db, thresholdDb);

๊ทธ๋ฆฌ๊ณ  Mathf.InverseLerp()๋ฅผ ํ†ตํ•ด -20dB ~ 0dB ๋ฒ”์œ„๋ฅผ 0~1๋กœ ์ •๊ทœํ™”ํ•ด์„œ Image.fillAmount์— ๋ฐ˜์˜ํ–ˆ๋‹ค.

๐Ÿ’ก ์ตœ์ข… ์ฝ”๋“œ ์š”์•ฝ

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

// ์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ค๋ธŒ์ ํŠธ์—๋Š” AudioSource๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค๋Š” ์˜๋ฏธ
[RequireComponent(typeof(AudioSource))]
public class MicrophoneTest : MonoBehaviour
{
    private AudioSource audioSource; // ๋งˆ์ดํฌ ์ž…๋ ฅ์„ ๋ฐ›์„ AudioSource ์ปดํฌ๋„ŒํŠธ
    public Image soundImage; // ์†Œ๋ฆฌ ํฌ๊ธฐ๋ฅผ ์‹œ๊ฐํ™”ํ•  UI Image (fillAmount๋กœ ์‚ฌ์šฉ)

    void Start()
    {
        audioSource = GetComponent<AudioSource>(); // AudioSource ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€์ ธ์˜ด

        // ๋งˆ์ดํฌ ์žฅ์น˜๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
        if (Microphone.devices.Length > 0)
        {
            string mic = Microphone.devices[0]; // ์ฒซ ๋ฒˆ์งธ ๋งˆ์ดํฌ ์žฅ์น˜๋ฅผ ์‚ฌ์šฉ
            Debug.Log("์‚ฌ์šฉ์ค‘์ธ ๋งˆ์ดํฌ : " + mic);

            // ๋งˆ์ดํฌ ์ž…๋ ฅ์„ AudioSource์— ์—ฐ๊ฒฐ (10์ดˆ์งœ๋ฆฌ ๋ฃจํ”„ ๋…น์Œ, ์ƒ˜ํ”Œ๋ ˆ์ดํŠธ 44100Hz)
            audioSource.clip = Microphone.Start(mic, true, 10, 44100);
            audioSource.loop = true; // ๋ฐ˜๋ณต ์žฌ์ƒ ์„ค์ •

            // ๋งˆ์ดํฌ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
            while (!(Microphone.GetPosition(mic) > 0)) {}

            // ์˜ค๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘ (์‹ค์ œ๋กœ๋Š” ๋งˆ์ดํฌ ์ž…๋ ฅ ์†Œ๋ฆฌ ์žฌ์ƒ)
            audioSource.Play();
        }
        else
        {
            Debug.LogWarning("๋งˆ์ดํฌ ์žฅ์น˜๋ฅผ ์ฐพ์„์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค");
        }
    }

    void Update()
    {
        float[] samples = new float[256]; // ์˜ค๋””์˜ค ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ๋ฐฐ์—ด
        audioSource.GetOutputData(samples, 0); // ํ˜„์žฌ ์žฌ์ƒ ์ค‘์ธ ์˜ค๋””์˜ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด (์ฑ„๋„ 0)

        float sum = 0f;
        // RMS(Root Mean Square)๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•œ ์ œ๊ณฑํ•ฉ ๊ณ„์‚ฐ
        for (int i = 0; i < samples.Length; i++)
        {
            sum += samples[i] * samples[i];
        }

        float rms = Mathf.Sqrt(sum / samples.Length); // RMS ๊ณ„์‚ฐ (์†Œ๋ฆฌ์˜ ์—๋„ˆ์ง€)
        float db = 20 * Mathf.Log10(rms / 0.1f); // RMS ๊ฐ’์„ ๋ฐ์‹œ๋ฒจ(dB)๋กœ ๋ณ€ํ™˜

        // 1๋‹จ๊ณ„: ๋„ˆ๋ฌด ์ž‘์€ ์†Œ๋ฆฌ๋Š” ๋ฌด์‹œํ•˜๊ธฐ ์œ„ํ•œ ์ž„๊ณ„๊ฐ’ ์„ค์ •
        float thresholdDb = -20f;
        db = Mathf.Max(db, thresholdDb); // ๋ฐ์‹œ๋ฒจ์ด -20๋ณด๋‹ค ์ž‘์œผ๋ฉด -20์œผ๋กœ ๊ณ ์ • (๋…ธ์ด์ฆˆ ๋ฐฉ์ง€)

        // 2๋‹จ๊ณ„: ๋ฐ์‹œ๋ฒจ์„ 0~1 ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”ํ•˜๊ณ  ๊ฐ๋„ ์ ์šฉ
        float sensitivity = 1.0f; // ๊ฐ๋„ ์„ค์ • (๊ฐ’์ด ํด์ˆ˜๋ก ๋ฏผ๊ฐ)
        float normalizedVolume = Mathf.Clamp01(sensitivity * Mathf.InverseLerp(-20f, 0f, db));
        // Mathf.InverseLerp: -20dB ~ 0dB ๋ฒ”์œ„๋ฅผ 0~1๋กœ ๋งคํ•‘
        // Clamp01: ๊ฐ’์ด 0๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ 1๋ณด๋‹ค ํฌ์ง€ ์•Š๋„๋ก ์ œํ•œ

        // 3๋‹จ๊ณ„: UI ์ด๋ฏธ์ง€์— ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ฐ˜์˜ (๋ณผ๋ฅจ ๋ฐ”์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋ณด๊ฐ„ ์ ์šฉ)
        float currentFill = soundImage.fillAmount;
        soundImage.fillAmount = Mathf.Lerp(currentFill, normalizedVolume, Time.deltaTime * 10f);
        // Mathf.Lerp๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ‘์ž‘์Šค๋Ÿฌ์šด ๋ณ€ํ™” ์—†์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ fillAmount๋ฅผ ๋ณ€๊ฒฝ
    }
}

โœจ ๊ฒฐ๊ณผ

  • ๋งํ•˜์ง€ ์•Š์„ ๋•Œ๋Š” ๋ฐ”๊ฐ€ 0์— ๋จธ๋ฌด๋ฅธ๋‹ค.

  • ๋ง์„ ํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐ”๊ฐ€ ์˜ฌ๋ผ๊ฐ„๋‹ค.

  • fillAmount์— Mathf.Lerp()๋กœ ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜๋„ ์ ์šฉํ–ˆ๋‹ค.

๐Ÿ“Œ ๋А๋‚€ ์ 

Unity์—์„œ ๋งˆ์ดํฌ ์ž…๋ ฅ์„ ๋‹ค๋ค„๋ณธ ๊ฑด ์ฒ˜์Œ์ด์—ˆ๋Š”๋ฐ, ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.
๋‹จ์ˆœํ•œ Image.fillAmount์™€ RMS ๊ณ„์‚ฐ๋งŒ์œผ๋กœ๋„ ์Œ์„ฑ ์‹œ๊ฐํ™”๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ํฅ๋ฏธ๋กœ์› ๊ณ ,
์ถ”ํ›„์—๋Š” ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์Œ์„ฑ ์ธ์‹ ํŠธ๋ฆฌ๊ฑฐ๋‚˜ ์Œ์„ฑ ๊ธฐ๋ฐ˜ ์ธํ„ฐ๋ž™์…˜๋„ ๋งŒ๋“ค์–ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

profile
๊ฒŒ์ž„๊ฐœ๋ฐœ์ž๊ฟˆ๋‚˜๋ฌด

0๊ฐœ์˜ ๋Œ“๊ธ€