shithub: zelda3

ref: 3da7cb4231ab12fc937c34405d888bca2bab03c4
dir: /audio.c/

View raw version
#include "audio.h"
#include "zelda_rtl.h"
#include "variables.h"
#include "features.h"
#include "snes/snes_regs.h"
#include "spc_player.h"
#include "third_party/opus-1.3.1-stripped/opus.h"
#include "config.h"
#include "assets.h"

// This needs to hold a lot more things than with just PCM
typedef struct MsuPlayerResumeInfo {
  uint32 tag;
  uint32 offset;
  uint32 samples_until_repeat;
  uint16 range_cur, range_repeat;
  uint64 initial_packet_bytes;  // To verify we seeked right
  uint8 orig_track;        // Using the old zelda track numbers
  uint8 actual_track;      // The MSU track index we're playing (Different if using msu deluxe)
} MsuPlayerResumeInfo;

enum {
  kMsuState_Idle = 0,
  kMsuState_FinishedPlaying = 1,
  kMsuState_Resuming = 2,
  kMsuState_Playing = 3,
};

typedef struct MsuPlayer {
  FILE *f;
  uint32 buffer_size, buffer_pos;
  uint32 preskip, samples_until_repeat;
  uint32 total_samples_in_file, repeat_position;
  uint32 cur_file_offs;
  MsuPlayerResumeInfo resume_info;
  uint8 enabled;
  uint8 state;
  float volume, volume_step, volume_target;
  uint16 range_cur, range_repeat;
  OpusDecoder *opus;
  int16 buffer[960 * 2];
} MsuPlayer;

static MsuPlayer g_msu_player;

static void MsuPlayer_Open(MsuPlayer *mp, int orig_track, bool resume_from_snapshot);

static const uint8 kMsuTracksWithRepeat[48] = {
  1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,0,
  1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,
  1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,
};

static const uint8 kIsMusicOwOrDungeon[] = {
  0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, // 1 = ow songs : 2 = lw, 5 = forest, 7 = town, 9 = dark world, 13 = mountain
  2, 2, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 0, // 2 = indoor songs : 16, 17, 18, 22, 23, 27
};


static const uint8 kMsuDeluxe_OW_Songs[] = {
  37, 37, 42, 38, 38, 38, 38, 39, 37, 37, 42, 38, 38, 38, 38, 41, // lw
  42, 42, 42, 42, 42, 42, 40, 40, 43, 43, 42, 47, 47, 42, 45, 45,
  43, 43, 43, 47, 47, 42, 45, 45, 112, 112, 48, 42, 42, 42, 42, 45,
  44, 44, 48, 48, 48, 46, 46, 46, 44, 44, 44, 48, 48, 46, 46, 46,
  49, 49, 51, 50, 50, 50, 50, 50, 49, 49, 51, 50, 50, 50, 50, 51, // dw
  51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 51, 56, 56, 51, 54, 54,
  52, 52, 52, 56, 56, 51, 54, 54, 58, 52, 57, 51, 51, 51, 51, 54,
  53, 53, 57, 57, 57, 55, 55, 110, 53, 53, 57, 57, 57, 55, 55, 110,
  37, 41, 41, 42, 42, 42, 42, 42, 42, 41, 41, 42, 42, 42, 42, 42, // special
  42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
};

static const uint8 kMsuDeluxe_Entrance_Songs[] = {
   59,  59,  60,  61,  61,  61,  62,  62,  63,  64,  64,  64, 105,  65,  65,  66,
   66,  62,  67,  62,  62,  68,  62,  62,  68,  68,  62,  62,  62,  62,  62,  62,
   62,  62,  62,  62,  69,  70,  71,  72,  73,  73,  73, 106, 102,  74,  62,  62,
   75,  75,  76,  77,  78,  68,  79,  80,  81,  62,  62,  62,  82,  75, 242,  59,
   59,  76, 242, 242, 242,  96,  83,  99,  59, 242, 242, 242,  84,  95, 104,  62,
   85,  62,  62,  86, 242,  67, 103,  83,  83,  87,  76,  88,  81,  98,  81,  88,
   83,  89,  75,  97,  90,  91,  91, 100,  92,  93,  92, 242,  93, 107,  62,  75,
   62,  67,  62, 242, 242, 242,  73,  73,  73,  73, 102, 114,  81,  76,  62,  67,
   62,  61,  94,  62, 103,
};


// Remap an track number into a potentially different track number (used for msu deluxe)
static uint8 RemapMsuDeluxeTrack(MsuPlayer *mp, uint8 track) {
  if (!(mp->enabled & kMsuEnabled_MsuDeluxe) || track >= sizeof(kIsMusicOwOrDungeon))
    return track;
  switch (kIsMusicOwOrDungeon[track]) {
  case 1:
    return BYTE(overworld_area_index) < sizeof(kMsuDeluxe_OW_Songs) ? kMsuDeluxe_OW_Songs[BYTE(overworld_area_index)] : track;
  case 2:
    if (which_entrance >= sizeof(kMsuDeluxe_Entrance_Songs) || kMsuDeluxe_Entrance_Songs[which_entrance] == 242)
      return track;
    return kMsuDeluxe_Entrance_Songs[which_entrance];
  default:
    return track;
  }
}

bool ZeldaIsPlayingMusicTrack(uint8 track) {
  MsuPlayer *mp = &g_msu_player;
  if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe)
    return RemapMsuDeluxeTrack(mp, track) == mp->resume_info.actual_track;
  else
    return track == music_unk1;
}

bool ZeldaIsPlayingMusicTrackWithBug(uint8 track) {
  MsuPlayer *mp = &g_msu_player;
  if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe)
    return RemapMsuDeluxeTrack(mp, track) == mp->resume_info.actual_track;
  else
    return track == (enhanced_features0 & kFeatures0_MiscBugFixes ? music_unk1 : last_music_control);
}

uint8 ZeldaGetEntranceMusicTrack(int i) {
  MsuPlayer *mp = &g_msu_player;
  uint8 rv = kEntranceData_musicTrack[i];

  // For some entrances the original performs a fade out, while msu deluxe has new tracks.
  if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe) {
    if (rv == 242 && kMsuDeluxe_Entrance_Songs[which_entrance] != 242)
      rv = 16;
  }

  return rv;
}

static const uint8 kVolumeTransitionTarget[4] = { 0, 64, 255, 255};
static const uint8 kVolumeTransitionStep[4] = { 7, 3, 3, 24};
// These are precomputed in the config parse
static float kVolumeTransitionStepFloat[4];
static float kVolumeTransitionTargetFloat[4];

void ZeldaPlayMsuAudioTrack(uint8 music_ctrl) {
  MsuPlayer *mp = &g_msu_player;
  if (!mp->enabled) {
    mp->resume_info.tag = 0;
    zelda_apu_write(APUI00, music_ctrl);
    return;
  }
  ZeldaApuLock();
  if ((music_ctrl & 0xf0) != 0xf0)
    MsuPlayer_Open(mp, music_ctrl, false);
  else if (music_ctrl >= 0xf1 && music_ctrl <= 0xf3) {
    mp->volume_target = kVolumeTransitionTargetFloat[music_ctrl - 0xf1];
    mp->volume_step = kVolumeTransitionStepFloat[music_ctrl - 0xf1];
  }

  if (mp->state == 0) {
    zelda_apu_write(APUI00, music_ctrl);
  } else {
    zelda_apu_write(APUI00, 0xf0);  // pause spc player
  }
  ZeldaApuUnlock();
}

static void MsuPlayer_CloseFile(MsuPlayer *mp) {
  if (mp->f)
    fclose(mp->f);
  opus_decoder_destroy(mp->opus);
  mp->opus = NULL;
  mp->f = NULL;
  if (mp->state != kMsuState_FinishedPlaying)
    mp->state = kMsuState_Idle;
  memset(&mp->resume_info, 0, sizeof(mp->resume_info));
}

static void MsuPlayer_Open(MsuPlayer *mp, int orig_track, bool resume_from_snapshot) {
  MsuPlayerResumeInfo resume;
  int actual_track = RemapMsuDeluxeTrack(mp, orig_track);

  if (!resume_from_snapshot) {
    resume.tag = 0;
    // Attempt to resume MSU playback when exiting back to the overworld.
    if (main_module_index == 9 &&
        actual_track == ((MsuPlayerResumeInfo *)msu_resume_info_alt)->actual_track && g_config.resume_msu) {
      memcpy(&resume, msu_resume_info_alt, sizeof(mp->resume_info));
    }
    if (mp->state >= kMsuState_Resuming)
      memcpy(msu_resume_info_alt, &mp->resume_info, sizeof(mp->resume_info));
  } else {
    memcpy(&resume, msu_resume_info, sizeof(mp->resume_info));
  }

  mp->volume_target = kVolumeTransitionTargetFloat[3];
  mp->volume_step = kVolumeTransitionStepFloat[3];

  mp->state = kMsuState_Idle;
  MsuPlayer_CloseFile(mp);
  if (actual_track == 0)
    return;
  char fname[256], buf[8];
  snprintf(fname, sizeof(fname), "%s%d.%s", g_config.msu_path ? g_config.msu_path : "", actual_track, mp->enabled & kMsuEnabled_Opuz ? "opuz" : "pcm");
  printf("Loading MSU %s\n", fname);
  mp->f = fopen(fname, "rb");
  if (mp->f == NULL)
    goto READ_ERROR;
  setvbuf(mp->f, NULL, _IOFBF, 16384);
  if (fread(buf, 1, 8, mp->f) != 8) READ_ERROR: {
    fprintf(stderr, "Unable to read MSU file %s\n", fname);
    MsuPlayer_CloseFile(mp);
    return;
  }
  uint32 file_tag = *(uint32 *)(buf + 0);
  mp->repeat_position = *(uint32 *)(buf + 4);
  mp->state = (resume.actual_track == actual_track && resume.tag == file_tag) ? kMsuState_Resuming : kMsuState_Playing;
  if (mp->state == kMsuState_Resuming) {
    memcpy(&mp->resume_info, &resume, sizeof(mp->resume_info));
  } else {
    mp->resume_info.orig_track = orig_track;
    mp->resume_info.actual_track = actual_track;
    mp->resume_info.tag = file_tag;
    mp->resume_info.range_cur = 8;
  }
  mp->cur_file_offs = mp->resume_info.offset;
  mp->samples_until_repeat = mp->resume_info.samples_until_repeat;
  mp->range_cur = mp->resume_info.range_cur;
  mp->range_repeat = mp->resume_info.range_repeat;
  mp->buffer_size = mp->buffer_pos = 0;
  mp->preskip = 0;
  if (file_tag == (('Z' << 24) | ('U' << 16) | ('P' << 8) | 'O')) {
    mp->opus = opus_decoder_create(48000, 2, NULL);
    if (!mp->opus)
      goto READ_ERROR;
    if (mp->state == kMsuState_Resuming)
      fseek(mp->f, mp->cur_file_offs, SEEK_SET);
  } else if (file_tag == (('1' << 24) | ('U' << 16) | ('S' << 8) | 'M')) {
    fseek(mp->f, 0, SEEK_END);
    mp->total_samples_in_file = (ftell(mp->f) - 8) / 4;
    mp->samples_until_repeat = mp->total_samples_in_file - mp->cur_file_offs;
    fseek(mp->f, mp->cur_file_offs * 4 + 8, SEEK_SET);
  } else {
    goto READ_ERROR;
  }
}

static void MixToBufferWithVolume(int16 *dst, const int16 *src, size_t n, float volume) {
  if (volume == 1.0f) {
    for (size_t i = 0; i < n; i++) {
      dst[i * 2 + 0] += src[i * 2 + 0];
      dst[i * 2 + 1] += src[i * 2 + 1];
    }
  } else {
    uint32 vol = (int32)(65536 * volume);
    for (size_t i = 0; i < n; i++) {
      dst[i * 2 + 0] += src[i * 2 + 0] * vol >> 16;
      dst[i * 2 + 1] += src[i * 2 + 1] * vol >> 16;
    }
  }
}

static void MixToBufferWithVolumeRamp(int16 *dst, const int16 *src, size_t n, float volume, float volume_step, float ideal_target) {
  int64 vol = volume * 281474976710656.0f;
  int64 step = volume_step * 281474976710656.0f;
  for (size_t i = 0; i < n; i++) {
    uint32 v = (vol >> 32);
    dst[i * 2 + 0] += src[i * 2 + 0] * v >> 16;
    dst[i * 2 + 1] += src[i * 2 + 1] * v >> 16;
    vol += step;
  }
}

static void MixToBuffer(MsuPlayer *mp, int16 *dst, const int16 *src, uint32 n) {
  if (mp->volume != mp->volume_target) {
    float step = mp->volume < mp->volume_target ? mp->volume_step : -mp->volume_step;
    float new_vol = mp->volume + step * n;
    uint32 curn = n;
    if (step >= 0 ? new_vol >= mp->volume_target : new_vol < mp->volume_target) {
      uint32 maxn = (uint32)((mp->volume_target - mp->volume) / step);
      curn = UintMin(maxn, curn);
      new_vol = mp->volume_target;
    }
    float vol = mp->volume;
    mp->volume = new_vol;
    MixToBufferWithVolumeRamp(dst, src, curn, vol, step, new_vol);
    dst += curn * 2, src += curn * 2, n -= curn;
  }
  MixToBufferWithVolume(dst, src, n, mp->volume);
}

void MsuPlayer_Mix(MsuPlayer *mp, int16 *audio_buffer, int audio_samples) {
  int r;

  do {
    if (mp->buffer_size - mp->buffer_pos == 0) {
      if (mp->opus != NULL) {
        if (mp->samples_until_repeat == 0) {
          if (mp->range_cur == 0) FINISHED_PLAYING: {
            mp->state = kMsuState_FinishedPlaying;
            MsuPlayer_CloseFile(mp);
            return;
          }
          opus_decoder_ctl(mp->opus, OPUS_RESET_STATE);
          fseek(mp->f, mp->range_cur, SEEK_SET);
          uint8 *file_data = (uint8 *)mp->buffer;
          if (fread(file_data, 1, 10, mp->f) != 10) READ_ERROR: {
            fprintf(stderr, "MSU read/decode error!\n");
            zelda_apu_write(APUI00, mp->resume_info.orig_track);
            MsuPlayer_CloseFile(mp);
            return;
          }
          uint32 file_offs = *(uint32 *)&file_data[0];
          assert((file_offs & 0xF0000000) == 0);
          uint32 samples_until_repeat = *(uint32 *)&file_data[4];
          uint16 preskip = *(uint32 *)&file_data[8];
          mp->samples_until_repeat = samples_until_repeat;
          mp->preskip = preskip & 0x3fff;
          if (preskip & 0x4000)
            mp->range_repeat = mp->range_cur;
          mp->range_cur = (preskip & 0x8000) ? mp->range_repeat : mp->range_cur + 10;
          mp->cur_file_offs = file_offs;
          mp->resume_info.range_repeat = mp->range_repeat;
          mp->resume_info.range_cur = mp->range_cur;
          fseek(mp->f, file_offs, SEEK_SET);
        }
        assert(mp->samples_until_repeat != 0);
        for (;;) {
          uint8 *file_data = (uint8 *)mp->buffer;
          *(uint64 *)file_data = 0;
          if (fread(file_data, 1, 2, mp->f) != 2)
            goto READ_ERROR;
          int size = *(uint16 *)file_data & 0x7fff;
          if (size > 1275)
            goto READ_ERROR;
          int n = (*(uint16 *)file_data >> 15);
          if (fread(&file_data[2], 1, size, mp->f) != size)
            goto READ_ERROR;
          // Verify if the snapshot matches the file on disk.
          uint64 initial_file_data = *(uint64 *)file_data;
          if (mp->state == kMsuState_Resuming) {
            mp->state = kMsuState_Playing;
            if (mp->resume_info.initial_packet_bytes != initial_file_data)
              goto READ_ERROR;
          }
          mp->resume_info.initial_packet_bytes = initial_file_data;
          mp->resume_info.samples_until_repeat = mp->samples_until_repeat + mp->preskip;
          mp->resume_info.offset = mp->cur_file_offs;
          mp->cur_file_offs += 2 + size;
          file_data[1] = 0xfc;
          r = opus_decode(mp->opus, &file_data[2 - n], size + n, mp->buffer, 960, 0);
          if (r <= 0)
            goto READ_ERROR;
          if (r > mp->preskip)
            break;
          mp->preskip -= r;
        }
      } else {
        if (mp->samples_until_repeat == 0) {
          if (mp->resume_info.actual_track < sizeof(kMsuTracksWithRepeat) && !kMsuTracksWithRepeat[mp->resume_info.actual_track])
            goto FINISHED_PLAYING;
          mp->samples_until_repeat = mp->total_samples_in_file - mp->repeat_position;
          if (mp->samples_until_repeat == 0)
            goto READ_ERROR; // impossible to make progress
          mp->cur_file_offs = mp->repeat_position;
          fseek(mp->f, mp->cur_file_offs * 4 + 8, SEEK_SET);
        }
        r = UintMin(960, mp->samples_until_repeat);
        if (fread(mp->buffer, 4, r, mp->f) != r)
          goto READ_ERROR;
        mp->resume_info.offset = mp->cur_file_offs;
        mp->cur_file_offs += r;
      }
      uint32 n = UintMin(r - mp->preskip, mp->samples_until_repeat);
      mp->samples_until_repeat -= n;
      mp->buffer_pos = mp->preskip;
      mp->buffer_size = mp->buffer_pos + n;
      mp->preskip = 0;
    }
#if 0
    if (mp->samples_to_play > 44100 * 5) {
      mp->buffer_pos = mp->buffer_size;
    }
#endif
    int nr = IntMin(audio_samples, mp->buffer_size - mp->buffer_pos);
    int16 *buf = mp->buffer + mp->buffer_pos * 2;
    mp->buffer_pos += nr;

#if 0
    static int t;
    for (int i = 0; i < nr; i++) {
      buf[i * 2 + 0] = buf[i * 2 + 1] = 5000 * sinf(2 * 3.1415 * t++ / 440);
    }
#endif
    MixToBuffer(mp, audio_buffer, buf, nr);

#if 0
    static FILE *f;
    if (!f)f = fopen("out.pcm", "wb");
    fwrite(audio_buffer, 4, nr, f);
    fflush(f);
#endif
    audio_samples -= nr, audio_buffer += nr * 2;
  } while (audio_samples != 0);
}

// Maintain a queue cause the snes and audio callback are not in sync.
struct ApuWriteEnt {
  uint8 ports[4];
};
static struct ApuWriteEnt g_apu_write_ents[16], g_apu_write;
static uint8 g_apu_write_ent_pos, g_apu_write_count, g_apu_total_write;
void zelda_apu_write(uint32_t adr, uint8_t val) {
  g_apu_write.ports[adr & 0x3] = val;
}

void ZeldaPushApuState() {
  ZeldaApuLock();
  g_apu_write_ents[g_apu_write_ent_pos++ & 0xf] = g_apu_write;
  if (g_apu_write_count < 16)
    g_apu_write_count++;
  g_apu_total_write++;
  ZeldaApuUnlock();
}

static void ZeldaPopApuState() {
  if (g_apu_write_count != 0)
    memcpy(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count--) & 0xf], 4);
}

void ZeldaDiscardUnusedAudioFrames() {
  if (g_apu_write_count != 0 && memcmp(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count) & 0xf], 4) == 0) {
    if (g_apu_total_write >= 16) {
      g_apu_total_write = 14;
      g_apu_write_count--;
    }
  } else {
    g_apu_total_write = 0;
  }
}

static void ZeldaResetApuQueue() {
  g_apu_write_ent_pos = g_apu_total_write = g_apu_write_count = 0;
}

uint8_t zelda_read_apui00() {
  // This needs to be here because the ancilla code reads
  // from the apu and we don't want to make the core code
  // dependent on the apu timings, so relocated this value
  // to 0x648.
  return g_ram[kRam_APUI00];
}

uint8_t zelda_apu_read(uint32_t adr) {
  return g_zenv.player->port_to_snes[adr & 0x3];
}

void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels) {
  ZeldaApuLock();
  ZeldaPopApuState();
  SpcPlayer_GenerateSamples(g_zenv.player);
  dsp_getSamples(g_zenv.player->dsp, audio_buffer, samples, channels);
  if (g_msu_player.f && channels == 2)
    MsuPlayer_Mix(&g_msu_player, audio_buffer, samples);
  ZeldaApuUnlock();
}

bool ZeldaIsMusicPlaying() {
  if (g_msu_player.state != kMsuState_Idle) {
    return g_msu_player.state != kMsuState_FinishedPlaying;
  } else {
    return g_zenv.player->port_to_snes[0] != 0;
  }
}

void ZeldaRestoreMusicAfterLoad_Locked(bool is_reset) {
  // Restore spc variables from the ram dump.
  SpcPlayer_CopyVariablesFromRam(g_zenv.player);
  // This is not stored in the snapshot
  g_zenv.player->timer_cycles = 0;

  // Restore input ports state
  SpcPlayer *spc_player = g_zenv.player;
  memcpy(spc_player->input_ports, &spc_player->ram[0x410], 4);
  memcpy(g_apu_write.ports, spc_player->input_ports, 4);

  if (is_reset) {
    SpcPlayer_Initialize(g_zenv.player);
  }

  MsuPlayer *mp = &g_msu_player;
  if (mp->enabled) {
    mp->volume = 0.0;
    MsuPlayer_Open(mp, (music_unk1 == 0xf1) ? ((MsuPlayerResumeInfo*)msu_resume_info)->orig_track : 
                   music_unk1, true);

    // If resuming in the middle of a transition, then override
    // the volume with that of the transition.
    if (last_music_control >= 0xf1 && last_music_control <= 0xf3) {
      uint8 target = kVolumeTransitionTarget[last_music_control - 0xf1];
      if (target != msu_volume) {
        float f = kVolumeTransitionTargetFloat[3] * (1.0f / 255);
        mp->volume = msu_volume * f;
        mp->volume_target = target * f;
        mp->volume_step = kVolumeTransitionStepFloat[last_music_control - 0xf1];
      }
    }

    if (g_msu_player.state)
      zelda_apu_write(APUI00, 0xf0);  // pause spc player
  }
  ZeldaResetApuQueue();
}

void ZeldaSaveMusicStateToRam_Locked() {
  SpcPlayer_CopyVariablesToRam(g_zenv.player);
  // SpcPlayer.input_ports is not saved to the SpcPlayer ram by SpcPlayer_CopyVariablesToRam,
  // in any case, we want to save the most recently written data, and that might still
  // be in the queue. 0x410 is a free memory location in the SPC ram, so store it there.
  SpcPlayer *spc_player = g_zenv.player;
  memcpy(&spc_player->ram[0x410], g_apu_write.ports, 4);

  msu_volume = g_msu_player.volume * 255;
  memcpy(msu_resume_info, &g_msu_player.resume_info, sizeof(g_msu_player.resume_info));
}

void ZeldaEnableMsu(uint8 enable) {
  g_msu_player.volume = 1.0f;
  g_msu_player.enabled = enable;
  if (enable & kMsuEnabled_Opuz) {
    if (g_config.audio_freq != 48000)
      fprintf(stderr, "Warning: MSU Opuz requires: AudioFreq = 48000\n");
  } else if (enable) {
    if (g_config.audio_freq != 44100)
      fprintf(stderr, "Warning: MSU requires: AudioFreq = 44100\n");
  }

  float volscale = g_config.msuvolume * (1.0f / 255 / 100);
  float stepscale = g_config.msuvolume * (60.0f / 256 / 100) / g_config.audio_freq;
  for (size_t i = 0; i != countof(kVolumeTransitionStepFloat); i++) {
    kVolumeTransitionStepFloat[i] = kVolumeTransitionStep[i] * stepscale;
    kVolumeTransitionTargetFloat[i] = kVolumeTransitionTarget[i] * volscale;
  }
}

void LoadSongBank(const uint8 *p) {  // 808888
  ZeldaApuLock();
  SpcPlayer_Upload(g_zenv.player, p);
  ZeldaApuUnlock();
}