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 }