Пример. Создание корневого вывода
В этом примере показана настройка простого синусоидального корневого выхода и его подключение к основному аудиовыходу.
Сначала реализуйте 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);
}
}