tModLoader v0.11.8.9
A mod to make and play Terraria mods
MusicWrappers.cs
Go to the documentation of this file.
1using System;
2using Microsoft.Xna.Framework.Audio;
3using System.IO;
4using MP3Sharp;
5using NVorbis;
6
7//TODO refactor to Terraria.ModLoader, delayed due to breaking change (public in Mod[Content].GetMusic)
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() {
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
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
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() {
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() {
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
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 SetVariable(string name, float value)
abstract void Stop(AudioStopOptions options)
abstract void SetVariable(string name, float value)
DynamicSoundEffectInstance instance
virtual void FillBuffer(byte[] buffer)
override void SetVariable(string name, float value)
override void Stop(AudioStopOptions options)
override void Stop(AudioStopOptions options)
override void Stop(AudioStopOptions options)
override void FillBuffer(byte[] buffer)
static void Convert(float[] floatBuf, byte[] buffer)
Manages content added by mods. Liasons between mod content and Terraria's arrays and oversees the Loa...
Definition: ModContent.cs:27
static Stream OpenRead(string assetName, bool newFileStream=false)
Definition: ModContent.cs:629