forked from jefetienne/daggerfall-unity
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSndFile.cs
More file actions
342 lines (280 loc) · 9.84 KB
/
SndFile.cs
File metadata and controls
342 lines (280 loc) · 9.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Project: Daggerfall Tools For Unity
// Copyright: Copyright (C) 2009-2020 Daggerfall Workshop
// Web Site: http://www.dfworkshop.net
// License: MIT License (http://www.opensource.org/licenses/mit-license.php)
// Source Code: https://github.com/Interkarma/daggerfall-unity
// Original Author: Gavin Clayton (interkarma@dfworkshop.net)
// Contributors:
//
// Notes:
//
#region Using Statements
using System;
using System.IO;
using DaggerfallConnect.Utility;
#endregion
namespace DaggerfallConnect.Arena2
{
/// <summary>
/// Connects to DAGGER.SND to enumerate and extract sound data.
/// </summary>
public class SndFile
{
#region Class Variables
public const int SampleRate = 11025;
/// <summary>
/// The BsaFile representing DAGGER.SND.
/// </summary>
private readonly BsaFile bsaFile = new BsaFile();
/// <summary>
/// Array of decomposed sound records.
/// </summary>
internal SoundRecord[] sounds;
#endregion
#region Class Structures
/// <summary>
/// Represents a single sound record.
/// </summary>
internal struct SoundRecord
{
public FileProxy MemoryFile;
public DFSound DFSound;
}
#endregion
#region Public Properties
/// <summary>
/// Number of BSA records in DAGGER.SND.
/// </summary>
public int Count
{
get { return bsaFile.Count; }
}
/// <summary>
/// BSA file for sound effects.
/// </summary>
public BsaFile BsaFile
{
get { return bsaFile; }
}
#endregion
#region Static Properties
/// <summary>
/// Gets default DAGGER.SND filename.
/// </summary>
static public string Filename
{
get { return "DAGGER.SND"; }
}
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public SndFile()
{
}
/// <summary>
/// Load constructor.
/// </summary>
/// <param name="filePath">Absolute path to DAGGER.SND.</param>
/// <param name="usage">Determines if the BSA file will read from disk or memory.</param>
/// <param name="readOnly">File will be read-only if true, read-write if false.</param>
public SndFile(string filePath, FileUsage usage, bool readOnly)
{
Load(filePath, usage, readOnly);
}
#endregion
#region Public Methods
/// <summary>
/// Gets index of sound record with specified id.
/// </summary>
/// <param name="id">ID of mesh.</param>
/// <returns>Index of sound if located, or -1 if not found.</returns>
public int GetRecordIndex(uint id)
{
// Otherwise find and store index by searching for id
for (int i = 0; i < Count; i++)
{
if (bsaFile.GetRecordId(i) == id)
return i;
}
return -1;
}
/// <summary>
/// Load DAGGER.SND file.
/// </summary>
/// <param name="filePath">Absolute path to DAGGER.SND file.</param>
/// <param name="usage">Specify if file will be accessed from disk, or loaded into RAM.</param>
/// <param name="readOnly">File will be read-only if true, read-write if false.</param>
/// <returns>True if successful, otherwise false.</returns>
public bool Load(string filePath, FileUsage usage, bool readOnly)
{
// Validate filename
if (!filePath.EndsWith("DAGGER.SND", StringComparison.InvariantCultureIgnoreCase))
return false;
// Load file
if (!bsaFile.Load(filePath, usage, readOnly))
return false;
// Create records array
sounds = new SoundRecord[bsaFile.Count];
return true;
}
/// <summary>
/// Get a sound from index.
/// </summary>
/// <param name="sound">SoundEffect.</param>
/// <param name="soundOut">Sound data out.</param>
/// <returns>True if successful.</returns>
public bool GetSound(int sound, out DFSound soundOut)
{
soundOut = new DFSound();
if (!IsValidIndex(sound))
return false;
// Just return sound if already loaded
if (sounds[sound].DFSound.WaveHeader != null &&
sounds[sound].DFSound.WaveData != null)
{
soundOut = sounds[sound].DFSound;
return true;
}
// Load sound data
sounds[sound].MemoryFile = bsaFile.GetRecordProxy(sound);
if (sounds[sound].MemoryFile == null)
return false;
// Attempt to read sound
ReadSound(sound);
soundOut = sounds[sound].DFSound;
return true;
}
/// <summary>
/// Helper method to get an entire WAV file in a memory stream.
/// </summary>
/// <param name="sound">Sound index.</param>
/// <returns>Wave file in MemoryStream.</returns>
public MemoryStream GetStream(int sound)
{
// Get sound
DFSound dfSound;
GetSound(sound, out dfSound);
if (dfSound.WaveHeader == null ||
dfSound.WaveData == null)
{
return null;
}
// Create stream
byte[] data = new byte[dfSound.WaveHeader.Length + dfSound.WaveData.Length];
MemoryStream ms = new MemoryStream(data);
// Write header and data
BinaryWriter writer = new BinaryWriter(ms);
writer.Write(dfSound.WaveHeader);
writer.Write(dfSound.WaveData);
// Reset start position in stream
ms.Position = 0;
return ms;
}
/// <summary>
/// Discard a sound from memory.
/// </summary>
/// <param name="sound">Index of sound to discard.</param>
public void DiscardSound(int sound)
{
// Validate
if (sound >= bsaFile.Count)
return;
// Discard memory files and other data
if (sounds[sound].MemoryFile != null) sounds[sound].MemoryFile.Close();
sounds[sound].MemoryFile = null;
sounds[sound].DFSound = new DFSound();
}
/// <summary>
/// Quickly check if sound index in valid range.
/// </summary>
/// <param name="sound">Index of sound.</param>
/// <returns>True if index is within a valid range.</returns>
public bool IsValidIndex(int sound)
{
if (sound < 0 || sound >= bsaFile.Count)
return false;
return true;
}
#endregion
#region Readers
/// <summary>
/// Read a sound.
/// </summary>
/// <param name="sound">Sound index.</param>
private bool ReadSound(int sound)
{
try
{
CreatePcmHeader(sound);
ReadWaveData(sound);
}
catch (Exception e)
{
DiscardSound(sound);
Console.WriteLine(e.Message);
return false;
}
return true;
}
/// <summary>
/// Reads wave data for the sound.
/// </summary>
/// <param name="sound">Sound index.</param>
private void ReadWaveData(int sound)
{
// The entire BSA record is just raw sound bytes
sounds[sound].DFSound.Name = sounds[sound].MemoryFile.FileName;
sounds[sound].DFSound.WaveData = sounds[sound].MemoryFile.Buffer;
}
/// <summary>
/// Creates a PCM header for the sound, including the DATA prefix preceding raw sound bytes.
/// </summary>
/// <param name="sound">Sound index.</param>
private void CreatePcmHeader(int sound)
{
Int32 headerLength = 44;
Int32 dataLength = sounds[sound].MemoryFile.Length;
Int32 fileLength = dataLength + 36;
String sRIFF = "RIFF";
String sWAVE = "WAVE";
String sFmtID = "fmt ";
String sDataID = "data";
Int32 nFmtLength = 16;
Int16 nFmtFormat = 1;
Int16 nFmtChannels = 1;
Int32 nFmtSampleRate = 11025;
Int32 nFmtAvgBytesPerSec = 11025;
Int16 nFmtBlockAlign = 1;
Int16 nFmtBitsPerSample = 8;
// Create header bytes
byte[] header = new byte[headerLength];
// Create memory stream and writer
MemoryStream ms = new MemoryStream(header);
BinaryWriter writer = new BinaryWriter(ms);
// Write the RIFF tag and file length
writer.Write(sRIFF.ToCharArray());
writer.Write(fileLength);
// Write the WAVE tag and fmt header
writer.Write(sWAVE.ToCharArray());
writer.Write(sFmtID.ToCharArray());
// Write fmt information
writer.Write(nFmtLength);
writer.Write(nFmtFormat);
writer.Write(nFmtChannels);
writer.Write(nFmtSampleRate);
writer.Write(nFmtAvgBytesPerSec);
writer.Write(nFmtBlockAlign);
writer.Write(nFmtBitsPerSample);
// Write PCM data prefix
writer.Write(sDataID.ToCharArray());
writer.Write(dataLength);
// Close writer
writer.Close();
// Assign header to sound
sounds[sound].DFSound.WaveHeader = header;
}
#endregion
}
}