Terraria ModLoader  0.11.7.5
A mod to make and play Terraria mods
MusicWrappers.cs
Go to the documentation of this file.
1 using System;
2 using Microsoft.Xna.Framework.Audio;
3 using System.IO;
4 using MP3Sharp;
5 using NVorbis;
6 
7 //TODO refactor to Terraria.ModLoader, delayed due to breaking change (public in Mod[Content].GetMusic)
8 namespace Terraria.ModLoader.Audio
9 {
10  public abstract class Music
11  {
12  public static implicit operator Music(Cue cue) { return new MusicCue() { cue = cue }; }
13  public abstract bool IsPaused { get; }
14  public abstract bool IsPlaying { get; }
15  public abstract void Reset();
16  public abstract void Pause();
17  public abstract void Play();
18  public abstract void Resume();
19  public abstract void Stop(AudioStopOptions options);
20  public abstract void SetVariable(string name, float value);
21  public virtual void CheckBuffer() { }
22  }
23 
24  public class MusicCue : Music
25  {
26  internal Cue cue;
27  //public static implicit operator Cue(MusicCue musicCue){return musicCue.cue;}
28  public override bool IsPaused => cue.IsPaused;
29  public override bool IsPlaying => cue.IsPlaying;
30  public override void Pause() => cue.Pause();
31  public override void Play() => cue.Play();
32  public override void Resume() => cue.Resume();
33  public override void Stop(AudioStopOptions options) => cue.Stop(options);
34  public override void SetVariable(string name, float value) => cue.SetVariable(name, value);
35 
36  public override void Reset() {
37  cue = Main.soundBank.GetCue(cue.Name);
38  }
39  }
40 
41  public abstract class MusicStreaming : Music, IDisposable
42  {
43  // to play a 44.1kHz dual channel signal, we'd need to submit 735 samples per frame
44  // at 4 bytes per sample = 2940bytes.
45  // increasing the buffer size to 4096 and submitting 2 buffers at a time means we
46  // need to submit 21.5 times per second, or roughly every 3 frames at 60fps
47  private const int bufferLength = 4096;
48  private const int bufferCountPerSubmit = 2;
49 
50  // with a 4 buffer minimum, we have roughly 1/10th of a second of stutter before the music drops
51  private const int bufferMin = 4;
52 
53  private string path;
54 
55  private DynamicSoundEffectInstance instance;
56  protected Stream stream;
57  private byte[] buffer;
58 
59  protected int sampleRate;
60  protected AudioChannels channels;
61 
62  public MusicStreaming(string path) {
63  this.path = path;
64  }
65 
66  public override bool IsPaused => instance != null && instance.State == SoundState.Paused;
67  public override bool IsPlaying => instance != null && instance.State != SoundState.Stopped;
68  public override void Pause() => instance.Pause();
69  public override void Resume() => instance.Resume();
70  public override void Play() {
71  EnsureLoaded();
72  instance.Play();
73  }
74 
75  public override void SetVariable(string name, float value) {
76  switch (name) {
77  case "Volume": instance.Volume = value; return;
78  case "Pitch": instance.Pitch = value; return;
79  case "Pan": instance.Pan = value; return;
80  default: throw new Exception("Invalid field: '" + name + "'");
81  }
82  }
83 
84  private void EnsureLoaded() {
85  if (instance != null)
86  return;
87 
88  stream = ModContent.OpenRead(path, true);
89  PrepareStream();
90 
91  instance = new DynamicSoundEffectInstance(sampleRate, channels);
92  buffer = new byte[bufferLength]; // could use a buffer pool but swapping music isn't likely to thrash the GC too much
93 
94  CheckBuffer();
95  }
96 
97  protected abstract void PrepareStream();
98 
99  public override void Stop(AudioStopOptions options) {
100  instance.Stop();
101 
102  instance.Dispose();
103  instance = null;
104 
105  stream.Dispose();
106  stream = null;
107 
108  buffer = null;
109  }
110 
111  public override void CheckBuffer() {
112  if (!IsPlaying || instance.PendingBufferCount >= bufferMin)
113  return;
114 
115  for (int i = 0; i < bufferCountPerSubmit; i++)
116  SubmitSingle();
117  }
118 
119  private void SubmitSingle() {
120  FillBuffer(buffer);
121  instance.SubmitBuffer(buffer);
122  }
123 
124  protected virtual void FillBuffer(byte[] buffer) {
125  int read = stream.Read(buffer, 0, buffer.Length);
126  if (read < buffer.Length) {
127  Reset();
128  stream.Read(buffer, read, buffer.Length - read);
129  }
130  }
131 
132  public void Dispose() {
133  if (instance != null)
134  Stop(AudioStopOptions.Immediate);
135  }
136  }
137 
139  {
140  private long dataStart = -1;
141 
142  public MusicStreamingWAV(string path) : base(path) {}
143 
144  protected override void PrepareStream() {
145  if (dataStart >= 0) {
146  stream.Position = dataStart;
147  return;
148  }
149 
150  var reader = new BinaryReader(stream);
151  int chunkID = reader.ReadInt32();
152  int fileSize = reader.ReadInt32();
153  int riffType = reader.ReadInt32();
154  int fmtID = reader.ReadInt32();
155  int fmtSize = reader.ReadInt32();
156  int fmtCode = reader.ReadInt16();
157  channels = (AudioChannels)reader.ReadInt16();
158  sampleRate = reader.ReadInt32();
159  int fmtAvgBPS = reader.ReadInt32();
160  int fmtBlockAlign = reader.ReadInt16();
161  int bitDepth = reader.ReadInt16();
162 
163  if (fmtSize == 18) {
164  // Read any extra values
165  int fmtExtraSize = reader.ReadInt16();
166  reader.ReadBytes(fmtExtraSize);
167  }
168 
169  int dataID = reader.ReadInt32();
170  int dataSize = reader.ReadInt32();
171  dataStart = stream.Position;
172  }
173 
174  public override void Reset() {
175  if (stream != null)
176  stream.Position = dataStart;
177  }
178  }
179 
181  {
182  private Stream underlying;
183 
184  public MusicStreamingMP3(string path) : base(path) {}
185 
186  protected override void PrepareStream() {
187  underlying = stream;
188 
189  var mp3Stream = new MP3Stream(stream);
190  sampleRate = mp3Stream.Frequency;
191  channels = (AudioChannels)mp3Stream.ChannelCount;
192  stream = mp3Stream;
193  }
194 
195  public override void Stop(AudioStopOptions options) {
196  base.Stop(options);
197  underlying = null;
198  }
199 
200  public override void Reset() {
201  if (stream != null) {
202  underlying.Position = 0;
203  //mp3 is not designed to loop and creates static if you just reset the stream due to fourier encoding carryover
204  //if you're really smart, you can make a looping version and PR it
205  stream = new MP3Stream(underlying);
206  }
207  }
208  }
209 
211  {
212  private VorbisReader reader;
213  private float[] floatBuf;
214  private int loopStart;
215  private int loopEnd;
216 
217  public MusicStreamingOGG(string path) : base(path) {}
218 
219  protected override void PrepareStream() {
220  reader = new VorbisReader(stream, true);
221  sampleRate = reader.SampleRate;
222  channels = (AudioChannels)reader.Channels;
223 
224  string[] comments = reader.Comments;
225  for (int i = 0; i < comments.Length; i++) {
226  if (comments[i].StartsWith("LOOPSTART"))
227  int.TryParse(comments[i].Split('=')[1], out loopStart);
228  else if (comments[i].StartsWith("LOOPEND"))
229  int.TryParse(comments[i].Split('=')[1], out loopEnd);
230  }
231  }
232 
233  protected override void FillBuffer(byte[] buffer) {
234  if (floatBuf == null)
235  floatBuf = new float[buffer.Length/2];
236 
237  int read = reader.ReadSamples(floatBuf, 0, floatBuf.Length);
238  if ((loopEnd > 0 && reader.DecodedPosition >= loopEnd) || read < floatBuf.Length) {
239  reader.DecodedPosition = loopStart;
240  reader.ReadSamples(floatBuf, read, floatBuf.Length - read);
241  }
242 
243  Convert(floatBuf, buffer);
244  }
245 
246  public override void Stop(AudioStopOptions options) {
247  base.Stop(options);
248 
249  reader.Dispose();
250  reader = null;
251  floatBuf = null;
252  }
253 
254  public override void Reset() {
255  if (reader != null)
256  reader.DecodedPosition = 0;
257  }
258 
259  public static void Convert(float[] floatBuf, byte[] buffer) {
260  for (int i = 0; i < floatBuf.Length; i++) {
261  short val = (short)(floatBuf[i] * short.MaxValue);
262  buffer[i * 2] = (byte)val;
263  buffer[i * 2 + 1] = (byte)(val >> 8);
264  }
265  }
266  }
267 }
override void Stop(AudioStopOptions options)
override void Stop(AudioStopOptions options)
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:28
Manages content added by mods. Liasons between mod content and Terraria&#39;s arrays and oversees the Loa...
Definition: ModContent.cs:26
static Stream OpenRead(string assetName, bool newFileStream=false)
Definition: ModContent.cs:621
override void SetVariable(string name, float value)
override void Stop(AudioStopOptions options)
override void FillBuffer(byte[] buffer)
DynamicSoundEffectInstance instance
virtual void FillBuffer(byte[] buffer)
static void Convert(float[] floatBuf, byte[] buffer)