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 }