A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/* MikMod sound library
2 (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file
3 AUTHORS for complete list.
4
5 This library is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of
8 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. See the
13 GNU Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 02111-1307, USA.
19*/
20
21/*==============================================================================
22
23 $Id$
24
25 These routines are used to access the available module loaders
26
27==============================================================================*/
28
29#ifdef HAVE_CONFIG_H
30#include "config.h"
31#endif
32
33#ifdef HAVE_UNISTD_H
34#include <unistd.h>
35#endif
36
37#ifdef HAVE_MEMORY_H
38#include <memory.h>
39#endif
40#include <string.h>
41
42#include "mikmod_internals.h"
43
44#ifdef SUNOS
45extern int fprintf(FILE *, const char *, ...);
46#endif
47
48MREADER *modreader;
49MODULE of;
50
51static MLOADER *firstloader=NULL;
52
53#ifndef NO_DEPACKERS
54static MUNPACKER unpackers[] = {
55 PP20_Unpack,
56 MMCMP_Unpack,
57 XPK_Unpack,
58 S404_Unpack,
59 NULL
60};
61#endif
62
63const UWORD finetune[16] = {
64 8363,8413,8463,8529,8581,8651,8723,8757,
65 7895,7941,7985,8046,8107,8169,8232,8280
66};
67
68MIKMODAPI CHAR* MikMod_InfoLoader(void)
69{
70 int len=0;
71 MLOADER *l;
72 CHAR *list=NULL;
73
74 MUTEX_LOCK(lists);
75 /* compute size of buffer */
76 for(l = firstloader; l; l = l->next)
77 len += 1 + (l->next ? 1 : 0) + strlen(l->version);
78
79 if(len)
80 if((list=(CHAR*)MikMod_malloc(len*sizeof(CHAR))) != NULL) {
81 CHAR *list_end = list;
82 list[0] = 0;
83 /* list all registered module loders */
84 for(l = firstloader; l; l = l->next) {
85 list_end += sprintf(list_end, "%s%s", l->version, (l->next) ? "\n" : "");
86 }
87 }
88 MUTEX_UNLOCK(lists);
89 return list;
90}
91
92void _mm_registerloader(MLOADER* ldr)
93{
94 MLOADER *cruise=firstloader;
95
96 if(cruise) {
97 while(cruise->next) cruise = cruise->next;
98 cruise->next=ldr;
99 } else
100 firstloader=ldr;
101}
102
103MIKMODAPI void MikMod_RegisterLoader(struct MLOADER* ldr)
104{
105 /* if we try to register an invalid loader, or an already registered loader,
106 ignore this attempt */
107 if ((!ldr)||(ldr->next))
108 return;
109
110 MUTEX_LOCK(lists);
111 _mm_registerloader(ldr);
112 MUTEX_UNLOCK(lists);
113}
114
115int ReadComment(UWORD len)
116{
117 if(len) {
118 CHAR *ptr;
119
120 of.comment=(CHAR*)MikMod_calloc(1,len+1);
121 if(!of.comment) return 0;
122 _mm_read_UBYTES(of.comment,len,modreader);
123
124 /* translate IT linefeeds */
125 ptr=of.comment;
126 while(*ptr) {
127 if(*ptr=='\r') *ptr='\n';
128 ++ptr;
129 }
130 }
131 if(of.comment && !of.comment[0]) {
132 MikMod_free(of.comment);
133 of.comment=NULL;
134 }
135 return 1;
136}
137
138int ReadLinedComment(UWORD len,UWORD linelen)
139{
140 /* Adapted from the OpenMPT project, C'ified. */
141 CHAR *buf, *storage, *p;
142 size_t numlines, line, fpos, cpos, lpos, cnt;
143
144 if (!linelen) return 0;
145 if (!len) return 1;
146
147 numlines = (len + linelen - 1) / linelen;
148 cnt = (linelen + 1) * numlines;
149 buf = (CHAR *) MikMod_calloc(1, len);
150 if (!buf) return 0;
151 storage = (CHAR *) MikMod_calloc(1, cnt + 1);
152 if (!storage) {
153 MikMod_free(buf);
154 return 0;
155 }
156
157 _mm_read_UBYTES(buf,len,modreader);
158 for (line = 0, fpos = 0, cpos = 0; line < numlines; line++, fpos += linelen, cpos += (linelen + 1))
159 {
160 cnt = len - fpos;
161 if (cnt > linelen) cnt = linelen;
162 p = storage + cpos;
163 memcpy(p, buf + fpos, cnt);
164 p[cnt] = '\r';
165 /* fix weird chars */
166 for (lpos = 0; lpos < linelen; lpos++, p++) {
167 switch (*p) {
168 case '\0':
169 case '\n':
170 case '\r':
171 *p = ' ';
172 break;
173 }
174 }
175 }
176
177 of.comment = storage;
178 MikMod_free(buf);
179 return 1;
180}
181
182int AllocPositions(int total)
183{
184 if(!total) {
185 _mm_errno=MMERR_NOT_A_MODULE;
186 return 0;
187 }
188 if(!(of.positions=(UWORD*)MikMod_calloc(total,sizeof(UWORD)))) return 0;
189 return 1;
190}
191
192int AllocPatterns(void)
193{
194 int s,t,tracks = 0;
195
196 if((!of.numpat)||(!of.numchn)) {
197 _mm_errno=MMERR_NOT_A_MODULE;
198 return 0;
199 }
200 /* Allocate track sequencing array */
201 if(!(of.patterns=(UWORD*)MikMod_calloc((ULONG)(of.numpat+1)*of.numchn,sizeof(UWORD)))) return 0;
202 if(!(of.pattrows=(UWORD*)MikMod_calloc(of.numpat+1,sizeof(UWORD)))) return 0;
203
204 for(t=0;t<=of.numpat;t++) {
205 of.pattrows[t]=64;
206 for(s=0;s<of.numchn;s++)
207 of.patterns[(t*of.numchn)+s]=tracks++;
208 }
209
210 return 1;
211}
212
213int AllocTracks(void)
214{
215 if(!of.numtrk) {
216 _mm_errno=MMERR_NOT_A_MODULE;
217 return 0;
218 }
219 if(!(of.tracks=(UBYTE **)MikMod_calloc(of.numtrk,sizeof(UBYTE *)))) return 0;
220 return 1;
221}
222
223int AllocInstruments(void)
224{
225 int t,n;
226
227 if(!of.numins) {
228 _mm_errno=MMERR_NOT_A_MODULE;
229 return 0;
230 }
231 if(!(of.instruments=(INSTRUMENT*)MikMod_calloc(of.numins,sizeof(INSTRUMENT))))
232 return 0;
233
234 for(t=0;t<of.numins;t++) {
235 for(n=0;n<INSTNOTES;n++) {
236 /* Init note / sample lookup table */
237 of.instruments[t].samplenote[n] = n;
238 of.instruments[t].samplenumber[n] = t;
239 }
240 of.instruments[t].globvol = 64;
241 }
242 return 1;
243}
244
245int AllocSamples(void)
246{
247 UWORD u;
248
249 if(!of.numsmp) {
250 _mm_errno=MMERR_NOT_A_MODULE;
251 return 0;
252 }
253 if(!(of.samples=(SAMPLE*)MikMod_calloc(of.numsmp,sizeof(SAMPLE)))) return 0;
254
255 for(u=0;u<of.numsmp;u++) {
256 of.samples[u].panning = 128; /* center */
257 of.samples[u].handle = -1;
258 of.samples[u].globvol = 64;
259 of.samples[u].volume = 64;
260 }
261 return 1;
262}
263
264static int ML_LoadSamples(void)
265{
266 SAMPLE *s;
267 int u;
268
269 for(u=of.numsmp,s=of.samples;u;u--,s++)
270 if(s->length) SL_RegisterSample(s,MD_MUSIC,modreader);
271
272 return 1;
273}
274
275/* Creates a CSTR out of a character buffer of 'len' bytes, but strips any
276 terminating non-printing characters like 0, spaces etc. */
277CHAR *DupStr(const CHAR* s, UWORD len, int strict)
278{
279 UWORD t;
280 CHAR *d=NULL;
281
282 /* Scan for last printing char in buffer [includes high ascii up to 254] */
283 while(len) {
284 if(s[len-1]>0x20) break;
285 len--;
286 }
287
288 /* Scan forward for possible NULL character */
289 if(strict) {
290 for(t=0;t<len;t++) if (!s[t]) break;
291 if (t<len) len=t;
292 }
293
294 /* When the buffer wasn't completely empty, allocate a cstring and copy the
295 buffer into that string, except for any control-chars */
296 if((d=(CHAR*)MikMod_malloc(sizeof(CHAR)*(len+1))) != NULL) {
297 for(t=0;t<len;t++) d[t]=(s[t]<32)?'.':s[t];
298 d[len]=0;
299 }
300 return d;
301}
302
303static void ML_XFreeSample(SAMPLE *s)
304{
305 if(s->handle>=0)
306 MD_SampleUnload(s->handle);
307
308/* moved samplename freeing to our caller ML_FreeEx(),
309 * because we are called conditionally. */
310}
311
312static void ML_XFreeInstrument(INSTRUMENT *i)
313{
314 MikMod_free(i->insname);
315}
316
317static void ML_FreeEx(MODULE *mf)
318{
319 UWORD t;
320
321 MikMod_free(mf->songname);
322 MikMod_free(mf->comment);
323
324 MikMod_free(mf->modtype);
325 MikMod_free(mf->positions);
326 MikMod_free(mf->patterns);
327 MikMod_free(mf->pattrows);
328
329 if(mf->tracks) {
330 for(t=0;t<mf->numtrk;t++)
331 MikMod_free(mf->tracks[t]);
332 MikMod_free(mf->tracks);
333 }
334 if(mf->instruments) {
335 for(t=0;t<mf->numins;t++)
336 ML_XFreeInstrument(&mf->instruments[t]);
337 MikMod_free(mf->instruments);
338 }
339 if(mf->samples) {
340 for(t=0;t<mf->numsmp;t++) {
341 MikMod_free(mf->samples[t].samplename);
342 if(mf->samples[t].length) ML_XFreeSample(&mf->samples[t]);
343 }
344 MikMod_free(mf->samples);
345 }
346 memset(mf,0,sizeof(MODULE));
347 if(mf!=&of) MikMod_free(mf);
348}
349
350static MODULE *ML_AllocUniMod(void)
351{
352 return (MODULE *) MikMod_malloc(sizeof(MODULE));
353}
354
355#ifndef NO_DEPACKERS
356static int ML_TryUnpack(MREADER *reader,void **out,long *outlen)
357{
358 int i;
359
360 *out = NULL;
361 *outlen = 0;
362
363 for(i=0;unpackers[i]!=NULL;++i) {
364 _mm_rewind(reader);
365 if(unpackers[i](reader,out,outlen)) return 1;
366 }
367 return 0;
368}
369#endif
370
371static void Player_Free_internal(MODULE *mf)
372{
373 if(mf) {
374 Player_Exit_internal(mf);
375 ML_FreeEx(mf);
376 }
377}
378
379MIKMODAPI void Player_Free(MODULE *mf)
380{
381 MUTEX_LOCK(vars);
382 Player_Free_internal(mf);
383 MUTEX_UNLOCK(vars);
384}
385
386static CHAR* Player_LoadTitle_internal(MREADER *reader)
387{
388 MLOADER *l;
389 CHAR *title;
390 #ifndef NO_DEPACKERS
391 void *unpk;
392 long newlen;
393 #endif
394
395 modreader=reader;
396 _mm_errno = 0;
397 _mm_critical = 0;
398 _mm_iobase_setcur(modreader);
399
400 #ifndef NO_DEPACKERS
401 if(ML_TryUnpack(modreader,&unpk,&newlen)) {
402 if(!(modreader=_mm_new_mem_reader(unpk,newlen))) {
403 modreader=reader;
404 MikMod_free(unpk);
405 return NULL;
406 }
407 }
408 #endif
409
410 /* Try to find a loader that recognizes the module */
411 for(l=firstloader;l;l=l->next) {
412 _mm_rewind(modreader);
413 if(l->Test()) break;
414 }
415
416 if(l) {
417 title = l->LoadTitle();
418 }
419 else {
420 _mm_errno = MMERR_NOT_A_MODULE;
421 if(_mm_errorhandler) _mm_errorhandler();
422 title = NULL;
423 }
424
425 #ifndef NO_DEPACKERS
426 if (modreader!=reader) {
427 _mm_delete_mem_reader(modreader);
428 modreader=reader;
429 MikMod_free(unpk);
430 }
431 #endif
432 return title;
433}
434
435MIKMODAPI CHAR* Player_LoadTitleFP(int fp)
436{
437 CHAR* result=NULL;
438 MREADER* reader;
439
440 if(fp && (reader=_mm_new_file_reader(fp)) != NULL) {
441 MUTEX_LOCK(lists);
442 result=Player_LoadTitle_internal(reader);
443 MUTEX_UNLOCK(lists);
444 _mm_delete_file_reader(reader);
445 }
446 return result;
447}
448
449MIKMODAPI CHAR* Player_LoadTitleMem(const char *buffer,int len)
450{
451 CHAR *result=NULL;
452 MREADER* reader;
453
454 if (!buffer || len <= 0) return NULL;
455 if ((reader=_mm_new_mem_reader(buffer,len)) != NULL)
456 {
457 MUTEX_LOCK(lists);
458 result=Player_LoadTitle_internal(reader);
459 MUTEX_UNLOCK(lists);
460 _mm_delete_mem_reader(reader);
461 }
462
463 return result;
464}
465
466MIKMODAPI CHAR* Player_LoadTitleGeneric(MREADER *reader)
467{
468 CHAR *result=NULL;
469
470 if (reader) {
471 MUTEX_LOCK(lists);
472 result=Player_LoadTitle_internal(reader);
473 MUTEX_UNLOCK(lists);
474 }
475 return result;
476}
477
478MIKMODAPI CHAR* Player_LoadTitle(const CHAR* filename)
479{
480 CHAR* result=NULL;
481 int fp;
482 MREADER* reader;
483
484 if((fp=_mm_fopen(filename,"rb")) >= 0) {
485 if((reader=_mm_new_file_reader(fp)) != NULL) {
486 MUTEX_LOCK(lists);
487 result=Player_LoadTitle_internal(reader);
488 MUTEX_UNLOCK(lists);
489 _mm_delete_file_reader(reader);
490 }
491 _mm_fclose(fp);
492 }
493 return result;
494}
495
496/* Loads a module given an reader */
497static MODULE* Player_LoadGeneric_internal(MREADER *reader,int maxchan,int curious)
498{
499 int t;
500 MLOADER *l;
501 int ok;
502 MODULE *mf;
503 #ifndef NO_DEPACKERS
504 void *unpk;
505 long newlen;
506 #endif
507
508 modreader = reader;
509 _mm_errno = 0;
510 _mm_critical = 0;
511 _mm_iobase_setcur(modreader);
512
513 #ifndef NO_DEPACKERS
514 if(ML_TryUnpack(modreader,&unpk,&newlen)) {
515 if(!(modreader=_mm_new_mem_reader(unpk,newlen))) {
516 modreader=reader;
517 MikMod_free(unpk);
518 return NULL;
519 }
520 }
521 #endif
522
523 /* Try to find a loader that recognizes the module */
524 for(l=firstloader;l;l=l->next) {
525 _mm_rewind(modreader);
526 if(l->Test()) break;
527 }
528
529 if(!l) {
530 _mm_errno = MMERR_NOT_A_MODULE;
531 #ifndef NO_DEPACKERS
532 if(modreader!=reader) {
533 _mm_delete_mem_reader(modreader);
534 modreader=reader;
535 MikMod_free(unpk);
536 }
537 #endif
538 if(_mm_errorhandler) _mm_errorhandler();
539 _mm_rewind(modreader);
540 _mm_iobase_revert(modreader);
541 return NULL;
542 }
543
544 /* init unitrk routines */
545 if(!UniInit()) {
546 #ifndef NO_DEPACKERS
547 if(modreader!=reader) {
548 _mm_delete_mem_reader(modreader);
549 modreader=reader;
550 MikMod_free(unpk);
551 }
552 #endif
553 if(_mm_errorhandler) _mm_errorhandler();
554 _mm_rewind(modreader);
555 _mm_iobase_revert(modreader);
556 return NULL;
557 }
558
559 /* init the module structure with vanilla settings */
560 memset(&of,0,sizeof(MODULE));
561 of.bpmlimit = 33;
562 of.initvolume = 128;
563 for (t = 0; t < UF_MAXCHAN; t++) of.chanvol[t] = 64;
564 for (t = 0; t < UF_MAXCHAN; t++)
565 of.panning[t] = ((t + 1) & 2) ? PAN_RIGHT : PAN_LEFT;
566
567 /* init module loader and load the header / patterns */
568 if (!l->Init || l->Init()) {
569 _mm_rewind(modreader);
570 ok = l->Load(curious);
571 if (ok) {
572 /* propagate inflags=flags for in-module samples */
573 for (t = 0; t < of.numsmp; t++)
574 if (of.samples[t].inflags == 0)
575 of.samples[t].inflags = of.samples[t].flags;
576 }
577 } else
578 ok = 0;
579
580 /* free loader and unitrk allocations */
581 if (l->Cleanup) l->Cleanup();
582 UniCleanup();
583
584 if(ok) ok = ML_LoadSamples();
585 if(ok) ok = ((mf=ML_AllocUniMod()) != NULL);
586 if(!ok) {
587 ML_FreeEx(&of);
588 #ifndef NO_DEPACKERS
589 if(modreader!=reader) {
590 _mm_delete_mem_reader(modreader);
591 modreader=reader;
592 MikMod_free(unpk);
593 }
594 #endif
595 if(_mm_errorhandler) _mm_errorhandler();
596 _mm_rewind(modreader);
597 _mm_iobase_revert(modreader);
598 return NULL;
599 }
600
601 /* If the module doesn't have any specific panning, create a
602 MOD-like panning, with the channels half-separated. */
603 if (!(of.flags & UF_PANNING))
604 for (t = 0; t < of.numchn; t++)
605 of.panning[t] = ((t + 1) & 2) ? PAN_HALFRIGHT : PAN_HALFLEFT;
606
607 /* Copy the static MODULE contents into the dynamic MODULE struct. */
608 memcpy(mf,&of,sizeof(MODULE));
609
610 if(maxchan>0) {
611 if(!(mf->flags&UF_NNA)&&(mf->numchn<maxchan))
612 maxchan = mf->numchn;
613 else
614 if((mf->numvoices)&&(mf->numvoices<maxchan))
615 maxchan = mf->numvoices;
616
617 if(maxchan<mf->numchn) mf->flags |= UF_NNA;
618
619 ok = !MikMod_SetNumVoices_internal(maxchan,-1);
620 }
621
622 if(ok) ok = !SL_LoadSamples();
623 if(ok) ok = !Player_Init(mf);
624
625 #ifndef NO_DEPACKERS
626 if(modreader!=reader) {
627 _mm_delete_mem_reader(modreader);
628 modreader=reader;
629 MikMod_free(unpk);
630 }
631 #endif
632 _mm_iobase_revert(modreader);
633
634 if(!ok) {
635 Player_Free_internal(mf);
636 return NULL;
637 }
638 return mf;
639}
640
641MIKMODAPI MODULE* Player_LoadGeneric(MREADER *reader,int maxchan,int curious)
642{
643 MODULE* result;
644
645 MUTEX_LOCK(vars);
646 MUTEX_LOCK(lists);
647 result=Player_LoadGeneric_internal(reader,maxchan,curious);
648 MUTEX_UNLOCK(lists);
649 MUTEX_UNLOCK(vars);
650
651 return result;
652}
653
654MIKMODAPI MODULE* Player_LoadMem(const char *buffer,int len,int maxchan,int curious)
655{
656 MODULE* result=NULL;
657 MREADER* reader;
658
659 if (!buffer || len <= 0) return NULL;
660 if ((reader=_mm_new_mem_reader(buffer, len)) != NULL) {
661 result=Player_LoadGeneric(reader,maxchan,curious);
662 _mm_delete_mem_reader(reader);
663 }
664 return result;
665}
666
667/* Loads a module given a file pointer.
668 File is loaded from the current file seek position. */
669MIKMODAPI MODULE* Player_LoadFP(int fp,int maxchan,int curious)
670{
671 MODULE* result=NULL;
672 struct MREADER* reader;
673
674 if (fp && (reader=_mm_new_file_reader(fp)) != NULL) {
675 result=Player_LoadGeneric(reader,maxchan,curious);
676 _mm_delete_file_reader(reader);
677 }
678 return result;
679}
680
681/* Open a module via its filename. The loader will initialize the specified
682 song-player 'player'. */
683MIKMODAPI MODULE* Player_Load(const CHAR* filename,int maxchan,int curious)
684{
685 int fp;
686 MODULE *mf=NULL;
687
688 if((fp=_mm_fopen(filename,"rb")) >= 0) {
689 mf=Player_LoadFP(fp,maxchan,curious);
690 _mm_fclose(fp);
691 }
692 return mf;
693}
694
695/* ex:set ts=4: */