diff options
author | Takamichi Horikawa <takamichiho@gmail.com> | 2016-11-26 20:57:57 +0900 |
---|---|---|
committer | Takamichi Horikawa <takamichiho@gmail.com> | 2016-11-26 20:57:57 +0900 |
commit | 6fd10cdacb5cbe47a4fc339c20a733d4a9a384a1 (patch) | |
tree | c7f479a70c350dca0b73d76078e46db41d9c4133 /fmdriver/ppz8.c |
initial
Diffstat (limited to 'fmdriver/ppz8.c')
-rw-r--r-- | fmdriver/ppz8.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/fmdriver/ppz8.c b/fmdriver/ppz8.c new file mode 100644 index 0000000..7984575 --- /dev/null +++ b/fmdriver/ppz8.c @@ -0,0 +1,293 @@ +#include "ppz8.h" +#include "fmdriver_common.h" + +void ppz8_init(struct ppz8 *ppz8, uint16_t srate, uint16_t mix_volume) { + for (int i = 0; i < 2; i++) { + struct ppz8_pcmbuf *buf = &ppz8->buf[i]; + buf->data = 0; + buf->buflen = 0; + for (int j = 0; j < 128; j++) { + struct ppz8_pcmvoice *voice = &buf->voice[j]; + voice->start = 0; + voice->len = 0; + voice->loopstart = 0; + voice->loopend = 0; + voice->origfreq = 0; + } + } + for (int i = 0; i < 8; i++) { + struct ppz8_channel *channel = &ppz8->channel[i]; + channel->ptr = -1; + channel->loopstartptr = -1; + channel->loopendptr = -1; + channel->endptr = 0; + channel->freq = 0; + channel->loopstartoff = -1; + channel->loopendoff = -1; + channel->prevout[0] = 0; + channel->prevout[1] = 0; + channel->vol = 8; + channel->pan = 5; + channel->voice = 0; + } + ppz8->srate = srate; + ppz8->totalvol = 12; + ppz8->mix_volume = mix_volume; +} + +static uint64_t ppz8_loop(const struct ppz8_channel *channel, uint64_t ptr) { + if (channel->loopstartptr != (uint64_t)-1) { + if (channel->loopendptr != (uint64_t)-1) { + if (ptr >= channel->loopendptr) { + if (channel->loopendptr == channel->loopstartptr) return (uint64_t)-1; + uint32_t offset = (ptr - channel->loopendptr) >> 16; + offset %= (uint32_t)((channel->loopendptr - channel->loopstartptr) >> 16); + offset += (uint32_t)(channel->loopstartptr >> 16); + return (ptr & ((1<<16)-1)) | (((uint64_t)offset) << 16); + } + } else if (ptr >= channel->endptr) { + if (channel->endptr == channel->loopstartptr) return (uint64_t)-1; + uint32_t offset = (ptr - channel->endptr) >> 16; + offset %= (uint32_t)((channel->endptr - channel->loopstartptr) >> 16); + offset += (uint32_t)(channel->loopstartptr >> 16); + return (ptr & ((1<<16)-1)) | (((uint64_t)offset) << 16); + } + } + if (ptr >= channel->endptr) { + return (uint64_t)-1; + } + return ptr; +} + +static int32_t ppz8_channel_calc(struct ppz8 *ppz8, struct ppz8_channel *channel) { + struct ppz8_pcmbuf *buf = &ppz8->buf[channel->voice>>7]; + struct ppz8_pcmvoice *voice = &buf->voice[channel->voice & 0x7f]; + int32_t out = 0; + if (channel->vol) { + uint16_t coeff = channel->ptr & 0xffffu; + out += (int32_t)channel->prevout[0] * (0x10000u - coeff); + out += (int32_t)channel->prevout[1] * coeff; + out >>= 16; + + // volume: out * 2**((volume-15)/2) + out >>= (7 - ((channel->vol&0xf)>>1)); + if (!(channel->vol&1)) { + out *= 0xb505; + out >>= 16; + } + } + + uint64_t ptrdiff = (((uint64_t)channel->freq * voice->origfreq) << 1) / ppz8->srate; + uint64_t oldptr = channel->ptr; + channel->ptr += ptrdiff; + uint32_t bufdiff = (channel->ptr>>16) - (oldptr>>16); + if (bufdiff) { + if (/*bufdiff == 1*/0) { + channel->prevout[0] = channel->prevout[1]; + channel->prevout[1] = 0; + channel->ptr = ppz8_loop(channel, channel->ptr); + if (channel->ptr != (uint64_t)-1) { + uint32_t bufptr = channel->ptr >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[1] = buf->data[bufptr]; + } + } else { + channel->playing = false; + } + } else { + channel->prevout[0] = 0; + channel->prevout[1] = 0; + uint64_t ptr1 = ppz8_loop(channel, channel->ptr - 0x10000); + if (ptr1 != (uint64_t)-1) { + uint32_t bufptr = ptr1 >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[0] = buf->data[bufptr]; + } + channel->ptr = ppz8_loop(channel, channel->ptr); + if (channel->ptr != (uint64_t)-1) { + bufptr = channel->ptr >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[1] = buf->data[bufptr]; + } + } else { + channel->playing = false; + } + } else { + channel->playing = false; + } + } + } + return out; +} + +void ppz8_mix(struct ppz8 *ppz8, int16_t *buf, unsigned samples) { + static const uint8_t pan_vol[10][2] = { + {0, 0}, + {4, 0}, + {4, 1}, + {4, 2}, + {4, 3}, + {4, 4}, + {3, 4}, + {2, 4}, + {1, 4}, + {0, 4} + }; + for (unsigned i = 0; i < samples; i++) { + int32_t lo = buf[i*2+0]; + int32_t ro = buf[i*2+1]; + for (int p = 0; p < 8; p++) { + //if (p < 3) continue; + //if (p >= 6) continue; + struct ppz8_channel *channel = &ppz8->channel[p]; + if (!channel->playing) continue; + int32_t out = ppz8_channel_calc(ppz8, channel); + out *= ppz8->mix_volume; + out >>= 15; + lo += (out * pan_vol[channel->pan][0]) >> 2; + ro += (out * pan_vol[channel->pan][1]) >> 2; + } + if (lo < INT16_MIN) lo = INT16_MIN; + if (lo > INT16_MAX) lo = INT16_MAX; + if (ro < INT16_MIN) ro = INT16_MIN; + if (ro > INT16_MAX) ro = INT16_MAX; + buf[i*2+0] = lo; + buf[i*2+1] = ro; + } +} + +static int16_t calc_acc(int16_t acc, uint16_t adpcmd, uint8_t data) { + data &= 0xf; + int32_t acc_d = (((data&7)<<1)|1); + if (data&8) acc_d = -acc_d; + int32_t newacc = acc + (acc_d*adpcmd/8); + if (newacc < -32768) newacc = -32768; + if (newacc > 32767) newacc = 32767; + return newacc; +} + +static uint16_t calc_adpcmd(uint16_t adpcmd, uint8_t data) { + static const uint8_t adpcm_table[8] = { + 57, 57, 57, 57, 77, 102, 128, 153, + }; + uint32_t newadpcmd = adpcmd*adpcm_table[data&7]/64; + if (newadpcmd < 127) newadpcmd = 127; + if (newadpcmd > 24576) newadpcmd = 24576; + return newadpcmd; +} + +bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t bnum, + const uint8_t *pvidata, uint32_t pvidatalen, + int16_t *decodebuf) { + if (bnum >= 2) return false; + //if (pvidatalen > (0x210+(1<<18))) return false; + struct ppz8_pcmbuf *buf = &ppz8->buf[bnum]; + //uint16_t origfreq = ((uint32_t)read16le(&pvidata[0x8]) * 55467) >> 16; + uint16_t origfreq = (0x49ba*55467)>>16; + uint32_t lastaddr = 0; + for (int i = 0; i < 0x80; i++) { + uint32_t startaddr = read16le(&pvidata[0x10+i*4+0]) << 6; + uint32_t endaddr = (read16le(&pvidata[0x10+i*4+2])+1) << 6; + if (startaddr != lastaddr) break; + if (startaddr >= endaddr) break; + struct ppz8_pcmvoice *voice = &buf->voice[i]; + voice->start = startaddr<<1; + voice->len = (endaddr-startaddr)<<1; + voice->loopstart = (uint32_t)-1; + voice->loopend = (uint32_t)-1; + voice->origfreq = origfreq; + + int16_t acc = 0; + uint16_t adpcmd = 127; + for (uint32_t a = startaddr; a < endaddr; a++) { + if (pvidatalen <= (0x210+(a>>1))) return false; + uint8_t data = pvidata[0x210+(a>>1)]; + if (a&1) { + data &= 0xf; + } else { + data >>= 4; + } + acc = calc_acc(acc, adpcmd, data); + adpcmd = calc_adpcmd(adpcmd, data); + decodebuf[a] = acc; + } + lastaddr = endaddr; + } + buf->data = decodebuf; + buf->buflen = ppz8_pvi_decodebuf_samples(pvidatalen); + return true; +} + +static void ppz8_channel_play(struct ppz8 *ppz8, uint8_t ch, uint8_t v) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->voice = v; + struct ppz8_pcmbuf *buf = &ppz8->buf[channel->voice>>7]; + struct ppz8_pcmvoice *voice = &buf->voice[channel->voice & 0x7f]; + channel->ptr = ((uint64_t)(voice->start)>>1)<<16; + channel->endptr = ((uint64_t)(voice->start+voice->len)>>1)<<16; +/* + channel->loopstartptr = ((uint64_t)(voice->loopstart)>>1)<<16; + channel->loopendptr = ((uint64_t)(voice->loopend)>>1)<<16; +*/ + channel->loopstartptr = (channel->loopstartoff == (uint32_t)-1) ? (uint64_t)-1 + : channel->ptr + (((uint64_t)(channel->loopstartoff))<<16); + channel->loopendptr = (channel->loopendoff == (uint32_t)-1) ? (uint64_t)-1 + : channel->ptr + (((uint64_t)(channel->loopendoff))<<16); + channel->prevout[0] = 0; + channel->prevout[1] = 0; + channel->playing = true; + uint32_t bufptr = channel->ptr >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[1] = buf->data[bufptr]; + } +} + +static void ppz8_channel_stop(struct ppz8 *ppz8, uint8_t ch) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->playing = false; +} + +static void ppz8_channel_volume(struct ppz8 *ppz8, uint8_t ch, uint8_t vol) { + if (ch >= 8) return; + if (vol >= 16) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->vol = vol; +} + +static void ppz8_channel_freq(struct ppz8 *ppz8, uint8_t ch, uint32_t freq) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->freq = freq; +} + +static void ppz8_channel_loopoffset(struct ppz8 *ppz8, uint8_t ch, + uint32_t startoff, uint32_t endoff) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->loopstartoff = startoff; + channel->loopendoff = endoff; +} + +static void ppz8_channel_pan(struct ppz8 *ppz8, uint8_t ch, uint8_t pan) { + if (ch >= 8) return; + if (pan >= 10) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->pan = pan; +} + +static void ppz8_total_volume(struct ppz8 *ppz8, uint8_t vol) { + if (vol >= 16) return; + ppz8->totalvol = vol; +} + +const struct ppz8_functbl ppz8_functbl = { + ppz8_channel_play, + ppz8_channel_stop, + ppz8_channel_volume, + ppz8_channel_freq, + ppz8_channel_loopoffset, + ppz8_channel_pan, + ppz8_total_volume +}; |