/// <summary> /// Render callback for the output node. Can simulataneously write to a file. /// </summary> /// <returns>The render delegate.</returns> /// <param name="actionFlags">Action flags.</param> /// <param name="timeStamp">Time stamp.</param> /// <param name="busNumber">Bus number.</param> /// <param name="numberFrames">Number frames.</param> /// <param name="data">Data.</param> AudioUnitStatus OutputRenderDelegate(AudioUnitRenderActionFlags actionFlags, AudioTimeStamp timeStamp, uint busNumber, uint numberFrames, AudioBuffers data) { // propagate tempo change on start of a new cycle if (_tempoChanged) { PropagateTempoChange(_tempoChangeRatio); _tempoChanged = false; } var e = MixerNode.Render(ref actionFlags, timeStamp, busNumber, numberFrames, data); cycle++; // check for a queued layer change if (Metronome.Instance.NeedToChangeLayer == true) { Metronome.Instance.CycleToChange = cycle; Metronome.Instance.NeedToChangeLayer = false; Metronome.Instance.ChangeLayerTurnstyle.Set(); } else if (Metronome.Instance.NeedToChangeLayer == null) { // top off the fat forward double cycleDiff = cycle - Metronome.Instance.CycleToChange; Metronome.Instance.FastForwardChangedLayers(cycleDiff); foreach (KeyValuePair <int, Layer> pair in Metronome.Instance.LayersToChange) { Layer copy = pair.Value; Layer real = Metronome.Instance.Layers[pair.Key]; int numberRemoved = 0; bool isMuted = false; // remove old sources foreach (IStreamProvider src in real.GetAllStreams()) { RemoveStream(src); src.Dispose(); numberRemoved++; isMuted = src.IsMuted; } // transfer sources to real layer real.AudioSources = copy.AudioSources; real.BaseAudioSource = copy.BaseAudioSource; real.PitchSource = copy.PitchSource; real.BaseSourceName = copy.BaseSourceName; real.HasHiHatOpen = copy.HasHiHatOpen; real.HasHiHatClosed = copy.HasHiHatClosed; real.Beat = copy.Beat; foreach (IStreamProvider src in real.GetAllStreams().OrderBy(x => x.Info.HiHatStatus != StreamInfoProvider.HiHatStatuses.Down)) { src.IsMuted = isMuted; src.Layer = real; if (numberRemoved <= 0) { // it crashes if we try to add a rendercallback for preexisting bus Metronome.Instance.AddAudioSource(src); } else { Streams.Add(src); } numberRemoved--; } copy.AudioSources = null; copy.BaseAudioSource = null; copy.PitchSource = null; copy.Beat = null; Metronome.Instance.Layers.Remove(copy); foreach (IStreamProvider src in Streams) { // keep muting consistent when shuffling buffer indexs if (src.IsMuted) { EnableInput(src, false); } else { EnableInput(src, true); } SetPan(src, src.Pan); SetInputVolume(src, (float)src.Volume); } } Metronome.Instance.LayersToChange.Clear(); Metronome.Instance.NeedToChangeLayer = false; // trigger beat changed event AppKit.NSApplication.SharedApplication.BeginInvokeOnMainThread( () => { Metronome.Instance.OnBeatChanged(null); }); } // check if recording to file if (_fileRecordingQueued) { // convert the buffer using (AudioBuffers convBuffer = new AudioBuffers(1)) { convBuffer[0] = new AudioBuffer() { DataByteSize = data[0].DataByteSize, NumberChannels = 1, Data = Marshal.AllocHGlobal(sizeof(float) * data[0].DataByteSize) }; _converter.ConvertComplexBuffer((int)numberFrames, data, convBuffer); _file.Write(numberFrames, convBuffer); } } return(AudioUnitStatus.OK); }