CVSD Compression of Sounds

Ask anything your want about the 32X Mushroom programming.

Moderator: BigEvilCorporation

Post Reply
Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

CVSD Compression of Sounds

Post by Chilly Willy » Fri Jun 26, 2009 8:32 pm

As part of my work of Wolf32X, I've been experimenting with various ways to handle the music. One thing would be to simply sample or synthesize the music to raw data, then compress it for storage in the rom. It could then be played back later by decompressing the data on the fly. The trick is to find a compression format that compresses the data enough to make it worthwhile while still being simple enough to decompress in real time with the Z80 or 68000. Doing that AND keeping the noise down is a real chore.

MP3 or Ogg-Vorbis or other such modern compression schemes are clearly out from the get-go. MP3 takes more power to decode than ogg-vorbis, and that takes a minimum of a 40-45 MHz ARM processor to decode in real-time using specially coded versions of Tremor. One of the alternatives I've been looking at is CVSD (Continuously Variable Slope Delta modulation) - this is a codec used primarily in voice communications, and features 8 to 1 compression (typically).

We start with a raw stream of 16 bit mono samples at a certain frequency - 11025 for this example. You can generate these from a MIDI file using timidity like so:

Code: Select all

timidity -Or1lM -o wolf_24.raw -s 11025 wolf_24.mid
You then compress that to 1 bit per sample using the CVSD algorithm. That seems it would yield 16:1 compression - the issue is that noise is too great if you do that, so what you do instead is use each sample twice for an effective sample rate of 22050 samples per second. This gives us 8:1 compression.

Code: Select all

./pcm2cvsd ./wsw_music/wolf_24.raw wolf_24.bin
To retrieve the data, you decompress the data. With the app I provide to generate raw unsigned 8 bit mono samples, you do:

Code: Select all

./cvsd2pcm wolf_24.bin wolf_24.raw
While the algorithm generates 16 bit samples, I convert those to 8 bit before outputting them since that's the format they'd need to be for use by the Genesis. This app is just so you can see how the compressed data would sound once decompressed.

Let's look at the compressor...

pcm2cvsd.c

Code: Select all

#include <string.h>
#include <stdio.h>
#include <fcntl.h>

#ifndef O_BINARY
#define O_BINARY 0
#endif

static int first = 1;
static int last_sample;
static int last_bit;
static int delta;

#define minDelta 2
#define maxDelta 1024

#define minValue -32768
#define maxValue 32767

unsigned char pcm2cvsd(short int input, int bit)
{
    int this_bit = ((input - last_sample) >= 0) ? 1 : 0;
    if (this_bit == last_bit)
    {
        delta = delta << 1;
        if (delta > maxDelta)
            delta = maxDelta;
    }
    else
    {
        last_bit = this_bit;
        delta = delta >> 1;
        if (delta < minDelta)
            delta = minDelta;
    }

    last_sample += (this_bit) ? delta : -delta;
    if (last_sample > maxValue)
        last_sample = maxValue;
    if (last_sample < minValue)
        last_sample = minValue;

    return (unsigned char)(this_bit << bit);
}

int main(int argc, char **argv)
{
    int cvsd_fd, pcm_fd;
    int cnt;
    short int pcm_values[4];

    printf("PCM to CVSD converter 1.0\n");

    if (argc != 3)
    {
        printf("use '%s <pcmfile> <cvsdfile>' ", argv[0]);
        printf("to convert <pcmfile> to <cvsdfile>\n");
        return 0;
    }

    if ((pcm_fd = open(argv[1], O_RDONLY | O_BINARY)) == -1)
    {
        printf("Error opening %s\n", argv[1]);
        return 0;
    }

    if ((cvsd_fd = open(argv[2], O_CREAT | O_TRUNC | O_RDWR | O_BINARY, 0662)) == -1)
    {
        printf("Error creating %s\n", argv[2]);
        return 0;
    }

    printf("PCM to CVSD conversion started\n");

    memset(pcm_values, 0, 8);
    while ((cnt = read(pcm_fd, pcm_values, 8)))
    {
        unsigned char cvsd_value = 0;
        if (first)
        {
            first = 0;
            last_sample = 0;
            last_bit = (pcm_values[0] >= 0) ? 0 : 1; // make last_bit the inverse of the first bit in stream
        }
        cvsd_value |= pcm2cvsd(pcm_values[0], 7);
        cvsd_value |= pcm2cvsd(pcm_values[0], 6);
        cvsd_value |= pcm2cvsd(pcm_values[1], 5);
        cvsd_value |= pcm2cvsd(pcm_values[1], 4);
        cvsd_value |= pcm2cvsd(pcm_values[2], 3);
        cvsd_value |= pcm2cvsd(pcm_values[2], 2);
        cvsd_value |= pcm2cvsd(pcm_values[3], 1);
        cvsd_value |= pcm2cvsd(pcm_values[3], 0);
        write(cvsd_fd, &cvsd_value, 1);
        memset(pcm_values, 0, 8);
    }

    close(cvsd_fd);
    close(pcm_fd);

    printf("PCM to CVSD conversion completed\n");
    return 0;
}
Notice that we read 4 samples, then use them twice in the compressor loop. The compression is done in the function pcm2cvsd(). It takes an input sample and bit position for the output bit. The compression is slightly different than standard CVSD algorithms - this is my own modification. Instead of changing the delta value after three or four 1s or 0s in a row, I change it as long as the current bit is the same as the previous. It doesn't seem to effect the sound quality, and it's easier to code for. The maxDelta variable is actually the most important one in this algorithm: too low and it filters out all the high frequencies; too high and you get excessive granular noise.

Now let's look at the decompressor...

cvsd2pcm.c

Code: Select all

#include <string.h>
#include <stdio.h>
#include <fcntl.h>

#ifndef O_BINARY
#define O_BINARY 0
#endif

static int first = 1;
static int last_sample;
static int last_bit;
static int delta;
static int filter_sample;

#define minDelta 2
#define maxDelta 1024

#define minValue -32768
#define maxValue 32767

unsigned char cvsd2pcm(unsigned char input, int bit)
{
    int this_sample;
    int this_bit = (input >> bit) & 1;
    if (this_bit == last_bit)
    {
        delta = delta << 1;
        if (delta > maxDelta)
            delta = maxDelta;
    }
    else
    {
        last_bit = this_bit;
        delta = delta >> 1;
        if (delta < minDelta)
            delta = minDelta;
    }

    last_sample += (this_bit) ? delta : -delta;
    if (last_sample > maxValue)
        last_sample = maxValue;
    if (last_sample < minValue)
        last_sample = minValue;

    this_sample = (last_sample + filter_sample)>>1;
    filter_sample = this_sample;
    return (unsigned char)((this_sample >> 8) + 128);
}

int main(int argc, char **argv)
{
    int cvsd_fd, pcm_fd;
    int cnt;
    unsigned char cvsd_value;

    printf("CVSD to PCM converter 1.0\n");

    if (argc != 3)
    {
        printf("use '%s <cvsdfile> <pcmfile>' ", argv[0]);
        printf("to convert <cvsdfile> to <pcmfile>\n");
        return 0;
    }

    if ((cvsd_fd = open(argv[1], O_RDONLY | O_BINARY)) == -1)
    {
        printf("Error opening %s\n", argv[1]);
        return 0;
    }

    if ((pcm_fd = open(argv[2], O_CREAT | O_TRUNC | O_RDWR | O_BINARY, 0662)) == -1)
    {
        printf("Error creating %s\n", argv[2]);
        return 0;
    }

    printf("CVSD to PCM conversion started\n");

    while ((cnt = read(cvsd_fd, &cvsd_value, 1)))
    {
        unsigned char pcm_value;
        if (first)
        {
            first = 0;
            last_sample = 0;
            last_bit = (~cvsd_value >> 7) & 1; // make last_bit the inverse of the first bit in stream
            filter_sample = 0;
        }
        pcm_value = cvsd2pcm(cvsd_value, 7);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 6);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 5);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 4);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 3);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 2);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 1);
        write(pcm_fd, &pcm_value, 1);
        pcm_value = cvsd2pcm(cvsd_value, 0);
        write(pcm_fd, &pcm_value, 1);
    }

    close(cvsd_fd);
    close(pcm_fd);

    printf("CVSD to PCM conversion completed\n");
    return 0;
}
Notice how it reads a byte, then outputs eight samples. Each bit in the stream is a separate sample decoded by cvsd2pcm(). This routine just does the reverse of the compressor - adding the delta to the last value to give our output value. Note that I do a simple two value average to filter the granular noise a little. You could go with a more powerful filter if you have more cpu time available, or eliminate the filter for the quickest decoding possible. I then convert the 16 bit sample into an unsigned 8 bit sample.

So how does it sound? Pretty decent. Not as clean as the original, obviously, but fairly good for 8:1 compression using such a simple algorithm. You can get all the files here:

cvsd.7z

Note that only the cvsd2pcm() function needs to be converted for use in the Genesis. The raw compressed data would be embedded in the rom. Note that RLE could also be applied to this for a little more compression with virtually no extra slow down.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sun Jul 05, 2009 3:13 am

Okay, following up on the initial post, I've written a Z80 driver to play back CVSD compressed audio. It turns out the Z80 code handles 22050 Hz almost perfectly without waits... which means that with the Z80, that's the maximum frequency for playback. I could make it go slower with a delay after each sample, but that's not the point of this post. You'll find the complete source for the driver and an example using it in the demo forum, but here's the driver based on the previous discussion:

Code: Select all

#target bin
#code $0000, $2000

; cvsd compressed sample player Z80 driver
; Joe Fenton (C)2009 based partly on code by
; Stéphane Dallongeville (C)2007

YMPORT0     EQU $4000   ; YM2612 port 0
YMPORT1     EQU $4001   ; YM2612 port 1
YMPORT2     EQU $4002   ; YM2612 port 2
YMPORT3     EQU $4003   ; YM2612 port 3
BANKREG     EQU $6000   ; Bank register
PSGPORT     EQU $7F11   ; PSG port

STARTPTRL   EQU $0040   ; start pointer low
STARTPTRM   EQU $0041   ; start pointer mid
STARTPTRH   EQU $0042   ; start pointer high
STARTLENL   EQU $0043   ; start length low
STARTLENM   EQU $0044   ; start length mid
STARTLENH   EQU $0045   ; start length high

CURRLENL    EQU $0046   ; current length low
CURRLENM    EQU $0047   ; current length mid
CURRLENH    EQU $0048   ; current length high

FIRST       EQU $004C   ; first byte in stream

PAN         EQU $004D   ; left, center, right
PLAY        EQU $004E   ; command from 68K
PAUSE       EQU $004F   ; pause playback

; ########################### Code starts here ##############################

;   ORG $0000

; basic init code

    DI                      ; Disable ints
    LD  SP, $2000           ; Setup stack
    IM  $01                 ; Set int mode 1
    JP  start               ; Jump to start


; ########################## Interrupt handler ##############################

;   ORG  $0038
    defs $0038 - $

interrupt
    RETI


; ############################## Main code ##################################

;   ORG  $0080
    defs $0080 - $

start
    LD  B, $10
    XOR A
    LD  HL, $0040
clr_vars
    LD  (HL), A             ; clear ram variables
    INC HL
    DJNZ clr_vars

    LD  IY, YMPORT0
    LD  (IY+2), $B6         ; point to channel 6 stereo register

    LD  HL, YMPORT0
    LD  BC, YMPORT1
    LD  A, $80
    LD  (HL), $2B
    LD  (BC), A             ; enable DAC
    LD  (HL), $2A
    LD  (BC), A             ; DAC data to silent

idle
    LD  A, (PLAY)           ; load command in A
    OR  A
    JR  Z, idle             ; wait for command

; start playback

    LD  A, (PAN)
    LD  (YMPORT3), A        ; set pan

restart
    ; initialize last bit, delta, and last sample
    LD  A, (FIRST)
    XOR $80                 ; inverse of first bit
    RLCA
    LD  C, A                ; last bit = inverse of first bit
    LD  DE, 2               ; delta = minDelta
    LD  HL, 0               ; last sample = 0
    EXX                     ; save

    ; copy length
    LD  BC, (STARTLENL)
    LD  A, (STARTLENH)
    LD  (CURRLENL), BC
    LD  (CURRLENH), A

    LD  DE, (STARTPTRM)     ; initial bank address

    LD  BC, (STARTPTRL)
    SET 7, B                ; initial bank ptr = 0x8000 + (STARTPTR & 0x7FFF)
    LD  YH, B
    LD  YL, C

    RES 7, B                ; STARTPTR & 0x7FFF
    LD  HL, $8000
    AND A                   ; clear CF
    SBC HL, BC              ; bank count = length to next 32KB boundary

outer_loop
    LD  A, (CURRLENH)
    AND A
    JP  NZ, set_bank_len    ; at least 64KB left
    LD  A, (CURRLENL)
    SUB L
    LD  A, (CURRLENM)
    SBC A, H
    JP  P, set_bank_len     ; > 0 means current length > bank length
    LD  HL, (CURRLENL)      ; <= 0 means current length <= bank length
set_bank_len
    PUSH HL
    POP IX                  ; set working bank count

    CALL set_bank           ; set bank address using DE

    EXX                     ; swap bank count and address with last bit, delta, and last sample

inner_loop
    LD  A, (PAUSE)
    OR  A
    JP  NZ, pause           ; playback paused

    LD  B, (IY+0)           ; next 8 bits of compressed data

    ; check bit 7
    RLC B                   ; get bit 7
    JR  C, this_bit7_1

    ; this bit 7 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_70     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_70          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_70
not_same_70
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_70          ; no
    LD  E, 2                ; delta = minDelta
cont_70
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_70
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_70
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_6

this_bit7_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_71      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_71          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_71
not_same_71
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_71          ; no
    LD  E, 2                ; delta = minDelta
cont_71
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_71
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_71
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_6
    RLC B                   ; get bit 6
    JR  C, this_bit6_1

    ; this bit 6 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_60     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_60          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_60
not_same_60
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_60          ; no
    LD  E, 2                ; delta = minDelta
cont_60
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_60
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_60
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_5

this_bit6_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_61      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_61          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_61
not_same_61
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_61          ; no
    LD  E, 2                ; delta = minDelta
cont_61
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_61
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_61
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_5
    INC IY
    RLC B                   ; get bit 5
    JR  C, this_bit5_1

    ; this bit 5 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_50     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_50          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_50
not_same_50
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_50          ; no
    LD  E, 2                ; delta = minDelta
cont_50
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_50
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_50
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_4

this_bit5_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_51      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_51          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_51
not_same_51
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_51          ; no
    LD  E, 2                ; delta = minDelta
cont_51
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_51
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_51
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_4
    RLC B                   ; get bit 4
    JR  C, this_bit4_1

    ; this bit 4 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_40     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_40          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_40
not_same_40
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_40          ; no
    LD  E, 2                ; delta = minDelta
cont_40
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_40
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_40
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_3

this_bit4_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_41      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_41          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_41
not_same_41
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_41          ; no
    LD  E, 2                ; delta = minDelta
cont_41
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_41
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_41
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_3
    RLC B                   ; get bit 3
    JR  C, this_bit3_1

    ; this bit 3 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_30     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_30          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_30
not_same_30
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_30          ; no
    LD  E, 2                ; delta = minDelta
cont_30
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_30
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_30
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_2

this_bit3_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_31      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_31          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_31
not_same_31
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_31          ; no
    LD  E, 2                ; delta = minDelta
cont_31
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_31
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_31
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_2
    DEC IX
    RLC B                   ; get bit 2
    JR  C, this_bit2_1

    ; this bit 2 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_20     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_20          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_20
not_same_20
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_20          ; no
    LD  E, 2                ; delta = minDelta
cont_20
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_20
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_20
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_1

this_bit2_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_21      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_21          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_21
not_same_21
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_21          ; no
    LD  E, 2                ; delta = minDelta
cont_21
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_21
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_21
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_1
    RLC B                   ; get bit 1
    JR  C, this_bit1_1

    ; this bit 1 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_10     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_10          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_10
not_same_10
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_10          ; no
    LD  E, 2                ; delta = minDelta
cont_10
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_10
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_10
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_0

this_bit1_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_11      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_11          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_11
not_same_11
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_11          ; no
    LD  E, 2                ; delta = minDelta
cont_11
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_11
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_11
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_0
    RLC B                   ; get bit 0
    JR  C, this_bit0_1

    ; this bit 0 is 0
    BIT 0, C                ; check last bit
    JR  NZ, not_same_00     ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_00          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_00
not_same_00
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_00          ; no
    LD  E, 2                ; delta = minDelta
cont_00
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, filter_00
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
filter_00
;    ADD A, H                ; this sample = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  next_byte

this_bit0_1
    BIT 0, C                ; check last bit
    JR  Z, not_same_01      ; this bit != last bit
    ; this bit == last bit
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 3, D                ; delta > maxDelta?
    JR  Z, cont_01          ; no
    LD  D, 4                ; delta = maxDelta
    JP  cont_01
not_same_01
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_01          ; no
    LD  E, 2                ; delta = minDelta
cont_01
    LD  C, B                ; last bit = this bit
;    LD  A, H                ; previous last sample high byte
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, filter_01
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
filter_01
;    ADD A, H                ; this sample high = previous last sample high + last sample high
;    RR  A                   ; >> 1 => average of last two last sample high bytes
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

next_byte
    LD  A, XL
    OR  XH
    JP  NZ, inner_loop

    EXX                     ; swap bank count and address with last bit, delta, and last sample

    ; subtract length of bank played from current length
    LD  A, (CURRLENL)
    SUB L
    LD  L, A
    LD  A, (CURRLENM)
    SBC A, H
    LD  H, A
    LD  A, (CURRLENH)
    LD  B, 0
    SBC A, B
    LD  B, A

    ; test if more data
    OR  H
    OR  L
    JP  Z, playback_done    ; no more data

    ; new current length
    LD  (CURRLENL), HL
    LD  A, B
    LD  (CURRLENH), A

    LD  HL, $8000           ; next 32KB (or whatever left if < 32KB)
    LD  IY, $8000           ; reset bank ptr for next loop

    ; increment bank address
    LD  A, E
    ADD A, $80
    LD  E, A
    JP  NC, outer_loop
    INC D
    JP  outer_loop

playback_done
    LD  A, (PLAY)
    OR  A
    JP  M, restart
    DEC A
    LD  (PLAY), A
    JP  NZ, restart

; done with repeats

    LD  A, $80
    LD  (YMPORT1), A        ; set silent to DAC
    JP  idle

pause
    LD  A, $80
    LD  (YMPORT1), A        ; set silent to DAC
paused
    LD  A, (PAUSE)
    OR  A
    JP  Z, inner_loop       ; playback resumed
    LD  A, (PLAY)
    OR  A
    JP  NZ, paused
    ; playback stopped
    XOR A
    LD  (PAUSE), A          ; clear pause
    JP  idle

; set bank address register to msb of DE - destroys A, B, and C

set_bank
    LD  BC, BANKREG         ; BC = BANKREG

    LD  A, E                ; A = sample address bit 8-15
    RLCA
    LD  (BC), A             ; #1 (bit 15)

    LD  A, D                ; A = sample address bits 16-23

    LD  (BC), A             ; #2 (bit 16)
    RRCA
    LD  (BC), A             ; #3 (bit 17)
    RRCA
    LD  (BC), A             ; #4 (bit 18)
    RRCA
    LD  (BC), A             ; #5 (bit 19)
    RRCA
    LD  (BC), A             ; #6 (bit 20)
    RRCA
    LD  (BC), A             ; #7 (bit 21)
    RRCA
    LD  (BC), A             ; #8 (bit 22)
    RRCA
    LD  (BC), A             ; #9 (bit 23)

    RET

#end
First, I use zasm for my Z80 compiler. It was pretty much all there was for linux users who need undocumented instruction support, so because of that, certain things are commented out, like "; ORG $0000" and some other stuff at the start. It shouldn't be too hard to make it compatible with other Z80 compilers if needed. The code I started with was the sample player driver in the Mini-Devkit. It's not exactly the same, but you'll notice the similarities.

I didn't bother with my simple filtering, although the code is still there (just commented out). I don't think the YM2612 DAC needs it... maybe it would with a "Crystal Clear Audio Mod". :wink:

When I wrote out this code, the decompressor was originally a subroutine, but I noticed on the timing that it was VERY close to being just over the cycles needed for 22050 Hz, so I took advantage of the Z80 RAM and unrolled it, so it does a byte, 8 bits, or 8 samples in the inner loop. The loop is over a 32 KB bank, or whatever there is up to the first 32 KB boundary at the start, and whatever is left after the last full 32 KB bank. The code should handle any number of bytes on any boundary. Another thing I did was make it handle looping: PLAY = 0 means stopped; PLAY = 1 to 127 means loop that many times; PLAY < 0 means loop forever. Because the inner loop only checks pause, you need to pause before stopping or it could take up to a full bank worth of samples before it stopped.

The decompressor is nice - I took advantage of the 16 bit math and the alternate register bank to improve the speed. There's probably a bit more that could be done to tighten it up, but I don't think it's worth it given that it runs at a frequency that's "natural" for many older games.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Thu Jul 09, 2009 12:49 am

Another experiment. Where the original code above shifts the delta everytime the current bit matches the last bit, this code has a bit to say explicitly which way to shift. So it's two bits per sample instead of 1.

The original code was limited in how it could estimate the next sample. You could only specify the sign for delta of the next sample. If you changed signs, you were stuck with 1/2 the old delta value, and if you didn't, you were stuck with twice the old delta value.

The new code makes the sign and shift bits separate. The sign bit tells whether to add or subtract the delta, and the shift bit tells it to shift the delta left or right. So a stream of constantly increasing samples could vary between using 1/2 the old delta or twice the old delta, depending on which gives less noise.

Here's the new Z80 playback code. It's mostly the old code with the shifting based on the extra bit per sample in the stream instead of whether the last sign bit matched the current one. Because there's two bits per sample, there's only four samples per byte, so the code is half the size.

Code: Select all

#target bin
#code $0000, $2000

; cvsd compressed sample player Z80 driver
; Joe Fenton (C)2009 based partly on code by
; Stéphane Dallongeville (C)2007

YMPORT0     EQU $4000   ; YM2612 port 0
YMPORT1     EQU $4001   ; YM2612 port 1
YMPORT2     EQU $4002   ; YM2612 port 2
YMPORT3     EQU $4003   ; YM2612 port 3
BANKREG     EQU $6000   ; Bank register
PSGPORT     EQU $7F11   ; PSG port

STARTPTRL   EQU $0040   ; start pointer low
STARTPTRM   EQU $0041   ; start pointer mid
STARTPTRH   EQU $0042   ; start pointer high
STARTLENL   EQU $0043   ; start length low
STARTLENM   EQU $0044   ; start length mid
STARTLENH   EQU $0045   ; start length high

CURRLENL    EQU $0046   ; current length low
CURRLENM    EQU $0047   ; current length mid
CURRLENH    EQU $0048   ; current length high

PAN         EQU $004D   ; left, center, right
PLAY        EQU $004E   ; command from 68K
PAUSE       EQU $004F   ; pause playback

; ########################### Code starts here ##############################

;   ORG $0000

; basic init code

    DI                      ; Disable ints
    LD  SP, $2000           ; Setup stack
    IM  $01                 ; Set int mode 1
    JP  start               ; Jump to start


; ########################## Interrupt handler ##############################

;   ORG  $0038
    defs $0038 - $

interrupt
    RETI


; ############################## Main code ##################################

;   ORG  $0080
    defs $0080 - $

start
    LD  B, $10
    XOR A
    LD  HL, $0040
clr_vars
    LD  (HL), A             ; clear ram variables
    INC HL
    DJNZ clr_vars

    LD  IY, YMPORT0
    LD  (IY+2), $B6         ; point to channel 6 stereo register

    LD  HL, YMPORT0
    LD  BC, YMPORT1
    LD  A, $80
    LD  (HL), $2B
    LD  (BC), A             ; enable DAC
    LD  (HL), $2A
    LD  (BC), A             ; DAC data to silent

idle
    LD  A, (PLAY)           ; load command in A
    OR  A
    JR  Z, idle             ; wait for command

; start playback

    LD  A, (PAN)
    LD  (YMPORT3), A        ; set pan

restart
    ; initialize delta and last sample
    LD  DE, 2               ; delta = minDelta
    LD  HL, 0               ; last sample = 0
    EXX                     ; save

    ; copy length
    LD  BC, (STARTLENL)
    LD  A, (STARTLENH)
    LD  (CURRLENL), BC
    LD  (CURRLENH), A

    LD  DE, (STARTPTRM)     ; initial bank address

    LD  BC, (STARTPTRL)
    SET 7, B                ; initial bank ptr = 0x8000 + (STARTPTR & 0x7FFF)
    LD  YH, B
    LD  YL, C

    RES 7, B                ; STARTPTR & 0x7FFF
    LD  HL, $8000
    AND A                   ; clear CF
    SBC HL, BC              ; bank count = length to next 32KB boundary

outer_loop
    LD  A, (CURRLENH)
    AND A
    JP  NZ, set_bank_len    ; at least 64KB left
    LD  A, (CURRLENL)
    SUB L
    LD  A, (CURRLENM)
    SBC A, H
    JP  P, set_bank_len     ; > 0 means current length > bank length
    LD  HL, (CURRLENL)      ; <= 0 means current length <= bank length
set_bank_len
    PUSH HL
    POP IX                  ; set working bank count

    CALL set_bank           ; set bank address using DE

    EXX                     ; swap bank count and address with delta and last sample

inner_loop
    LD  A, (PAUSE)
    OR  A
    JP  NZ, pause           ; playback paused

    LD  B, (IY+0)           ; next 8 bits of compressed data

; check bits 7 & 6
    BIT 7, B                ; get shift bit
    JR  Z, rshift_3
    ; left shift delta
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 6, D                ; delta > maxDelta?
    JR  Z, cont_3           ; no
    LD  D, $20              ; delta = maxDelta
    JP  cont_3
    ; right shift delta
rshift_3
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_3           ; no
    LD  E, 2                ; delta = minDelta

cont_3
    BIT 6, B                ; get sign bit
    JR  Z, add_3
    ; subtract delta from last sample
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, out_31
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
out_31
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_5_4
    ; add delta to last sample
add_3
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, out_30
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
out_30
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_5_4
    INC IY
    NOP
    NOP
    NOP
    NOP

    BIT 5, B                ; get shift bit
    JR  Z, rshift_2
    ; left shift delta
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 6, D                ; delta > maxDelta?
    JR  Z, cont_2           ; no
    LD  D, $20              ; delta = maxDelta
    JP  cont_2
    ; right shift delta
rshift_2
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_2           ; no
    LD  E, 2                ; delta = minDelta

cont_2
    BIT 4, B                ; get sign bit
    JR  Z, add_2
    ; subtract delta from last sample
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, out_21
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
out_21
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_3_2
    ; add delta to last sample
add_2
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, out_20
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
out_20
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_3_2
    DEC IX
    NOP
    NOP
    NOP
    NOP

    BIT 3, B                ; get shift bit
    JR  Z, rshift_1
    ; left shift delta
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 6, D                ; delta > maxDelta?
    JR  Z, cont_1           ; no
    LD  D, $20              ; delta = maxDelta
    JP  cont_1
    ; right shift delta
rshift_1
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_1           ; no
    LD  E, 2                ; delta = minDelta

cont_1
    BIT 2, B                ; get sign bit
    JR  Z, add_1
    ; subtract delta from last sample
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, out_11
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
out_11
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  chk_bit_1_0
    ; add delta to last sample
add_1
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, out_10
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
out_10
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

chk_bit_1_0
    NOP
    NOP
    NOP
    NOP

    BIT 1, B                ; get shift bit
    JR  Z, rshift_0
    ; left shift delta
    SLA E
    RL  D                   ; delta = delta << 1
    BIT 6, D                ; delta > maxDelta?
    JR  Z, cont_0           ; no
    LD  D, $20              ; delta = maxDelta
    JP  cont_0
    ; right shift delta
rshift_0
    SRL D
    RR  E                   ; delta = delta >> 1
    BIT 0,E                 ; delta < minDelta?
    JR  Z, cont_0           ; no
    LD  E, 2                ; delta = minDelta

cont_0
    BIT 0, B                ; get sign bit
    JR  Z, add_0
    ; subtract delta from last sample
    AND A                   ; clear CF
    SBC HL, DE              ; last sample -= delta
    JP  PO, out_01
    ; overflow, last sample < minValue
    LD  HL, $8000           ; last sample = minValue
out_01
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC
    JP  next_byte
    ; add delta to last sample
add_0
    AND A                   ; clear CF
    ADC HL, DE              ; last sample += delta
    JP  PO, out_00
    ; overflow, last sample > maxValue
    LD  HL, $7FFF           ; last sample = maxValue
out_00
    LD  A, H                ; this sample = last sample high byte
    ADD A, $80              ; signed -> unsigned
    LD  (YMPORT1), A        ; set sample to DAC

next_byte
    LD  A, XL
    OR  XH
    JP  NZ, inner_loop

    EXX                     ; swap bank count and address with delta and last sample

    ; subtract length of bank played from current length
    LD  A, (CURRLENL)
    SUB L
    LD  L, A
    LD  A, (CURRLENM)
    SBC A, H
    LD  H, A
    LD  A, (CURRLENH)
    LD  B, 0
    SBC A, B
    LD  B, A

    ; test if more data
    OR  H
    OR  L
    JP  Z, playback_done    ; no more data

    ; new current length
    LD  (CURRLENL), HL
    LD  A, B
    LD  (CURRLENH), A

    LD  HL, $8000           ; next 32KB (or whatever left if < 32KB)
    LD  IY, $8000           ; reset bank ptr for next loop

    ; increment bank address
    LD  A, E
    ADD A, $80
    LD  E, A
    JP  NC, outer_loop
    INC D
    JP  outer_loop

playback_done
    LD  A, (PLAY)
    OR  A
    JP  M, restart
    DEC A
    LD  (PLAY), A
    JP  NZ, restart

; done with repeats

    LD  A, $80
    LD  (YMPORT1), A        ; set silent to DAC
    JP  idle

pause
    LD  A, $80
    LD  (YMPORT1), A        ; set silent to DAC
paused
    LD  A, (PAUSE)
    OR  A
    JP  Z, inner_loop       ; playback resumed
    LD  A, (PLAY)
    OR  A
    JP  NZ, paused
    ; playback stopped
    XOR A
    LD  (PAUSE), A          ; clear pause
    JP  idle

; set bank address register to msb of DE - destroys A, B, and C

set_bank
    LD  BC, BANKREG         ; BC = BANKREG

    LD  A, E                ; A = sample address bit 8-15
    RLCA
    LD  (BC), A             ; #1 (bit 15)

    LD  A, D                ; A = sample address bits 16-23

    LD  (BC), A             ; #2 (bit 16)
    RRCA
    LD  (BC), A             ; #3 (bit 17)
    RRCA
    LD  (BC), A             ; #4 (bit 18)
    RRCA
    LD  (BC), A             ; #5 (bit 19)
    RRCA
    LD  (BC), A             ; #6 (bit 20)
    RRCA
    LD  (BC), A             ; #7 (bit 21)
    RRCA
    LD  (BC), A             ; #8 (bit 22)
    RRCA
    LD  (BC), A             ; #9 (bit 23)

    RET

#end

Post Reply