Ryle Radio 1.1.2
An open-source "radio" system for Unity, allowing tracks, tuning, broadcasters, and more!
Loading...
Searching...
No Matches
RadioOutput.cs
1using NaughtyAttributes;
4using System;
5using System.Collections;
6using System.Collections.Generic;
7using System.Linq;
8using UnityEngine;
9using Random = UnityEngine.Random;
10
12{
13
14 /// <summary>
15 /// The main scene component for a radio that plays it through an AudioSource.
16 ///
17 /// <b>See </b>\ref RadioTrackPlayer as well for more info on how playback works
18 /// </summary>
19 [AddComponentMenu("Ryle Radio/Radio Output")]
20 [RequireComponent(typeof(AudioSource))]
22 {
23 /// <summary>
24 /// The method by which a RadioTrackPlayer is chosen from this output. Really only matters when you're playing the same track repeatedly causing overlaps
25 /// </summary>
27 {
28 Youngest, ///< Selects the youngest player
29 Oldest, ///< Selects the oldest player
30 Random ///< Selects a random player (probably useless but funny to have)
31 }
32
33
34 /// <summary>
35 /// The current tune value of this output- akin to the frequency of a real radio. Controls what tracks can be heard through tune power. Never modify this directly except for in the inspector, use \ref Tune instead
36 /// </summary>
37 [SerializeField, Range(RadioData.LOW_TUNE, RadioData.HIGH_TUNE), OnValueChanged("ExecOnTune")]
38 protected float tune;
39
40 /// <summary>
41 /// The players used by this output
42 /// </summary>
43 protected List<RadioTrackPlayer> players = new();
44
45 /// <summary>
46 /// The position of this object as of the last frame update. We can't access `transform.position` from the audio thread, so we cache it here
47 /// </summary>
48 protected Vector3 cachedPos;
49
50 /// <summary>
51 /// The normal sample rate of this output, applied to each \ref RadioTrackPlayer
52 /// </summary>
53 private float baseSampleRate;
54
55 /// <summary>
56 /// Called at the end of every audio cycle so that we don't interrupt threads when manipulating RadioTrackPlayers
57 /// </summary>
58 private Action playEvents = () => { };
59
60
61 /// <summary>
62 /// The AudioClip used internally to emulate audio streaming on WebGL- half of this is played at a time, and the other half is generated in advance
63 /// </summary>
64 private AudioClip cacheClipSecond;
65 private AudioClip cacheClipFirst;
66
67 /// <summary>
68 /// The total number of samples in each half of the cache clip
69 /// </summary>
70 private int totalCacheChunkLength = 0;
71
72 /// <summary>
73 /// The size of half the cache clip used for WebGL playback- this is the amount of audio the cache clip stores in advance during playback before switching to the other half of the clip
74 /// </summary>
75 private float cacheClipChunkDuration = .04f;
76
77 /// <summary>
78 /// The AudioSource attached to this Output. This is assigned automatically.
79 /// </summary>
80 private AudioSource source;
81
82 /// <summary>
83 /// Whether or not the cache clip playback is in the second half of the clip
84 /// </summary>
85 private bool isInFirstHalf = false;
86
87 /// <summary>
88 /// Whether or not the clips in this radio have been loaded
89 /// </summary>
90 private bool loaded = false;
91
92 private float[] cacheClipSamples;
93
94
95 /// <summary>
96 /// Every \ref RadioObserver associated with this output
97 /// </summary>
98 public List<RadioObserver> Observers { get; private set; } = new();
99
100 /// <summary>
101 /// Event called whenever \ref Tune is changed
102 /// </summary>
103 public Action<float> OnTune { get; set; } = new(_ => { });
104
105 /// <summary>
106 /// Event called when this output is finished initializing- mainly useful for WebGL when we use \ref AwaitLoadingThenInit
107 /// </summary>
108 public Action OnInit { get; set; } = new(() => { });
109
110 /// <summary>
111 /// The \ref tune clamped to the full range
112 /// </summary>
113 public float Tune
114 {
115 get => tune;
116 set
117 {
118 // clamp the tune to the available range (not needed in inspector, needed if tune changed in code)
119 tune = Mathf.Clamp(value, RadioData.LOW_TUNE, RadioData.HIGH_TUNE);
120
121 // invoke the tune change callback
122 OnTune(tune);
123 }
124 }
125
126 /// <summary>
127 /// \ref Tune scaled to [0 - 1], useful for UI
128 /// </summary>
129 public float Tune01
130 {
131 get => Mathf.InverseLerp(RadioData.LOW_TUNE, RadioData.HIGH_TUNE, Tune);
132 }
133
134 /// <summary>
135 /// \ref Tune with limited decimal points- looks better when displayed, more like an actual radio
136 /// </summary>
137 public float DisplayTune
138 {
139 get => Mathf.Round(tune * 10) / 10;
140 }
141
142 /// <summary>
143 /// Public accessor for \ref loaded
144 /// </summary>
145 public bool Loaded => loaded;
146
147
148 /// <summary>
149 /// Called when tune is modified in the inspector
150 /// </summary>
151 private void ExecOnTune()
152 {
153 // we invoke the callback here too so that it reacts when the tune is changed in the inspector
154 OnTune(tune);
155 }
156
157 /// <summary>
158 /// Updates \ref cachedPos
159 /// </summary>
160 protected virtual void Update()
161 {
162 // cache the position of this output
163 cachedPos = transform.position;
164
165 }
166
167 /// <summary>
168 /// Updates audio playback in WebGL, does nothing on other platforms.
169 ///
170 /// <b>See:</b> \ref FillCacheClip
171 /// </summary>
172 private void FixedUpdate()
173 {
174#if UNITY_WEBGL // we have to do things a bit differently in WebGL
175 if (!loaded) // if the clips haven't been loaded, wait until they have
176 return;
177
178 // check if the radio was in the first half of the chunk in the last update, and is now in the second- or vice versa
179 bool wasInFirstHalf = isInFirstHalf;
180 isInFirstHalf = source.timeSamples < totalCacheChunkLength;
181
182 // if the half we're in this frame differs from that of the previous, generate the audio for the other chunk
183 if (isInFirstHalf != wasInFirstHalf)
185#endif
186 }
187
188#if !SKIP_IN_DOXYGEN
189 // starts the radio system- this component basically serves as a manager
190 private void Start()
191 {
192 source = GetComponent<AudioSource>();
193
194 #if UNITY_WEBGL
195 // if this is a webgl build, wait for clips to load before initializing
196 StartCoroutine(AwaitLoadingThenInit());
197
198 #else // UNITY_WEBGL
199 // if this is not webgl, init right away
200 LocalInit();
201 #endif // UNITY_WEBGL
202 }
203#endif // !SKIP_IN_DOXYGEN
204
205 private void OnDestroy()
206 {
207 source.Stop();
208 source.clip = null;
209
210 source = null;
211
212 cacheClipFirst = null;
213 cacheClipSecond = null;
214
216 }
217
218 /// <summary>
219 /// Waits for all AudioClips used in this radio to be loaded before initializing the radio. This is used as WebGL will cause errors if a clip is used before it's fully loaded.
220 /// </summary>
221 private IEnumerator AwaitLoadingThenInit()
222 {
223 // store all child audioclips
224 List<AudioClip> unloadedClips = new List<AudioClip>();
225
226 // populate list of child audioclips
227 foreach (RadioTrackWrapper wrapper in data.TrackWrappers)
228 {
229 if (wrapper.TryGetClip(out AudioClip clip))
230 unloadedClips.Add(clip);
231 }
232
233 foreach (AudioClip clip in unloadedClips)
234 clip.LoadAudioData();
235
236 // while clips aren't yet loaded...
237 while (unloadedClips.Count > 0)
238 {
239 // iterate through all the clips
240 for (int i = 0; i < unloadedClips.Count; i++)
241 {
242 // and find those which are now loaded
243 if (unloadedClips[i].loadState == AudioDataLoadState.Loaded)
244 {
245 // and remove them
246 unloadedClips.Remove(unloadedClips[i]);
247 i--;
248 }
249
250 }
251
252 yield return null;
253 }
254
255 LocalInit();
256
257 loaded = true;
258 Debug.Log("loaded!");
259 }
260
261 // we have to separate this and Init as otherwise data.Init() would call Init(), which calls data.Init(), which calls Init()......
262 // this is just a consequence of how the RadioComponent class works really
263 /// <summary>
264 /// Initializes the RadioData- this needs to be separated from \ref Init() as it would be recursive otherwise
265 /// </summary>
266 protected void LocalInit()
267 {
268 // initialize the associated RadioData
269 // note: this should be the only component that calls this- just for safety
270 data.Init();
271
272 }
273
274 /// <summary>
275 /// Initializes the output itself, and creates all required every required \ref RadioTrackPlayer
276 /// </summary>
277 public override void Init()
278 {
279 // save the sample rate of the whole Unity player
280 baseSampleRate = AudioSettings.outputSampleRate;
281
282 // create and start all track players
283 StartPlayers();
284
285 cacheClipChunkDuration = Time.fixedDeltaTime * 2; // get the duration of a chunk in seconds- this is only used in webgl but suppresses a warning in the editor if here
286
287#if UNITY_WEBGL // if this a WebGL project, we need to stream audio a different way as OnAudioFilterRead is not called
288 totalCacheChunkLength = (int)(AudioSettings.outputSampleRate * cacheClipChunkDuration); // get the duration of a chunk in samples
289
290 // generate an empty first clip
291 cacheClipFirst = AudioClip.Create(
292 this.GetInstanceID() + "_CacheClip2",
294 1, (int)baseSampleRate, false
295 );
296
297 // generate an empty second clip
298 cacheClipSecond = AudioClip.Create(
299 this.GetInstanceID() + "_CacheClip",
301 1, (int)baseSampleRate, false
302 );
303
304 // create an empty array for samples
305 cacheClipSamples = new float[totalCacheChunkLength * 2];
306
307 // assign a default clip
308 source.clip = cacheClipFirst;
309
310 // populate the array and start playback
312#endif
313
314 OnTune(tune);
315 }
316
317 /// <summary>
318 /// Sets up and stores a new \ref RadioTrackPlayer and alerts any \ref RadioObserver of its creation
319 /// </summary>
320 /// <param name="_player">The new player to set up</param>
321 protected void PlayerCreation(RadioTrackPlayer _player)
322 {
323 foreach (RadioObserver observer in Observers)
324 {
325 // if an observer is watching this track
326 if (observer.AffectedTracks.Contains(_player.TrackW.name))
327 {
328 observer.AssignEvents(_player); // point it towards this new player
329 }
330 }
331
332 // below here is a section where we insert the new player into the list while preserving the order of the tracks in the Data inspector.
333 // we do this so that attenuation is preserved. if we just added players to the list one by one, the order would change, and so tracks
334 // that are supposed to get quieter when another is played (attenuate) will not do so if the other player was created afterwards.
335
336 // the index of the track in the RadioData track list- the order of the Data in the inspector
337 int indexInData = Data.TrackIDs.IndexOf(_player.TrackW.id);
338
339 // the index at which to put the newly created player
340 int indexForNewPlayer = players.Count;
341
342 // search through the currently existent players to find one with a higher index in Data
343 for (int i = 0; i < players.Count; i++)
344 {
345 if (Data.TrackIDs.IndexOf(players[i].TrackW.id) > indexInData)
346 {
347 indexForNewPlayer = i;
348 break;
349 }
350 }
351
352 // store the player
353 if (players.Count == 0)
354 players.Add(_player);
355 else
356 players.Insert(indexForNewPlayer, _player);
357 }
358
359 /// <summary>
360 /// Creates every \ref RadioTrackPlayer that this output needs for playback
361 /// </summary>
362 private void StartPlayers()
363 {
364 // for every track in the RadioData
365 foreach (RadioTrackWrapper trackW in Data.TrackWrappers)
366 {
367 // if the track is supposed to play on game start
368 if (trackW.playOnInit)
369 {
370 // create a looping player for it
372
373 // and store the player
374 PlayerCreation(player);
375 }
376 }
377 }
378
379 /// <summary>
380 /// Plays a track as a one-shot. A one-shot destroys itself when its track ends.
381 /// </summary>
382 /// <param name="_id"></param>
383 /// <returns></returns>
384 public RadioTrackPlayer PlayOneShot(string _id)
385 {
386 // get the track with the given id
387 if (Data.TryGetTrack(_id, out RadioTrackWrapper track))
388 {
389 // create a new player for the track, set to one-shot
390 RadioTrackPlayer player = new(track, RadioTrackPlayer.PlayerType.OneShot, baseSampleRate);
391
392 // store the player
393 lock (playEvents)
394 playEvents += () => PlayerCreation(player);
395
396 // ensure that the new player is cleaned up when it's destroyed
397 player.DoDestroy += player =>
398 {
399 lock (playEvents)
400 playEvents += () => players.Remove(player);
401 };
402
403 // return the player
404 return player;
405 }
406 // if it can't find a player with the id, warn the user
407 else
408 {
409 Debug.LogWarning($"Can't find track with id {_id} to play as a one-shot!");
410 return null;
411 }
412 }
413
414 /// <summary>
415 /// Plays a track as a loop. A loop restarts when the track ends, then continues to play.
416 /// </summary>
417 /// <param name="_id"></param>
418 /// <returns></returns>
419 public RadioTrackPlayer PlayLoop(string _id)
420 {
421 // get the track with the given id
422 if (Data.TryGetTrack(_id, out RadioTrackWrapper track))
423 {
424 // create a new player for the track, set to loop
426
427 // store the player
428 lock (playEvents)
429 playEvents += () => PlayerCreation(player);
430
431 // return the player
432 return player;
433 }
434 // if it can't find a player with the id, warn the user
435 else
436 {
437 Debug.LogWarning($"Can't find track with id {_id} to play as a loop!");
438 return null;
439 }
440
441 }
442
443 // try to find an active RadioTrackPlayer on this output
444 /// <summary>
445 /// Gets an active \ref RadioTrackPlayer from this output.
446 /// </summary>
447 /// <param name="_trackID">The ID of the track used by the player</param>
448 /// <param name="_player">Output parameter containing the found player</param>
449 /// <param name="_createNew">Whether or not a new player should be created if one can't be found. Players created this way are always one-shots</param>
450 /// <param name="_multiplePlayerSelector">How a player is selected when multiple are present for the same track</param>
451 /// <returns>True if a player was found or created, false if not</returns>
452 public bool TryGetPlayer(
453 string _trackID,
454 out RadioTrackPlayer _player,
455 bool _createNew = false,
456 MultiplePlayersSelector _multiplePlayerSelector = MultiplePlayersSelector.Youngest
457 )
458 {
459 // find any players associated with the track with the given id
460 var found = players.Where(p => p.TrackW.id == _trackID);
461
462 // if there are multiple players,
463 if (found.Count() > 1)
464 {
465 // select one based off the given selector
466 switch (_multiplePlayerSelector)
467 {
468 default: // choose the youngest player
469 case MultiplePlayersSelector.Youngest:
470 _player = found.Last();
471 break;
472
473 // choose the oldest player
474 case MultiplePlayersSelector.Oldest:
475 _player = found.First();
476 break;
477
478 // choose a random player
479 case MultiplePlayersSelector.Random:
480 int index = Random.Range(0, found.Count());
481 _player = found.ElementAt(index);
482
483 break;
484 }
485
486 // a player was found, so return true
487 return true;
488 }
489 // if there is one player,
490 else if (found.Count() > 0)
491 {
492 // choose it and return true
493 _player = found.First();
494 return true;
495 }
496
497 // if there aren't any players for this track,
498 // and the method is set to make a new one,
499 else if (_createNew)
500 {
501 // create a new player for this track
502 // if you want it to be a loop, play it manually- this is more of an error catch than an actual method of creation
503 _player = PlayOneShot(_trackID);
504 return true; // and return true
505 }
506 // and we aren't creating a new one,
507 else
508 {
509 _player = null; // there is no player
510 return false; // so we return false
511 }
512 }
513
514 /// <summary>
515 /// Assigns a set of samples from the radio to play from the AudioSource- this preserves the settings on the Source, e.g: volume, 3D. This is the main driving method for the radio's playback.
516 ///
517 /// The method itself appears to have been initially introduced so devs could create custom audio filters, but it just so happens we can use it for direct output of samples too!
518 ///
519 /// <h3>IMPORTANT NOTE ABOUT WEBGL:</h3> WebGL can't use this method, as it doesn't support audio streaming. I've implemented a "streaming emulation" system creating and storing clips repeatedly, but it's a bit rough around the edges and causes some popping artefacts. Please try to avoid WebGL with this package when possible, or use a pop removal filter at runtime. See \ref FillCacheClip for a bit more info :)
520 ///
521 /// <b>See:</b> \ref GetOutput
522 /// </summary>
523 /// <param name="_data">Whatever other audio is playing from the AudioSource- preferably nothing</param>
524 /// <param name="_channels">The number of channels the AudioSource is using- the radio itself is limited to one channel, but still outputs as two- they'll just be identical.</param>
525 protected virtual void OnAudioFilterRead(float[] _data, int _channels)
526 {
527#if !UNITY_WEBGL
528 GetOutput(ref _data, _channels);
529#endif
530 }
531
532 /// <summary>
533 /// Gets a set of samples from the radio, and push it along to the next samples- that is, get some samples from the radio, and move it on.
534 /// </summary>
535 /// <param name="_data">An array (usually empty) that the samples will be written to. The length of the array is the number of samples used recorded</param>
536 /// <param name="_channels">The number of channels being played- usually this should be at one, I'm not confident in its functionality otherwise</param>
537 protected void GetOutput(ref float[] _data, int _channels)
538 {
539 // the output only plays back one channel, so we have to account for this when the radio is using
540 // tracks with more than one channel
541
542 // the number of samples for, total- the _data array has an entry for each channel by default. e.g if _data was 2048
543 // entries long, and there were two channels- there would actually only be 1024 samples
544 int monoSampleCount = _data.Length / _channels;
545
546 // for every sample in the data array
547 for (int index = 0; index < monoSampleCount; index++)
548 {
549 // we want to store the sample itself, and the added volume of all previous tracks
550 float sample = 0;
551 float combinedVolume = 0;
552
553 // for every active track,
554 foreach (RadioTrackPlayer player in players)
555 {
556 // get the audio in this sample
557 sample += player.GetSample(Tune, cachedPos, combinedVolume, out float outVolume, true);
558
559 // then move along to the next sample
560 player.IncrementSample();
561
562 // store the volume so far so that tracks with attenuation can adjust accordingly- see RadioTrackPlayer.GetSample()
563 combinedVolume += outVolume;
564 }
565
566 // this function uses _data for each sample packed into one big list- each channel is joined end to end
567 // that means if there are multiple _channels, we need to apply the audio to each of them before jumping to the next sample
568 // therefore we iterate through every channel, for every sample
569 int indexWithChannels = index * _channels;
570
571 // for each channel,
572 for (int channel = indexWithChannels; channel < indexWithChannels + _channels; channel++)
573 _data[channel] += sample; // apply the sample
574 }
575
576 // execute all events waiting for this read sequence to end
577 lock (playEvents)
578 {
579 playEvents(); // execute the delegate
580 playEvents = () => { }; // clear it
581 }
582 }
583
584 /// <summary>
585 /// Generate and store the next chunk of audio that will be played on WebGL. This is called just before the next chunk is generated.
586 /// </summary>
587 /// <remarks>We need to use this method as WebGL doesn't support audio streaming. Because this system also requires audio to be modified in realtime, we can't simply generate a couple seconds of audio and just play that- we need to modify that audio each frame depending on what the player does- e.g: tuning
588 /// The methodology for this system is as follows:
589 /// 1. Generate a small chunk of audio (usually `Time.fixedDeltaTime`)
590 /// 2. Assign this audio to one of two short AudioClips
591 /// 3. When the AudioSource reaches the halfway point or end of this chunk, generate some of the next one and assign it to a separate AudioClip
592 /// 4. Switch clips so that the audio updates on web
593 ///
594 /// This is obviously not perfect, and the main issue we face here is a constant popping sound when switching back and forth between clips- unfortunately, as it stands, this is a required tradeoff due to WebGL's audio limitations. If there's a smoother way to get this functionality working, please do give it a try or let me know :)</remarks>
595 protected void FillCacheClip()
596 {
597 // generates samples for half of a chunk
598 float[] newData = new float[totalCacheChunkLength];
599 GetOutput(ref newData, 1);
600
601 // audio in webgl can't be streamed, so we need to split our otherwise-streamed audio into small chunks, and play them sequentially
602 // to do this smoothly, we need to separate our chunks into two halves, so that while one half is playing, the other can be overwritten
603 // if the first half of a chunk is playing, we generate and store the second half- and vice versa
604 if (isInFirstHalf)
605 {
606 // we're now playing the first half of the chunk, so generate the second
607 // populate the second half of the sample array
608 for (int sample = 0; sample < totalCacheChunkLength; sample++)
609 {
610 cacheClipSamples[sample + totalCacheChunkLength] = newData[sample];
611 }
612
613 // save the source's progress through the last clip, as we need to switch it
614 int prog = source.timeSamples;
615
616 // assign the samples to the clips
617 cacheClipFirst.SetData(cacheClipSamples, 0);
618 cacheClipSecond.SetData(cacheClipSamples, 0);
619
620 // play the first clip
621 source.clip = cacheClipFirst;
622 source.Play();
623
624 // reassign progress to keep playback smooth
625 source.timeSamples = prog;
626 }
627 else
628 {
629 // we're now playing the second half of the chunk, so generate the first
630 // populate the first half of the sample array
631 for (int sample = 0; sample < totalCacheChunkLength; sample++)
632 {
633 cacheClipSamples[sample] = newData[sample];
634 }
635
636 // save the source's progress through the last clip, as we need to switch it
637 int prog = source.timeSamples;
638
639 // assign the samples to the clips
640 cacheClipFirst.SetData(cacheClipSamples, 0);
641 cacheClipSecond.SetData(cacheClipSamples, 0);
642
643 // play the second clip
644 source.clip = cacheClipSecond;
645 source.Play();
646
647 // reassign progress to keep playback smooth
648 source.timeSamples = prog;
649 }
650 }
651 }
652
653}
A scene component that holds a reference to a RadioData.
RadioData Data
Read-only accessor for data.
void OnDestroy()
Unlink Init() from the radio's init.
RadioData data
The RadioData (aka just radio) that this component is linked to.
A component used to watch for specific happenings on a RadioOutput, e.g: a clip being a certain volum...
string[] AffectedTracks
The tracks selected on this Observer.
void AssignEvents(RadioTrackPlayer _player)
Assigns each event to a RadioTrackPlayer for one of our AffectedTracks. This is called when a new Rad...
The main scene component for a radio that plays it through an AudioSource.
bool isInFirstHalf
Whether or not the cache clip playback is in the second half of the clip.
RadioTrackPlayer PlayLoop(string _id)
Plays a track as a loop. A loop restarts when the track ends, then continues to play.
Action< float > OnTune
Event called whenever Tune is changed.
List< RadioObserver > Observers
Every RadioObserver associated with this output.
float Tune01
Tune scaled to [0 - 1], useful for UI
void LocalInit()
Initializes the RadioData- this needs to be separated from Init() as it would be recursive otherwise.
virtual void OnAudioFilterRead(float[] _data, int _channels)
Assigns a set of samples from the radio to play from the AudioSource- this preserves the settings on ...
AudioSource source
The AudioSource attached to this Output. This is assigned automatically.
IEnumerator AwaitLoadingThenInit()
Waits for all AudioClips used in this radio to be loaded before initializing the radio....
float Tune
The tune clamped to the full range.
void StartPlayers()
Creates every RadioTrackPlayer that this output needs for playback.
void GetOutput(ref float[] _data, int _channels)
Gets a set of samples from the radio, and push it along to the next samples- that is,...
void FixedUpdate()
Updates audio playback in WebGL, does nothing on other platforms.
Vector3 cachedPos
The position of this object as of the last frame update. We can't access transform....
bool Loaded
Public accessor for loaded.
float cacheClipChunkDuration
The size of half the cache clip used for WebGL playback- this is the amount of audio the cache clip s...
float DisplayTune
Tune with limited decimal points- looks better when displayed, more like an actual radio
Action playEvents
Called at the end of every audio cycle so that we don't interrupt threads when manipulating RadioTrac...
override void Init()
Initializes the output itself, and creates all required every required RadioTrackPlayer.
bool loaded
Whether or not the clips in this radio have been loaded.
float tune
The current tune value of this output- akin to the frequency of a real radio. Controls what tracks ca...
List< RadioTrackPlayer > players
The players used by this output.
void FillCacheClip()
Generate and store the next chunk of audio that will be played on WebGL. This is called just before t...
Action OnInit
Event called when this output is finished initializing- mainly useful for WebGL when we use AwaitLoad...
MultiplePlayersSelector
The method by which a RadioTrackPlayer is chosen from this output. Really only matters when you're pl...
@ Random
Selects a random player (probably useless but funny to have)
void PlayerCreation(RadioTrackPlayer _player)
Sets up and stores a new RadioTrackPlayer and alerts any RadioObserver of its creation.
AudioClip cacheClipSecond
The AudioClip used internally to emulate audio streaming on WebGL- half of this is played at a time,...
float baseSampleRate
The normal sample rate of this output, applied to each RadioTrackPlayer.
void ExecOnTune()
Called when tune is modified in the inspector.
int totalCacheChunkLength
The total number of samples in each half of the cache clip.
bool TryGetPlayer(string _trackID, out RadioTrackPlayer _player, bool _createNew=false, MultiplePlayersSelector _multiplePlayerSelector=MultiplePlayersSelector.Youngest)
Gets an active RadioTrackPlayer from this output.
virtual void Update()
Updates cachedPos.
RadioTrackPlayer PlayOneShot(string _id)
Plays a track as a one-shot. A one-shot destroys itself when its track ends.
The central data object defining the radio. Contains the tracks and information required to play the ...
Definition RadioData.cs:17
void ClearCache()
Clears track names and IDs.
Definition RadioData.cs:165
const float LOW_TUNE
The lower limit for tune on this radio. This may become non-const at some point.
Definition RadioData.cs:20
const float HIGH_TUNE
The upper limit for tune on this radio. This may become non-const at some point.
Definition RadioData.cs:22
A class that plays a certain RadioTrack at runtime. It's created newly for each track on each RadioOu...
float GetSample(float _tune, Vector3 _receiverPosition, float _otherVolume, out float _outVolume, bool _applyVolume=true)
Gets the current sample from the track according to playback. I would recommend reading the code comm...
PlayerType
The different types of Player- mainly changes what it does when the end of the track is reached.
void IncrementSample()
Move this player to the next sample.
A wrapper class for RadioTrack so that track types can be switched between in the inspector!...
bool playOnInit
If true, this track plays on RadioData.Init() - usually on game start.
Base interfaces and classes for components, e.g: track accessors, output accessors.
Components to be placed on scene objects, e.g: Outputs, Broadcasters, Observers.
Tracks to be used on a radio- includes base classes.
Definition RadioUtils.cs:20