1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2017 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  */
24 
25 /**
26  * $(U InputSoundFile) decodes audio samples from a sound file. It is used
27  * internally by higher-level classes such as $(SOUNDBUFFER_LINK) and
28  * $(MUSIC_LINK), but can also be useful if you want to process or analyze audio
29  * files without playing them, or if you want to implement your own version of
30  * $(MUSIC_LINK) with more specific features.
31  *
32  * Example:
33  * ---
34  * // Open a sound file
35  * auto file = new InputSoundFile();
36  * if (!file.openFromFile("music.ogg"))
37  * {
38  *      //error
39  * }
40  *
41  * // Print the sound attributes
42  * writeln("duration: ", file.getDuration().total!"seconds");
43  * writeln("channels: ", file.getChannelCount());
44  * writeln("sample rate: ", file.getSampleRate());
45  * writeln("sample count: ", file.getSampleCount());
46  *
47  * // Read and process batches of samples until the end of file is reached
48  * short samples[1024];
49  * long count;
50  * do
51  * {
52  *     count = file.read(samples, 1024);
53  *
54  *     // process, analyze, play, convert, or whatever
55  *     // you want to do with the samples...
56  * }
57  * while (count > 0);
58  * ---
59  *
60  * See_Also:
61  * $(OUTPUTSOUNDFILE_LINK)
62  */
63 module dsfml.audio.inputsoundfile;
64 
65 import std..string;
66 import dsfml.system.inputstream;
67 import dsfml.system.err;
68 
69 public import core.time;
70 
71 /**
72  * Provide read access to sound files.
73  */
74 class InputSoundFile
75 {
76     private sfInputSoundFile* m_soundFile;
77 
78     //keeps an instance of the C++ interface stored if used
79     private soundFileStream m_stream;
80 
81     /// Default constructor.
82     this()
83     {
84         m_soundFile = sfInputSoundFile_create();
85     }
86 
87     /// Destructor.
88     ~this()
89     {
90         import dsfml.system.config: destructorOutput;
91         mixin(destructorOutput);
92         sfInputSoundFile_destroy(m_soundFile);
93     }
94 
95     /**
96      * Open a sound file from the disk for reading.
97      *
98      * The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC. The
99      * supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
100      *
101      * Params:
102      *	filename = Path of the sound file to load
103      *
104      * Returns: true if the file was successfully opened.
105      */
106     bool openFromFile(const(char)[] filename)
107     {
108         import dsfml.system..string;
109         bool toReturn = sfInputSoundFile_openFromFile(m_soundFile, filename.ptr, filename.length);
110         err.write(dsfml.system..string.toString(sfErr_getOutput()));
111         return toReturn;
112     }
113 
114     /**
115      * Open a sound file in memory for reading.
116      *
117      * The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC. The
118      * supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
119      *
120      * Params:
121      *	data = file data in memory
122      *
123      * Returns: true if the file was successfully opened.
124      */
125     bool openFromMemory(const(void)[] data)
126     {
127         import dsfml.system..string;
128         bool toReturn = sfInputSoundFile_openFromMemory(m_soundFile, data.ptr, data.length);
129         err.write(dsfml.system..string.toString(sfErr_getOutput()));
130         return toReturn;
131     }
132 
133     /**
134      * Open a sound file from a custom stream for reading.
135      *
136      * The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC. The
137      * supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
138      *
139      * Params:
140      *	stream = Source stream to read from
141      *
142      * Returns: true if the file was successfully opened.
143      */
144     bool openFromStream(InputStream stream)
145     {
146         import dsfml.system..string;
147         m_stream = new soundFileStream(stream);
148 
149         bool toReturn  = sfInputSoundFile_openFromStream(m_soundFile, m_stream);
150         err.write(dsfml.system..string.toString(sfErr_getOutput()));
151         return toReturn;
152     }
153 
154     /**
155      * Read audio samples from the open file.
156      *
157      * Params:
158      *	samples = array of samples to fill
159      *
160      * Returns: Number of samples actually read (may be less samples.length)
161      */
162     long read(short[] samples)
163     {
164         return sfInputSoundFile_read(m_soundFile, samples.ptr, samples.length);
165 
166     }
167 
168     /**
169      * Change the current read position to the given sample offset.
170      *
171      * This function takes a sample offset to provide maximum precision. If you
172      * need to jump to a given time, use the other overload.
173      *
174      * The sample offset takes the channels into account. Offsets can be
175      * calculated like this: sampleNumber * sampleRate * channelCount.
176      * If the given offset exceeds to total number of samples, this function
177      * jumps to the end of the sound file.
178      *
179      * Params:
180      *	sampleOffset = Index of the sample to jump to, relative to the beginning
181      */
182     void seek(long sampleOffset)
183     {
184         sfInputSoundFile_seek(m_soundFile, sampleOffset);
185 
186         //Temporary fix for a bug where attempting to write to err
187         //throws an exception in a thread created in C++. This causes
188         //the program to explode. Hooray.
189 
190         //This fix will skip the call to err.write if there was no error
191         //to report. If there is an error, well, the program will still explode,
192         //but the user should see the error prior to the call that will make the
193         //program explode.
194 
195         string temp = dsfml.system..string.toString(sfErr_getOutput());
196         if(temp.length > 0)
197         {
198             err.write(temp);
199         }
200     }
201 
202     /**
203      * Change the current read position to the given time offset.
204      *
205      * Using a time offset is handy but imprecise. If you need an accurate
206      * result, consider using the overload which takes a sample offset.
207      *
208      * If the given time exceeds to total duration, this function jumps to the
209      * end of the sound file.
210      *
211      * Params:
212      *	timeOffset = Time to jump to, relative to the beginning
213      */
214     void seek(Duration timeOffset)
215     {
216         seek(timeOffset.total!"usecs");
217     }
218 
219     /**
220      * Get the total number of audio samples in the file
221      *
222      * Returns: Number of samples.
223      */
224     long getSampleCount()
225     {
226         return sfInputSoundFile_getSampleCount(m_soundFile);
227     }
228 
229     /**
230      * Get the sample rate of the sound
231      *
232      * Returns: Sample rate, in samples per second.
233      */
234     uint getSampleRate()
235     {
236         return sfInputSoundFile_getSampleRate(m_soundFile);
237     }
238 
239     /**
240      * Get the number of channels used by the sound
241      *
242      * Returns: Number of channels (1 = mono, 2 = stereo).
243      */
244     uint getChannelCount()
245     {
246         return sfInputSoundFile_getChannelCount(m_soundFile);
247     }
248 }
249 
250 private
251 {
252 
253 extern(C++) interface soundInputStream
254 {
255     long read(void* data, long size);
256 
257     long seek(long position);
258 
259     long tell();
260 
261     long getSize();
262 }
263 
264 
265 class soundFileStream:soundInputStream
266 {
267     private InputStream myStream;
268 
269     this(InputStream stream)
270     {
271         myStream = stream;
272     }
273 
274     extern(C++)long read(void* data, long size)
275     {
276         return myStream.read(data[0..cast(size_t)size]);
277     }
278 
279     extern(C++)long seek(long position)
280     {
281         return myStream.seek(position);
282     }
283 
284     extern(C++)long tell()
285     {
286         return myStream.tell();
287     }
288 
289     extern(C++)long getSize()
290     {
291         return myStream.getSize();
292     }
293 }
294 
295 
296 extern(C) const(char)* sfErr_getOutput();
297 
298 
299 extern(C)
300 {
301 
302 struct sfInputSoundFile;
303 
304 sfInputSoundFile* sfInputSoundFile_create();
305 
306 void sfInputSoundFile_destroy(sfInputSoundFile* file);
307 
308 long sfInputSoundFile_getSampleCount(const sfInputSoundFile* file);
309 
310 uint sfInputSoundFile_getChannelCount( const sfInputSoundFile* file);
311 
312 uint sfInputSoundFile_getSampleRate(const sfInputSoundFile* file);
313 
314 bool sfInputSoundFile_openFromFile(sfInputSoundFile* file, const char* filename, size_t length);
315 
316 bool sfInputSoundFile_openFromMemory(sfInputSoundFile* file,const(void)* data, long sizeInBytes);
317 
318 bool sfInputSoundFile_openFromStream(sfInputSoundFile* file, soundInputStream stream);
319 
320 bool sfInputSoundFile_openForWriting(sfInputSoundFile* file, const(char)* filename,uint channelCount,uint sampleRate);
321 
322 long sfInputSoundFile_read(sfInputSoundFile* file, short* data, long sampleCount);
323 
324 void sfInputSoundFile_seek(sfInputSoundFile* file, long timeOffset);
325     }
326 }