/ source / audiolib / src / xmp.cpp
xmp.cpp
  1  
  2  #include "compat.h"
  3  
  4  #ifdef HAVE_XMP
  5  
  6  #include "_multivc.h"
  7  #include "multivoc.h"
  8  #include "pitch.h"
  9  #include "pragmas.h"
 10  
 11  #define BUILDING_STATIC
 12  #include "libxmp-lite/xmp.h"
 13  
 14  typedef struct {
 15     void * ptr;
 16     size_t length;
 17  
 18     xmp_context ctx;
 19  } xmp_data;
 20  
 21  int  MV_GetXMPPosition(VoiceNode *voice)               { return voice->position; }
 22  void MV_SetXMPPosition(VoiceNode *voice, int position) { xmp_seek_time(((xmp_data *)voice->rawdataptr)->ctx, position); }
 23  
 24  static playbackstatus MV_GetNextXMPBlock(VoiceNode *voice)
 25  {
 26      if (voice->rawdataptr == nullptr)
 27      {
 28          LOG_F(ERROR, "MV_GetNextXMPBlock: no XMP context!");
 29          return NoMoreData;
 30      }
 31  
 32      auto ctx = ((xmp_data *)voice->rawdataptr)->ctx;
 33  
 34      if (xmp_play_frame(ctx) != 0)
 35      {
 36  #if 0
 37          if (voice->Loop.Size > 0)
 38          {
 39              xmp_restart_module(ctx);
 40              if (xmp_play_frame(ctx) != 0)
 41                  return NoMoreData;
 42          }
 43          else
 44  #endif
 45              return NoMoreData;
 46      }
 47  
 48      xmp_frame_info mi;
 49      xmp_get_frame_info(ctx, &mi);
 50  
 51      uint32_t const samples = mi.buffer_size / (2 * (16/8)); // since 2-channel, 16-bit is hardcoded
 52      // uint32_t const samples = mi.buffer_size / (voice->channels * (voice->bits / 8));
 53  
 54      voice->sound    = (char const *)mi.buffer;
 55      voice->length   = samples << 16;
 56      voice->position = mi.time;
 57  
 58      MV_SetVoiceMixMode(voice);
 59  
 60      return KeepPlaying;
 61  }
 62  
 63  int MV_PlayXMP3D(char *ptr, uint32_t length, int loophow, int pitchoffset, int angle, int distance, int priority, fix16_t volume, intptr_t callbackval)
 64  {
 65      if (!MV_Installed)
 66          return MV_SetErrorCode(MV_NotInstalled);
 67  
 68      if (distance < 0)
 69      {
 70          distance  = -distance;
 71          angle    += MV_NUMPANPOSITIONS / 2;
 72      }
 73  
 74      int vol = MIX_VOLUME(distance);
 75  
 76      // Ensure angle is within 0 - 127
 77      angle &= MV_MAXPANPOSITION;
 78  
 79      int left  = MV_PanTable[angle][vol].left;
 80      int right = MV_PanTable[angle][vol].right;
 81      int mid   = max( 0, 255 - distance );
 82  
 83      return MV_PlayXMP(ptr, length, loophow, -1, pitchoffset, mid, left, right, priority, volume, callbackval);
 84  }
 85  
 86  int MV_PlayXMP(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)
 87  {
 88      UNREFERENCED_PARAMETER(loopend);
 89  
 90      if (!MV_Installed)
 91          return MV_SetErrorCode(MV_NotInstalled);
 92  
 93      // Request a voice from the voice pool
 94      auto voice = MV_AllocVoice(priority, sizeof(xmp_data));
 95      if (voice == nullptr)
 96          return MV_SetErrorCode(MV_NoVoices);
 97  
 98      voice->sound       = 0;
 99      voice->wavetype    = FMT_XMP;
100      voice->Paused      = TRUE;
101      voice->GetSound    = MV_GetNextXMPBlock;
102      voice->PitchScale  = PITCH_GetScale(pitchoffset);
103      voice->priority    = priority;
104      voice->callbackval = callbackval;
105  
106      voice->bits        = 16;
107      voice->channels    = 2;
108      voice->SamplingRate = MV_MixRate;
109  
110      voice->Loop = { nullptr, nullptr, 0, loopstart >= 0 };
111  
112      MV_SetVoiceMixMode(voice);
113      MV_SetVoiceVolume(voice, vol, left, right, volume);
114  
115      auto xd = (xmp_data *)voice->rawdataptr;
116  
117      xd->ptr = ptr;
118      xd->length = length;
119  
120      voice->task = async::spawn([voice]() -> int
121      {
122          auto xd = (xmp_data *)voice->rawdataptr;
123          auto ctx = xd->ctx;
124  
125          if (!ctx)
126          {
127              ctx = xmp_create_context();
128              xd->ctx = ctx;
129          }
130  
131          int xmp_status = 0;
132          if (ctx == nullptr || (xmp_status = xmp_load_module_from_memory(ctx, xd->ptr, xd->length)))
133          {
134              if (!xmp_status)
135                  LOG_F(ERROR, "MV_PlayXMP: error in xmp_create_context");
136              else
137              {
138                  xmp_free_context(ctx);
139                  LOG_F(ERROR, "MV_PlayXMP: error %i in xmp_load_module_from_memory", xmp_status);
140              }
141  
142              ALIGNED_FREE_AND_NULL(voice->rawdataptr);
143              voice->rawdatasiz = 0;
144              MV_PlayVoice(voice);
145              return MV_SetErrorCode(MV_InvalidFile);
146          }
147  
148          xmp_start_player(ctx, MV_MixRate, 0);
149          xmp_set_player(ctx, XMP_PLAYER_INTERP, MV_XMPInterpolation);
150  
151          // CODEDUP multivoc.c MV_SetVoicePitch
152          voice->RateScale = divideu64((uint64_t)voice->SamplingRate * voice->PitchScale, MV_MixRate);
153          voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) - voice->RateScale;
154          MV_PlayVoice(voice);
155          return MV_Ok;
156      }
157      );
158  
159      return voice->handle;
160  }
161  
162  void MV_ReleaseXMPVoice(VoiceNode * voice)
163  {
164      Bassert(voice->wavetype == FMT_XMP && voice->rawdataptr != nullptr && voice->rawdatasiz == sizeof(xmp_data));
165  
166      auto xd = (xmp_data *)voice->rawdataptr;
167  
168      xmp_end_player(xd->ctx);
169      xmp_release_module(xd->ctx);
170  
171      if (MV_LazyAlloc)
172          return;
173  
174      xmp_free_context(xd->ctx);
175      voice->rawdataptr = nullptr;
176      voice->rawdatasiz = 0;
177      ALIGNED_FREE_AND_NULL(xd);
178  }
179  
180  void MV_SetXMPInterpolation(int interp)
181  {
182      if (!MV_Installed)
183          return;
184  
185      for (VoiceNode *voice = VoiceList.next; voice != &VoiceList; voice = voice->next)
186          if (voice->wavetype == FMT_XMP)
187              xmp_set_player(((xmp_data *)voice->rawdataptr)->ctx, XMP_PLAYER_INTERP, interp);
188  }
189  
190  #else
191  
192  #include "_multivc.h"
193  
194  static char const NoXMP[] = "MV_PlayXMP: libxmp-lite support not included in this binary.";
195  
196  int MV_PlayXMP(char *, uint32_t, int, int, int, int, int, int, int, fix16_t, intptr_t)
197  {
198      LOG_F(ERROR, NoXMP);
199      return -1;
200  }
201  
202  int MV_PlayXMP3D(char *, uint32_t, int, int, int, int, int, fix16_t, intptr_t)
203  {
204      LOG_F(ERROR, NoXMP);
205      return -1;
206  }
207  
208  #endif
209  
210  // KEEPINSYNC libxmp-lite/src/*_load.c
211  
212  static int it_test_memory(char const *ptr, uint32_t ptrlength)
213  {
214      static char const it_magic[] = "IMPM";
215      return !!(ptrlength < sizeof(it_magic) - 1 || Bmemcmp(ptr, it_magic, sizeof(it_magic) - 1));
216  }
217  
218  static int mod_test_memory(char const *ptr, uint32_t ptrlength)
219  {
220      if (ptrlength < 1084)
221          return -1;
222  
223      char const * const buf = ptr + 1080;
224  
225      if (!Bstrncmp(buf + 2, "CH", 2) && isdigit((int)buf[0]) && isdigit((int)buf[1]))
226      {
227          int i = (buf[0] - '0') * 10 + buf[1] - '0';
228          if (i > 0 && i <= 32)
229              return 0;
230      }
231  
232      if (!Bstrncmp(buf + 1, "CHN", 3) && isdigit((int)*buf))
233      {
234          if (*buf >= '0' && *buf <= '9')
235              return 0;
236      }
237  
238      if (!Bmemcmp(buf, "M.K.", 4))
239          return 0;
240  
241      return -1;
242  }
243  
244  static int s3m_test_memory(char const *ptr, uint32_t ptrlength)
245  {
246      static char const s3m_magic[] = "SCRM";
247      #define s3m_magic_offset 44
248  
249      return !!(ptrlength < s3m_magic_offset + sizeof(s3m_magic)-1 ||
250          Bmemcmp(ptr + s3m_magic_offset, s3m_magic, sizeof(s3m_magic)-1) ||
251          ptr[29] != 0x10);
252  }
253  
254  static int xm_test_memory(char const *ptr, uint32_t ptrlength)
255  {
256      static char const xm_magic[] = "Extended Module: ";
257      return !!(ptrlength < sizeof(xm_magic) - 1 || Bmemcmp(ptr, xm_magic, sizeof(xm_magic) - 1));
258  }
259  
260  static int mtm_test_memory(char const *ptr, uint32_t ptrlength)
261  {
262      static char const mtm_magic[] = "MTM\x10";
263      return !!(ptrlength < sizeof(mtm_magic) - 1 || Bmemcmp(ptr, mtm_magic, sizeof(mtm_magic) - 1));
264  }
265  
266  int MV_IdentifyXMP(char const *ptr, uint32_t ptrlength)
267  {
268      static decltype(mod_test_memory) * const module_test_functions[] =
269      {
270          it_test_memory,
271          mod_test_memory,
272          s3m_test_memory,
273          xm_test_memory,
274          mtm_test_memory,
275      };
276  
277      for (auto const test_module : module_test_functions)
278      {
279          if (test_module(ptr, ptrlength) == 0)
280              return 1;
281      }
282  
283      return 0;
284  }