Merge pull request #90 from gizmo98/patch-2
[libretro-ppsspp.git] / UI / BackgroundAudio.cpp
blobcccb9ba7dedaf07577867cf2253681a47cf46eb5
1 #include <string>
2 #include "base/logging.h"
3 #include "base/timeutil.h"
4 #include "base/mutex.h"
5 #include "native/file/chunk_file.h"
7 #include "Common/CommonTypes.h"
8 #include "Core/HW/SimpleAudioDec.h"
9 #include "Core/HLE/__sceAudio.h"
10 #include "Common/FixedSizeQueue.h"
11 #include "GameInfoCache.h"
12 #include "Core/Config.h"
14 // Really simple looping in-memory AT3 player that also takes care of reading the file format.
15 // Turns out that AT3 files used for this are modified WAVE files so fairly easy to parse.
16 class AT3PlusReader {
17 public:
18 AT3PlusReader(const std::string &data)
19 : file_((const uint8_t *)&data[0],
20 (int32_t)data.size()),
21 raw_data_(0),
22 raw_data_size_(0),
23 raw_offset_(0),
24 buffer_(0),
25 decoder_(0) {
27 // Normally 8k but let's be safe.
28 buffer_ = new short[32 * 1024];
30 int codec = PSP_CODEC_AT3PLUS;
31 u8 at3_extradata[16];
33 int num_channels, sample_rate, numFrames, samplesPerSec, avgBytesPerSec, Nothing;
34 if (file_.descend('RIFF')) {
35 file_.readInt(); //get past 'WAVE'
36 if (file_.descend('fmt ')) { //enter the format chunk
37 int temp = file_.readInt();
38 int format = temp & 0xFFFF;
39 switch (format) {
40 case 0xFFFE:
41 codec = PSP_CODEC_AT3PLUS;
42 break;
43 case 0x270:
44 codec = PSP_CODEC_AT3;
45 break;
46 default:
47 ERROR_LOG(HLE, "Unexpected SND0.AT3 format %04x", format);
48 return;
51 num_channels = temp >> 16;
53 samplesPerSec = file_.readInt();
54 avgBytesPerSec = file_.readInt();
56 temp = file_.readInt();
57 raw_bytes_per_frame_ = temp & 0xFFFF;
58 Nothing = temp >> 16;
60 if (codec == PSP_CODEC_AT3) {
61 // The first two bytes are actually not a useful part of the extradata.
62 // We already read 16 bytes, so make sure there's enough left.
63 if (file_.getCurrentChunkSize() >= 32) {
64 file_.readData(at3_extradata, 16);
65 } else {
66 memset(at3_extradata, 0, sizeof(at3_extradata));
69 file_.ascend();
70 // ILOG("got fmt data: %i", samplesPerSec);
71 } else {
72 ELOG("Error - no format chunk in wav");
73 file_.ascend();
74 return;
77 if (file_.descend('data')) { //enter the data chunk
78 int numBytes = file_.getCurrentChunkSize();
79 numFrames = numBytes / raw_bytes_per_frame_; // numFrames
81 raw_data_ = (uint8_t *)malloc(numBytes);
82 raw_data_size_ = numBytes;
83 if (/*raw_bytes_per_frame_ == 280 && */ num_channels == 2) {
84 file_.readData(raw_data_, numBytes);
85 } else {
86 ELOG("Error - bad blockalign or channels");
87 free(raw_data_);
88 raw_data_ = 0;
89 return;
91 file_.ascend();
92 } else {
93 ELOG("Error - no data chunk in wav");
94 file_.ascend();
95 return;
97 file_.ascend();
98 } else {
99 ELOG("Could not descend into RIFF file");
100 return;
102 sample_rate = samplesPerSec;
103 decoder_ = new SimpleAudio(codec, sample_rate, num_channels);
104 if (codec == PSP_CODEC_AT3) {
105 decoder_->SetExtraData(&at3_extradata[2], 14, raw_bytes_per_frame_);
107 ILOG("read ATRAC, frames: %i, rate %i", numFrames, sample_rate);
110 ~AT3PlusReader() {
113 void Shutdown() {
114 free(raw_data_);
115 raw_data_ = 0;
116 delete[] buffer_;
117 buffer_ = 0;
118 delete decoder_;
119 decoder_ = 0;
122 bool IsOK() { return raw_data_ != 0; }
124 bool Read(int *buffer, int len) {
125 if (!raw_data_)
126 return false;
128 while (bgQueue.size() < (size_t)(len * 2)) {
129 int outBytes;
130 decoder_->Decode(raw_data_ + raw_offset_, raw_bytes_per_frame_, (uint8_t *)buffer_, &outBytes);
131 if (!outBytes)
132 return false;
134 for (int i = 0; i < outBytes / 2; i++) {
135 bgQueue.push(buffer_[i]);
138 // loop!
139 raw_offset_ += raw_bytes_per_frame_;
140 if (raw_offset_ >= raw_data_size_) {
141 raw_offset_ = 0;
145 for (int i = 0; i < len * 2; i++) {
146 buffer[i] = bgQueue.pop_front();
148 return true;
151 private:
152 ChunkFile file_;
153 uint8_t *raw_data_;
154 int raw_data_size_;
155 int raw_offset_;
156 int raw_bytes_per_frame_;
157 FixedSizeQueue<s16, 128 * 1024> bgQueue;
158 short *buffer_;
159 SimpleAudio *decoder_;
162 static recursive_mutex bgMutex;
163 static std::string bgGamePath;
164 static int playbackOffset;
165 static AT3PlusReader *at3Reader;
166 static double gameLastChanged;
167 static double lastPlaybackTime;
168 static int buffer[44100];
170 static void ClearBackgroundAudio() {
171 if (at3Reader) {
172 at3Reader->Shutdown();
173 delete at3Reader;
174 at3Reader = 0;
176 playbackOffset = 0;
179 void SetBackgroundAudioGame(const std::string &path) {
180 time_update();
182 lock_guard lock(bgMutex);
183 if (path == bgGamePath) {
184 // Do nothing
185 return;
188 if (!g_Config.bEnableSound) {
189 ClearBackgroundAudio();
190 return;
193 ClearBackgroundAudio();
194 gameLastChanged = time_now_d();
195 bgGamePath = path;
198 int PlayBackgroundAudio() {
199 time_update();
201 lock_guard lock(bgMutex);
203 // Immediately stop the sound if it is turned off while playing.
204 if (!g_Config.bEnableSound) {
205 ClearBackgroundAudio();
206 __PushExternalAudio(0, 0);
207 return 0;
210 // If there's a game, and some time has passed since the selected game
211 // last changed... (to prevent crazy amount of reads when skipping through a list)
212 if (!at3Reader && bgGamePath.size() && (time_now_d() - gameLastChanged > 0.5)) {
213 // Grab some audio from the current game and play it.
214 GameInfo *gameInfo = g_gameInfoCache.GetInfo(NULL, bgGamePath, GAMEINFO_WANTSND);
215 if (!gameInfo)
216 return 0;
218 if (gameInfo->sndFileData.size()) {
219 const std::string &data = gameInfo->sndFileData;
220 at3Reader = new AT3PlusReader(data);
221 lastPlaybackTime = 0.0;
225 double now = time_now();
226 if (at3Reader) {
227 int sz = lastPlaybackTime <= 0.0 ? 44100 / 60 : (int)((now - lastPlaybackTime) * 44100);
228 sz = std::min((int)ARRAY_SIZE(buffer) / 2, sz);
229 if (sz >= 16) {
230 if (at3Reader->Read(buffer, sz))
231 __PushExternalAudio(buffer, sz);
232 lastPlaybackTime = now;
234 } else {
235 __PushExternalAudio(0, 0);
236 lastPlaybackTime = now;
239 return 0;