CommandGenerator.cs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. using Ryujinx.Audio.Common;
  2. using Ryujinx.Audio.Renderer.Common;
  3. using Ryujinx.Audio.Renderer.Dsp.Command;
  4. using Ryujinx.Audio.Renderer.Dsp.State;
  5. using Ryujinx.Audio.Renderer.Parameter;
  6. using Ryujinx.Audio.Renderer.Server.Effect;
  7. using Ryujinx.Audio.Renderer.Server.Mix;
  8. using Ryujinx.Audio.Renderer.Server.Performance;
  9. using Ryujinx.Audio.Renderer.Server.Sink;
  10. using Ryujinx.Audio.Renderer.Server.Splitter;
  11. using Ryujinx.Audio.Renderer.Server.Voice;
  12. using Ryujinx.Audio.Renderer.Utils;
  13. using System;
  14. using System.Diagnostics;
  15. using System.Runtime.CompilerServices;
  16. namespace Ryujinx.Audio.Renderer.Server
  17. {
  18. public class CommandGenerator
  19. {
  20. private readonly CommandBuffer _commandBuffer;
  21. private readonly RendererSystemContext _rendererContext;
  22. private readonly VoiceContext _voiceContext;
  23. private readonly MixContext _mixContext;
  24. private readonly EffectContext _effectContext;
  25. private readonly SinkContext _sinkContext;
  26. private readonly SplitterContext _splitterContext;
  27. private readonly PerformanceManager _performanceManager;
  28. public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager)
  29. {
  30. _commandBuffer = commandBuffer;
  31. _rendererContext = rendererContext;
  32. _voiceContext = voiceContext;
  33. _mixContext = mixContext;
  34. _effectContext = effectContext;
  35. _sinkContext = sinkContext;
  36. _splitterContext = splitterContext;
  37. _performanceManager = performanceManager;
  38. _commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId);
  39. }
  40. private void GenerateDataSource(ref VoiceState voiceState, Memory<VoiceUpdateState> dspState, int channelIndex)
  41. {
  42. if (voiceState.MixId != Constants.UnusedMixId)
  43. {
  44. ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
  45. _commandBuffer.GenerateDepopPrepare(
  46. dspState,
  47. _rendererContext.DepopBuffer,
  48. mix.BufferCount,
  49. mix.BufferOffset,
  50. voiceState.NodeId,
  51. voiceState.WasPlaying);
  52. }
  53. else if (voiceState.SplitterId != Constants.UnusedSplitterId)
  54. {
  55. int destinationId = 0;
  56. while (true)
  57. {
  58. SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
  59. if (destination.IsNull)
  60. {
  61. break;
  62. }
  63. if (destination.IsConfigured())
  64. {
  65. int mixId = destination.DestinationId;
  66. if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
  67. {
  68. ref MixState mix = ref _mixContext.GetState(mixId);
  69. _commandBuffer.GenerateDepopPrepare(
  70. dspState,
  71. _rendererContext.DepopBuffer,
  72. mix.BufferCount,
  73. mix.BufferOffset,
  74. voiceState.NodeId,
  75. voiceState.WasPlaying);
  76. destination.MarkAsNeedToUpdateInternalState();
  77. }
  78. }
  79. }
  80. }
  81. if (!voiceState.WasPlaying)
  82. {
  83. Debug.Assert(voiceState.SampleFormat != SampleFormat.Adpcm || channelIndex == 0);
  84. if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
  85. {
  86. _commandBuffer.GenerateDataSourceVersion2(
  87. ref voiceState,
  88. dspState,
  89. (ushort)_rendererContext.MixBufferCount,
  90. (ushort)channelIndex,
  91. voiceState.NodeId);
  92. }
  93. else
  94. {
  95. switch (voiceState.SampleFormat)
  96. {
  97. case SampleFormat.PcmInt16:
  98. _commandBuffer.GeneratePcmInt16DataSourceVersion1(
  99. ref voiceState,
  100. dspState,
  101. (ushort)_rendererContext.MixBufferCount,
  102. (ushort)channelIndex,
  103. voiceState.NodeId);
  104. break;
  105. case SampleFormat.PcmFloat:
  106. _commandBuffer.GeneratePcmFloatDataSourceVersion1(
  107. ref voiceState,
  108. dspState,
  109. (ushort)_rendererContext.MixBufferCount,
  110. (ushort)channelIndex,
  111. voiceState.NodeId);
  112. break;
  113. case SampleFormat.Adpcm:
  114. _commandBuffer.GenerateAdpcmDataSourceVersion1(
  115. ref voiceState,
  116. dspState,
  117. (ushort)_rendererContext.MixBufferCount,
  118. voiceState.NodeId);
  119. break;
  120. default:
  121. throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
  122. }
  123. }
  124. }
  125. }
  126. private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
  127. {
  128. bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing();
  129. if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
  130. {
  131. Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
  132. Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
  133. _commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
  134. }
  135. else
  136. {
  137. for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
  138. {
  139. ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
  140. if (filter.Enable)
  141. {
  142. Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
  143. Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
  144. _commandBuffer.GenerateBiquadFilter(
  145. baseIndex,
  146. ref filter,
  147. stateMemory.Slice(i, 1),
  148. bufferOffset,
  149. bufferOffset,
  150. !voiceState.BiquadFilterNeedInitialization[i],
  151. nodeId);
  152. }
  153. }
  154. }
  155. }
  156. private void GenerateVoiceMixWithSplitter(
  157. SplitterDestination destination,
  158. Memory<VoiceUpdateState> state,
  159. uint bufferOffset,
  160. uint bufferCount,
  161. uint bufferIndex,
  162. int nodeId)
  163. {
  164. ReadOnlySpan<float> mixVolumes = destination.MixBufferVolume;
  165. ReadOnlySpan<float> previousMixVolumes = destination.PreviousMixBufferVolume;
  166. ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
  167. ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
  168. Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
  169. bool isFirstMixBuffer = true;
  170. for (int i = 0; i < bufferCount; i++)
  171. {
  172. float previousMixVolume = previousMixVolumes[i];
  173. float mixVolume = mixVolumes[i];
  174. if (mixVolume != 0.0f || previousMixVolume != 0.0f)
  175. {
  176. if (bqf0.Enable && bqf1.Enable)
  177. {
  178. _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
  179. previousMixVolume,
  180. mixVolume,
  181. bufferIndex,
  182. bufferOffset + (uint)i,
  183. i,
  184. state,
  185. ref bqf0,
  186. ref bqf1,
  187. bqfState[..1],
  188. bqfState.Slice(1, 1),
  189. bqfState.Slice(2, 1),
  190. bqfState.Slice(3, 1),
  191. !destination.IsBiquadFilterEnabledPrev(),
  192. !destination.IsBiquadFilterEnabledPrev(),
  193. true,
  194. isFirstMixBuffer,
  195. nodeId);
  196. destination.UpdateBiquadFilterEnabledPrev(0);
  197. destination.UpdateBiquadFilterEnabledPrev(1);
  198. }
  199. else if (bqf0.Enable)
  200. {
  201. _commandBuffer.GenerateBiquadFilterAndMix(
  202. previousMixVolume,
  203. mixVolume,
  204. bufferIndex,
  205. bufferOffset + (uint)i,
  206. i,
  207. state,
  208. ref bqf0,
  209. bqfState[..1],
  210. bqfState.Slice(1, 1),
  211. !destination.IsBiquadFilterEnabledPrev(),
  212. true,
  213. isFirstMixBuffer,
  214. nodeId);
  215. destination.UpdateBiquadFilterEnabledPrev(0);
  216. }
  217. else if (bqf1.Enable)
  218. {
  219. _commandBuffer.GenerateBiquadFilterAndMix(
  220. previousMixVolume,
  221. mixVolume,
  222. bufferIndex,
  223. bufferOffset + (uint)i,
  224. i,
  225. state,
  226. ref bqf1,
  227. bqfState[..1],
  228. bqfState.Slice(1, 1),
  229. !destination.IsBiquadFilterEnabledPrev(),
  230. true,
  231. isFirstMixBuffer,
  232. nodeId);
  233. destination.UpdateBiquadFilterEnabledPrev(1);
  234. }
  235. isFirstMixBuffer = false;
  236. }
  237. }
  238. }
  239. private void GenerateVoiceMix(
  240. ReadOnlySpan<float> mixVolumes,
  241. ReadOnlySpan<float> previousMixVolumes,
  242. Memory<VoiceUpdateState> state,
  243. uint bufferOffset,
  244. uint bufferCount,
  245. uint bufferIndex,
  246. int nodeId)
  247. {
  248. if (bufferCount > Constants.VoiceChannelCountMax)
  249. {
  250. _commandBuffer.GenerateMixRampGrouped(
  251. bufferCount,
  252. bufferIndex,
  253. bufferOffset,
  254. previousMixVolumes,
  255. mixVolumes,
  256. state,
  257. nodeId);
  258. }
  259. else
  260. {
  261. for (int i = 0; i < bufferCount; i++)
  262. {
  263. float previousMixVolume = previousMixVolumes[i];
  264. float mixVolume = mixVolumes[i];
  265. if (mixVolume != 0.0f || previousMixVolume != 0.0f)
  266. {
  267. _commandBuffer.GenerateMixRamp(
  268. previousMixVolume,
  269. mixVolume,
  270. bufferIndex,
  271. bufferOffset + (uint)i,
  272. i,
  273. state,
  274. nodeId);
  275. }
  276. }
  277. }
  278. }
  279. private void GenerateVoice(ref VoiceState voiceState)
  280. {
  281. int nodeId = voiceState.NodeId;
  282. uint channelsCount = voiceState.ChannelsCount;
  283. for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++)
  284. {
  285. Memory<VoiceUpdateState> dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]);
  286. ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]);
  287. PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm;
  288. if (voiceState.SampleFormat == SampleFormat.PcmInt16)
  289. {
  290. dataSourceDetailType = PerformanceDetailType.PcmInt16;
  291. }
  292. else if (voiceState.SampleFormat == SampleFormat.PcmFloat)
  293. {
  294. dataSourceDetailType = PerformanceDetailType.PcmFloat;
  295. }
  296. bool performanceInitialized = false;
  297. PerformanceEntryAddresses performanceEntry = new();
  298. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId))
  299. {
  300. performanceInitialized = true;
  301. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  302. }
  303. GenerateDataSource(ref voiceState, dspStateMemory, channelIndex);
  304. if (performanceInitialized)
  305. {
  306. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  307. }
  308. if (voiceState.WasPlaying)
  309. {
  310. voiceState.PreviousVolume = 0.0f;
  311. }
  312. else if (voiceState.HasAnyDestination())
  313. {
  314. performanceInitialized = false;
  315. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId))
  316. {
  317. performanceInitialized = true;
  318. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  319. }
  320. GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId);
  321. if (performanceInitialized)
  322. {
  323. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  324. }
  325. performanceInitialized = false;
  326. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId))
  327. {
  328. performanceInitialized = true;
  329. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  330. }
  331. _commandBuffer.GenerateVolumeRamp(
  332. voiceState.PreviousVolume,
  333. voiceState.Volume,
  334. _rendererContext.MixBufferCount + (uint)channelIndex,
  335. nodeId);
  336. if (performanceInitialized)
  337. {
  338. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  339. }
  340. voiceState.PreviousVolume = voiceState.Volume;
  341. if (voiceState.MixId == Constants.UnusedMixId)
  342. {
  343. if (voiceState.SplitterId != Constants.UnusedSplitterId)
  344. {
  345. int destinationId = channelIndex;
  346. while (true)
  347. {
  348. SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
  349. if (destination.IsNull)
  350. {
  351. break;
  352. }
  353. destinationId += (int)channelsCount;
  354. if (destination.IsConfigured())
  355. {
  356. int mixId = destination.DestinationId;
  357. if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
  358. {
  359. ref MixState mix = ref _mixContext.GetState(mixId);
  360. if (destination.IsBiquadFilterEnabled())
  361. {
  362. GenerateVoiceMixWithSplitter(
  363. destination,
  364. dspStateMemory,
  365. mix.BufferOffset,
  366. mix.BufferCount,
  367. _rendererContext.MixBufferCount + (uint)channelIndex,
  368. nodeId);
  369. }
  370. else
  371. {
  372. GenerateVoiceMix(
  373. destination.MixBufferVolume,
  374. destination.PreviousMixBufferVolume,
  375. dspStateMemory,
  376. mix.BufferOffset,
  377. mix.BufferCount,
  378. _rendererContext.MixBufferCount + (uint)channelIndex,
  379. nodeId);
  380. }
  381. destination.MarkAsNeedToUpdateInternalState();
  382. }
  383. }
  384. }
  385. }
  386. }
  387. else
  388. {
  389. ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
  390. performanceInitialized = false;
  391. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId))
  392. {
  393. performanceInitialized = true;
  394. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  395. }
  396. GenerateVoiceMix(
  397. channelResource.Mix.AsSpan(),
  398. channelResource.PreviousMix.AsSpan(),
  399. dspStateMemory,
  400. mix.BufferOffset,
  401. mix.BufferCount,
  402. _rendererContext.MixBufferCount + (uint)channelIndex,
  403. nodeId);
  404. if (performanceInitialized)
  405. {
  406. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  407. }
  408. channelResource.UpdateState();
  409. }
  410. for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++)
  411. {
  412. voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable;
  413. }
  414. }
  415. }
  416. }
  417. public void GenerateVoices()
  418. {
  419. for (int i = 0; i < _voiceContext.GetCount(); i++)
  420. {
  421. ref VoiceState sortedState = ref _voiceContext.GetSortedState(i);
  422. if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext))
  423. {
  424. int nodeId = sortedState.NodeId;
  425. PerformanceEntryAddresses performanceEntry = new();
  426. bool performanceInitialized = false;
  427. if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId))
  428. {
  429. performanceInitialized = true;
  430. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  431. }
  432. GenerateVoice(ref sortedState);
  433. if (performanceInitialized)
  434. {
  435. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  436. }
  437. }
  438. }
  439. _splitterContext.UpdateInternalState();
  440. }
  441. public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId)
  442. {
  443. _commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId);
  444. }
  445. private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId)
  446. {
  447. Debug.Assert(effect.Type == EffectType.BufferMix);
  448. if (effect.IsEnabled)
  449. {
  450. for (int i = 0; i < effect.Parameter.MixesCount; i++)
  451. {
  452. if (effect.Parameter.Volumes[i] != 0.0f)
  453. {
  454. _commandBuffer.GenerateMix(
  455. (uint)bufferOffset + effect.Parameter.Input[i],
  456. (uint)bufferOffset + effect.Parameter.Output[i],
  457. nodeId,
  458. effect.Parameter.Volumes[i]);
  459. }
  460. }
  461. }
  462. }
  463. private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId)
  464. {
  465. Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer);
  466. if (effect.IsEnabled)
  467. {
  468. effect.GetWorkBuffer(0);
  469. effect.GetWorkBuffer(1);
  470. }
  471. if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 0)
  472. {
  473. int i = 0;
  474. uint writeOffset = 0;
  475. for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
  476. {
  477. uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
  478. uint updateCount;
  479. if (channelIndex != 1)
  480. {
  481. updateCount = 0;
  482. }
  483. else
  484. {
  485. updateCount = newUpdateCount;
  486. }
  487. _commandBuffer.GenerateAuxEffect(
  488. bufferOffset,
  489. effect.Parameter.Input[i],
  490. effect.Parameter.Output[i],
  491. ref effect.State,
  492. effect.IsEnabled,
  493. effect.Parameter.BufferStorageSize,
  494. effect.State.SendBufferInfoBase,
  495. effect.State.ReturnBufferInfoBase,
  496. updateCount,
  497. writeOffset,
  498. nodeId);
  499. writeOffset = newUpdateCount;
  500. i++;
  501. }
  502. }
  503. }
  504. private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported)
  505. {
  506. Debug.Assert(effect.Type == EffectType.Delay);
  507. ulong workBuffer = effect.GetWorkBuffer(-1);
  508. _commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
  509. }
  510. private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
  511. {
  512. Debug.Assert(effect.Type == EffectType.Reverb);
  513. ulong workBuffer = effect.GetWorkBuffer(-1);
  514. _commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
  515. }
  516. private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported)
  517. {
  518. Debug.Assert(effect.Type == EffectType.Reverb3d);
  519. ulong workBuffer = effect.GetWorkBuffer(-1);
  520. _commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
  521. }
  522. private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId)
  523. {
  524. Debug.Assert(effect.Type == EffectType.BiquadFilter);
  525. if (effect.IsEnabled)
  526. {
  527. bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
  528. (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
  529. BiquadFilterParameter parameter = new()
  530. {
  531. Enable = true,
  532. };
  533. effect.Parameter.Denominator.AsSpan().CopyTo(parameter.Denominator.AsSpan());
  534. effect.Parameter.Numerator.AsSpan().CopyTo(parameter.Numerator.AsSpan());
  535. for (int i = 0; i < effect.Parameter.ChannelCount; i++)
  536. {
  537. _commandBuffer.GenerateBiquadFilter(
  538. (int)bufferOffset,
  539. ref parameter,
  540. effect.State.Slice(i, 1),
  541. effect.Parameter.Input[i],
  542. effect.Parameter.Output[i],
  543. needInitialization,
  544. nodeId);
  545. }
  546. }
  547. else
  548. {
  549. for (int i = 0; i < effect.Parameter.ChannelCount; i++)
  550. {
  551. uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i];
  552. uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i];
  553. // If the input and output isn't the same, generate a command.
  554. if (inputBufferIndex != outputBufferIndex)
  555. {
  556. _commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId);
  557. }
  558. }
  559. }
  560. }
  561. private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId)
  562. {
  563. Debug.Assert(effect.Type == EffectType.Limiter);
  564. ulong workBuffer = effect.GetWorkBuffer(-1);
  565. if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported())
  566. {
  567. Memory<EffectResultState> dspResultState;
  568. if (effect.Parameter.StatisticsEnabled)
  569. {
  570. dspResultState = _effectContext.GetDspStateMemory(effectId);
  571. }
  572. else
  573. {
  574. dspResultState = Memory<EffectResultState>.Empty;
  575. }
  576. _commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId);
  577. }
  578. else
  579. {
  580. _commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
  581. }
  582. }
  583. private void GenerateCaptureEffect(uint bufferOffset, CaptureBufferEffect effect, int nodeId)
  584. {
  585. Debug.Assert(effect.Type == EffectType.CaptureBuffer);
  586. if (effect.IsEnabled)
  587. {
  588. effect.GetWorkBuffer(0);
  589. }
  590. if (effect.State.SendBufferInfoBase != 0)
  591. {
  592. int i = 0;
  593. uint writeOffset = 0;
  594. for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
  595. {
  596. uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
  597. uint updateCount;
  598. if (channelIndex != 1)
  599. {
  600. updateCount = 0;
  601. }
  602. else
  603. {
  604. updateCount = newUpdateCount;
  605. }
  606. _commandBuffer.GenerateCaptureEffect(
  607. bufferOffset,
  608. effect.Parameter.Input[i],
  609. effect.State.SendBufferInfo,
  610. effect.IsEnabled,
  611. effect.Parameter.BufferStorageSize,
  612. effect.State.SendBufferInfoBase,
  613. updateCount,
  614. writeOffset,
  615. nodeId);
  616. writeOffset = newUpdateCount;
  617. i++;
  618. }
  619. }
  620. }
  621. private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId, int effectId)
  622. {
  623. Debug.Assert(effect.Type == EffectType.Compressor);
  624. Memory<EffectResultState> dspResultState;
  625. if (effect.Parameter.StatisticsEnabled)
  626. {
  627. dspResultState = _effectContext.GetDspStateMemory(effectId);
  628. }
  629. else
  630. {
  631. dspResultState = Memory<EffectResultState>.Empty;
  632. }
  633. _commandBuffer.GenerateCompressorEffect(
  634. bufferOffset,
  635. effect.Parameter,
  636. effect.State,
  637. dspResultState,
  638. effect.IsEnabled,
  639. nodeId);
  640. }
  641. private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
  642. {
  643. int nodeId = mix.NodeId;
  644. bool isFinalMix = mix.MixId == Constants.FinalMixId;
  645. PerformanceEntryAddresses performanceEntry = new();
  646. bool performanceInitialized = false;
  647. if (_performanceManager != null && _performanceManager.GetNextEntry(
  648. out performanceEntry,
  649. effect.GetPerformanceDetailType(),
  650. isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
  651. nodeId))
  652. {
  653. performanceInitialized = true;
  654. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  655. }
  656. switch (effect.Type)
  657. {
  658. case EffectType.BufferMix:
  659. GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId);
  660. break;
  661. case EffectType.AuxiliaryBuffer:
  662. GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId);
  663. break;
  664. case EffectType.Delay:
  665. GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
  666. break;
  667. case EffectType.Reverb:
  668. GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
  669. break;
  670. case EffectType.Reverb3d:
  671. GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
  672. break;
  673. case EffectType.BiquadFilter:
  674. GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
  675. break;
  676. case EffectType.Limiter:
  677. GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId);
  678. break;
  679. case EffectType.CaptureBuffer:
  680. GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
  681. break;
  682. case EffectType.Compressor:
  683. GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
  684. break;
  685. default:
  686. throw new NotImplementedException($"Unsupported effect type {effect.Type}");
  687. }
  688. if (performanceInitialized)
  689. {
  690. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  691. }
  692. effect.UpdateForCommandGeneration();
  693. }
  694. private void GenerateEffects(ref MixState mix)
  695. {
  696. ReadOnlySpan<int> effectProcessingOrderArray = mix.EffectProcessingOrderArray;
  697. Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty);
  698. for (int i = 0; i < _effectContext.GetCount(); i++)
  699. {
  700. int effectOrder = effectProcessingOrderArray[i];
  701. if (effectOrder == Constants.InvalidProcessingOrder)
  702. {
  703. break;
  704. }
  705. // BaseEffect is a class, we don't need to pass it by ref
  706. BaseEffect effect = _effectContext.GetEffect(effectOrder);
  707. Debug.Assert(effect.Type != EffectType.Invalid);
  708. Debug.Assert(effect.MixId == mix.MixId);
  709. if (!effect.ShouldSkip())
  710. {
  711. GenerateEffect(ref mix, effectOrder, effect);
  712. }
  713. }
  714. }
  715. private void GenerateMixWithSplitter(
  716. uint inputBufferIndex,
  717. uint outputBufferIndex,
  718. float volume,
  719. SplitterDestination destination,
  720. ref bool isFirstMixBuffer,
  721. int nodeId)
  722. {
  723. ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
  724. ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
  725. Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
  726. if (bqf0.Enable && bqf1.Enable)
  727. {
  728. _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
  729. 0f,
  730. volume,
  731. inputBufferIndex,
  732. outputBufferIndex,
  733. 0,
  734. Memory<VoiceUpdateState>.Empty,
  735. ref bqf0,
  736. ref bqf1,
  737. bqfState[..1],
  738. bqfState.Slice(1, 1),
  739. bqfState.Slice(2, 1),
  740. bqfState.Slice(3, 1),
  741. !destination.IsBiquadFilterEnabledPrev(),
  742. !destination.IsBiquadFilterEnabledPrev(),
  743. false,
  744. isFirstMixBuffer,
  745. nodeId);
  746. destination.UpdateBiquadFilterEnabledPrev(0);
  747. destination.UpdateBiquadFilterEnabledPrev(1);
  748. }
  749. else if (bqf0.Enable)
  750. {
  751. _commandBuffer.GenerateBiquadFilterAndMix(
  752. 0f,
  753. volume,
  754. inputBufferIndex,
  755. outputBufferIndex,
  756. 0,
  757. Memory<VoiceUpdateState>.Empty,
  758. ref bqf0,
  759. bqfState[..1],
  760. bqfState.Slice(1, 1),
  761. !destination.IsBiquadFilterEnabledPrev(),
  762. false,
  763. isFirstMixBuffer,
  764. nodeId);
  765. destination.UpdateBiquadFilterEnabledPrev(0);
  766. }
  767. else if (bqf1.Enable)
  768. {
  769. _commandBuffer.GenerateBiquadFilterAndMix(
  770. 0f,
  771. volume,
  772. inputBufferIndex,
  773. outputBufferIndex,
  774. 0,
  775. Memory<VoiceUpdateState>.Empty,
  776. ref bqf1,
  777. bqfState[..1],
  778. bqfState.Slice(1, 1),
  779. !destination.IsBiquadFilterEnabledPrev(),
  780. false,
  781. isFirstMixBuffer,
  782. nodeId);
  783. destination.UpdateBiquadFilterEnabledPrev(1);
  784. }
  785. isFirstMixBuffer = false;
  786. }
  787. private void GenerateMix(ref MixState mix)
  788. {
  789. if (mix.HasAnyDestination())
  790. {
  791. Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId);
  792. if (mix.DestinationMixId == Constants.UnusedMixId)
  793. {
  794. if (mix.DestinationSplitterId != Constants.UnusedSplitterId)
  795. {
  796. int destinationId = 0;
  797. while (true)
  798. {
  799. int destinationIndex = destinationId++;
  800. SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
  801. if (destination.IsNull)
  802. {
  803. break;
  804. }
  805. if (destination.IsConfigured())
  806. {
  807. int mixId = destination.DestinationId;
  808. if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt)
  809. {
  810. ref MixState destinationMix = ref _mixContext.GetState(mixId);
  811. uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
  812. bool isFirstMixBuffer = true;
  813. for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
  814. {
  815. float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
  816. if (volume != 0.0f)
  817. {
  818. if (destination.IsBiquadFilterEnabled())
  819. {
  820. GenerateMixWithSplitter(
  821. inputBufferIndex,
  822. destinationMix.BufferOffset + bufferDestinationIndex,
  823. volume,
  824. destination,
  825. ref isFirstMixBuffer,
  826. mix.NodeId);
  827. }
  828. else
  829. {
  830. _commandBuffer.GenerateMix(
  831. inputBufferIndex,
  832. destinationMix.BufferOffset + bufferDestinationIndex,
  833. mix.NodeId,
  834. volume);
  835. }
  836. }
  837. }
  838. }
  839. }
  840. }
  841. }
  842. }
  843. else
  844. {
  845. ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId);
  846. for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++)
  847. {
  848. for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
  849. {
  850. float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex);
  851. if (volume != 0.0f)
  852. {
  853. _commandBuffer.GenerateMix(
  854. mix.BufferOffset + bufferIndex,
  855. destinationMix.BufferOffset + bufferDestinationIndex,
  856. mix.NodeId,
  857. volume);
  858. }
  859. }
  860. }
  861. }
  862. }
  863. }
  864. private void GenerateSubMix(ref MixState subMix)
  865. {
  866. _commandBuffer.GenerateDepopForMixBuffers(
  867. _rendererContext.DepopBuffer,
  868. subMix.BufferOffset,
  869. subMix.BufferCount,
  870. subMix.NodeId,
  871. subMix.SampleRate);
  872. GenerateEffects(ref subMix);
  873. PerformanceEntryAddresses performanceEntry = new();
  874. int nodeId = subMix.NodeId;
  875. bool performanceInitialized = false;
  876. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId))
  877. {
  878. performanceInitialized = true;
  879. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  880. }
  881. GenerateMix(ref subMix);
  882. if (performanceInitialized)
  883. {
  884. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  885. }
  886. }
  887. public void GenerateSubMixes()
  888. {
  889. for (int id = 0; id < _mixContext.GetCount(); id++)
  890. {
  891. ref MixState sortedState = ref _mixContext.GetSortedState(id);
  892. if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId)
  893. {
  894. int nodeId = sortedState.NodeId;
  895. PerformanceEntryAddresses performanceEntry = new();
  896. bool performanceInitialized = false;
  897. if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId))
  898. {
  899. performanceInitialized = true;
  900. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  901. }
  902. GenerateSubMix(ref sortedState);
  903. if (performanceInitialized)
  904. {
  905. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  906. }
  907. }
  908. }
  909. }
  910. private void GenerateFinalMix()
  911. {
  912. ref MixState finalMix = ref _mixContext.GetFinalState();
  913. _commandBuffer.GenerateDepopForMixBuffers(
  914. _rendererContext.DepopBuffer,
  915. finalMix.BufferOffset,
  916. finalMix.BufferCount,
  917. finalMix.NodeId,
  918. finalMix.SampleRate);
  919. GenerateEffects(ref finalMix);
  920. PerformanceEntryAddresses performanceEntry = new();
  921. int nodeId = finalMix.NodeId;
  922. bool performanceInitialized = false;
  923. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId))
  924. {
  925. performanceInitialized = true;
  926. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  927. }
  928. // Only generate volume command if the volume isn't 100%.
  929. if (finalMix.Volume != 1.0f)
  930. {
  931. for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++)
  932. {
  933. bool performanceSubInitialized = false;
  934. if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId))
  935. {
  936. performanceSubInitialized = true;
  937. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  938. }
  939. _commandBuffer.GenerateVolume(
  940. finalMix.Volume,
  941. finalMix.BufferOffset + bufferIndex,
  942. nodeId);
  943. if (performanceSubInitialized)
  944. {
  945. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  946. }
  947. }
  948. }
  949. if (performanceInitialized)
  950. {
  951. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  952. }
  953. }
  954. public void GenerateFinalMixes()
  955. {
  956. int nodeId = _mixContext.GetFinalState().NodeId;
  957. PerformanceEntryAddresses performanceEntry = new();
  958. bool performanceInitialized = false;
  959. if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId))
  960. {
  961. performanceInitialized = true;
  962. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
  963. }
  964. GenerateFinalMix();
  965. if (performanceInitialized)
  966. {
  967. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId);
  968. }
  969. }
  970. private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix)
  971. {
  972. _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId);
  973. }
  974. private void GenerateDevice(DeviceSink sink, ref MixState finalMix)
  975. {
  976. if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null)
  977. {
  978. sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate();
  979. }
  980. bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled;
  981. if (useCustomDownMixingCommand)
  982. {
  983. _commandBuffer.GenerateDownMixSurroundToStereo(
  984. finalMix.BufferOffset,
  985. sink.Parameter.Input.AsSpan(),
  986. sink.Parameter.Input.AsSpan(),
  987. sink.DownMixCoefficients,
  988. Constants.InvalidNodeId);
  989. }
  990. // NOTE: We do the downmixing at the DSP level as it's easier that way.
  991. else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
  992. {
  993. _commandBuffer.GenerateDownMixSurroundToStereo(
  994. finalMix.BufferOffset,
  995. sink.Parameter.Input.AsSpan(),
  996. sink.Parameter.Input.AsSpan(),
  997. Constants.DefaultSurroundToStereoCoefficients,
  998. Constants.InvalidNodeId);
  999. }
  1000. CommandList commandList = _commandBuffer.CommandList;
  1001. if (sink.UpsamplerState != null)
  1002. {
  1003. _commandBuffer.GenerateUpsample(
  1004. finalMix.BufferOffset,
  1005. sink.UpsamplerState,
  1006. sink.Parameter.InputCount,
  1007. sink.Parameter.Input.AsSpan(),
  1008. commandList.BufferCount,
  1009. commandList.SampleCount,
  1010. commandList.SampleRate,
  1011. Constants.InvalidNodeId);
  1012. }
  1013. _commandBuffer.GenerateDeviceSink(
  1014. finalMix.BufferOffset,
  1015. sink,
  1016. _rendererContext.SessionId,
  1017. commandList.Buffers,
  1018. Constants.InvalidNodeId);
  1019. }
  1020. private void GenerateSink(BaseSink sink, ref MixState finalMix)
  1021. {
  1022. bool performanceInitialized = false;
  1023. PerformanceEntryAddresses performanceEntry = new();
  1024. if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId))
  1025. {
  1026. performanceInitialized = true;
  1027. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId);
  1028. }
  1029. if (!sink.ShouldSkip)
  1030. {
  1031. switch (sink.Type)
  1032. {
  1033. case SinkType.CircularBuffer:
  1034. GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix);
  1035. break;
  1036. case SinkType.Device:
  1037. GenerateDevice((DeviceSink)sink, ref finalMix);
  1038. break;
  1039. default:
  1040. throw new NotImplementedException($"Unsupported sink type {sink.Type}");
  1041. }
  1042. sink.UpdateForCommandGeneration();
  1043. }
  1044. if (performanceInitialized)
  1045. {
  1046. GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId);
  1047. }
  1048. }
  1049. public void GenerateSinks()
  1050. {
  1051. ref MixState finalMix = ref _mixContext.GetFinalState();
  1052. for (int i = 0; i < _sinkContext.GetCount(); i++)
  1053. {
  1054. // BaseSink is a class, we don't need to pass it by ref
  1055. BaseSink sink = _sinkContext.GetSink(i);
  1056. if (sink.IsUsed)
  1057. {
  1058. GenerateSink(sink, ref finalMix);
  1059. }
  1060. }
  1061. }
  1062. }
  1063. }