Ryle Radio 1.0.1
An open-source "radio" system for Unity, allowing tracks, tuning, broadcasters, and more!
Loading...
Searching...
No Matches
RadioTrackWrapper.cs
1using NaughtyAttributes;
3using System;
4using System.Collections.Generic;
5using System.Linq;
6using UnityEditor;
7using UnityEngine;
8
9namespace RyleRadio.Tracks
10{
11 /// <summary>
12 /// A wrapper class for \ref RadioTrack so that track types can be switched between in the inspector! Also contains various values that are track-agnostic.
13 ///
14 /// This is how a \ref RadioTrack is stored and accessed in RadioData.
15 /// <br>If we didn't use a wrapper like this, you wouldn't be able to choose \ref trackType in a dropdown and see it change in the inspector- it's not possible (to my knowledge) to do that without some kind of wrapper and `[SerializeReference]`.
16 /// <br>Wrappers also contain variables that exist for every track eventType, such as \ref range and \ref gain.
17 /// </summary>
18 [System.Serializable]
19 public class RadioTrackWrapper
20 {
21 /// <summary>
22 /// The default \ref rangeCurve to use when an empty wrapper is created. It's a super basic and smooth closed curve from 0 to 0.
23 /// </summary>
24 public static AnimationCurve DefaultRangeCurve => new(new Keyframe[3] {
25 new(0, 0, 0, 0),
26 new(0.5f, 1, 0, 0),
27 new(1, 0, 0, 0)
28 });
29
30 /// <summary>
31 /// The number of decimal places used in the range- the number of zeroes is the number of decimal points, e.g: 10 == 1dp, 100 == 2dp, 1 == 0dp (whole numbers)
32 /// </summary>
33 private const float RANGE_DECIMAL_MULTIPLIER = 10f;
34
35 /// <summary>
36 /// The ID of this track- used to find and manipulate it in custom code.
37 /// </summary>
38 public string id;
39
40 /// <summary>
41 /// The name of this track for use (and easy identification) in the inspector. This is usually in the format of `{ID}, {range.x} - {range.y}`
42 ///
43 /// This is assigned in \ref RadioDataEditor.InitNewTrack()
44 /// </summary>
45 [HideInInspector] public string name; // the typeName of this track for inspector usage- this is assigned in RadioData when the wrapper is created
46
47 /// <summary>
48 /// The range of tunes in which this track can be heard. If a \ref RadioOutput.Tune value is within this range, the tune power of this track will be > 0, and it will be audible (not counting spatial components+gain+etc)
49 ///
50 /// This range is clamped between \ref RadioData.LOW_TUNE and \ref RadioData.HIGH_TUNE
51 /// </summary>
52 [MinMaxSlider(RadioData.LOW_TUNE, RadioData.HIGH_TUNE), OnValueChanged("ScaleRange")]
53 public Vector2 range;
54
55 /// <summary>
56 /// The curve defining the loudness of the track over its range. The progress between `range.x` and `range.y` the \ref RadioOutput.Tune value is, is the progress along this curve the tune power is.
57 /// </summary>
58 /// <example><list eventType="bullet">
59 /// <item>If the curve is the default curve (smooth from 0 to 1 to 0), it will smoothly get louder towards the center of the range, and quieter towards the edge.</item>
60 /// <item>If the curve is a flat line at y=1, it will be the same volume across the entire range</item>
61 /// <item>If the curve is a line from 0 - 1, it will be louder the further along the range the tune is, getting loudest at `range.y`</item>
62 /// <item>If the curve is goes up and down repeatedly, it will be at various different volumes depending on what you set, moving between them along the range</item>
63 /// </list></example>
64 [CurveRange(0, 0, 1, 1)]
65 public AnimationCurve rangeCurve = new(DefaultRangeCurve.keys);
66
67 /// <summary>
68 /// An added value to the volume of the track. This is applied before any other volume is calculated
69 /// </summary>
70 [Range(0, 500), SerializeField]
71 private float gain = 100; // the volume of the track
72
73 /// <summary>
74 /// The amount that this track gets quieter when another track is playing on top of it (and that other track is above this one in \ref RadioData.trackWs
75 /// </summary>
76 [Range(0, 1)]
77 public float attenuation = 0.1f;
78
79 /// <summary>
80 /// If true, this track ignores any \ref RadioBroadcaster influence and plays everywhere
81 /// </summary>
82 public bool forceGlobal = true;
83
84 /// <summary>
85 /// If true, this track plays on \ref RadioData.Init() - usually on game start
86 /// </summary>
87 public bool playOnInit = true;
88
89 /// <summary>
90 /// The broadcasters in the scene that have this track selected.
91 ///
92 /// A \ref RadioBroadcaster is a scene component that allows a track to be heard exclusively or louder in a certain area.
93 /// <br><b>See also: </b>\ref insulators
94 /// </summary>
95 [HideInInspector] public List<RadioBroadcaster> broadcasters;
96
97 /// <summary>
98 /// The insulators in the scene that have this track selected.
99 ///
100 /// A \ref RadioInsulator is a scene component that makes a track quieter in a certain area.
101 /// <br><b>See also: </b>\ref broadcasters
102 /// </summary>
103 [HideInInspector] public List<RadioInsulator> insulators;
104
105 /// <summary>
106 /// The eventType of track for this wrapper to contain, selectable in the inspector. This variable is stored as the track name and displays with a dropdown according to \ref TrackNames
107 /// </summary>
108 [SerializeField, Space(8), AllowNesting, OnValueChanged("CreateTrackLocal"), Dropdown("TrackNames")]
109 private string trackType;
110
111 // the track itself
112 // we keep this private so that no other classes can access the track directly- this isn't really necessary but is very safe when it comes to custom tracks
113 /// <summary>
114 /// The actual \ref RadioTrack in this wrapper, its eventType chosen in \ref trackType
115 /// </summary>
116 /// <remarks>We keep this private so that no other classes can access the track directly- this isn't really necessary but it <i>is</i> very safe for custom code</remarks>
117 [SerializeReference] protected IRadioTrack track;
118
119
120 /// An event called when the wrapper is initialised
121 public Action<RadioTrackWrapper> OnInit { get; set; } = new(_ => { });
122 /// An event called just before the wrapper is initialised
123 public Action<RadioTrackWrapper> BeforeInit { get; set; } = new(_ => { });
124
125 /// An event called when a broadcaster is added to the track
126 public Action<RadioBroadcaster, RadioTrackWrapper> OnAddBroadcaster { get; set; } = new((_, _) => { });
127 /// An event called when a broadcaster is removed from this track
128 public Action<RadioBroadcaster, RadioTrackWrapper> OnRemoveBroadcaster { get; set; } = new((_, _) => { });
129
130 /// An event called when an insulator is added to the track
131 public Action<RadioInsulator, RadioTrackWrapper> OnAddInsulator { get; set; } = new((_, _) => { });
132 /// An event called when an insulator is removed from this track.
133 public Action<RadioInsulator, RadioTrackWrapper> OnRemoveInsulator { get; set; } = new((_, _) => { });
134
135
136 /// <summary>
137 /// The gain value scaled down to ones- e.g \ref gain at 200 is \ref Gain at 2
138 /// </summary>
139 public float Gain
140 {
141 get => gain / 100f;
142 set => gain = value * 100f;
143 }
144
145 /// <summary>
146 /// The gain value as it is displayed and modified in the editor- in the 100s
147 /// </summary>
148 public float GainDisplay
149 {
150 get => gain;
151 set => gain = value;
152 }
153
154
155#if !SKIP_IN_DOXYGEN
156 // internal variable for TrackTypes
157 private static Type[] trackTypes;
158#endif
159 /// <summary>
160 /// A list of each eventType of track that this wrapper can contain- this is anything that inherits from \ref IRadioTrack, and updates dynamically when creating new track types.
161 /// <br><br><b>See also: </b> \ref RadioUtils.FindDerivedTypes()
162 /// </summary>
163 private static Type[] TrackTypes
164 {
165 get
166 {
167 // dynamically gets all available track types- automatically detects any new ones
168 trackTypes ??= RadioUtils.FindDerivedTypes(typeof(IRadioTrack));
169
170 return trackTypes;
171 }
172 }
173
174#if !SKIP_IN_DOXYGEN
175 // internal variable for TrackTypesAsStrings
176 private static string[] trackTypesAsStrings;
177#endif
178 /// <summary>
179 /// Static; the list of available track types stored as their typename, NOT as their display names. This is used when reassigning track types in \ref RadioDataEditor when the editor reloads, as we only have the typename of the track
180 /// </summary>
181 private static string[] TrackTypesAsStrings
182 {
183 get
184 {
185 // get all the track types, and convert them to their eventType names
186 trackTypesAsStrings ??= TrackTypes
187 .Select(t => t.Name.Split(".")[^1])
188 .ToArray();
189
190 return trackTypesAsStrings;
191 }
192 }
193
194#if !SKIP_IN_DOXYGEN
195 // internal variable for TrackNames
196 private static string[] trackNames;
197#endif
198 /// <summary>
199 /// Static; the list of track types stored as their display names. This is shown as a dropdown for \ref trackType and is how types are usually displayed in the inspector
200 /// </summary>
201 private static string[] TrackNames
202 {
203 get
204 {
205 // get all the track types, and convert them to their dev-defined their display names
206 trackNames ??= TrackTypes
207 .Select(t => (string)t.GetField("DISPLAY_NAME").GetValue(null)) // the track eventType needs a const named DISPLAY_NAME to be accessed here
208 .ToArray();
209
210 return trackNames;
211 }
212 }
213
214
215 /// An alias for the track's SampleRate as other classes cannot access \ref track directly.
216 public float SampleRate => track.SampleRate;
217
218 /// An alias for the track's SampleCount as other classes cannot access \ref track directly.
219 public int SampleCount => track.SampleCount;
220
221
222 /// <summary>
223 /// Creates an empty wrapper
224 /// </summary>
226 {
227 track = null;
228 trackType = "";
229
230 // forces this wrapper to have a track on creation so that we don't get inspector errors
231 // this gets called when the wrapper is created, not when it's initialized
233 }
234
235#if UNITY_EDITOR
236 /// <summary>
237 /// Static; resets available track types and names
238 /// </summary>
239 [InitializeOnLoadMethod]
240 public static void OnReload()
241 {
242 trackTypes = null;
243 trackTypesAsStrings = null;
244 trackNames = null;
245 }
246#endif
247
248 /// <summary>
249 /// Initialize this wrapper and its track
250 /// </summary>
251 public void Init()
252 {
253 BeforeInit(this);
254
255 // initialize the track itself
256 track.Init();
257
258 // reset stored components as they will otherwise be kept from the last play mode
259 // this doesn't really matter in a build but is needed for the editor
260 broadcasters.Clear();
261 insulators.Clear();
262
263 OnInit(this);
264 }
265
266 /// <summary>
267 /// Changes the name of this class from "RadioTrackWrapper" to "Wrapper for `track.Name`"
268 /// </summary>
269 public override string ToString() => "Wrapper for " + name;
270
271 /// <summary>
272 /// Converts the typename (NOT display name) of a track eventType to the actual eventType
273 /// </summary>
274 /// <param name="_typeName">The name of the eventType</param>
275 /// <returns>The eventType with that name</returns>
276 public static string GetTrackType(string _typeName)
277 {
278 int index = Array.IndexOf(TrackTypesAsStrings, _typeName.Split(".")[^1]);
279 return TrackNames[index];
280 }
281
282 /// <summary>
283 /// Static; creates a new track for a wrapper using the given track eventType's display name
284 /// </summary>
285 /// <param name="_name">Display name of a track eventType</param>
286 /// <returns>The newly created \ref IRadioTrack</returns>
287 public static IRadioTrack CreateTrackEditor(string _name)
288 {
289 // get the index of the chosen track eventType
290 int index = Array.IndexOf(TrackNames, _name);
291
292 // if you somehow have an invalid track eventType, don't create anything
293 if (index < 0)
294 return null;
295
296 // create the track generically
297 // (we have to use Activator.CreateInstance here as it uses the Type as an argument- we can't generically instantiate a variable
298 // from a given Type any other viable way
299 IRadioTrack outTrack = (IRadioTrack)Activator.CreateInstance(TrackTypes[index]);
300
301 // return the new track
302 return outTrack;
303 }
304
305
306 /// <summary>
307 /// Set \ref track to a new track with eventType defined by \ref trackType
308 /// <br><b>See also: </b>\ref CreateTrackEditor()
309 /// </summary>
310 public void CreateTrackLocal()
311 {
313 }
314
315 /// <summary>
316 /// Used if \ref track needs to access a \ref RadioTrackPlayer that it's linked to when that player ends, this method adds an event for it. Really only needed for a \ref StationRadioTrack
317 /// </summary>
318 /// <param name="_callback">The event called on \ref RadioTrackPlayer.OnEnd</param>
319 public void AddToPlayerEndCallback(ref Action<RadioTrackPlayer> _callback)
320 {
321 track.AddToPlayerEndCallback(ref _callback);
322 }
323
324 /// <summary>
325 /// Limits the number of decimal points on the \ref range
326 /// <br>This is called whenever the range is changed.
327 /// <br><b>See: </b>\ref RANGE_DECIMAL_MULTIPLIER
328 /// </summary>
329 private void ScaleRange()
330 {
331 range = new(
332 ((int)(range.x * RANGE_DECIMAL_MULTIPLIER)) / RANGE_DECIMAL_MULTIPLIER, // increase the size of the clampedRange clampedValue, then cut its decimals and shrink it back down
334 );
335 }
336
337
338 /// <summary>
339 /// Calculates the power of this track when an Output is at a specific Tune value. It does this by finding where the Tune is over the track's \ref range, where that point lies on the \ref rangeCurve, and applies \ref attenuation
340 /// </summary>
341 /// <param name="_tune">The tune value to evaluate</param>
342 /// <param name="_otherVolume">The volume of any previous tracks, used for attenuation</param>
343 /// <returns>The tune power of this track with the provided values</returns>
344 public float GetTunePower(float _tune, float _otherVolume)
345 {
346 if (_tune < range.x || _tune > range.y) // if the tune is out of this track's range, it cannot be heard
347 return 0;
348
349 float tunePower = rangeCurve.Evaluate(_tune.Remap(range.x, range.y, 0f, 1f)); // get the volume based on the tune and where it sits on the gain curve
350 float attenPower = 1f - (Mathf.Clamp01(_otherVolume) * attenuation); // get the volume based on attenuation and other playing trackWs
351
352 return tunePower * attenPower; // combine the values into one singular volume
353 }
354
355 /// <summary>
356 /// Get a sample from the contained \ref track
357 /// </summary>
358 /// <param name="_sampleIndex">The index of the sample to get</param>
359 /// <returns>The sample as given by \ref RadioTrack.GetSample()</returns>
360 public float GetSample(int _sampleIndex)
361 {
362 return track.GetSample(_sampleIndex);
363 }
364
365 public bool TryGetClip(out AudioClip _clip)
366 {
367 if (track is ClipRadioTrack clipTrack)
368 {
369 _clip = clipTrack.clip;
370 return true;
371 }
372
373 _clip = null;
374 return false;
375 }
376 }
377
378}
The central data object defining the radio. Contains the tracks and information required to play the ...
Definition RadioData.cs:17
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
Various utilities used throughout the project.
Definition RadioUtils.cs:28
static Type[] FindDerivedTypes(Type _baseType)
Gets all types derived from a given one. Does not include:Interfaces Generic classes Abstract classes
Definition RadioUtils.cs:72
A eventType of RadioTrack that plays from a chosen AudioClip object.
Action< RadioTrackWrapper > OnInit
An event called when the wrapper is initialised.
Action< RadioInsulator, RadioTrackWrapper > OnRemoveInsulator
An event called when an insulator is removed from this track.
void ScaleRange()
Limits the number of decimal points on the range This is called whenever the range is changed....
Action< RadioBroadcaster, RadioTrackWrapper > OnAddBroadcaster
An event called when a broadcaster is added to the track.
AnimationCurve rangeCurve
The curve defining the loudness of the track over its range. The progress between range....
Action< RadioBroadcaster, RadioTrackWrapper > OnRemoveBroadcaster
An event called when a broadcaster is removed from this track.
bool playOnInit
If true, this track plays on RadioData.Init() - usually on game start.
void AddToPlayerEndCallback(ref Action< RadioTrackPlayer > _callback)
Used if track needs to access a RadioTrackPlayer that it's linked to when that player ends,...
string id
The ID of this track- used to find and manipulate it in custom code.
bool forceGlobal
If true, this track ignores any RadioBroadcaster influence and plays everywhere.
float SampleRate
An alias for the track's SampleRate as other classes cannot access track directly.
Action< RadioInsulator, RadioTrackWrapper > OnAddInsulator
An event called when an insulator is added to the track.
int SampleCount
An alias for the track's SampleCount as other classes cannot access track directly.
float attenuation
The amount that this track gets quieter when another track is playing on top of it (and that other tr...
float GetSample(int _sampleIndex)
Get a sample from the contained track.
List< RadioBroadcaster > broadcasters
The broadcasters in the scene that have this track selected.
static string[] TrackNames
Static; the list of track types stored as their display names. This is shown as a dropdown for trackT...
float Gain
The gain value scaled down to ones- e.g gain at 200 is Gain at 2.
float GainDisplay
The gain value as it is displayed and modified in the editor- in the 100s.
static AnimationCurve DefaultRangeCurve
The default rangeCurve to use when an empty wrapper is created. It's a super basic and smooth closed ...
Vector2 range
The range of tunes in which this track can be heard. If a RadioOutput.Tune value is within this range...
RadioTrackWrapper()
Creates an empty wrapper.
static string GetTrackType(string _typeName)
Converts the typename (NOT display name) of a track eventType to the actual eventType.
void CreateTrackLocal()
Set track to a new track with eventType defined by trackType See also: CreateTrackEditor()
List< RadioInsulator > insulators
The insulators in the scene that have this track selected.
void Init()
Initialize this wrapper and its track.
IRadioTrack track
The actual RadioTrack in this wrapper, its eventType chosen in trackType.
string name
The name of this track for use (and easy identification) in the inspector. This is usually in the for...
Action< RadioTrackWrapper > BeforeInit
An event called just before the wrapper is initialised.
const float RANGE_DECIMAL_MULTIPLIER
The number of decimal places used in the range- the number of zeroes is the number of decimal points,...
static IRadioTrack CreateTrackEditor(string _name)
Static; creates a new track for a wrapper using the given track eventType's display name.
float gain
An added value to the volume of the track. This is applied before any other volume is calculated.
float GetTunePower(float _tune, float _otherVolume)
Calculates the power of this track when an Output is at a specific Tune value. It does this by findin...
string trackType
The eventType of track for this wrapper to contain, selectable in the inspector. This variable is sto...
override string ToString()
Changes the name of this class from "RadioTrackWrapper" to "Wrapper for `track.Name`".
static string[] TrackTypesAsStrings
Static; the list of available track types stored as their typename, NOT as their display names....
static Type[] TrackTypes
A list of each eventType of track that this wrapper can contain- this is anything that inherits from ...
Internal interface for a RadioTrack.
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