Unity 6.3
0 онлайн 25 гостей 3 в системе
Вход

Пример: Создание генератора

В этом примере показана настройка генератора синусоидального сигнала и его подключение к AudioSource с помощью рабочего процесса, основанного на компонентах.

Он состоит из двух частей:

  • Часть I (минимальная): Используется синусоидальная волна с фиксированной частотой.
  • Часть II (параметризованная): Расширяет минимальный пример с управлением частотой.

Обязательно включает

Некоторые коды в этом разделе требуют следующего, включая:

using Unity.Burst;
using Unity.IntegerTime;
using UnityEngine;
using UnityEngine.Audio;
using static UnityEngine.Audio.ProcessorInstance;

Убедитесь, что вы добавили эти включения в начале вашего кода для их компиляции.

Часть I — Минимальный пример

Осуществление GeneratorInstance.IRealtime для генерации аудио в Process. Структура реального времени хранит накопитель фазы и предвычисленный инкремент.

[BurstCompile(CompileSynchronously = true)]
struct Realtime : GeneratorInstance.IRealtime
{
    private const float k_TwoPi = 2.0f * Mathf.PI;

    // Normalized phase accumulator in [0, 1).
    private float phase;

    // Precomputed phase step per sample (cycles per sample).
    internal float phaseIncrement;

    // Capabilities must match those reported from IAudioGenerator and IRealtime.
    public bool isFinite => false;
    public bool isRealtime => false;
    public DiscreteTime? length => null;

    // Called when the real-time side of the graph updates (e.g., new control data available).
    // Keep this method allocation-free and exception-free.
    public void Update(UpdatedDataContext context, Pipe pipe) { }

    public GeneratorInstance.Result Process(
        in RealtimeContext context, Pipe pipe,
        ChannelBuffer buffer,
        GeneratorInstance.Arguments args)
    {
        // Compute a mono sine and copy it to all channels.
        for (int frame = 0; frame < buffer.frameCount; frame++)
        {
            float s = Mathf.Sin(phase * k_TwoPi);

            for (int ch = 0; ch < buffer.channelCount; ch++)
                buffer[ch, frame] = s;

            // Advance and wrap the phase into [0, 1).
            phase += phaseIncrement;
            if (phase >= 1.0f) phase -= 1.0f;
        }

        // Return the number of frames written.
        return buffer.frameCount;
    }
}

Затем реализуйте GeneratorInstance.IControl<Realtime>. Используйте Configure для установки любых полей реального времени, которые зависят от формата.

struct Control : GeneratorInstance.IControl<Realtime>
{
    private const float k_Frequency = 440.0f; // A4

    // Dispose is called when the generator instance is destroyed.
    public void Dispose(ControlContext context, ref Realtime realtime) { }

    // Control-side tick; e.g., poll external state or schedule events.
    public void Update(ControlContext context, Pipe pipe) { }

    // Optional message hook; return `Unhandled` for messages you don't consume.
    public Response OnMessage(ControlContext context, Pipe pipe, Message message) => Response.Unhandled;

    // Called initially when constructed and additionally when the system changes configuration.
    public void Configure(
        ControlContext context,
        ref Realtime realtime,
        in AudioFormat format,
        out GeneratorInstance.Setup setup,
        ref GeneratorInstance.Properties properties)
    {
        // Configure real-time fields that depend on the audio format.
        realtime.phaseIncrement = k_Frequency / format.sampleRate; // cycles/sample

        // Prefer matching the host's format when possible to avoid conversion.
        setup = new GeneratorInstance.Setup(format.speakerMode, format.sampleRate);
    }
}

Наконец, связать все вместе в MonoBehaviour, который реализует IAudioGenerator:

public class Driver : MonoBehaviour, IAudioGenerator
{
    public bool isFinite => false;
    public bool isRealtime => false;
    public DiscreteTime? length => null;

    public GeneratorInstance CreateInstance(
        ControlContext context,
        AudioFormat? nestedConfiguration,
        CreationParameters creationParameters)
    {
        // Allocate a new generator instance pairing the realtime and control structs.
        return context.AllocateGenerator(new Realtime(), new Control(), nestedConfiguration, creationParameters);
    }
}

Наконец, добавьте компонент SineGeneratorDriver в GameObject, назначьте его как Generator в AudioSourceи введите режим воспроизведения, чтобы услышать синусоидальный звук.

Часть II — Настройка частоты

Чтобы параметризовать частоту генератора синусоидальных колебаний, начните с добавления поля frequency в драйвер. Затем отправьте обновления частоты в часть реального времени с помощью канала в методе Update, используя легкий тип значения для представления сообщений об изменении частоты:

// Small value-type message for the pipe.
readonly struct FrequencyEvent
{
    public readonly float value;
    public FrequencyEvent(float value) => this.value = value;
}

Во-вторых, обновите структуру реального времени, чтобы она хранила frequency и sampleRate. В Update вместо &quot; метода &quot; читать &quot; любого рассматриваемого метода &quot; FrequencyEvent Этот подход обеспечивает, что аудио поток безопасно обрабатывает любое количество ожидающих событий, не бросая ошибок на аудио поток. Затем фазовый прирост рассчитывается для каждого аудио блока на основе текущей частоты и частоты дискретизации.

[BurstCompile(CompileSynchronously = true)]
struct Realtime : GeneratorInstance.IRealtime
{
    private const float k_TwoPi = 2.0f * Mathf.PI;

    private float phase;       // [0, 1)
    internal float frequency;  // Hz, set from control messages
    internal float sampleRate; // Hz, set from Configure

    public bool isFinite => false;
    public bool isRealtime => false;
    public DiscreteTime? length => null;

    public void Update(UpdatedDataContext context, Pipe pipe)
    {
        // Iterate over all available events (newer overwrite older).
        foreach (var element in pipe.GetAvailableData(context))
        {
            if (element.TryGetData(out FrequencyEvent evt))
            {
                frequency = evt.value;
            }

            // Ignore other message types gracefully.
        }
    }

    public GeneratorInstance.Result Process(
        in RealtimeContext context,
        Pipe pipe,
        ChannelBuffer buffer,
        GeneratorInstance.Arguments args)
    {
        // Compute increment locally from current control values.
        float phaseIncrement = frequency / sampleRate;

        for (int frame = 0; frame < buffer.frameCount; frame++)
        {
            float s = Mathf.Sin(phase * k_TwoPi);

            for (int ch = 0; ch < buffer.channelCount; ch++)
                buffer[ch, frame] = s;

            phase += phaseIncrement;
            if (phase >= 1.0f) phase -= 1.0f;
        }

        return buffer.frameCount;
    }
}

В управляющей структуре захватите время выполнения sampleRate в Configure и настроить моно выход. Перехват сообщений, отправленных через экземпляр и перенаправить их на часть реального времени.

struct Control : GeneratorInstance.IControl<Realtime>
{
    public void Dispose(ControlContext context, ref Realtime realtime) { }

    public void Update(ControlContext context, Pipe pipe) { }

    public Response OnMessage(ControlContext context, Pipe pipe, Message message)
    {
        if (message.Is<FrequencyEvent>())
        {
            pipe.SendData(context, message.Get<FrequencyEvent>());

            return Response.Handled;
        }

        return Response.Unhandled;
    }

    public void Configure(
        ControlContext context,
        ref Realtime realtime,
        in AudioFormat format,
        out GeneratorInstance.Setup setup,
        ref GeneratorInstance.Properties properties)
    {
        realtime.sampleRate = format.sampleRate;

        setup = new GeneratorInstance.Setup(AudioSpeakerMode.Mono, format.sampleRate);
    }
}

Наконец, добавьте ползунок частоты и обновляйте аудио экземпляр только при изменении значения, чтобы избежать спама в контроллере.

Примечание: Всегда охраняйте доступ к экземпляру. Он может отсутствовать или быть уничтожен, если источник звука был остановлен в другом месте.

public class Driver : MonoBehaviour, IAudioGenerator
{
    private AudioSource m_AudioSource;

    [Range(100f, 10000f)]
    public float frequency = 440.0f; // A4

    private float m_PreviousFrequency;

    public bool isFinite => false;
    public bool isRealtime => false;
    public DiscreteTime? length => null;

    public GeneratorInstance CreateInstance(
        ControlContext context,
        AudioFormat? nestedConfiguration,
        CreationParameters creationParameters)
        => context.AllocateGenerator(new Realtime(), new Control());

    private void Awake()
    {
        // Expects an AudioSource on the same GameObject.
        m_AudioSource = GetComponent<AudioSource>();
    }

    private void Update()
    {
        // Early out if unchanged (use Approximate to avoid redundant updates).
        if (Mathf.Approximately(frequency, m_PreviousFrequency))
            return;

        // Access the instance via the AudioSource.
        var instance = m_AudioSource.generatorInstance;

        // Guard the handle: instance may be missing or have been destroyed, if the audio source was stopped elsewhere.
        if (!ControlContext.builtIn.Exists(instance))
            return;

        var message = new FrequencyEvent(frequency);

        // Send frequency change to the control side.
        ControlContext.builtIn.SendMessage(instance, ref message);
        m_PreviousFrequency = frequency;
    }
}