/ source / audiolib / src / vorbis.cpp
vorbis.cpp
  1  /*
  2   Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
  3   Copyright (C) 2020 EDuke32 developers and contributors
  4  
  5   This program is free software; you can redistribute it and/or
  6   modify it under the terms of the GNU General Public License
  7   as published by the Free Software Foundation; either version 2
  8   of the License, or (at your option) any later version.
  9  
 10   This program is distributed in the hope that it will be useful,
 11   but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 13  
 14   See the GNU General Public License for more details.
 15  
 16   You should have received a copy of the GNU General Public License
 17   along with this program; if not, write to the Free Software
 18   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 19  
 20   */
 21  
 22  /**
 23   * OggVorbis source support for MultiVoc
 24   */
 25  
 26  #include "_multivc.h"
 27  
 28  #include "baselayer.h"
 29  
 30  #ifdef _WIN32
 31  #include "winbits.h"
 32  #endif
 33  
 34  #ifdef HAVE_VORBIS
 35  
 36  #define BLOCKSIZE 512
 37  
 38  #define OGG_IMPL
 39  #define VORBIS_IMPL
 40  #define OV_EXCLUDE_STATIC_CALLBACKS
 41  
 42  #include "minivorbis.h"
 43  
 44  typedef struct {
 45     void * ptr;
 46     size_t length;
 47     size_t pos;
 48  
 49     OggVorbis_File vf;
 50  
 51     char block[BLOCKSIZE];
 52     int lastbitstream;
 53  } vorbis_data;
 54  
 55  // designed with multiple calls in mind
 56  static void MV_GetVorbisCommentLoops(VoiceNode *voice, vorbis_comment *vc)
 57  {
 58      const char *vc_loopstart = nullptr;
 59      const char *vc_loopend = nullptr;
 60      const char *vc_looplength = nullptr;
 61  
 62      for (int comment = 0; comment < vc->comments; ++comment)
 63      {
 64          auto entry = (const char *)vc->user_comments[comment];
 65          if (entry != nullptr && entry[0] != '\0')
 66          {
 67              const char *value = Bstrchr(entry, '=');
 68  
 69              if (!value)
 70                  continue;
 71  
 72              const size_t field = value - entry;
 73              value += 1;
 74  
 75              for (int t = 0; t < loopStartTagCount && vc_loopstart == nullptr; ++t)
 76              {
 77                  auto tag = loopStartTags[t];
 78                  if (field == Bstrlen(tag) && Bstrncasecmp(entry, tag, field) == 0)
 79                      vc_loopstart = value;
 80              }
 81  
 82              for (int t = 0; t < loopEndTagCount && vc_loopend == nullptr; ++t)
 83              {
 84                  auto tag = loopEndTags[t];
 85                  if (field == Bstrlen(tag) && Bstrncasecmp(entry, tag, field) == 0)
 86                      vc_loopend = value;
 87              }
 88  
 89              for (int t = 0; t < loopLengthTagCount && vc_looplength == nullptr; ++t)
 90              {
 91                  auto tag = loopLengthTags[t];
 92                  if (field == Bstrlen(tag) && Bstrncasecmp(entry, tag, field) == 0)
 93                      vc_looplength = value;
 94              }
 95          }
 96      }
 97  
 98      auto vd    = (vorbis_data *)voice->rawdataptr;
 99      auto total = ov_pcm_total(&vd->vf, -1);
100  
101      if (vc_loopstart != nullptr)
102      {
103          const ogg_int64_t ov_loopstart = Batol(vc_loopstart);
104          if ((unsigned)(ov_loopstart-1) <= total)
105          {
106              voice->Loop.Start = (const char *) (intptr_t) ov_loopstart;
107              voice->Loop.Size  = 1;
108          }
109          else LOG_F(WARNING, "MV_GetVorbisCommentLoops: loop start is beyond end of data");
110      }
111      if (vc_loopend != nullptr)
112      {
113          if (voice->Loop.Size > 0)
114          {
115              const ogg_int64_t ov_loopend = Batol(vc_loopend);
116              if ((unsigned)(ov_loopend-1) <= total)
117                  voice->Loop.End = (const char *) (intptr_t) ov_loopend;
118              else LOG_F(WARNING, "MV_GetVorbisCommentLoops: loop end is beyond end of data");
119          }
120      }
121      if (vc_looplength != nullptr)
122      {
123          if (voice->Loop.Size > 0 && voice->Loop.End == 0)
124          {
125              const ogg_int64_t ov_looplength = Batol(vc_looplength);
126              if (ov_looplength > 0) // a loop of length 0 is invalid
127                  voice->Loop.End = (const char *) ((intptr_t) ov_looplength + (intptr_t) voice->Loop.Start);
128              else LOG_F(WARNING, "MV_GetVorbisCommentLoops: loop length is zero");
129          }
130      }
131  }
132  
133  // callbacks
134  
135  static size_t read_vorbis(void *ptr, size_t size, size_t nmemb, void *datasource)
136  {
137      auto vorb = (vorbis_data *)datasource;
138  
139      errno = 0;
140  
141      if (vorb->length == vorb->pos)
142          return 0;
143  
144      int nread = 0;
145  
146      for (; nmemb > 0; nmemb--, nread++)
147      {
148          int bytes = vorb->length - vorb->pos;
149  
150          if ((signed)size < bytes)
151              bytes = (int)size;
152  
153          memcpy(ptr, (uint8_t *)vorb->ptr + vorb->pos, bytes);
154          vorb->pos += bytes;
155          ptr = (uint8_t *)ptr + bytes;
156  
157          if (vorb->length == vorb->pos)
158          {
159              nread++;
160              break;
161          }
162      }
163  
164      return nread;
165  }
166  
167  
168  static int seek_vorbis(void *datasource, ogg_int64_t offset, int whence)
169  {
170      auto vorb = (vorbis_data *)datasource;
171  
172      switch (whence)
173      {
174          case SEEK_SET: vorb->pos = 0; break;
175          case SEEK_CUR: break;
176          case SEEK_END: vorb->pos = vorb->length; break;
177      }
178  
179      vorb->pos += offset;
180  
181      if (vorb->pos > vorb->length)
182          vorb->pos = vorb->length;
183  
184      return vorb->pos;
185  }
186  
187  static int close_vorbis(void *) { return 0; }
188  
189  static long tell_vorbis(void *datasource)
190  {
191      auto vorb = (vorbis_data *)datasource;
192  
193      return vorb->pos;
194  }
195  
196  
197  int MV_GetVorbisPosition(VoiceNode *voice)
198  {
199      auto vd = (vorbis_data *) voice->rawdataptr;
200  
201      return ov_pcm_tell(&vd->vf);
202  }
203  
204  void MV_SetVorbisPosition(VoiceNode *voice, int position)
205  {
206      auto vd = (vorbis_data *) voice->rawdataptr;
207  
208      ov_pcm_seek(&vd->vf, position);
209  }
210  
211  static playbackstatus MV_GetNextVorbisBlock(VoiceNode *voice)
212  {
213      int bitstream;
214      int bytesread = 0;
215      auto vd = (vorbis_data *)voice->rawdataptr;
216  
217      if (!vd)
218      {
219          LOG_F(ERROR, "MV_GetNextVorbisBlock: bad rawdataptr!");
220          return NoMoreData;
221      }
222  
223      do
224      {
225  #ifdef USING_TREMOR
226          int bytes = ov_read(&vd->vf, vd->block + bytesread, BLOCKSIZE - bytesread, &bitstream);
227  #else
228          int bytes = ov_read(&vd->vf, vd->block + bytesread, BLOCKSIZE - bytesread, 0, 2, 1, &bitstream);
229  #endif
230          // fprintf(stderr, "ov_read = %d\n", bytes);
231          if (bytes > 0)
232          {
233              ogg_int64_t currentPosition;
234              bytesread += bytes;
235              if ((ogg_int64_t)(intptr_t)voice->Loop.End > 0 &&
236                  (currentPosition = ov_pcm_tell(&vd->vf)) >= (ogg_int64_t)(intptr_t)voice->Loop.End)
237              {
238                  bytesread -=
239                  (currentPosition - (ogg_int64_t)(intptr_t)voice->Loop.End) * voice->channels * 2;  // (voice->bits>>3)
240  
241                  int const err = ov_pcm_seek(&vd->vf, (ogg_int64_t)(intptr_t)voice->Loop.Start);
242  
243                  if (err != 0)
244                  {
245                      LOG_F(ERROR, "MV_GetNextVorbisBlock: error %d in ov_pcm_seek (LOOP_START %" PRIi64 ", LOOP_END %" PRIi64 ")",
246                                err, (ogg_int64_t)(intptr_t)voice->Loop.Start, (ogg_int64_t)(intptr_t)voice->Loop.End);
247                  }
248              }
249              continue;
250          }
251          else if (bytes == OV_HOLE)
252              continue;
253          else if (bytes == 0)
254          {
255              if (voice->Loop.Size > 0)
256              {
257                  int const err = ov_pcm_seek(&vd->vf, (ogg_int64_t)(intptr_t)voice->Loop.Start);
258  
259                  if (err != 0)
260                  {
261                      LOG_F(ERROR, "MV_GetNextVorbisBlock: error %d in ov_pcm_seek (LOOP_START %" PRIi64 ")",
262                                err, (ogg_int64_t)(intptr_t)voice->Loop.Start);
263                  }
264                  else
265                      continue;
266              }
267              else
268              {
269                  break;
270              }
271          }
272          else if (bytes < 0)
273          {
274              LOG_F(ERROR, "MV_GetNextVorbisBlock: error %d in ov_read", bytes);
275              voice->rawdataptr = nullptr;
276              voice->rawdatasiz = 0;
277              ov_clear(&vd->vf);
278              ALIGNED_FREE_AND_NULL(vd);
279              return NoMoreData;
280          }
281      } while (bytesread < BLOCKSIZE);
282  
283      if (bytesread == 0)
284          return NoMoreData;
285  
286      if (bitstream != vd->lastbitstream)
287      {
288          vorbis_info *vi = ov_info(&vd->vf, -1);
289          if (!vi || (vi->channels != 1 && vi->channels != 2))
290              return NoMoreData;
291  
292          voice->channels     = vi->channels;
293          voice->SamplingRate = vi->rate;
294          voice->RateScale    = divideu64((uint64_t)voice->SamplingRate * voice->PitchScale, MV_MixRate);
295  
296          voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) - voice->RateScale;
297          vd->lastbitstream = bitstream;
298          MV_SetVoiceMixMode(voice);
299      }
300  
301      uint32_t const samples = divideu32(bytesread, ((voice->bits>>3) * voice->channels));
302  
303      voice->position = 0;
304      voice->sound = vd->block;
305      voice->length = samples << 16;
306  
307  #ifdef GEKKO
308      // If libtremor had the three additional ov_read() parameters that libvorbis has,
309      // this would be better handled using the endianness parameter.
310      int16_t *data = (int16_t *)(vd->block);  // assumes signed 16-bit
311      for (bytesread = 0; bytesread < BLOCKSIZE / 2; ++bytesread)
312          data[bytesread] = (data[bytesread] & 0xff) << 8 | ((data[bytesread] & 0xff00) >> 8);
313  #endif
314  
315      return KeepPlaying;
316  }
317  
318  int MV_PlayVorbis3D(char *ptr, uint32_t length, int loophow, int pitchoffset, int angle, int distance, int priority, fix16_t volume, intptr_t callbackval)
319  {
320      if (!MV_Installed)
321          return MV_SetErrorCode(MV_NotInstalled);
322  
323      if (distance < 0)
324      {
325          distance = -distance;
326          angle += MV_NUMPANPOSITIONS / 2;
327      }
328  
329      int const vol = MIX_VOLUME(distance);
330  
331      // Ensure angle is within 0 - 127
332      angle &= MV_MAXPANPOSITION;
333  
334      return MV_PlayVorbis(ptr, length, loophow, -1, pitchoffset, max(0, 255 - distance),
335                           MV_PanTable[angle][vol].left, MV_PanTable[angle][vol].right, priority, volume, callbackval);
336  }
337  
338  static constexpr ov_callbacks vorbis_callbacks = { read_vorbis, seek_vorbis, close_vorbis, tell_vorbis };
339  
340  int MV_PlayVorbis(char *ptr, uint32_t length, int loopstart, int loopend, int pitchoffset, int vol, int left, int right, int priority, fix16_t volume, intptr_t callbackval)
341  {
342      UNREFERENCED_PARAMETER(loopend);
343  
344      if (!MV_Installed)
345          return MV_SetErrorCode(MV_NotInstalled);
346  
347      auto voice = MV_AllocVoice(priority, sizeof(vorbis_data));
348      if (voice == nullptr)
349          return MV_SetErrorCode(MV_NoVoices);
350  
351      auto vd = (vorbis_data *)voice->rawdataptr;
352  
353      vd->ptr    = ptr;
354      vd->pos    = 0;
355      vd->length = length;
356  
357      // pitchoffset is passed into the worker tasks using the preexisting lastbitstream member
358      vd->lastbitstream = pitchoffset;
359  
360      voice->wavetype    = FMT_VORBIS;
361      voice->bits        = 16;
362      voice->NextBlock   = vd->block;
363      voice->priority    = priority;
364      voice->callbackval = callbackval;
365      voice->Loop        = { nullptr, nullptr, 0, (loopstart >= 0) };
366      voice->GetSound    = MV_GetNextVorbisBlock;
367      voice->Paused      = true;
368  
369      MV_SetVoiceMixMode(voice);
370      MV_SetVoiceVolume(voice, vol, left, right, volume);
371  
372      //if (vd->task.valid() && !vd->task.ready())
373      //    vd->task.wait();
374  
375      voice->task = async::spawn([voice]() -> int
376      {
377  #if defined _WIN32
378          debugThreadName("MV_PlayVorbis");
379  #endif
380          auto vd = (vorbis_data *)voice->rawdataptr;
381  
382          // yoinked ptr indicates we're in some shitshow scenario where we tried to cancel
383          // the voice before the decoder even got a chance to start initializing
384  
385          if (!vd)
386          {
387              voice->rawdatasiz = 0;
388              MV_PlayVoice(voice);
389              return MV_SetErrorCode(MV_VoiceNotFound);
390          }
391  
392          int status = ov_open_callbacks((void *)vd, &vd->vf, 0, 0, vorbis_callbacks);
393          vorbis_info *vi = nullptr;
394  
395          if (status < 0 || ((vi = ov_info(&vd->vf, 0)) == nullptr) || vi->channels < 1 || vi->channels > 2)
396          {
397              if (status == 0)
398              {
399                  ov_clear(&vd->vf);
400                  if (vi) LOG_F(ERROR, "MV_PlayVorbis: unsupported subformat: channels = %d", vi->channels);
401                  else LOG_F(ERROR, "MV_PlayVorbis: bad ov_info");
402              }
403              else
404                  LOG_F(ERROR, "MV_PlayVorbis: error %d in ov_open_callbacks", status);
405  
406              ALIGNED_FREE_AND_NULL(voice->rawdataptr);
407              voice->rawdatasiz = 0;
408              MV_PlayVoice(voice);
409              return MV_SetErrorCode(MV_InvalidFile);
410          }
411  
412          voice->channels = vi->channels;
413  
414          // load loop tags from metadata
415          if (auto comment = ov_comment(&vd->vf, 0))
416              MV_GetVorbisCommentLoops(voice, comment);
417  
418          MV_SetVoicePitch(voice, vi->rate, vd->lastbitstream);
419          vd->lastbitstream = -1;
420          MV_PlayVoice(voice);
421          return MV_Ok;
422      });
423  
424      return voice->handle;
425  }
426  
427  void MV_ReleaseVorbisVoice(VoiceNode *voice)
428  {
429      Bassert(voice->wavetype == FMT_VORBIS && voice->rawdataptr != nullptr && voice->rawdatasiz == sizeof(vorbis_data));
430  
431      auto vd = (vorbis_data *)voice->rawdataptr;
432      //vd->task.wait();
433  
434      ov_clear(&vd->vf);
435  
436      if (MV_LazyAlloc)
437          return;
438  
439      voice->rawdataptr = nullptr;
440      voice->rawdatasiz = 0;
441      ALIGNED_FREE_AND_NULL(vd);
442  }
443  #else
444  #include "_multivc.h"
445  
446  int MV_PlayVorbis(char *, uint32_t, int, int, int, int, int, int, int, fix16_t, intptr_t)
447  {
448      LOG_F(ERROR, "MV_PlayVorbis: OggVorbis support not included in this binary.");
449      return -1;
450  }
451  
452  int MV_PlayVorbis3D(char *, uint32_t, int, int, int, int, int, fix16_t, intptr_t)
453  {
454      LOG_F(ERROR, "MV_PlayVorbis: OggVorbis support not included in this binary.");
455      return -1;
456  }
457  #endif //HAVE_VORBIS