Unity 6.3
0 онлайн 102 гостей 3 в системе
Вход
Аудио Шаг 13 из 68

Пример. Создание корневого вывода

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

Сначала реализуйте RootOutputInstance.IRealtime. Для лучшей масштабируемости переместите интенсивные вычисления в задание, которое запускается на стадии обработки. Используйте временный NativeArray<float> в качестве промежуточного буфера. На заключительной стадии загородите задание и скопируйте результат в вывод.

[BurstCompile(CompileSynchronously = true)]
struct Realtime : RootOutputInstance.IRealtime
{
    internal NativeArray<float> phase; // Normalized phase accumulator in the range [0,1).
    internal float phaseIncrement; // Precomputed phase step.
    internal AudioFormat format; // Captured format from Configure.
    internal NativeArray<float> nativeBuffer; // Native backing buffer.
    JobHandle m_Job;

    struct Job : IJob
    {
        const float k_TwoPi = 2.0f * Mathf.PI;

        public NativeArray<float> phase;
        public float phaseIncrement;
        public AudioFormat format;
        public NativeArray<float> nativeBuffer;

        public void Execute()
        {
            // Use `nativeBuffer` as a backing buffer for the temporary channel buffer.
            var buffer = new ChannelBuffer(nativeBuffer, format.channelCount);

            for (var frame = 0; frame < buffer.frameCount; frame++)
            {
                var s = Mathf.Sin(phase[0] * k_TwoPi);

                for (var channel = 0; channel < buffer.channelCount; channel++)
                {
                    buffer[channel, frame] = s;
                }

                phase[0] += phaseIncrement;

                if (phase[0] >= 1.0f) phase[0] -= 1f;
            }
        }
    }

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

    public JobHandle EarlyProcessing(in RealtimeContext context, Pipe pipe) { return default; }

    public void Process(in RealtimeContext context, Pipe pipe, JobHandle input)
    {
        m_Job = new Job
        {
            phase = phase,
            phaseIncrement = phaseIncrement,
            format = format,
            nativeBuffer = nativeBuffer
        }.Schedule(input);
    }

    public void EndProcessing(in RealtimeContext context, Pipe pipe, ChannelBuffer output)
    {
        // Wait for the job to finish.
        m_Job.Complete();

        // Copy from the temp buffer to the output buffer.
        var buffer = new ChannelBuffer(nativeBuffer, format.channelCount);

        // Assumes format/channel layout matches `output`. If not, convert/mix here.
        for (var frame = 0; frame < output.frameCount; frame++)
        {
            for (var channel = 0; channel < output.channelCount; channel++)
            {
                output[channel, frame] = buffer[channel, frame];
            }
        }
    }

    public void RemovedFromProcessing()
    {
        // We'll dispose `nativeBuffer` in `Control.Dispose` or when we reconfigure.
    }
}

Затем реализуйте RootOutputInstance.IControl<Realtime> для настройки образца на части Realtime. Вы можете управлять сроком службы NativeArray из контролирующей части. Это гарантирует, что он будет правильно выделен при настройке и удаляется, когда больше не требуется, предотвращая утечки памяти и обеспечивая эффективное использование ресурсов.

struct Control : RootOutputInstance.IControl<Realtime>
{
    const float k_Frequency = 440.0f;

    public void Dispose(ControlContext context, ref Realtime realtime)
    {
        realtime.phase.Dispose();
        realtime.nativeBuffer.Dispose();
    }

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

    public Response OnMessage(ControlContext context, Pipe pipe, Message message)
    {
        return Response.Unhandled;
    }

    public JobHandle Configure(ControlContext context, ref Realtime realtime, in AudioFormat format)
    {
        realtime.format = format;
        realtime.phaseIncrement = k_Frequency / format.sampleRate;

        // (Re)allocate the temp buffer.

        if (realtime.nativeBuffer.IsCreated)
        {
            realtime.phase.Dispose();
            realtime.nativeBuffer.Dispose();
        }

        realtime.phase = new NativeArray<float>(1, Allocator.Persistent);
        realtime.nativeBuffer = new NativeArray<float>(format.bufferFrameCount * format.channelCount, Allocator.Persistent);

        return default;
    }
}

Наконец, добавьте MonoBehaviour для обработки выделения в Start и очистки в OnDestroy.

public class Driver : MonoBehaviour
{
    RootOutputInstance m_RootOutputInstance;

    void Start()
    {
        // Allocate the root output and attach it to the main audio output.
        m_RootOutputInstance = ControlContext.builtIn.AllocateRootOutput(new Realtime(), new Control());
    }

    void OnDestroy()
    {
        // Detach and destroy the root output.
        ControlContext.builtIn.Destroy(m_RootOutputInstance);
    }
}