musix 0.3.3

Music player library for esoteric audio formats (music from C64,Amiga etc)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/////////////////////////////////////////////////////////////////////////////
//
// psx - Top-level emulation
//
/////////////////////////////////////////////////////////////////////////////

#ifndef EMU_COMPILE
#error "Hi I forgot to set EMU_COMPILE"
#endif

#include "psx.h"

#include "r3000.h"
#include "iop.h"
#include "spu.h"
#include "bios.h"
#include "ioptimer.h"
#include "spucore.h"
#include "vfs.h"

/////////////////////////////////////////////////////////////////////////////
//
// Static init for the whole library
//
static uint8 library_was_initialized = 0;

static uint32 ps1preboot;
static uint32 ps2preboot;

//
// Deliberately create a NULL dereference
// Useful for calling attention to show-stopper problems like forgetting to
// call psx_init() or compiling with the wrong byte order
//
static void psx_hang(const char *message) {
  for(;;) { *((volatile char*)0) = *message; }
}

//
// Endian check
//
static void psx_endian_check(void) {
  uint32 num = 0x61626364;
  // Big
  if(!memcmp(&num, "abcd", 4)) {
#ifdef EMU_BIG_ENDIAN
    return;
#endif
    psx_hang("endian check");
  }
  // Little
  if(!memcmp(&num, "dcba", 4)) {
#ifndef EMU_BIG_ENDIAN
    return;
#endif
    psx_hang("endian check");
  }
  // Don't know what!
  psx_hang("endian check");
}

//
// Data type size check
//
static void psx_size_check(void) {
  if(sizeof(uint8 ) != 1) psx_hang("size check");
  if(sizeof(uint16) != 2) psx_hang("size check");
  if(sizeof(uint32) != 4) psx_hang("size check");
  if(sizeof(uint64) != 8) psx_hang("size check");
  if(sizeof(sint8 ) != 1) psx_hang("size check");
  if(sizeof(sint16) != 2) psx_hang("size check");
  if(sizeof(sint32) != 4) psx_hang("size check");
  if(sizeof(sint64) != 8) psx_hang("size check");
}

static uint32 EMU_CALL getenvhex(const char *name) {
  uint32 value;
  char s[100];
  char *t = s;
  if(bios_getenv(name, s, sizeof(s))) psx_hang("getenv failed");
  s[99] = 0;
  value = 0;
  for(;;) {
    int c = *t++;
    if(!c) break;
         if(c >= 'a' && c <= 'f') { c -= 'a'; c += 10; }
    else if(c >= 'A' && c <= 'F') { c -= 'A'; c += 10; }
    else if(c >= '0' && c <= '9') { c -= '0'; c +=  0; }
    else psx_hang("invalid hex string");
    value <<= 4;
    value += c;
  }
  return value;
}

sint32 EMU_CALL psx_init(void) {
  sint32 r;
  psx_endian_check();
  psx_size_check();

  // BIOS must be loaded first
  if ( !bios_get_image_native() || !bios_get_imagesize() ) return 0;
  //
  // BIOS must be a power of 2, or all hell breaks loose
  //
  { uint32 s = bios_get_imagesize();
    if(s & (s - 1)) psx_hang("imagesize error");
  }
  //
  // Environment inits
  //
  ps1preboot = getenvhex("ps1preboot");
  ps2preboot = getenvhex("ps2preboot");

  r = iop_init(); if(r) return r;
  r = ioptimer_init(); if(r) return r;
  r = r3000_init(); if(r) return r;
  r = spu_init(); if(r) return r;
  r = spucore_init(); if(r) return r;
  r = vfs_init(); if(r) return r;
  library_was_initialized = 1;
  return 0;
}

/////////////////////////////////////////////////////////////////////////////
//
// Version information
//
const char* EMU_CALL psx_getversion(void) {
  static const char s[] = "PSXCore0008 (built " __DATE__ ")";
  return s;
}

/////////////////////////////////////////////////////////////////////////////
//
// State information
//
struct PSX_STATE {
  uint8 version;
  uint32 offset_to_vfs;
  uint32 offset_to_iop;
  psx_console_out_t console_callback;
  void *console_context;
  uint8 console_enable;
};

#define PSXSTATE ((struct PSX_STATE*)(state))
#define VFSSTATE ((void*)(((char*)(state))+(PSXSTATE->offset_to_vfs)))
#define IOPSTATE ((void*)(((char*)(state))+(PSXSTATE->offset_to_iop)))

#define HAVE_VFS (PSXSTATE->offset_to_vfs!=0)
#define HAVE_IOP (PSXSTATE->offset_to_iop!=0)

uint32 EMU_CALL psx_get_state_size(uint8 version) {
  uint32 size = 0;
  if(version != 2) version = 1;
  size += sizeof(struct PSX_STATE);
  size += iop_get_state_size(version);
  if(version == 2) size += vfs_get_state_size();
  return size;
}

static EMU_INLINE void EMU_CALL preboot(struct PSX_STATE *state, uint32 target_address) {
  sint32 r;
  for(;;) {
    uint32 s = 10000;
    r = iop_execute(IOPSTATE, state, 10000, NULL, &s, 0);
    if(r < 0) break;
    if(r3000_getreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC) == target_address) break;
  }
}

void EMU_CALL psx_clear_state(void *state, uint8 version) {
  uint32 offset;
  //
  // If we haven't initialized, we really SHOULD have.
  //
  if(!library_was_initialized) psx_hang("library not initialized");

  if(version != 2) version = 1;
  // Clear local struct
  memset(state, 0, sizeof(struct PSX_STATE));
  // Set local version
  PSXSTATE->version = version;
  // Set up offsets
  offset = sizeof(struct PSX_STATE);
  if(version == 2) {
    PSXSTATE->offset_to_vfs = offset; offset += vfs_get_state_size();
  }
  PSXSTATE->offset_to_iop = offset; offset += iop_get_state_size(version);
  // Take care of VFS state
  if(HAVE_VFS) vfs_clear_state(VFSSTATE);
  // Take care of IOP state
  if(HAVE_IOP) iop_clear_state(IOPSTATE, version);
  //
  // Do some final inits
  //
  switch(version) {
  case 1:
    //
    // preboot for PS1
    //
    preboot(PSXSTATE, 0xBFC00000 | (ps1preboot & 0x003FFFFF));
    r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC    , 0x80010000);
    r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_GEN+29, 0x801FFFF0);
    break;
  case 2:
    //
    // preboot for PS2
    //
    preboot(PSXSTATE, 0xBFC00000 | (ps2preboot & 0x003FFFFF));
    //
    // simulate jr $v0 to transfer execution to loadcore
    //
    r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC,
      r3000_getreg(iop_get_r3000_state(IOPSTATE), R3000_REG_GEN+2)
    );
    break;
  }
  // Done
}

//
// Obtain substates
//
void* EMU_CALL psx_get_iop_state(void *state) { return IOPSTATE; }

/////////////////////////////////////////////////////////////////////////////
//
// Set readfile
//
void EMU_CALL psx_set_readfile(
  void *state,
  psx_readfile_t callback,
  void *context
) {
  if(HAVE_VFS) vfs_set_readfile(VFSSTATE, callback, context);
}

/////////////////////////////////////////////////////////////////////////////
//
// Console
//
void EMU_CALL psx_set_console_out(
  void *state,
  psx_console_out_t callback,
  void *context
) {
  PSXSTATE->console_callback = callback;
  PSXSTATE->console_context = context;
}

void EMU_CALL psx_console_in(void *state, char c) {

}

/////////////////////////////////////////////////////////////////////////////

static EMU_INLINE uint32 EMU_CALL get32lsb(uint8 *src) {
  return
    ((((uint32)(src[0])) & 0xFF) <<  0) |
    ((((uint32)(src[1])) & 0xFF) <<  8) |
    ((((uint32)(src[2])) & 0xFF) << 16) |
    ((((uint32)(src[3])) & 0xFF) << 24);
}

/////////////////////////////////////////////////////////////////////////////
//
// Determine if an ASCII string exists in a byte block
//
static int string_exists(const char *block, uint32 len, const char *string) {
  uint32 sl = strlen(string);
  uint32 i;
  if(sl > len) return 0;
  len = (len - sl) + 1;
  for(i = 0; i < len; i++) {
    if(memcmp(block + i, string, sl) == 0) return 1;
  }
  return 0;
}

/////////////////////////////////////////////////////////////////////////////
//
// Upload PS-X EXE - must INCLUDE the header.
// If the header includes the strings "North America", "Japan", or "Europe",
// the appropriate refresh rate is set.
// Returns nonzero on error.
// Will return error for PS2.
//
sint32 EMU_CALL psx_upload_psxexe(void *state, void *program, uint32 size) {
  uint32 init_pc;
  uint32 init_sp;
  uint32 text_start;
  uint32 text_size;

  if(PSXSTATE->version != 1) return -1;

  if(size < 0x801) return -1;
  if(memcmp(program, "PS-X EXE", 8)) return -1;

  text_start = get32lsb(((uint8*)program) + 0x18);
  text_size  = get32lsb(((uint8*)program) + 0x1C);
  init_pc    = get32lsb(((uint8*)program) + 0x10);
  init_sp    = get32lsb(((uint8*)program) + 0x30);

  // Try to determine the region, or leave it at the default if it's not found
       if(string_exists(program, 0x800, "North America")) { psx_set_refresh(state, 60); }
  else if(string_exists(program, 0x800, "Japan"        )) { psx_set_refresh(state, 60); }
  else if(string_exists(program, 0x800, "Europe"       )) { psx_set_refresh(state, 50); }

  if(text_size > (size - 0x800)) { text_size = (size - 0x800); }

  iop_upload_to_ram(IOPSTATE, text_start, ((uint8*)program) + 0x800, text_size);

  r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC    , init_pc);
  r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_GEN+29, init_sp);

  return 0;
}

/////////////////////////////////////////////////////////////////////////////
//
// Set the screen refresh rate in Hz (50 or 60 for PAL or NTSC)
// Only 50 or 60 are valid; other values will be ignored
//
void EMU_CALL psx_set_refresh(void *state, uint32 refresh) {
  iop_set_refresh(IOPSTATE, refresh);
}

/////////////////////////////////////////////////////////////////////////////
//
// Executes the given number of cycles or the given number of samples
// (whichever is less)
//
// Sets *sound_samples to the number of samples actually generated,
// which may be ZERO or LESS than the number requested, but never more.
//
// Return value:
// >= 0   The number of cycles actually executed, which may be ZERO, MORE,
//        or LESS than the number requested
// -1     Halted successfully (only applicable to PS2 environment)
// <= -2  Unrecoverable error
//
sint32 EMU_CALL psx_execute(
  void   *state,
  sint32  cycles,
  sint16 *sound_buf,
  uint32 *sound_samples,
  uint32  event_mask
) {
  return iop_execute(IOPSTATE, state, cycles, sound_buf, sound_samples, event_mask);
}

/////////////////////////////////////////////////////////////////////////////
//
// emu virtual I/O
//
static sint32 EMU_CALL vopen(void *vfsstate, uint8 *ram_native, uint32 ram_size, sint32 ofs) {
  char path[256];
  sint32 i;
  for(i = 0; i < 255; i++) {
    char c = ram_native[(ofs & (ram_size-1)) ^ (EMU_ENDIAN_XOR(3))]; ofs++;
    if(!c) break;
    path[i] = c;
  }
  if(!i) return -2; // ENOENT if path is empty
  path[i] = 0;
  i = vfs_open(vfsstate, path);
  return i;
}

static sint32 EMU_CALL vclose(void *vfsstate, sint32 emufd) {
  if(emufd < 0) return -9;
  return vfs_close(vfsstate, emufd);
}

#ifdef PSX_BIG_ENDIAN
//
// This byte swapper performs NO bounds checking.  Prepare accordingly.
// Also, it assumes you're only calling it if PSX_BIG_ENDIAN is defined.
//
static void EMU_CALL vreadswap(uint8 *base, uint32 ofs, uint32 len) {
  uint32 slop_0 = ofs & 3;
  uint32 slop_1 = (0-(ofs+len)) & 3;
  uint32 start = ofs - slop_0;
  uint32 end = ofs + len + slop_1;
  for(; start < end; start += 4) {
    uint8 a, b, c, d;
    a = base[start+0];
    b = base[start+1];
    c = base[start+2];
    d = base[start+3];
    base[start+0] = d;
    base[start+1] = c;
    base[start+2] = b;
    base[start+3] = a;
  }
}
#endif

static sint32 EMU_CALL vread(void *vfsstate, uint8 *ram_native, uint32 ram_size, sint32 emufd, sint32 ofs, sint32 len) {
  sint32 r;
  if(emufd < 0) return -9;
  if(len < 0) return -22;
  if(len >= (sint32)ram_size) return -22;
  ofs &= (ram_size-1);
  if((len + ofs) > (sint32)ram_size) return -22;
#ifdef PSX_BIG_ENDIAN
  vreadswap(ram_native, ofs, len);
#endif
  r = vfs_read(vfsstate, emufd, ram_native + ofs, len);
#ifdef PSX_BIG_ENDIAN
  vreadswap(ram_native, ofs, len);
#endif
  return r;
}

static sint32 EMU_CALL vlseek(void *vfsstate, sint32 emufd, sint32 offset, sint32 whence) {
  if(emufd < 0) return -9;
  if(whence < 0 || whence > 2) return -22;
  if(whence == 0 && offset < 0) return -22;
  return vfs_lseek(vfsstate, emufd, offset, whence);
}

/////////////////////////////////////////////////////////////////////////////
//
// hefile emucall handler
//
sint32 EMU_CALL psx_emucall(
  void   *state,
  uint8  *ram_native,
  uint32  ram_size,
  sint32  type,
  sint32  emufd,
  sint32  ofs,
  sint32  arg1,
  sint32  arg2
) {
  if(type == 0) {
    if(PSXSTATE->console_callback) {
      sint32 i;
      for(i = 0; i < arg1; i++) {
        char c = ram_native[ofs & (ram_size-1)]; ofs++;
        if(c == 'H') { PSXSTATE->console_enable = 1; }
        if(PSXSTATE->console_enable) {
          (PSXSTATE->console_callback)(PSXSTATE->console_context, c);
        }
      }
    }
    return arg1;
  }
  if(!(HAVE_VFS)) return -5;
  switch(type) {
  case 3: return vopen (VFSSTATE, ram_native, ram_size, ofs);
  case 4: return vclose(VFSSTATE, emufd);
  case 5: return vread (VFSSTATE, ram_native, ram_size, emufd, ofs, arg1);
  case 6: return -13; // EACCES permission denied
  case 7: return vlseek(VFSSTATE, emufd, arg1, arg2);
  default: return -5;
  }
}

/////////////////////////////////////////////////////////////////////////////