1 module soundtab.audio.mp3player;
2 
3 import soundtab.audio.audiosource;
4 public import soundtab.audio.loader;
5 import dlangui.core.logger;
6 import derelict.mpg123;
7 
8 struct PlayPosition {
9     /// current playback position, seconds
10     float currentPosition;
11     /// total length, seconds
12     float length;
13 
14     /// returns current position as percent*100 : 0..10000
15     @property int positionPercent() {
16         if (length < 0.1)
17             return 0;
18         int res = cast(int)(10000 * currentPosition / length);
19         if (res < 0)
20             res = 0;
21         if (res > 10000)
22             res = 10000;
23         return res;
24     }
25 
26     float percentToSeconds(int percent) {
27         return percent * length / 10000;
28     }
29 }
30 
31 class Mp3Player : AudioSource {
32     private string _filename;
33     private bool _loaded;
34     private int _sourcePosition;
35     private int _sourceFrames;
36     protected int _loopStart;
37     protected int _loopEnd;
38     private bool _paused = true;
39     private bool _ownWave;
40 
41     private WaveFile _file;
42 
43     @property bool paused() { return _paused; }
44     @property Mp3Player paused(bool pauseFlag) {
45         _paused = pauseFlag;
46         return this;
47     }
48 
49     /// returns current filename
50     @property string filename() {
51         lock();
52         scope(exit)unlock();
53         return _filename;
54     }
55 
56     /// set current filename and load MP3 file; returns true if successful
57     @property bool filename(string filename) {
58         return loadFromFile(filename);
59     }
60 
61     /// get current play position and total track length (seconds)
62     @property PlayPosition position() {
63         lock();
64         scope(exit)unlock();
65         if (!_loaded)
66             return PlayPosition(0, 0);
67         return PlayPosition(_sourcePosition / cast(float)_file.sampleRate, _file.frames / cast(float)_file.sampleRate);
68     }
69 
70     /// set current play position (seconds)
71     @property void position(float positionSeconds) {
72         lock();
73         scope(exit)unlock();
74         if (!_loaded)
75             return;
76         int newPosition = cast(int)(positionSeconds * _file.sampleRate);
77         if (newPosition < 0)
78             newPosition = 0;
79         if (newPosition > _sourceFrames)
80             newPosition = _sourceFrames;
81         _sourcePosition = newPosition;
82     }
83 
84     @property float loopStart() { return _file ? _file.frameToTime(_loopStart) : 0; }
85     @property float loopEnd() { return _file ? _file.frameToTime(_loopEnd) : 0; }
86     void removeLoop() {
87         setLoop(0, 0);
88     }
89     void setLoop(float startSeconds, float endSeconds) {
90         lock();
91         scope(exit)unlock();
92         if (!_loaded)
93             return;
94         int start = cast(int)(startSeconds * _file.sampleRate);
95         int end = cast(int)(endSeconds * _file.sampleRate);
96         _file.limitFrameIndex(start);
97         _file.limitFrameIndex(end);
98         if (start + 50 < end) {
99             _loopStart = start;
100             _loopEnd = end;
101             _sourcePosition = _loopStart;
102         } else {
103             _loopStart = _loopEnd = 0;
104         }
105     }
106 
107     @property bool loaded() { return _loaded; }
108     /// returns playback position frame number
109     @property int sourcePosition() { 
110         lock();
111         scope(exit)unlock();
112         return _loaded ? _sourcePosition : 0; 
113     }
114     /// sets playback position frame number
115     @property void sourcePosition(int newPosition) { 
116         lock();
117         scope(exit)unlock();
118         if (newPosition < 0)
119             newPosition = 0;
120         if (newPosition > _sourceFrames)
121             newPosition = _sourceFrames;
122         _sourcePosition = newPosition;
123     }
124 
125 
126     protected void clear() {
127         {
128             lock();
129             scope(exit)unlock();
130             _loaded = false;
131             _sourcePosition = 0;
132             _paused = true;
133             _filename = null;
134             if (_file && _ownWave) {
135                 destroy(_file);
136                 _file = null;
137             }
138         }
139     }
140 
141     // set loaded MP3 file
142     bool setWave(WaveFile wave, bool ownWave = false) {
143         if (_file is wave)
144             return _loaded;
145         import std..string : toStringz;
146         clear();
147         if (wave && wave.frames) {
148             lock();
149             scope(exit)unlock();
150             _file = wave;
151             _loaded = true;
152             _ownWave = ownWave;
153             _filename = wave.filename;
154             _sourcePosition = 0;
155             _sourceFrames = _file.frames;
156         } else {
157             // no file
158         }
159         return _loaded;
160     }
161 
162     /// load MP3 file
163     bool loadFromFile(string filename) {
164         import std..string : toStringz;
165         if (filename == _filename) {
166             lock();
167             scope(exit)unlock();
168             // opening the same file as already opened - just move to start
169             _sourcePosition = 0;
170             _paused = true;
171             return _loaded;
172         }
173         clear();
174         WaveFile f = loadSoundFile(filename, false);
175         if (f) {
176             lock();
177             scope(exit)unlock();
178             _file = f;
179             _ownWave = true;
180             _loaded = true;
181             _filename = filename;
182             _sourcePosition = 0;
183             _sourceFrames = _file.frames;
184             Log.e("File is loaded");
185         } else {
186             Log.e("Load error");
187         }
188         return _loaded;
189     }
190 
191     /// load data into buffer
192     override bool loadData(int frameCount, ubyte * buf, ref uint flags) {
193         lock();
194         scope(exit)unlock();
195         flags = 0;
196         // silence
197         if (!_loaded || _paused || !_sourceFrames || _zeroVolume || _sourcePosition >= _sourceFrames) {
198             generateSilence(frameCount, buf);
199             flags |= AUDIO_SOURCE_SILENCE_FLAG;
200             return true;
201         }
202         int i = 0;
203         int srcpos = _sourcePosition * _file.channels;
204         bool inLoop = (_loopStart < _loopEnd && _sourcePosition >= _loopStart && _sourcePosition <= _loopEnd);
205         if (_file.sampleRate != samplesPerSecond) {
206             // need resampling
207             // simple get-nearest-frame resampler
208             float sampleTime = 1.0f / samplesPerSecond;
209             float time = _file.frameToTime(_sourcePosition);
210             for (; i < frameCount; i++) {
211                 float sample1 = _file.getSampleInterpolated(time, 0);
212                 float sample2 = _file.getSampleInterpolated(time, 1);
213                 if (!_unityVolume) {
214                     sample1 *= _volume;
215                     sample2 *= _volume;
216                 }
217                 putSamples(buf, sample1, sample2);
218                 buf += blockAlign;
219                 time += sampleTime;
220                 _sourcePosition = _file.timeToFrame(time);
221                 if (inLoop && _sourcePosition >= _loopEnd) {
222                     _sourcePosition = _loopStart;
223                     time = _file.frameToTime(_sourcePosition);
224                 }
225             }
226             if (_sourcePosition > _sourceFrames)
227                 _sourcePosition = _sourceFrames;
228         } else {
229             // no resampling
230             for (; i < frameCount; i++) {
231                 float sample1 = _file.data.ptr[srcpos++];
232                 float sample2 = _file.channels > 1 ? _file.data.ptr[srcpos++] : sample1;
233                 if (!_unityVolume) {
234                     sample1 *= _volume;
235                     sample2 *= _volume;
236                 }
237                 putSamples(buf, sample1, sample2);
238                 buf += blockAlign;
239                 if (inLoop && _sourcePosition == _loopEnd)
240                     _sourcePosition = _loopStart;
241                 else
242                     _sourcePosition++;
243                 if (_sourcePosition >= _sourceFrames)
244                     break;
245             }
246             for (; i < frameCount; i++) {
247                 putSamples(buf, 0.0f, 0.0f);
248                 buf += blockAlign;
249             }
250         }
251         return true;
252     }
253 }