diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3b147ad..fbc4f182 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,7 @@ set( app/zelda3/overworld.cc app/zelda3/title_screen.cc app/zelda3/sprite.cc + app/zelda3/tracker.cc ) set( diff --git a/src/app/zelda3/tracker.cc b/src/app/zelda3/tracker.cc new file mode 100644 index 00000000..f6faf61f --- /dev/null +++ b/src/app/zelda3/tracker.cc @@ -0,0 +1,1339 @@ +/** + * @file tracker.cc + * + * @brief Legacy code from Hyrule Magic TrackerLogic.c + * + * @details Attemtping to extract the song banks and convert them into SPC + * format for the snes_spc library. + * + */ +#include "tracker.h" + +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "app/core/constants.h" +#include "app/gfx/bitmap.h" +#include "app/gfx/snes_tile.h" +#include "app/rom.h" +#include "snes_spc/snes_spc/spc.h" + +namespace yaze { +namespace app { +namespace zelda3 { + +namespace { + +void AddSPCReloc(SongSPCBlock *sbl, short addr) { + sbl->relocs[sbl->relnum++] = addr; + if (sbl->relnum == sbl->relsz) { + sbl->relsz += 16; + sbl->relocs = (unsigned short *)realloc(sbl->relocs, sbl->relsz << 1); + } +} + +} // namespace + +// ============================================================================= + +SongSPCBlock *Tracker::AllocSPCBlock(int len, int bank) { + SongSPCBlock *sbl; + if (!len) { + printf("warning zero length block allocated"); + } + if (ss_num == ss_size) { + ss_size += 512; + ssblt = (SongSPCBlock **)realloc(ssblt, ss_size << 2); + } + ssblt[ss_num] = sbl = (SongSPCBlock *)malloc(sizeof(SongSPCBlock)); + ss_num++; + sbl->start = ss_next; + sbl->len = len; + sbl->buf = (uchar *)malloc(len); + sbl->relocs = (ushort *)malloc(32); + sbl->relsz = 16; + sbl->relnum = 0; + sbl->bank = bank & 7; + sbl->flag = bank >> 3; + ss_next += len; + return sbl; +} + +// ============================================================================= + +unsigned char *Tracker::GetSPCAddr(ROM &rom, unsigned short addr, short bank) { + unsigned char *rom_ptr; + unsigned short a; + unsigned short b; + spcbank = bank + 1; + +again: + rom_ptr = rom.data() + sbank_ofs[spcbank]; + + for (;;) { + a = *(unsigned short *)rom_ptr; + + if (!a) { + if (spcbank) { + spcbank = 0; + + goto again; + } else + return nullptr; + } + + b = *(unsigned short *)(rom_ptr + 2); + rom_ptr += 4; + + if (addr >= b && addr - b < a) { + spclen = a; + + return rom_ptr + addr - b; + } + + rom_ptr += a; + } +} + +// ============================================================================= + +short Tracker::AllocSPCCommand() { + int i = m_free; + int j; + int k; + SPCCommand *spc_command; + if (i == -1) { + j = m_size; + m_size += 1024; + spc_command = current_spc_command_ = (SPCCommand *)realloc( + current_spc_command_, m_size * sizeof(SPCCommand)); + k = 1023; + while (k--) spc_command[j].next = j + 1, j++; + spc_command[j].next = -1; + k = 1023; + while (k--) spc_command[j].prev = j - 1, j--; + spc_command[j].prev = -1; + i = j; + } else + spc_command = current_spc_command_; + m_free = spc_command[m_free].next; + if (m_free != -1) spc_command[m_free].prev = -1; + return i; +} + +// ============================================================================= + +short Tracker::GetBlockTime(ROM &rom, short num, short prevtime) { + SPCCommand *spc_command = current_spc_command_; + SPCCommand *spc_command2; + + int i = -1; + int j = 0; + int k = 0; + int l; + int m = 0; + int n = prevtime; + l = num; + + if (l == -1) return 0; + + for (;;) { + if (spc_command[l].flag & 4) { + j = spc_command[l].tim; + m = spc_command[l].tim2; + k = 1; + } + + if (!k) i = l; + + if (spc_command[l].flag & 1) n = spc_command[l].b1; + + l = spc_command[l].next; + + if (l == -1) { + if (!k) { + m = 0; + j = 0; + } + + break; + } + } + + if (i != -1) + for (;;) { + if (i == -1) { + printf("Error"); + m_modf = 1; + return 0; + } + spc_command2 = spc_command + i; + if (spc_command2->cmd == 0xef) { + k = *(short *)&(spc_command2->p1); + if (k >= m_size) { + printf("Invalid music address\n"); + m_modf = 1; + return 0; + } + if (spc_command2->flag & 1) { + j += GetBlockTime(rom, k, 0) * spc_command2->p3; + if (ss_lasttime) { + j += ss_lasttime * m; + j += spc_command[k].tim2 * spc_command2->b1; + j += (spc_command2->p3 - 1) * spc_command[k].tim2 * ss_lasttime; + } else { + j += + spc_command2->b1 * (m + spc_command[k].tim2 * spc_command2->p3); + } + m = 0; + } else { + j += GetBlockTime(rom, k, 0) * spc_command2->p3; + j += ss_lasttime * m; + if (ss_lasttime) + j += (spc_command2->p3 - 1) * ss_lasttime * spc_command[k].tim2, + m = spc_command[k].tim2; + else + m += spc_command[k].tim2 * spc_command2->p3; + } + } else { + if (spc_command2->cmd < 0xe0) m++; + if (spc_command2->flag & 1) { + j += m * spc_command[i].b1; + m = 0; + } + } + spc_command2->tim = j; + spc_command2->tim2 = m; + spc_command2->flag |= 4; + + if (i == num) break; + + i = spc_command2->prev; + } + + ss_lasttime = n; + return spc_command[num].tim + prevtime * spc_command[num].tim2; +} + +// ============================================================================= + +short Tracker::LoadSPCCommand(ROM &rom, unsigned short addr, short bank, + int t) { + int b = 0; + int c = 0; + int d = 0; + int e = 0; + int f = 0; + int g = 0; + int h = 0; + int i = 0; + int l = 0; + int m = 0; + int n = 0; + int o = 0; + + unsigned char j = 0; + unsigned char k = 0; + + unsigned char *a = nullptr; + + SongRange *sr; + SPCCommand *spc_command = current_spc_command_; + SPCCommand *spc_command2; + if (!addr) return -1; + + a = GetSPCAddr(rom, addr, bank); + d = spcbank; + if (!a) { + printf("Address not found when loading track"); + return -1; + } + sr = song_range_; + e = srnum; + f = 0x10000; + + for (c = 0; c < e; c++) { + if (sr[c].bank == d) + if (sr[c].start > addr) { + if (sr[c].start < f) f = sr[c].start; + n = c; + } else if (sr[c].end > addr) { + for (f = sr[c].first; f != -1; f = spc_command[f].next) { + if (spc_command[f].flag & 4) + m = spc_command[f].tim, o = spc_command[f].tim2; + if (spc_command[f].addr == addr) { + spc_command[f].tim = m; + spc_command[f].tim2 = o; + lastsr = c; + return f; + } + if (spc_command[f].flag & 1) k = spc_command[f].b1; + if (spc_command[f].cmd < 0xca) + if (k) + m -= k; + else + o--; + } + printf("Misaligned music pointer"); + return -1; + } + } + + c = n; + i = h = m_free; + a -= addr; + m = 0; + k = 0; + o = 0; + + for (g = addr; g < f;) { + spc_command2 = spc_command + i; + if (spc_command2->next == -1) { + l = m_size; + spc_command = current_spc_command_ = (SPCCommand *)realloc( + spc_command, sizeof(SPCCommand) * (m_size += 1024)); + spc_command2 = spc_command + i; + n = l + 1023; + while (l < n) spc_command[l].next = l + 1, l++; + spc_command[l].next = -1; + n -= 1023; + while (l > n) spc_command[l].prev = l - 1, l--; + spc_command[l].prev = i; + spc_command2->next = l; + } + spc_command2->addr = g; + b = a[g]; + if (!b) break; + if (m >= t && b != 0xf9) break; + g++; + j = 0; + if (b < 128) { + j = 1; + k = spc_command2->b1 = b; + b = a[g++]; + if (b < 128) j = 3, spc_command2->b2 = b, b = a[g++]; + } + if (b < 0xe0) + if (k) + m += k; + else + o++; + spc_command2->cmd = b; + spc_command2->flag = j; + if (b >= 0xe0) { + b -= 0xe0; + if (op_len[b]) spc_command2->p1 = a[g++]; + if (op_len[b] > 1) spc_command2->p2 = a[g++]; + if (op_len[b] > 2) spc_command2->p3 = a[g++]; + if (b == 15) { + m_free = spc_command2->next; + spc_command[spc_command2->next].prev = -1; + l = LoadSPCCommand(rom, *(short *)(&(spc_command2->p1)), bank, t - m); + spc_command = current_spc_command_; + spc_command2 = spc_command + i; + *(short *)(&(spc_command2->p1)) = l; + spc_command2->next = m_free; + spc_command[spc_command2->next].prev = i; + GetBlockTime(rom, l, 0); + if (spc_command[l].flag & 4) + m += + (spc_command[l].tim + spc_command[l].tim2 * k) * spc_command2->p3; + else { + i = spc_command2->next; + break; + } + if (song_range_[lastsr].endtime) k = song_range_[lastsr].endtime; + } + } + i = spc_command2->next; + } + + spc_command[h].tim = m; + spc_command[h].tim2 = o; + spc_command[h].flag |= 4; + + if (f == g && m < t) { + l = spc_command[i].prev; + lastsr = c; + spc_command[sr[lastsr].first].prev = l; + l = spc_command[l].next = sr[lastsr].first; + if (spc_command[l].flag & 4) + spc_command[h].tim = spc_command[l].tim + m + spc_command[l].tim2 * k, + spc_command[h].flag |= 4; + sr[lastsr].first = h; + sr[lastsr].start = addr; + sr[lastsr].inst++; + } else { + if (srsize == srnum) + song_range_ = + (SongRange *)realloc(song_range_, (srsize += 16) * sizeof(SongRange)); + + lastsr = srnum; + sr = song_range_ + (srnum++); + sr->start = addr; + sr->end = g; + sr->first = h; + sr->endtime = k; + sr->inst = 1; + sr->editor = 0; + sr->bank = d; + spc_command[spc_command[i].prev].next = -1; + } + + spc_command[i].prev = -1; + m_free = i; + + return h; +} + +// ============================================================================= + +void Tracker::LoadSongs(ROM &rom) { + unsigned char *b; + unsigned char *c; + unsigned char *d; + short *e; + + Song *song; + Song *song2; + SongPart *sp; + SPCCommand *spc_command; + ZeldaWave *zelda_wave; + + int i; + int j; + int k; + int l = 0; + int m; + int n; + int o; + int p; + int q; + int r; + int t; + int u; + int range; + int filter; + + spc_command = current_spc_command_ = + (SPCCommand *)malloc(1024 * sizeof(SPCCommand)); + m_free = 0; + m_size = 1024; + srnum = 0; + srsize = 0; + song_range_ = 0; + sp_mark = 0; + b = rom.data(); + + sbank_ofs[1] = (b[0x91c] << 15) + ((b[0x918] & 127) << 8) + b[0x914]; + sbank_ofs[3] = (b[0x93a] << 15) + ((b[0x936] & 127) << 8) + b[0x932]; + + for (i = 0; i < 1024; i++) { + spc_command[i].next = i + 1; + spc_command[i].prev = i - 1; + } + + spc_command[1023].next = -1; + for (i = 0; i < 3; i++) { + b = GetSPCAddr(rom, 0xd000, i); + + for (j = 0;; j++) { + if ((r = ((unsigned short *)b)[j]) >= 0xd000) { + r = (r - 0xd000) >> 1; + break; + } + } + + numsong[i] = r; + for (j = 0; j < r; j++) { + k = ((unsigned short *)b)[j]; + if (!k) + songs[l] = 0; + else { + c = GetSPCAddr(rom, k, i); + + if (!spcbank) + m = 0; + else + m = l - j; + + for (; m < l; m++) + if (songs[m] && songs[m]->addr == k) { + (songs[l] = songs[m])->inst++; + + break; + } + + if (m == l) { + songs[l] = song = (Song *)malloc(sizeof(Song)); + song->inst = 1; + song->addr = k; + song->flag = !spcbank; + + for (m = 0;; m++) + if ((n = ((unsigned short *)c)[m]) < 256) break; + + if (n > 0) { + song->flag |= 2; + song->lopst = (((unsigned short *)c)[m + 1] - k) >> 1; + } + + song->numparts = m; + song->tbl = (SongPart **)malloc(4 * m); + + for (m = 0; m < song->numparts; m++) { + k = ((unsigned short *)c)[m]; + d = GetSPCAddr(rom, k, i); + if (!spcbank) + n = 0; + else + n = l - j; + + for (; n < l; n++) { + song2 = songs[n]; + if (song2) + for (o = 0; o < song2->numparts; o++) + if (song2->tbl[o]->addr == k) { + (song->tbl[m] = song2->tbl[o])->inst++; + goto foundpart; + } + } + + for (o = 0; o < m; o++) + if (song->tbl[o]->addr == k) { + (song->tbl[m] = song->tbl[o])->inst++; + goto foundpart; + } + + sp = song->tbl[m] = (SongPart *)malloc(sizeof(SongPart)); + sp->flag = !spcbank; + sp->inst = 1; + sp->addr = k; + p = 50000; + for (o = 0; o < 8; o++) { + q = sp->tbl[o] = + LoadSPCCommand(rom, ((unsigned short *)d)[o], i, p); + spc_command = current_spc_command_ + q; + if ((spc_command->flag & 4) && spc_command->tim < p) + p = spc_command->tim; + } + foundpart:; + } + } + } + l++; + } + } + + b = GetSPCAddr(rom, 0x800, 0); + snddat1 = (char *)malloc(spclen); + sndlen1 = spclen; + memcpy(snddat1, b, spclen); + + b = GetSPCAddr(rom, 0x17c0, 0); + snddat2 = (char *)malloc(spclen); + sndlen2 = spclen; + memcpy(snddat2, b, spclen); + + b = GetSPCAddr(rom, 0x3d00, 0); + insts = (ZeldaInstrument *)malloc(spclen); + memcpy(insts, b, spclen); + numinst = spclen / 6; + + b = GetSPCAddr(rom, 0x3e00, 0); + m_ofs = b - rom.data() + spclen; + sndinsts = (ZeldaSfxInstrument *)malloc(spclen); + memcpy(sndinsts, b, spclen); + numsndinst = spclen / 9; + + b = GetSPCAddr(rom, 0x3c00, 0); + zelda_wave = waves = (ZeldaWave *)malloc(sizeof(ZeldaWave) * (spclen >> 2)); + p = spclen >> 1; + + for (i = 0; i < p; i += 2) { + j = ((unsigned short *)b)[i]; + + if (j == 65535) break; + + for (k = 0; k < i; k += 2) { + if (((unsigned short *)b)[k] == j) { + zelda_wave->copy = (short)(k >> 1); + goto foundwave; + } + } + + zelda_wave->copy = -1; + + foundwave: + + d = GetSPCAddr(rom, j, 0); + e = (short *)malloc(2048); + + k = 0; + l = 1024; + u = t = 0; + + for (;;) { + m = *(d++); + + range = (m >> 4) + 8; + filter = (m & 12) >> 2; + + for (n = 0; n < 8; n++) { + o = (*d) >> 4; + + if (o > 7) o -= 16; + + o <<= range; + + if (filter) + o += (t * fil1[filter] >> fil2[filter]) - + ((u & -256) * fil3[filter] >> 4); + + if (o > 0x7fffff) o = 0x7fffff; + + if (o < -0x800000) o = -0x800000; + + u = o; + + // \code if(t>0x7fffff) t=0x7fffff; + // \code if(t < -0x800000) t=-0x800000; + + e[k++] = o >> 8; + + o = *(d++) & 15; + + if (o > 7) o -= 16; + + o <<= range; + + if (filter) + o += (u * fil1[filter] >> fil2[filter]) - + ((t & -256) * fil3[filter] >> 4); + + if (o > 0x7fffff) o = 0x7fffff; + + if (o < -0x800000) o = -0x800000; + + t = o; + // \code if(u>0x7fffff) u=0x7fffff; + // \code if(u < -0x800000) u= -0x800000; + e[k++] = o >> 8; + } + + if (m & 1) { + zelda_wave->lflag = (m & 2) >> 1; + break; + } + if (k == l) { + l += 1024; + e = (short *)realloc(e, l << 1); + } + } + + e = zelda_wave->buf = (short *)realloc(e, (k + 1) << 1); + + zelda_wave->lopst = (((unsigned short *)b)[i + 1] - j) * 16 / 9; + + if (zelda_wave->lflag) + e[k] = e[zelda_wave->lopst]; + else + e[k] = 0; + + zelda_wave->end = k; + + zelda_wave++; + } + + numwave = i >> 1; + m_loaded = 1; + w_modf = 0; +} + +short Tracker::SaveSPCCommand(ROM &rom, short num, short songtime, + short endtr) { + SPCCommand *spc_command = current_spc_command_; + SPCCommand *spc_command2; + SongRange *sr = song_range_; + SongSPCBlock *sbl; + + text_buf_ty buf; + + unsigned char *b; + int i = num; + int j = 0; + int k = 0; + int l = 0; + int m = 0; + int n = 0; + int o = 0; + int p = 0; + + if (i == -1) return 0; + + if (i >= m_size) { + printf("Error.\n"); + m_modf = 1; + return 0; + } + + if (spc_command[i].flag & 8) return spc_command[i].addr; + + for (;;) { + j = spc_command[i].prev; + if (j == -1) break; + i = j; + } + + for (j = 0; j < srnum; j++) { + if (sr[j].first == i) { + l = GetBlockTime(rom, i, 0); + m = i; + for (;;) { + if (m == -1) break; + k++; + spc_command2 = spc_command + m; + if (spc_command2->flag & 1) k++, n = spc_command2->b1; + if (spc_command2->flag & 2) k++; + if (spc_command2->cmd >= 0xe0) k += op_len[spc_command2->cmd - 0xe0]; + m = spc_command2->next; + } + songtime -= l; + + if (songtime > 0) { + l = (songtime + 126) / 127; + if (songtime % l) l += 2; + l++; + if (n && !songtime % n) { + p = songtime / n; + if (p < l) l = p; + } else + p = -1; + k += l; + } + k++; + sbl = AllocSPCBlock(k, sr[j].bank | ((!endtr) << 3) | 16); + b = sbl->buf; + + for (;;) { + if (i == -1) break; + spc_command2 = spc_command + i; + spc_command2->addr = b - sbl->buf + sbl->start; + spc_command2->flag |= 8; + if (spc_command2->flag & 1) *(b++) = spc_command2->b1; + if (spc_command2->flag & 2) *(b++) = spc_command2->b2; + *(b++) = spc_command2->cmd; + if (spc_command2->cmd >= 0xe0) { + o = op_len[spc_command2->cmd - 0xe0]; + if (spc_command2->cmd == 0xef) { + *(short *)b = + SaveSPCCommand(rom, *(short *)&(spc_command2->p1), 0, 1); + if (b) AddSPCReloc(sbl, b - sbl->buf); + b[2] = spc_command2->p3; + b += 3; + } else { + if (o) *(b++) = spc_command2->p1; + if (o > 1) *(b++) = spc_command2->p2; + if (o > 2) *(b++) = spc_command2->p3; + } + } + i = spc_command2->next; + } + + if (songtime > 0) { + if (l != p) { + l = (songtime + 126) / 127; + if (songtime % l) + n = 127; + else + n = songtime / l; + *(b++) = n; + } + + for (; songtime >= n; songtime -= n) *(b++) = 0xc9; + + if (songtime) { + *(b++) = (uint8_t)songtime; + *(b++) = 0xc9; + } + } + *(b++) = 0; + return spc_command[num].addr; + } + } + + printf("Address %04X not found", num); + + printf("Error"); + + m_modf = 1; + + return 0; +} + +// ============================================================================= + +int Tracker::WriteSPCData(ROM &rom, void *buf, int len, int addr, int spc, + int limit) { + unsigned char *rom_data = rom.data(); + + if (!len) return addr; + + if (((addr + len + 4) & 0x7fff) > 0x7ffb) { + if (addr + 5 > limit) goto error; + *(int *)(rom_data + addr) = 0x00010140; + rom_data[addr + 4] = 0xff; + addr += 5; + } + + if (addr + len + 4 > limit) { + error: + printf("Not enough space for sound data"); + m_modf = 1; + return 0xc8000; + } + + *(short *)(rom_data + addr) = len; + *(short *)(rom_data + addr + 2) = spc; + + memcpy(rom_data + addr + 4, buf, len); + + return addr + len + 4; +} + +// ============================================================================= + +void Tracker::SaveSongs(ROM &rom) { + int i; + int j; + int k; + int l = 0; + int m; + int n; + int o; + int p; + int q; + int r; + int t; + int u; + int v; + int w; + int a; + int e; + int f; + int g; + unsigned short bank_next[4]; + unsigned short bank_lwr[4]; + short *c, *d; + unsigned char *rom_data, *b; + + Song *s; + SPCCommand *spc_command; + SongPart *sp; + SongSPCBlock *stbl; + SongSPCBlock *sptbl; + SongSPCBlock *trtbl; + SongSPCBlock *pstbl; + ZeldaWave *zelda_wave; + ZeldaWave *zelda_wave2; + ZeldaInstrument *zi; + SampleEdit *sed; + + short wtbl[128], x[16], y[18]; + unsigned char z[64]; + + ss_num = 0; + ss_size = 512; + ss_next = 0; + + // if the music has not been modified, return. + if (!(m_modf)) return; + + ssblt = (SongSPCBlock **)malloc(512 * sizeof(SongSPCBlock)); + + // set it so the music has not been modified. (reset the status) + m_modf = 0; + rom_data = rom.data(); + + // SetCursor(wait_cursor); + + for (i = 0; i < 3; i++) { + k = numsong[i]; + + for (j = 0; j < k; j++) { + s = songs[l++]; + + if (!s) continue; + + s->flag &= -5; + + for (m = 0; m < s->numparts; m++) { + sp = s->tbl[m]; + sp->flag &= -3; + } + } + } + + j = m_size; + spc_command = current_spc_command_; + + for (i = 0; i < j; i++) { + spc_command->flag &= -13; + spc_command++; + } + + l = 0; + + for (i = 0; i < 3; i++) { + k = numsong[i]; + stbl = AllocSPCBlock(k << 1, i + 1); + + for (j = 0; j < k; j++) { + s = songs[l++]; + + if (!s) { + ((short *)(stbl->buf))[j] = 0; + + continue; + } + + if (s->flag & 4) goto alreadysaved; + + sptbl = AllocSPCBlock(((s->numparts + 1) << 1) + (s->flag & 2), + (s->flag & 1) ? 0 : (i + 1)); + + for (m = 0; m < s->numparts; m++) { + sp = s->tbl[m]; + + if (sp->flag & 2) goto spsaved; + + trtbl = AllocSPCBlock(16, (sp->flag & 1) ? 0 : (i + 1)); + + p = 0; + + for (n = 0; n < 8; n++) { + o = GetBlockTime(rom, sp->tbl[n], 0); + + if (o > p) p = o; + } + + q = 1; + + for (n = 0; n < 8; n++) { + stle16b_i(trtbl->buf, n, SaveSPCCommand(rom, sp->tbl[n], p, q)); + + if (ldle16b_i(trtbl->buf, n)) AddSPCReloc(trtbl, n << 1), q = 0; + } + + sp->addr = trtbl->start; + sp->flag |= 2; + spsaved: + ((short *)(sptbl->buf))[m] = sp->addr; + + AddSPCReloc(sptbl, m << 1); + } + + if (s->flag & 2) { + ((short *)(sptbl->buf))[m++] = 255; + ((short *)(sptbl->buf))[m] = sptbl->start + (s->lopst << 1); + + AddSPCReloc(sptbl, m << 1); + } else + ((short *)(sptbl->buf))[m++] = 0; + + s->addr = sptbl->start; + s->flag |= 4; + alreadysaved: + ((short *)(stbl->buf))[j] = s->addr; + + AddSPCReloc(stbl, j << 1); + } + } + + if (w_modf) { + b = (uint8_t *)malloc(0xc000); + j = 0; + + zelda_wave = waves; + + // if (mbanks[3]) + // sed = (SampleEdit *)GetWindowLongPtr(mbanks[3], GWLP_USERDATA); + // else + // sed = 0; + + for (i = 0; i < numwave; i++, zelda_wave++) { + if (zelda_wave->copy != -1) continue; + + wtbl[i << 1] = j + 0x4000; + + if (zelda_wave->lflag) { + l = zelda_wave->end - zelda_wave->lopst; + + if (l & 15) { + k = (l << 15) / ((l + 15) & -16); + p = (zelda_wave->end << 15) / k; + c = (short *)malloc(p << 1); + n = 0; + d = zelda_wave->buf; + + for (m = 0;;) { + c[n++] = (d[m >> 15] * ((m & 32767) ^ 32767) + + d[(m >> 15) + 1] * (m & 32767)) / + 32767; + + m += k; + + if (n >= p) break; + } + + zelda_wave->lopst = (zelda_wave->lopst << 15) / k; + zelda_wave->end = p; + zelda_wave->buf = + (short *)realloc(zelda_wave->buf, (zelda_wave->end + 1) << 1); + memcpy(zelda_wave->buf, c, zelda_wave->end << 1); + free(c); + zelda_wave->buf[zelda_wave->end] = zelda_wave->buf[zelda_wave->lopst]; + zelda_wave2 = waves; + + for (m = 0; m < numwave; m++, zelda_wave2++) + if (zelda_wave2->copy == i) + zelda_wave2->lopst = zelda_wave2->lopst << 15 / k; + + zi = insts; + + for (m = 0; m < numinst; m++) { + n = zi->samp; + + if (n >= numwave) continue; + + if (n == i || waves[n].copy == i) { + o = (zi->multhi << 8) + zi->multlo; + o = (o << 15) / k; + zi->multlo = o; + zi->multhi = o >> 8; + + if (sed && sed->editinst == m) { + sed->init = 1; + // SetDlgItemInt(sed->dlg, 3014, o, 0); + sed->init = 0; + } + } + + zi++; + } + + // Modifywaves(rom, i); + } + } + k = (-zelda_wave->end) & 15; + d = zelda_wave->buf; + n = 0; + wtbl[(i << 1) + 1] = ((zelda_wave->lopst + k) >> 4) * 9 + wtbl[i << 1]; + y[0] = y[1] = 0; + u = 4; + for (;;) { + for (o = 0; o < 16; o++) { + if (k) + k--, x[o] = 0; + else + x[o] = d[n++]; + } + p = 0x7fffffff; + a = 0; + for (t = 0; t < 4; t++) { + r = 0; + for (o = 0; o < 16; o++) { + l = x[o]; + y[o + 2] = l; + l += (y[o] * fil3[t] >> 4) - (y[o + 1] * fil1[t] >> fil2[t]); + if (l > r) + r = l; + else if (-l > r) + r = -l; + } + r <<= 1; + if (t) + m = 14; + else + m = 15; + for (q = 0; q < 12; q++, m += m) + if (m >= r) break; + tryagain: + if (q && (q < 12 || m == r)) + v = (1 << (q - 1)) - 1; + else + v = 0; + m = 0; + for (o = 0; o < 16; o++) { + l = (y[o + 1] * fil1[t] >> fil2[t]) - (y[o] * fil3[t] >> 4); + w = x[o]; + r = (w - l + v) >> q; + if ((r + 8) & 0xfff0) { + q++; + a -= o; + goto tryagain; + } + z[a++] = r; + l = (r << q) + l; + y[o + 2] = l; + l -= w; + m += l * l; + } + if (u == 4) { + u = 0, e = q, f = y[16], g = y[17]; + break; + } + if (m < p) p = m, u = t, e = q, f = y[16], g = y[17]; + } + m = (e << 4) | (u << 2); + if (n == zelda_wave->end) m |= 1; + if (zelda_wave->lflag) m |= 2; + b[j++] = m; + m = 0; + a = u << 4; + for (o = 0; o < 16; o++) { + m |= z[a++] & 15; + if (o & 1) + b[j++] = m, m = 0; + else + m <<= 4; + } + if (n == zelda_wave->end) break; + y[0] = f; + y[1] = g; + } + } + + // if (sed) { + // SetDlgItemInt(sed->dlg, ID_Samp_SampleLengthEdit, sed->zelda_wave->end, + // 0); SetDlgItemInt(sed->dlg, ID_Samp_LoopPointEdit, + // sed->zelda_wave->lopst, 0); + + // InvalidateRect(GetDlgItem(sed->dlg, ID_Samp_Display), 0, 1); + // } + + zelda_wave = waves; + + for (i = 0; i < numwave; i++, zelda_wave++) { + if (zelda_wave->copy != -1) { + wtbl[i << 1] = wtbl[zelda_wave->copy << 1]; + wtbl[(i << 1) + 1] = (zelda_wave->lopst >> 4) * 9 + wtbl[i << 1]; + } + } + + m = WriteSPCData(rom, wtbl, numwave << 2, 0xc8000, 0x3c00, 0xd74fc); + m = WriteSPCData(rom, b, j, m, 0x4000, 0xd74fc); + + free(b); + m = WriteSPCData(rom, insts, numinst * 6, m, 0x3d00, 0xd74fc); + m = WriteSPCData(rom, snddat1, sndlen1, m, 0x800, 0xd74fc); + m = WriteSPCData(rom, snddat2, sndlen2, m, 0x17c0, 0xd74fc); + m = WriteSPCData(rom, sndinsts, numsndinst * 9, m, 0x3e00, 0xd74fc); + m_ofs = m; + } else { + m = m_ofs; + } + + bank_next[0] = 0x2880; + bank_next[1] = 0xd000; + bank_next[2] = 0xd000; + bank_next[3] = 0xd000; + bank_lwr[0] = 0x2880; + + for (k = 0; k < 4; k++) { + pstbl = 0; + for (i = 0; i < ss_num; i++) { + stbl = ssblt[i]; + if (stbl->bank != k) continue; + j = bank_next[k]; + if (j + stbl->len > 0xffc0) { + if (k == 3) + j = 0x2880; + else + j = bank_next[0]; + bank_lwr[k] = j; + pstbl = 0; + } + if (j + stbl->len > 0x3c00 && j < 0xd000) { + text_buf_ty buf; + + // SetCursor(normal_cursor); + + printf("Not enough space for music bank %d", k); + printf("Error"); + + m_modf = 1; + + return; + } + if (pstbl && (pstbl->flag & 1) && (stbl->flag & 2)) j--, pstbl->len--; + stbl->addr = j; + pstbl = stbl; + bank_next[k] = j + stbl->len; + } + } + for (i = 0; i < ss_num; i++) { + stbl = ssblt[i]; + for (j = stbl->relnum - 1; j >= 0; j--) { + k = *(unsigned short *)(stbl->buf + stbl->relocs[j]); + for (l = 0; l < ss_num; l++) { + sptbl = ssblt[l]; + if (sptbl->start <= k && sptbl->len > k - sptbl->start) goto noerror; + } + printf("Internal error"); + m_modf = 1; + return; + noerror: + + if (((!sptbl->bank) && stbl->bank < 3) || (sptbl->bank == stbl->bank)) { + *(unsigned short *)(stbl->buf + stbl->relocs[j]) = + sptbl->addr + k - sptbl->start; + } else { + text_buf_ty buf; + + // wsprintf(buf, "An address outside the bank was referenced", + // sptbl->bank, + // stbl->bank); + + printf("Error"); + + m_modf = 1; + + return; + } + } + } + l = m; + for (k = 0; k < 4; k++) { + switch (k) { + case 1: + rom[0x914] = l; + rom[0x918] = (l >> 8) | 128; + rom[0x91c] = l >> 15; + break; + case 2: + l = 0xd8000; + break; + case 3: + l = m; + rom[0x932] = l; + rom[0x936] = (l >> 8) | 128; + rom[0x93a] = l >> 15; + break; + } + for (o = 0; o < 2; o++) { + n = l + 4; + for (i = 0; i < ss_num; i++) { + stbl = ssblt[i]; + if (!stbl) continue; + if ((stbl->addr < 0xd000) ^ o) continue; + if (stbl->bank != k) continue; + if (n + stbl->len > ((k == 2) ? 0xdb7fc : 0xd74fc)) { + printf("Not enough space for music"); + m_modf = 1; + return; + } + memcpy(rom.data() + n, stbl->buf, stbl->len); + n += stbl->len; + free(stbl->relocs); + free(stbl->buf); + free(stbl); + ssblt[i] = 0; + } + if (n > l + 4) { + *(short *)(rom + l) = n - l - 4; + *(short *)(rom + l + 2) = o ? bank_lwr[k] : 0xd000; + l = n; + } + } + *(short *)(rom + l) = 0; + *(short *)(rom + l + 2) = 0x800; + if (k == 1) m = l + 4; + } + free(ssblt); + // SetCursor(normal_cursor); +} + +// ============================================================================= + +void Tracker::EditTrack(ROM &rom, short i) { + int j, k, l; + SongRange *sr = song_range_; + SPCCommand *spc_command; + + text_buf_ty buf; + + // ----------------------------- + + k = srnum; + + spc_command = current_spc_command_; + + if (i == -1) return; + + if (i >= m_size) { + printf("Invalid address: %04X", i); + goto error; + } + + for (;;) { + if ((j = spc_command[i].prev) != -1) + i = j; + else + break; + } + + for (l = 0; l < k; l++) + if (sr->first == i) + break; + else + sr++; + + if (l == k) { + printf("Not found: %04X", i); + error: + printf("Error"); + return; + } + + // if (sr->editor) + // HM_MDI_ActivateChild(clientwnd, sr->editor); + // else + // Editwin(rom, "TRACKEDIT", "Song editor", l + (i << 16), + // sizeof(TRACKEDIT)); +} + +// CRITICAL_SECTION cs_song; +// ============================================================================= + +void Tracker::NewSR(ROM &rom, int bank) { + SPCCommand *spc_command; + SongRange *sr; + + if (srnum == srsize) { + srsize += 16; + song_range_ = (SongRange *)realloc(song_range_, srsize * sizeof(SongRange)); + } + + sr = song_range_ + srnum; + srnum++; + sr->first = AllocSPCCommand(rom); + sr->bank = bank; + sr->editor = 0; + spc_command = current_spc_command_ + sr->first; + spc_command->prev = -1; + spc_command->next = -1; + spc_command->cmd = 128; + spc_command->flag = 0; + EditTrack(rom, sr->first); +} + +// ============================================================================= + +} // namespace zelda3 +} // namespace app +} // namespace yaze diff --git a/src/app/zelda3/tracker.h b/src/app/zelda3/tracker.h new file mode 100644 index 00000000..9baac431 --- /dev/null +++ b/src/app/zelda3/tracker.h @@ -0,0 +1,336 @@ +#ifndef YAZE_APP_ZELDA3_TRACKER_H +#define YAZE_APP_ZELDA3_TRACKER_H + +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "app/core/constants.h" +#include "app/gfx/bitmap.h" +#include "app/gfx/snes_tile.h" +#include "app/rom.h" +#include "snes_spc/snes_spc/spc.h" + +namespace yaze { +namespace app { +namespace zelda3 { + +// bank 19, 1A, 1B +// iirc 1A is OW, 1B is dungeon +// 19 is general spc stuff like samples, ects +char op_len[32] = {1, 1, 2, 3, 0, 1, 2, 1, 2, 1, 1, 3, 0, 1, 2, 3, + 1, 3, 3, 0, 1, 3, 0, 3, 3, 3, 1, 2, 0, 0, 0, 0}; + +// ============================================================================= + +static int sbank_ofs[] = {0xc8000, 0, 0xd8000, 0}; + +char fil1[4] = {0, 15, 61, 115}; +char fil2[4] = {0, 4, 5, 6}; +char fil3[4] = {0, 0, 15, 13}; + +constexpr int kOverworldMusicBank = 0x0D0000; +constexpr int kDungeonMusicBank = 0x0D8000; + +void stle16b_i(uint8_t *const p_arr, size_t const p_index, + uint16_t const p_val); +uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index); + +void stle16b(uint8_t *const p_arr, uint16_t const p_val); + +// "Store little endian 16-bit value using a byte pointer, offset by an +// index before dereferencing" +void stle16b_i(uint8_t *const p_arr, size_t const p_index, + uint16_t const p_val) { + stle16b(p_arr + (p_index * 2), p_val); +} + +void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) { + uint8_t v = (p_val >> (8 * p_index)) & 0xff; + + p_arr[p_index] = v; +} + +void stle0(uint8_t *const p_arr, unsigned const p_val) { + stle(p_arr, 0, p_val); +} + +void stle1(uint8_t *const p_arr, unsigned const p_val) { + stle(p_arr, 1, p_val); +} + +void stle2(uint8_t *const p_arr, unsigned const p_val) { + stle(p_arr, 2, p_val); +} + +void stle3(uint8_t *const p_arr, unsigned const p_val) { + stle(p_arr, 3, p_val); +} +void stle16b(uint8_t *const p_arr, uint16_t const p_val) { + stle0(p_arr, p_val); + stle1(p_arr, p_val); +} + +// "load little endian value at the given byte offset and shift to get its +// value relative to the base offset (powers of 256, essentially)" +unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) { + uint32_t v = p_arr[p_index]; + + v <<= (8 * p_index); + + return v; +} + +// Helper function to get the first byte in a little endian number +uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); } + +// Helper function to get the second byte in a little endian number +uint32_t ldle1(uint8_t const *const p_arr) { return ldle(p_arr, 1); } + +// Helper function to get the third byte in a little endian number +uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); } + +// Helper function to get the third byte in a little endian number +uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); } +// Load little endian halfword (16-bit) dereferenced from +uint16_t ldle16b(uint8_t const *const p_arr) { + uint16_t v = 0; + + v |= (ldle0(p_arr) | ldle1(p_arr)); + + return v; +} +// Load little endian halfword (16-bit) dereferenced from an arrays of bytes. +// This version provides an index that will be multiplied by 2 and added to the +// base address. +uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) { + return ldle16b(p_arr + (2 * p_index)); +} + +using text_buf_ty = char[512]; +// ============================================================================ + +using SongSPCBlock = struct { + unsigned short start; + unsigned short len; + unsigned short relnum; + unsigned short relsz; + unsigned short *relocs; + unsigned short bank; + unsigned short addr; + unsigned char *buf; + int flag; +}; + +// ============================================================================= + +using SongRange = struct { + unsigned short start; + unsigned short end; + + short first; + short inst; + short bank; + + unsigned char endtime; + unsigned char filler; + + int editor; +}; + +// ============================================================================= + +using SongPart = struct { + uchar flag; + uchar inst; + short tbl[8]; + unsigned short addr; +}; + +// ============================================================================= + +using Song = struct { + unsigned char flag; + unsigned char inst; + SongPart **tbl; + short numparts; + short lopst; + unsigned short addr; +}; +// ============================================================================= + +using ZeldaWave = struct { + int lopst; + int end; + short lflag; + short copy; + short *buf; +}; + +// ============================================================================ + +using SampleEdit = struct { + // EDITWIN ew; + // HWND dlg; + unsigned short flag; + unsigned short init; + unsigned short editsamp; + int width; + int height; + int pageh; + int pagev; + int zoom; + int scroll; + int page; + + /// Left hand sample selection point + int sell; + + /// Right hand sample selection point + int selr; + + int editinst; + + ZeldaWave *zw; +}; + +// ============================================================================= + +using ZeldaInstrument = struct { + unsigned char samp; + unsigned char ad; + unsigned char sr; + unsigned char gain; + unsigned char multhi; + unsigned char multlo; +}; + +// ============================================================================= + +using ZeldaSfxInstrument = struct { + unsigned char voll; + unsigned char volr; + short freq; + unsigned char samp; + unsigned char ad; + unsigned char sr; + unsigned char gain; + unsigned char multhi; +}; + +// ============================================================================= + +using SPCCommand = struct { + unsigned short addr; + short next; + short prev; + unsigned char flag; + unsigned char cmd; + unsigned char p1; + unsigned char p2; + unsigned char p3; + unsigned char b1; + unsigned char b2; + unsigned char tim2; + unsigned short tim; +}; + +// ============================================================================= + +class Tracker { + public: + SongSPCBlock *AllocSPCBlock(int len, int bank); + + unsigned char *GetSPCAddr(ROM &rom, unsigned short addr, short bank); + + short AllocSPCCommand(); + + short GetBlockTime(ROM &rom, short num, short prevtime); + + short SaveSPCCommand(ROM &rom, short num, short songtime, short endtr); + short LoadSPCCommand(ROM &rom, unsigned short addr, short bank, int t); + + void SaveSongs(ROM &rom); + + void LoadSongs(ROM &rom); + + int WriteSPCData(ROM &rom, void *buf, int len, int addr, int spc, int limit); + + void EditTrack(ROM &rom, short i); + + void NewSR(ROM &rom, int bank); + + private: + // A "modified" flag + int modf; + + int mark_sr; + + int mark_start; + int mark_end; + int mark_first; + int mark_last; + + int numwave; + int numinst; + int numsndinst; + + int sndinit = 0; + + int sndlen1; + int sndlen2; + int m_ofs; + int w_modf; + + int ss_num; + int ss_size; + + char op_len[32]; + + char *snddat1; + char *snddat2; // more music stuff. + + unsigned short ss_next = 0; + unsigned short spclen; + unsigned short numseg; + + short spcbank; + short lastsr; + short ss_lasttime; + short srnum; + short srsize; + short numsong[3]; // ditto + short m_size; + short m_free; + short m_modf; // ??? + short m_loaded; + + short t_loaded; + short t_modf; + short withhead; + + size_t t_number; + + Song *songs[128]; + SongPart *sp_mark; + SongRange *song_range_; + SPCCommand *current_spc_command_; + + SongSPCBlock **ssblt; + + ZeldaWave *waves; + ZeldaInstrument *insts; + ZeldaSfxInstrument *sndinsts; + // HWND mbanks[4]; // ??? + // HWND t_wnd; +}; + +// ============================================================================= + +} // namespace zelda3 +} // namespace app +} // namespace yaze + +#endif \ No newline at end of file