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