diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d77b85 --- /dev/null +++ b/Makefile @@ -0,0 +1,68 @@ +ifdef $(GENDEV) +ROOTDIR = $(GENDEV) +else +ROOTDIR = /opt/toolchains/sega +endif + +LDSCRIPTSDIR = $(ROOTDIR)/ldscripts + +BOOTBLOCKDIR = $(ROOTDIR)/bootblocks +#BOOTBLOCK = $(BOOTBLOCKDIR)/US_BOOT.BIN +BOOTBLOCK = $(BOOTBLOCKDIR)/EU_BOOT.BIN +#BOOTBLOCK = $(BOOTBLOCKDIR)/JP_BOOT.BIN + +LIBPATH = -L$(ROOTDIR)/m68k-elf/lib -L$(ROOTDIR)/m68k-elf/lib/gcc/m68k-elf/4.6.2 -L$(ROOTDIR)/m68k-elf/m68k-elf/lib +INCPATH = -I. -I../include -I$(ROOTDIR)/m68k-elf/include -I$(ROOTDIR)/m68k-elf/m68k-elf/include + +CCFLAGS = -m68000 -Wall -O1 -c -fomit-frame-pointer +HWFLAGS = -m68000 -Wall -O1 -c -fomit-frame-pointer +LDFLAGS = -T $(LDSCRIPTSDIR)/cd.ld -Wl,-Map=output.map -nostdlib +ASFLAGS = -m68000 --register-prefix-optional + +PREFIX = $(ROOTDIR)/m68k-elf/bin/m68k-elf- +CC = $(PREFIX)gcc +AS = $(PREFIX)as +LD = $(PREFIX)ld +OBJC = $(PREFIX)objcopy + +ASMZ80 = $(ROOTDIR)/bin/zasm +FLAGSZ80 = -vb2 + +DD = dd +RM = rm -rf + +TARGET = CDModPlayer +LIBS = $(LIBPATH) -lpcm -lc -lgcc -lnosys +OBJS = crt0.o main.o module.o cdfh.o hw_md.o +FILES = + +all: $(TARGET).bin + +$(TARGET).bin: $(TARGET).elf + $(OBJC) -O binary $< temp.bin + $(DD) if=temp.bin of=$@ bs=2048 conv=sync + +$(TARGET).elf: $(OBJS) $(FILES) + $(CC) $(LDFLAGS) $(OBJS) $(LIBS) $(FILES) -o $(TARGET).elf + +%.o80: %.s80 + $(ASMZ80) $(FLAGSZ80) -o $@ $< + +module.o: module.c + $(CC) $(HWFLAGS) -DPROC_PATTERNS $(INCPATH) $< -o $@ + +%.o: %.c + $(CC) $(CCFLAGS) $(INCPATH) $< -o $@ + +%.o: %.s + $(AS) $(ASFLAGS) $(INCPATH) $< -o $@ + +cd: $(TARGET).bin + mkdir -p cd + mkdir -p cd/MODs + cp $(TARGET).bin cd/APP.BIN + cp -r MODs/* cd/MODs/ + genisoimage -sysid "SEGA SEGACD" -volid "CDMODPLAYER" -generic-boot $(BOOTBLOCK) -full-iso9660-filenames -o $(TARGET).iso cd + +clean: + $(RM) *.o *.o80 *.bin *.elf *.map *.log *.iso cd diff --git a/cdfh.c b/cdfh.c new file mode 100644 index 0000000..138664f --- /dev/null +++ b/cdfh.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include + +#include "hw_md.h" +#include "cdfh.h" + +//---------------------------------------------------------------------- +// SegaCD File Handler - by Chilly Willy +// Inspired by the MikMod READER structure. +//---------------------------------------------------------------------- + +static uint8_t cd_Eof(CDFileHandle_t *handle) +{ + if (!handle) + return 1; + + if (handle->pos >= handle->length) + return 1; + + return 0; +} + +static int32_t cd_Read(CDFileHandle_t *handle, void *ptr, int32_t size) +{ + int32_t pos, blk, len, read = 0; + uint8_t *dst = ptr; + + if (!handle) + return 0; + + while (size != 0) + { + if (handle->Eof(handle)) + return read; + + pos = handle->pos; + blk = (pos >> 11) + handle->offset; + if (handle->block != blk) + { + read_cd(blk, 1, (void *)0x6800); + handle->block = blk; + } + + len = 0x800 - (pos & 0x7FF); + if (len > size) + len = size; + if (len > (handle->length - pos)) + len = (handle->length - pos); + + memcpy(dst, (char *)0x6800 + (pos & 0x7FF), len); + + handle->pos += len; + dst += len; + read += len; + size -= len; + } + + return read; +} + +static uint8_t cd_Get(CDFileHandle_t *handle) +{ + int32_t pos, blk; + + if (handle->Eof(handle)) + return 0; + + pos = handle->pos; + blk = (pos >> 11) + handle->offset; + if (handle->block != blk) + { + read_cd(blk, 1, (void *)0x6800); + handle->block = blk; + } + + handle->pos++; + return ((uint8_t*)0x6800)[pos & 0x7FF]; +} + +static int32_t cd_Seek(CDFileHandle_t *handle, int32_t offset, int32_t whence) +{ + int32_t pos; + + if (!handle) + return -1; + + pos = handle->pos; + switch(whence) + { + case SEEK_CUR: + pos += offset; + break; + case SEEK_SET: + pos = offset; + break; + case SEEK_END: + pos = handle->length - offset - 1; + break; + } + if (pos < 0) + handle->pos = 0; + else if (pos > handle->length) + handle->pos = handle->length; + else + handle->pos = pos; + + return handle->pos; +} + +static int32_t cd_Tell(CDFileHandle_t *handle) +{ + return handle ? handle->pos : 0; +} + + +CDFileHandle_t *cd_handle_from_offset(int32_t offset, int32_t length) +{ + CDFileHandle_t *handle = (CDFileHandle_t*)malloc(sizeof(CDFileHandle_t)); + if (handle) + { + handle->Eof = &cd_Eof; + handle->Read = &cd_Read; + handle->Get = &cd_Get; + handle->Seek = &cd_Seek; + handle->Tell = &cd_Tell; + handle->offset = offset; + handle->length = length; + handle->block = -1; // nothing read yet + handle->pos = 0; + } + return handle; +} + +CDFileHandle_t *cd_handle_from_name(char *name) +{ + int32_t i; + char temp[256]; + + CDFileHandle_t *handle = (CDFileHandle_t*)malloc(sizeof(CDFileHandle_t)); + if (handle) + { + handle->Eof = &cd_Eof; + handle->Read = &cd_Read; + handle->Get = &cd_Get; + handle->Seek = &cd_Seek; + handle->Tell = &cd_Tell; + + i = strlen(name); + while (i && (name[i] != '/')) + i--; + if (name[i] == '/') + { + if (i) + { + strncpy(temp, name, i); + temp[i] = 0; + } + else + { + strcpy(temp, "/"); + } + if (set_cwd(temp) < 0) + { + // error setting working directory + free(handle); + return NULL; + } + strncpy(temp, &name[i+1], 255); + temp[255] = 0; + } + else + { + strncpy(temp, name, 255); + temp[255] = 0; + } + + if (find_dir_entry(temp) < 0) + { + // error finding entry + free(handle); + return NULL; + } + + handle->offset = global_vars->DENTRY_OFFSET; + handle->length = global_vars->DENTRY_LENGTH; + handle->block = -1; // nothing read yet + handle->pos = 0; + } + return handle; +} + +void delete_cd_handle(CDFileHandle_t *handle) +{ + if (handle) + free(handle); +} + diff --git a/cdfh.h b/cdfh.h new file mode 100644 index 0000000..616934c --- /dev/null +++ b/cdfh.h @@ -0,0 +1,30 @@ +#ifndef _CDFH_H +#define _CDFH_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CDFileHandle { + int32_t (*Seek)(struct CDFileHandle *handle, int32_t offset, int32_t whence); + int32_t (*Tell)(struct CDFileHandle *handle); + int32_t (*Read)(struct CDFileHandle *handle, void *ptr, int32_t size); + uint8_t (*Get)(struct CDFileHandle *handle); + uint8_t (*Eof)(struct CDFileHandle *handle); + int32_t offset; // start block of file + int32_t length; // length of file + int32_t block; // current block in buffer + int32_t pos; // current position in file +} CDFileHandle_t; + +extern CDFileHandle_t *cd_handle_from_name(char *name); +extern CDFileHandle_t *cd_handle_from_offset(int32_t offset, int32_t length); +extern void delete_cd_handle(CDFileHandle_t *handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/crt0.s b/crt0.s new file mode 100644 index 0000000..d5ecc0d --- /dev/null +++ b/crt0.s @@ -0,0 +1,45 @@ +| SEGA CD support code +| by Chilly Willy + + .text + +| CD startup at 0x8000 + + .global _start +_start: + move #0x2700,sr /* disable interrupts */ + +| Clear BSS + lea __bss_start,a0 + lea __bss_end,a1 + moveq #0,d0 +1: + move.l d0,(a0)+ + cmpa.l a0,a1 + bhi.b 1b + + move.l sp,__stack_save /* save BIOS stack pointer */ + lea __stack,a0 + movea.l a0,sp /* set stack pointer to top of Program RAM */ + link.w a6,#-8 /* set up initial stack frame */ + + jsr init_hardware /* initialize the console hardware */ + + jsr __INIT_SECTION__ /* do all program initializers */ + jsr main /* call program main() */ + jsr __FINI_SECTION__ /* do all program finishers */ + + movea.l __stack_save,sp /* restore BIOS stack pointer */ + moveq #0,d0 + rts + + + .data + + .align 4 + +__stack_save: + .long 0 + + .text + diff --git a/files.h b/files.h new file mode 100644 index 0000000..df56249 --- /dev/null +++ b/files.h @@ -0,0 +1,5 @@ +#define NUM_FILES 5 + +extern char *fileName[NUM_FILES]; +extern int fileSize[NUM_FILES]; +extern int filePtr[NUM_FILES]; diff --git a/files.s b/files.s new file mode 100644 index 0000000..7f7b3d8 --- /dev/null +++ b/files.s @@ -0,0 +1,4 @@ + .text + + .align 4 + diff --git a/font.s b/font.s new file mode 100644 index 0000000..7a39600 --- /dev/null +++ b/font.s @@ -0,0 +1,1072 @@ +/* ASCII font from Steph's Mini DevKit */ + +font_data: +| 0x20 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0xFFFFFFFF + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0xFFFFFFFF + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x000FF000 + .long 0x00FFFFF0 + .long 0x0FF00000 + .long 0x00FFFF00 + .long 0x00000FF0 + .long 0x0FFFFF00 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF0FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x0FF00FF0 + .long 0x0F000FF0 + .long 0x00000000 + + + .long 0x000FFF00 + .long 0x00FF0FF0 + .long 0x000FFF00 + .long 0x00FFF000 + .long 0x0FF0FFFF + .long 0x0FF00FF0 + .long 0x00FFF0FF + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0000FFF0 + .long 0x000FFF00 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FFF00 + .long 0x0000FFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFF0000 + .long 0x00FFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00FFF000 + .long 0x0FFF0000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0xFFFFFFFF + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x0FFFFFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00FF0000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000FF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x0FF00000 + .long 0x0F000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF0FFF0 + .long 0x0FFF0FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x00FFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x0000FF00 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0000FF00 + .long 0x000FFF00 + .long 0x00FFFF00 + .long 0x0FF0FF00 + .long 0x0FFFFFF0 + .long 0x0000FF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x0FF00000 + .long 0x0FFFFF00 + .long 0x00000FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x00000FF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x00FF0000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000FF0 + .long 0x0000FF00 + .long 0x00FFF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00FF0000 + + + .long 0x00000FF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x000FF000 + .long 0x0000FF00 + .long 0x00000FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x00000000 + .long 0x00000000 + + + .long 0x0FF00000 + .long 0x00FF0000 + .long 0x000FF000 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x0FF00000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00000000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF0FFF0 + .long 0x0FF0FFF0 + .long 0x0FF00000 + .long 0x00FFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFFF0 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFF000 + .long 0x0FF0FF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF0FF00 + .long 0x0FFFF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x0FF00000 + .long 0x0FFFFF00 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x0FF00000 + .long 0x0FFFFF00 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFFF0 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF0FFF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFFF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF0FF00 + .long 0x0FFFF000 + .long 0x0FFFF000 + .long 0x0FF0FF00 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF000FF + .long 0x0FFF0FFF + .long 0x0FFFFFFF + .long 0x0FF0F0FF + .long 0x0FF000FF + .long 0x0FF000FF + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FFF0FF0 + .long 0x0FFFFFF0 + .long 0x0FFFFFF0 + .long 0x0FF0FFF0 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFF00 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF0FF00 + .long 0x00FF0FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFF00 + .long 0x0FF0FF00 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00000 + .long 0x00FFFF00 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF000FF + .long 0x0FF000FF + .long 0x0FF0F0FF + .long 0x0FFFFFFF + .long 0x0FFF0FFF + .long 0x0FF000FF + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x0FF00000 + .long 0x0FFFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FFFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0F000000 + .long 0x0FF00000 + .long 0x00FF0000 + .long 0x000FF000 + .long 0x0000FF00 + .long 0x00000FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FFFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x0FFFF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0000F000 + .long 0x000FFF00 + .long 0x00FF0FF0 + .long 0x0FF000FF + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0xFFFFFFFF + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x00FFFF00 + .long 0x0FFFFFF0 + .long 0x0FFFFFF0 + .long 0x00FFFF00 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFF00 + .long 0x00000FF0 + .long 0x00FFFFF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00FFFFF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FFFFFF0 + .long 0x0FF00000 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x0000FFF0 + .long 0x000FF000 + .long 0x00FFFFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFFF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000FF0 + .long 0x0FFFFF00 + + + .long 0x00000000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x00000000 + .long 0x00FFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000FF0 + .long 0x00000000 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00000FF0 + .long 0x00FFFF00 + + + .long 0x00000000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF0FF00 + .long 0x0FFFF000 + .long 0x0FF0FF00 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00FFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FFFFFFF + .long 0x0FFFFFFF + .long 0x0FF0F0FF + .long 0x0FF000FF + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FFFFF00 + .long 0x0FF00000 + .long 0x0FF00000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFFF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000FF0 + .long 0x00000FF0 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFF00 + .long 0x0FF00FF0 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x0FF00000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x00FFFFF0 + .long 0x0FF00000 + .long 0x00FFFF00 + .long 0x00000FF0 + .long 0x0FFFFF00 + .long 0x00000000 + + + .long 0x00000000 + .long 0x000FF000 + .long 0x0FFFFFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x0000FFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x000FF000 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FF000FF + .long 0x0FF0F0FF + .long 0x0FFFFFFF + .long 0x00FFFFF0 + .long 0x00FF0FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x00FFFF00 + .long 0x000FF000 + .long 0x00FFFF00 + .long 0x0FF00FF0 + .long 0x00000000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x0FF00FF0 + .long 0x00FFFFF0 + .long 0x0000FF00 + .long 0x0FFFF000 + + + .long 0x00000000 + .long 0x00000000 + .long 0x0FFFFFF0 + .long 0x0000FF00 + .long 0x000FF000 + .long 0x00FF0000 + .long 0x0FFFFFF0 + .long 0x00000000 + +| 7B + .long 0x00000000 + .long 0x0000FFF0 + .long 0x000FF000 + .long 0x000FF000 + .long 0x00FFF000 + .long 0x000FF000 + .long 0x0000FFF0 + .long 0x00000000 + +| 7C + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 7D + .long 0x00000000 + .long 0x0FFF0000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FFF00 + .long 0x000FF000 + .long 0x0FFF0000 + .long 0x00000000 + +| 7E + .long 0x00000000 + .long 0x00FFF0FF + .long 0x0FF0FFF0 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + +| 7F + .long 0x0000F000 + .long 0x000FF000 + .long 0x00FFF000 + .long 0x0FFFF000 + .long 0x00FFF000 + .long 0x000FF000 + .long 0x0000F000 + .long 0x00000000 + +| 80 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x000FFFFF + .long 0x000FFFFF + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 81 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FFFFF + .long 0x000FFFFF + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 82 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0xFFFFFFFF + .long 0xFFFFFFFF + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + +| 83 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0xFFFFF000 + .long 0xFFFFF000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + +| 84 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0xFFFFF000 + .long 0xFFFFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 85 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0xFFFFF000 + .long 0xFFFFF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 86 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FFFFF + .long 0x000FFFFF + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + +| 87 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0xFFFFFFFF + .long 0xFFFFFFFF + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 88 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0xFFFFFFFF + .long 0xFFFFFFFF + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + +| 89 + .long 0x000FF000 + .long 0x000FF000 + .long 0x000FF000 + .long 0xFFFFFFFF + .long 0xFFFFFFFF + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + +| 8A + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0xFFFFFFFF + .long 0xFFFFFFFF + .long 0xFFFFFFFF + .long 0xFFFFFFFF diff --git a/hw_md.h b/hw_md.h new file mode 100644 index 0000000..f907dda --- /dev/null +++ b/hw_md.h @@ -0,0 +1,135 @@ +#ifndef _HW_MD_H +#define _HW_MD_H + +#define SEGA_CTRL_BUTTONS 0x0FFF +#define SEGA_CTRL_UP 0x0001 +#define SEGA_CTRL_DOWN 0x0002 +#define SEGA_CTRL_LEFT 0x0004 +#define SEGA_CTRL_RIGHT 0x0008 +#define SEGA_CTRL_B 0x0010 +#define SEGA_CTRL_C 0x0020 +#define SEGA_CTRL_A 0x0040 +#define SEGA_CTRL_START 0x0080 +#define SEGA_CTRL_Z 0x0100 +#define SEGA_CTRL_Y 0x0200 +#define SEGA_CTRL_X 0x0400 +#define SEGA_CTRL_MODE 0x0800 + +#define SEGA_CTRL_TYPE 0xF000 +#define SEGA_CTRL_THREE 0x0000 +#define SEGA_CTRL_SIX 0x1000 +#define SEGA_CTRL_NONE 0xF000 + +/* default text colors */ +#define TEXT_WHITE 0x0000 +#define TEXT_GREEN 0x2000 +#define TEXT_RED 0x4000 + +/* Z80 control flags */ +#define Z80_BUS_REQUEST 0x0100 +#define Z80_BUS_RELEASE 0x0000 +#define Z80_ASSERT_RESET 0x0000 +#define Z80_CLEAR_RESET 0x0100 + +#define GET_PAD(p) (*(volatile unsigned short *)(0xFF8018 + p*2)) +#define GET_TICKS (*(volatile unsigned int *)0xFF801C) + +// Global Variable offsets - must match boot loader +typedef struct +{ + int VBLANK_HANDLER; + int VBLANK_PARAM; + int INIT_CD; + int READ_CD; + int SET_CWD; + int FIRST_DIR_SEC; + int NEXT_DIR_SEC; + int FIND_DIR_ENTRY; + int NEXT_DIR_ENTRY; + int LOAD_FILE; + short DISC_TYPE; + short DIR_ENTRY; + int CWD_OFFSET; + int CWD_LENGTH; + int CURR_OFFSET; + int CURR_LENGTH; + int ROOT_OFFSET; + int ROOT_LENGTH; + int DENTRY_OFFSET; + int DENTRY_LENGTH; + short DENTRY_FLAGS; + char DENTRY_NAME[256]; + char TEMP_NAME[256]; +} globals_t; + +extern globals_t *global_vars; + +// CDFS Error codes - must match boot loader +enum { + ERR_READ_FAILED = -2, + ERR_NO_PVD = -3, + ERR_NO_MORE_ENTRIES = -4, + ERR_BAD_ENTRY = -5, + ERR_NAME_NOT_FOUND = -6, + ERR_NO_DISC = -7 +}; + +// MD Hardware Calls +enum { + MD_CMD_INIT_HW = 1, + MD_CMD_SET_SR, + MD_CMD_GET_PAD, + MD_CMD_CLEAR_B, + MD_CMD_SET_VRAM, + MD_CMD_NEXT_VRAM, + MD_CMD_COPY_VRAM, + MD_CMD_CLEAR_A, + MD_CMD_PUT_STR, + MD_CMD_PUT_CHR, + MD_CMD_DELAY, + MD_CMD_SET_PALETTE, + MD_CMD_Z80_BUSREQUEST, + MD_CMD_Z80_RESET, + MD_CMD_Z80_MEMCLR, + MD_CMD_Z80_MEMCPY, + MD_CMD_DMA_SCREEN, + MD_CMD_INIT_32X, + MD_CMD_GET_COMM32X, + MD_CMD_SET_COMM32X, + MD_CMD_DMA_TO_32X, + MD_CMD_CPY_TO_32X, + MD_CMD_END +}; + +#ifdef __cplusplus +extern "C" { +#endif + +extern int do_md_cmd0(int cmd); +extern int do_md_cmd1(int cmd, int arg1); +extern int do_md_cmd2(int cmd, int arg1, int arg2); +extern int do_md_cmd3(int cmd, int arg1, int arg2, int arg3); +extern int do_md_cmd4(int cmd, int arg1, int arg2, int arg3, int arg4); +extern void send_md_cmd0(int cmd); +extern void send_md_cmd1(int cmd, int arg1); +extern void send_md_cmd2(int cmd, int arg1, int arg2); +extern void send_md_cmd3(int cmd, int arg1, int arg2, int arg3); +extern void send_md_cmd4(int cmd, int arg1, int arg2, int arg3, int arg4); +extern int wait_md_cmd(void); +extern void switch_banks(void); +extern void sub_delay(int cnt); + +extern int init_cd(void); +extern int read_cd(int lba, int len, void *buffer); +extern int set_cwd(char *path); +extern int first_dir_sec(void); +extern int next_dir_sec(void); +extern int find_dir_entry(char *name); +extern int next_dir_entry(void); +extern int load_file(char *filename, void *buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hw_md.s b/hw_md.s new file mode 100644 index 0000000..93cc4ad --- /dev/null +++ b/hw_md.s @@ -0,0 +1,1523 @@ +| SEGA MegaCD support code +| by Chilly Willy + + +|-----------------------------------------------------------------------| +| Sub-CPU code | +|-----------------------------------------------------------------------| + +| Global Variable offsets - must match boot loader + .equ VBLANK_HANDLER, 0 + .equ VBLANK_PARAM, 4 + .equ INIT_CD, 8 + .equ READ_CD, 12 + .equ SET_CWD, 16 + .equ FIRST_DIR_SEC, 20 + .equ NEXT_DIR_SEC, 24 + .equ FIND_DIR_ENTRY, 28 + .equ NEXT_DIR_ENTRY, 32 + .equ LOAD_FILE, 36 + .equ DISC_TYPE, 40 + .equ DIR_ENTRY, 42 + .equ CWD_OFFSET, 44 + .equ CWD_LENGTH, 48 + .equ CURR_OFFSET, 52 + .equ CURR_LENGTH, 56 + .equ ROOT_OFFSET, 60 + .equ ROOT_LENGTH, 64 + .equ DENTRY_OFFSET, 68 + .equ DENTRY_LENGTH, 72 + .equ DENTRY_FLAGS, 76 + .equ DENTRY_NAME, 78 + .equ TEMP_NAME, 78+256 + .equ SIZE_GLOBALVARS,78+256+256 + +| Disc Read Buffer + .equ DISC_BUFFER, 0x6800 + +| Program Load Buffer + .equ LOAD_BUFFER, 0x8000 + +| ISO directory offsets (big-endian where applicable) + .equ RECORD_LENGTH, 0 + .equ EXTENT, 6 + .equ FILE_LENGTH, 14 + .equ FILE_FLAGS, 25 + .equ FILE_NAME_LEN, 32 + .equ FILE_NAME, 33 + +| Primary Volume Descriptor offset + .equ PVD_ROOT, 0x9C + +| CDFS Error codes + .equ ERR_READ_FAILED, -2 + .equ ERR_NO_PVD, -3 + .equ ERR_NO_MORE_ENTRIES,-4 + .equ ERR_BAD_ENTRY, -5 + .equ ERR_NAME_NOT_FOUND, -6 + .equ ERR_NO_DISC, -7 + +| XCMD Error codes + .equ ERR_UNKNOWN_CMD, -1 + .equ ERR_CMDDONE_TIMEOUT,-2 + .equ ERR_CMDACK_TIMEOUT, -3 + +| MD Hardware Calls - keep in sync with the enums in hw_md.h + + .equ MD_CMD_INIT_HW, 1 + .equ MD_CMD_SET_SR, 2 + .equ MD_CMD_GET_PAD, 3 + .equ MD_CMD_CLEAR_B, 4 + .equ MD_CMD_SET_VRAM, 5 + .equ MD_CMD_NEXT_VRAM, 6 + .equ MD_CMD_COPY_VRAM, 7 + .equ MD_CMD_CLEAR_A, 8 + .equ MD_CMD_PUT_STR, 9 + .equ MD_CMD_PUT_CHR, 10 + .equ MD_CMD_DELAY, 11 + .equ MD_CMD_SET_PALETTE, 12 + .equ MD_CMD_Z80_BUSREQUEST, 13 + .equ MD_CMD_Z80_RESET, 14 + .equ MD_CMD_Z80_MEMCLR, 15 + .equ MD_CMD_Z80_MEMCPY, 16 + .equ MD_CMD_DMA_SCREEN, 17 + .equ MD_CMD_INIT_32X, 18 + .equ MD_CMD_GET_COMM32X, 19 + .equ MD_CMD_SET_COMM32X, 20 + .equ MD_CMD_DMA_TO_32X, 21 + .equ MD_CMD_CPY_TO_32X, 22 + .equ MD_CMD_END, 23 + + .macro CD_VCOUNT +0: + move.l 0x801C.w,d0 + cmp.l 0x801C.w,d0 + bne.b 0b + .endm + + .macro MD_VCOUNT +0: + move.l 0xA1201C,d0 + cmp.l 0xA1201C,d0 + bne.b 0b + .endm + + .text + + .align 2 + +|----------------------------------------------------------------------- +| Global functions +|----------------------------------------------------------------------- + +| int init_cd(void); + .global init_cd +init_cd: + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + lea iso_pvd_magic,a5 + movea.l INIT_CD(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int read_cd(int lba, int len, void *buffer); + .global read_cd +read_cd: + move.l 4(sp),d0 /* lba */ + move.l 8(sp),d1 /* length */ + movea.l 12(sp),a0 /* buffer */ + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l READ_CD(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int set_cwd(char *path); + .global set_cwd +set_cwd: + movea.l 4(sp),a0 /* path */ + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l SET_CWD(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int first_dir_sec(void); + .global first_dir_sec +first_dir_sec: + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l FIRST_DIR_SEC(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int next_dir_sec(void); + .global next_dir_sec +next_dir_sec: + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l NEXT_DIR_SEC(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int find_dir_entry(char *name); + .global find_dir_entry +find_dir_entry: + movea.l 4(sp),a0 + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l FIND_DIR_ENTRY(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int next_dir_entry(void) + .global next_dir_entry +next_dir_entry: + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l NEXT_DIR_ENTRY(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int load_file(char *filename, void *buffer); + .global load_file +load_file: + movea.l 4(sp),a0 /* filename */ + movea.l 8(sp),a1 /* buffer */ + movem.l d2-d7/a2-a6,-(sp) + movea.l global_vars,a6 + movea.l LOAD_FILE(a6),a2 + jsr (a2) + movem.l (sp)+,d2-d7/a2-a6 + rts + +|----------------------------------------------------------------------- +| Cross console support +|----------------------------------------------------------------------- + +sub_wait_done: + moveq #0,d0 + move.b d0,0x800F.w /* send md command ACK handshake (for MD_CMD_DMA_SCREEN) */ + move.w #0xFFFF,d1 +0: + move.b 0x800E.w,d0 + bmi.b 1f /* MD resync request */ + dbeq d1,0b /* wait on previous md command done */ + beq.b 2f + /* timeout */ + moveq #ERR_CMDDONE_TIMEOUT,d0 +1: + move.b d0,0x800F.w /* xcmd error */ +2: + rts + +sub_wait_ack: + move.w #0xFFFF,d1 +0: + swap d0 + move.b 0x800E.w,d0 + bmi.b 1f /* MD resync request */ + swap d0 + cmp.b 0x800E.w,d0 + dbeq d1,0b /* wait on previous md command done */ + beq.b 1f + /* timeout */ + moveq #ERR_CMDACK_TIMEOUT,d0 +1: + move.b d0,0x800F.w /* xcmd error */ +2: + rts + +|----------------------------------------------------------------------- +| Cross console functions +|----------------------------------------------------------------------- + +| int do_md_cmd0(int cmd); + .global do_md_cmd0 +do_md_cmd0: + move.l 4(sp),d0 + bsr send_md_cmd + bpl wait_md_cmd + rts + +| int do_md_cmd1(int cmd, int arg1); + .global do_md_cmd1 +do_md_cmd1: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + bsr send_md_cmd + bpl wait_md_cmd + rts + +| int do_md_cmd2(int cmd, int arg1, int arg2); + .global do_md_cmd2 +do_md_cmd2: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + move.l 12(sp),0x8024.w + bsr send_md_cmd + bpl wait_md_cmd + rts + +| int do_md_cmd3(int cmd, int arg1, int arg2, int arg3); + .global do_md_cmd3 +do_md_cmd3: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + move.l 12(sp),0x8024.w + move.l 16(sp),0x8028.w + bsr send_md_cmd + bpl wait_md_cmd + rts + +| int do_md_cmd4(int cmd, int arg1, int arg2, int arg3, int arg4); + .global do_md_cmd4 +do_md_cmd4: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + move.l 12(sp),0x8024.w + move.l 16(sp),0x8028.w + move.l 20(sp),0x802C.w + bsr send_md_cmd + bpl wait_md_cmd + rts + +| void send_md_cmd0(int cmd); + .global send_md_cmd0 +send_md_cmd0: + move.l 4(sp),d0 + bra.b send_md_cmd + +| void send_md_cmd1(int cmd, int arg1); + .global send_md_cmd1 +send_md_cmd1: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + bra.b send_md_cmd + +| void send_md_cmd2(int cmd, int arg1, int arg2); + .global send_md_cmd2 +send_md_cmd2: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + move.l 12(sp),0x8024.w + bra.b send_md_cmd + +| void send_md_cmd3(int cmd, int arg1, int arg2, int arg3); + .global send_md_cmd3 +send_md_cmd3: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + move.l 12(sp),0x8024.w + move.l 16(sp),0x8028.w + bra.b send_md_cmd + +| void send_md_cmd4(int cmd, int arg1, int arg2, int arg3, int arg4); + .global send_md_cmd4 +send_md_cmd4: + move.l 4(sp),d0 + move.l 8(sp),0x8020.w + move.l 12(sp),0x8024.w + move.l 16(sp),0x8028.w + move.l 20(sp),0x802C.w + +send_md_cmd: + move.w d0,-(sp) + + /* wait on previous md command done */ + bsr sub_wait_done + bmi.b 2f /* error - resync */ + + move.w (sp)+,d0 + move.b d0,0x800F.w /* send md command */ + + /* wait on md command ACK */ + bsr sub_wait_ack + bmi.b 1f /* error - resync */ + + cmpi.b #MD_CMD_DMA_SCREEN,d0 + beq.b 1f /* no immediate command ACK handshake on MD_CMD_DMA_SCREEN! */ + move.b #0,0x800F.w /* send md command ACK handshake */ +1: + rts +2: + addq.l #2,sp + rts + +| int wait_md_cmd(void); + .global wait_md_cmd +wait_md_cmd: + bsr sub_wait_done + bmi.b 0f /* error */ + move.l 0x8010.w,d0 /* return value */ +0: + rts + +| void switch_banks(void); +| Switch 1M Banks + .global switch_banks +switch_banks: + bchg #0,0x8003.w /* switch banks */ +0: + btst #1,0x8003.w + bne.b 0b /* bank switch not finished */ + rts + +| void sub_delay(int count); +| wait count number of vertical blank periods - relies on vblank running +| entry: arg = count + .global sub_delay +sub_delay: + move.l 4(sp),d1 /* count */ + CD_VCOUNT + add.l d0,d1 /* add current vblank count */ +1: + CD_VCOUNT + cmp.l d0,d1 + bgt.b 1b + rts + +| Initialize the SCD hardware & MD code and hardware (called from crt0) + .global init_hardware +init_hardware: +| init any SCD hardware here + +| copy MD code to Word RAM and signal MD side + lea md_init_start(pc),a0 + lea 0x080000,a1 /* word ram in 2M mode */ + lea md_init_end(pc),a2 +1: + move.l (a0)+,(a1)+ + cmpa.l a0,a2 + bhi.b 1b + bset #0,0x8003.w /* give Main-CPU Word RAM */ + + move.b #'M,0x800F.w /* send MD Code command to MD */ +2: + cmpi.b #'M,0x800E.w /* wait for command ACK */ + bne.b 2b + move.b #0,0x800F.w /* send command ACK handshake */ +3: + tst.b 0x800E.w + bne.b 3b /* wait on command done */ + + bset #2,0x8003.w /* switch to 1M mode */ + + jsr 0x5F3A.w /* SPNull - returns boot loader globals */ + move.l d0,global_vars + + move #0x2000,sr /* allow interrupts */ + rts + + +|-----------------------------------------------------------------------| +| Main-CPU code | +|-----------------------------------------------------------------------| + +| Copied to Word RAM for MD to execute + .align 4 +md_init_start: + move.b #'M,0xA1200E /* md code command ACK */ + + lea md_start(pc),a0 + lea 0xFF1000,a1 + lea md_init_end(pc),a2 +1: + move.l (a0)+,(a1)+ + cmpa.l a0,a2 + bhi.b 1b + jmp 0xFF1000 + + .align 4 +md_start: + move.l #0,0xA12010 + move.l #0,0xA12014 + move.l #0,0xA12018 + move.l #0,0xA1201C + moveq #0,d7 /* 32X not initialized */ + bsr.w md_init_hw +cmd_done: + tst.b 0xA1200F + bmi.b 1f /* error - resync */ + bne.b cmd_done /* wait on ACK handshake */ +1: + move.b #0,0xA1200E /* command done */ + + /* main loop */ + moveq #0,d0 +cmd_loop: + tst.b d7 + beq.b md_check /* 32X not initialized */ + move.w 0xA15120,d0 + beq.b md_check + + /* handle 32X command */ + cmpi.w #1,d0 + beq.b md_check /* copy data to 32X */ + + +| unknown command + +mars_done: + moveq #0,d0 + move.w d0,0xA15120 /* command done */ +md_check: + move.b 0xA1200F,d0 + beq.b cmd_loop + bmi.b cmd_done /* error - resync */ + + move.b d0,0xA1200E /* command ack */ + + /* push args on stack */ + move.l 0xA1202C,-(sp) + move.l 0xA12028,-(sp) + move.l 0xA12024,-(sp) + move.l 0xA12020,-(sp) + + cmpi.b #'M,d0 + bne.b 2f +1: + btst #0,0xA12003 /* Main-CPU has Word RAM? */ + beq.b 1b + jmp 0x200000 +2: + cmpi.b #'I,d0 + bne.b 3f + bset #1,0xA12003 /* give Sub-CPU Word RAM */ + bra.b 4f +3: + move.l #ERR_UNKNOWN_CMD,0xA12010 + cmpi.b #MD_CMD_END,d0 + bhs.b 4f /* unknown command */ + add.w d0,d0 + lea cmd_table(pc),a0 + move.w 0(a0,d0.w),d0 + jsr 0(a0,d0.w) + move.l d0,0xA12010 /* return value */ +4: + lea 16(sp),sp + bra cmd_done + +cmd_table: + .word cmd_done - cmd_table /* 0 - not really a command */ + .word md_init_hw - cmd_table + .word set_sr - cmd_table + .word get_pad - cmd_table + .word clear_b - cmd_table + .word set_vram - cmd_table + .word next_vram - cmd_table + .word copy_vram - cmd_table + .word clear_a - cmd_table + .word put_str - cmd_table + .word put_chr - cmd_table + .word delay - cmd_table + .word set_palette - cmd_table + .word z80_busrequest - cmd_table + .word z80_reset - cmd_table + .word z80_memclr - cmd_table + .word z80_memcpy - cmd_table + .word dma_screen - cmd_table + .word init_32x - cmd_table + .word get_comm32x - cmd_table + .word set_comm32x - cmd_table + .word dma_to_32x - cmd_table + .word cpy_to_32x - cmd_table + +| void md_init_hw(void); +| initialize MD hardware +md_init_hw: + movem.l d2-d7/a2-a5,-(sp) + move.w #0x2700,sr /* disallow interrupts */ + +| init joyports + lea 0xA10000,a5 + move.b #0x40,0x09(a5) + move.b #0x40,0x0B(a5) + move.b #0x40,0x03(a5) + move.b #0x40,0x05(a5) + + lea 0xC00000,a3 /* VDP data reg */ + lea 0xC00004,a4 /* VDP cmd/sts reg */ + +| wait on VDP DMA (in case we reset in the middle of DMA) + move.w #0x8114,(a4) /* display off, dma enabled */ +0: + move.w (a4),d0 /* read VDP status */ + btst #1,d0 /* DMA busy? */ + bne.b 0b /* yes */ + + moveq #0,d0 + move.w #0x8000,d5 /* set VDP register 0 */ + move.w #0x0100,d7 + +| Set VDP registers + lea InitVDPRegs(pc),a5 + moveq #18,d1 +1: + move.b (a5)+,d5 /* lower byte = register data */ + move.w d5,(a4) /* set VDP register */ + add.w d7,d5 /* + 0x0100 = next register */ + dbra d1,1b + +| clear VRAM + move.w #0x8F02,(a4) /* set INC to 2 */ + move.l #0x40000000,(a4) /* write VRAM address 0 */ + move.w #0x7FFF,d1 /* 32K - 1 words */ +2: + move.w d0,(a3) /* clear VRAM */ + dbra d1,2b + +| The VDP state at this point is: Display disabled, ints disabled, Name Tbl A at 0xC000, +| Name Tbl B at 0xE000, Name Tbl W at 0xB000, Sprite Attr Tbl at 0xA800, HScroll Tbl at 0xAC00, +| H40 V28 mode, and Scroll size is 64x32. + +| Clear CRAM + lea InitVDPRAM(pc),a5 + move.l (a5)+,(a4) /* set reg 1 and reg 15 */ + move.l (a5)+,(a4) /* write CRAM address 0 */ + moveq #31,d3 +3: + move.l d0,(a3) + dbra d3,3b + +| Clear VSRAM + move.l (a5)+,(a4) /* write VSRAM address 0 */ + moveq #19,d4 +4: + move.l d0,(a3) + dbra d4,4b + +| halt Z80 and init FM chip + /* Allow the 68k to access the FM chip */ + move.w #0x100,0xA11100 + move.w #0x100,0xA11200 + +| reset YM2612 + lea FMReset(pc),a5 + lea 0xA00000,a0 + move.w #0x4000,d1 + moveq #26,d2 +5: + move.b (a5)+,d1 /* FM reg */ + move.b (a5)+,0(a0,d1.w) /* FM data */ + nop + nop + dbra d2,5b + + moveq #0x30,d0 + moveq #0x5F,d2 +6: + move.b d0,0x4000(a0) /* FM reg */ + nop + nop + move.b #0xFF,0x4001(a0) /* FM data */ + nop + nop + move.b d0,0x4002(a0) /* FM reg */ + nop + nop + move.b #0xFF,0x4003(a0) /* FM data */ + nop + nop + addq.b #1,d0 + dbra d2,6b + +| reset PSG + lea PSGReset(pc),a5 + lea 0xC00000,a0 + move.b (a5)+,0x0011(a0) + move.b (a5)+,0x0011(a0) + move.b (a5)+,0x0011(a0) + move.b (a5),0x0011(a0) + +| load font tile data + move.w #0x8F02,(a4) /* INC = 2 */ + move.l #0x40000000,(a4) /* write VRAM address 0 */ + lea font_data(pc),a0 + move.w #0x6B*8-1,d2 +7: + move.l (a0)+,d0 /* font fg mask */ + move.l d0,d1 + not.l d1 /* font bg mask */ + andi.l #0x11111111,d0 /* set font fg color */ + andi.l #0x00000000,d1 /* set font bg color */ + or.l d1,d0 + move.l d0,(a3) /* set tile line */ + dbra d2,7b + +| set the default palette for text + move.l #0xC0000000,(a4) /* write CRAM address 0 */ + move.l #0x00000CCC,(a3) /* entry 0 (black) and 1 (lt gray) */ + move.l #0xC0200000,(a4) /* write CRAM address 32 */ + move.l #0x000000A0,(a3) /* entry 16 (black) and 17 (green) */ + move.l #0xC0400000,(a4) /* write CRAM address 64 */ + move.l #0x0000000A,(a3) /* entry 32 (black) and 33 (red) */ + + lea vblank_int(pc),a0 + move.l a0,0xFFFD08 /* set level 6 int vector */ + + move.w #0x8174,(a4) /* display on, vblank enabled */ + movem.l (sp)+,d2-d7/a2-a5 + move #0x2000,sr /* allow interrupts */ + rts + +| VDP register initialization values +InitVDPRegs: + .byte 0x04 /* 8004 => write reg 0 = /IE1 (no HBL INT), /M3 (enable read H/V cnt) */ + .byte 0x14 /* 8114 => write reg 1 = /DISP (display off), /IE0 (no VBL INT), M1 (DMA enabled), /M2 (V28 mode) */ + .byte 0x30 /* 8230 => write reg 2 = Name Tbl A = 0xC000 */ + .byte 0x2C /* 832C => write reg 3 = Name Tbl W = 0xB000 */ + .byte 0x07 /* 8407 => write reg 4 = Name Tbl B = 0xE000 */ + .byte 0x54 /* 8554 => write reg 5 = Sprite Attr Tbl = 0xA800 */ + .byte 0x00 /* 8600 => write reg 6 = always 0 */ + .byte 0x00 /* 8700 => write reg 7 = BG color */ + .byte 0x00 /* 8800 => write reg 8 = always 0 */ + .byte 0x00 /* 8900 => write reg 9 = always 0 */ + .byte 0x00 /* 8A00 => write reg 10 = HINT = 0 */ + .byte 0x00 /* 8B00 => write reg 11 = /IE2 (no EXT INT), full scroll */ + .byte 0x81 /* 8C81 => write reg 12 = H40 mode, no lace, no shadow/hilite */ + .byte 0x2B /* 8D2B => write reg 13 = HScroll Tbl = 0xAC00 */ + .byte 0x00 /* 8E00 => write reg 14 = always 0 */ + .byte 0x01 /* 8F01 => write reg 15 = data INC = 1 */ + .byte 0x01 /* 9001 => write reg 16 = Scroll Size = 64x32 */ + .byte 0x00 /* 9100 => write reg 17 = W Pos H = left */ + .byte 0x00 /* 9200 => write reg 18 = W Pos V = top */ + + .align 2 + +| VDP Commands +InitVDPRAM: + .word 0x8104, 0x8F01 /* set registers 1 (display off) and 15 (INC = 1) */ + .word 0xC000, 0x0000 /* write CRAM address 0 */ + .word 0x4000, 0x0010 /* write VSRAM address 0 */ + +FMReset: + /* disable LFO */ + .byte 0,0x22 + .byte 1,0x00 + /* disable timer & set channel 6 to normal mode */ + .byte 0,0x27 + .byte 1,0x00 + /* all KEY_OFF */ + .byte 0,0x28 + .byte 1,0x00 + .byte 1,0x04 + .byte 1,0x01 + .byte 1,0x05 + .byte 1,0x02 + .byte 1,0x06 + /* disable DAC */ + .byte 0,0x2A + .byte 1,0x80 + .byte 0,0x2B + .byte 1,0x00 + /* turn off channels */ + .byte 0,0xB4 + .byte 1,0x00 + .byte 0,0xB5 + .byte 1,0x00 + .byte 0,0xB6 + .byte 1,0x00 + .byte 2,0xB4 + .byte 3,0x00 + .byte 2,0xB5 + .byte 3,0x00 + .byte 2,0xB6 + .byte 3,0x00 + +| PSG register initialization values +PSGReset: + .byte 0x9f /* set ch0 attenuation to max */ + .byte 0xbf /* set ch1 attenuation to max */ + .byte 0xdf /* set ch2 attenuation to max */ + .byte 0xff /* set ch3 attenuation to max */ + + .align 4 + + .include "font.s" + + .align 4 + +| short set_sr(short new_sr); +| set SR, return previous SR +| entry: arg = SR value +| exit: d0 = previous SR value +set_sr: + moveq #0,d0 + move.w sr,d0 + move.l 4(sp),d1 + move.w d1,sr + rts + +| short get_pad(short pad); +| return buttons for selected pad +| entry: arg = pad index (0 or 1) +| exit: d0 = pad value (0 0 0 1 M X Y Z S A C B R L D U) or (0 0 0 0 0 0 0 0 S A C B R L D U) +get_pad: + move.l d2,-(sp) + move.l 8(sp),d0 /* first arg is pad number */ + cmpi.w #1,d0 + bhi no_pad + add.w d0,d0 + addi.l #0xA10003,d0 /* pad control register */ + movea.l d0,a0 + bsr.b get_input /* - 0 s a 0 0 d u - 1 c b r l d u */ + move.w d0,d1 + andi.w #0x0C00,d0 + bne.b no_pad + bsr.b get_input /* - 0 s a 0 0 d u - 1 c b r l d u */ + bsr.b get_input /* - 0 s a 0 0 0 0 - 1 c b m x y z */ + move.w d0,d2 + bsr.b get_input /* - 0 s a 1 1 1 1 - 1 c b r l d u */ + andi.w #0x0F00,d0 /* 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 */ + cmpi.w #0x0F00,d0 + beq.b common /* six button pad */ + move.w #0x010F,d2 /* three button pad */ +common: + lsl.b #4,d2 /* - 0 s a 0 0 0 0 m x y z 0 0 0 0 */ + lsl.w #4,d2 /* 0 0 0 0 m x y z 0 0 0 0 0 0 0 0 */ + andi.w #0x303F,d1 /* 0 0 s a 0 0 0 0 0 0 c b r l d u */ + move.b d1,d2 /* 0 0 0 0 m x y z 0 0 c b r l d u */ + lsr.w #6,d1 /* 0 0 0 0 0 0 0 0 s a 0 0 0 0 0 0 */ + or.w d1,d2 /* 0 0 0 0 m x y z s a c b r l d u */ + eori.w #0x1FFF,d2 /* 0 0 0 1 M X Y Z S A C B R L D U */ + move.w d2,d0 + move.l (sp)+,d2 + rts + +| 3-button/6-button pad not found +no_pad: + .ifdef HAS_SMS_PAD + move.b (a0),d0 /* - 1 c b r l d u */ + andi.w #0x003F,d0 /* 0 0 0 0 0 0 0 0 0 0 c b r l d u */ + eori.w #0x003F,d0 /* 0 0 0 0 0 0 0 0 0 0 C B R L D U */ + .else + move.w #0xF000,d0 /* SEGA_CTRL_NONE */ + .endif + move.l (sp)+,d2 + rts + +| read single phase from controller +get_input: + move.b #0x00,(a0) + nop + nop + move.b (a0),d0 + move.b #0x40,(a0) + lsl.w #8,d0 + move.b (a0),d0 + rts + +| void clear_b(void); +| clear the name table for plane B +clear_b: + moveq #0,d0 + lea 0xC00000,a0 + move.w #0x8F02,4(a0) /* set INC to 2 */ + move.l #0x60000003,d1 /* VDP write VRAM at 0xE000 (scroll plane B) */ + move.l d1,4(a0) /* write VRAM at plane B start */ + move.w #64*32-1,d1 +1: + move.w d0,(a0) /* clear name pattern */ + dbra d1,1b + rts + +| void set_vram(int offset, int val); +| store word to vram at offset +| entry: first arg = offset in vram +| second arg = word to store +set_vram: + lea 0xC00000,a1 + move.w #0x8F02,4(a1) /* set INC to 2 */ + move.l 4(sp),d1 /* vram offset */ + lsl.l #2,d1 + lsr.w #2,d1 + swap d1 + ori.l #0x40000000,d1 /* VDP write VRAM */ + move.l d1,4(a1) /* write VRAM at offset*/ + move.l 8(sp),d0 /* data word */ + move.w d0,(a1) /* set vram word */ + rts + +| void next_vram(int val); +| store word to vram at next offset +| entry: first arg = word to store +next_vram: + move.l 4(sp),d0 /* data word */ + move.w d0,0xC00000 /* set vram word */ + rts + +| void copy_vram(int offset, void *src, int len); +| store word to vram at offset +| entry: first arg = offset in vram +| second arg = address of data to copy +| third arg = length of data to copy (in words) +copy_vram: + lea 0xC00000,a1 + move.w #0x8F02,4(a1) /* set INC to 2 */ + move.l 4(sp),d1 /* vram offset */ + lsl.l #2,d1 + lsr.w #2,d1 + swap d1 + ori.l #0x40000000,d1 /* VDP write VRAM */ + move.l d1,4(a1) /* write VRAM at offset*/ + movea.l 8(sp),a0 /* source pointer */ + move.l 12(sp),d0 /* length in words */ + subq.w #1,d0 +0: + move.w (a0)+,(a1) /* set vram word */ + dbra d0,0b + rts + +| void clear_a(void); +| clear the name table for plane A +clear_a: + moveq #0,d0 + lea 0xC00000,a0 + move.w #0x8F02,4(a0) /* set INC to 2 */ + move.l #0x40000003,d1 /* VDP write VRAM at 0xC000 (scroll plane A) */ + move.l d1,4(a0) /* write VRAM at plane A start */ + move.w #64*32-1,d1 +1: + move.w d0,(a0) /* clear name pattern */ + dbra d1,1b + rts + +| void put_str(char *str, int color, int x, int y); +| put string characters to the screen +| entry: first arg = string address +| second arg = 0 for normal color font, N * 0x0200 for alternate color font (use CP bits for different colors) +| third arg = column at which to start printing +| fourth arg = row at which to start printing +put_str: + movea.l 4(sp),a0 /* string pointer */ + move.l 8(sp),d0 /* color palette */ + lea 0xC00000,a1 + move.w #0x8F02,4(a1) /* set INC to 2 */ + move.l 16(sp),d1 /* y coord */ + lsl.l #6,d1 + or.l 12(sp),d1 /* cursor y<<6 | x */ + add.w d1,d1 /* pattern names are words */ + swap d1 + ori.l #0x40000003,d1 /* OR cursor with VDP write VRAM at 0xC000 (scroll plane A) */ + move.l d1,4(a1) /* write VRAM at location of cursor in plane A */ +1: + move.b (a0)+,d0 + subi.b #0x20,d0 /* font starts at space */ + move.w d0,(a1) /* set pattern name for character */ + tst.b (a0) + bne.b 1b + rts + +| void put_chr(char chr, int color, int x, int y); +| put a character to the screen +| entry: first arg = character +| second arg = 0 for normal color font, N * 0x0200 for alternate color font (use CP bits for different colors) +| third arg = column at which to start printing +| fourth arg = row at which to start printing +put_chr: + movea.l 4(sp),a0 /* character */ + move.l 8(sp),d0 /* color palette */ + lea 0xC00000,a1 + move.w #0x8F02,4(a1) /* set INC to 2 */ + move.l 16(sp),d1 /* y coord */ + lsl.l #6,d1 + or.l 12(sp),d1 /* cursor y<<6 | x */ + add.w d1,d1 /* pattern names are words */ + swap d1 + ori.l #0x40000003,d1 /* OR cursor with VDP write VRAM at 0xC000 (scroll plane A) */ + move.l d1,4(a1) /* write VRAM at location of cursor in plane A */ + + move.l a0,d1 + move.b d1,d0 + subi.b #0x20,d0 /* font starts at space */ + move.w d0,(a1) /* set pattern name for character */ + rts + +| void delay(int count); +| wait count number of vertical blank periods - relies on vblank running +| entry: arg = count +delay: + move.l 4(sp),d1 /* count */ + MD_VCOUNT + add.l d0,d1 /* add current vblank count */ +1: + MD_VCOUNT + cmp.l d0,d1 + bgt.b 1b + rts + +| void set_palette(short *pal, int start, int count) +| copy count entries pointed to by pal into the palette starting at the index start +| entry: pal = pointer to an array of words holding the colors +| start = index of the first color in the palette to set +| count = number of colors to copy +set_palette: + movea.l 4(sp),a0 /* pal */ + move.l 8(sp),d0 /* start */ + move.l 12(sp),d1 /* count */ + add.w d0,d0 /* start*2 */ + swap d0 /* high word holds address */ + ori.l #0xC0000000,d0 /* write CRAM address (0 + index*2) */ + subq.w #1,d1 /* for dbra */ + + lea 0xC00000,a1 + move.w #0x8F02,4(a1) /* set INC to 2 */ + move.l d0,4(a1) /* write CRAM */ +0: + move.w (a0)+,(a1) /* copy color to palette */ + dbra d1,0b + rts + +| void z80_busrequest(int flag) +| set Z80 bus request +| entry: flag = request/release +z80_busrequest: + move.l 4(sp),d0 /* flag */ + andi.w #0x0100,d0 + move.w d0,0xA11100 /* set bus request */ +0: + move.w 0xA11100,d1 + and.w d0,d1 + bne.b 0b + rts + +| void z80_reset(int flag) +| set Z80 reset +| entry: flag = assert/clear +z80_reset: + move.l 4(sp),d0 /* flag */ + andi.w #0x0100,d0 + move.w d0,0xA11200 /* set reset */ + rts + +| void z80_memclr(void *dst, int len) +| clear Z80 sram +| entry: dst = pointer to sram to clear +| len = length of memory to copy +| note: you need to request the Z80 bus first +z80_memclr: + movea.l 4(sp),a1 /* dst */ + move.l 8(sp),d0 /* len */ + subq.w #1,d0 + moveq #0,d1 +0: + move.b d1,(a1)+ + dbra d0,0b + rts + +| void z80_memcpy(void *dst, void *src, int len) +| copy memory to Z80 sram +| entry: dst = pointer to memory to copy to +| src = pointer to memory to copy from +| len = length of memory to copy +| note: you need to request the Z80 bus first +z80_memcpy: + movea.l 4(sp),a1 /* dst */ + movea.l 8(sp),a0 /* src */ + move.l 12(sp),d0 /* len */ + subq.w #1,d0 +0: + move.b (a0)+,d1 + move.b d1,(a1)+ + dbra d0,0b + rts + +| void dma_screen(unsigned short *buffer, int wide); +| dma buffer to background cmap entry +dma_screen: + move.l 4(sp),d0 /* buffer */ + move.l 8(sp),d1 /* wide flag */ + movem.l d2-d7/a2-a6,-(sp) + move.w #0x2700,sr /* disallow interrupts */ + + /* VDP commands for DMA */ + move.l #0x93E0948C,d2 /* DMALEN LO/HI = 0x8CE0 (161*224) */ + tst.b d1 + beq.b 0f /* narrow screen */ + /* wide screen */ + move.l #0x934094AD,d2 /* DMALEN LO/HI = 0xAD40 (198*224) */ +0: + move.l #0x96009500,d3 + lsr.l #1,d0 /* word bus */ + move.b d0,d3 /* LOW address */ + swap d3 + lsr.l #8,d0 + move.b d0,d3 /* MID address */ + move.l #0x81149700,d4 + lsr.l #8,d0 + andi.w #0x007F,d0 + move.b d0,d4 /* HI address and dma mode */ + swap d4 + move.l #0xC0000080,d5 /* dest = write cram => start DMA */ + + /* clear palette */ + moveq #0,d0 + lea 0xC00000,a2 + lea 0xC00004,a3 + moveq #31,d1 + move.l #0xC0000000,(a3) /* write CRAM address 0 */ +1: + move.l d0,(a2) /* clear palette */ + dbra d1,1b + + /* init VDP regs */ + move.w #0x8F00,(a3) /* clear INC register */ + cmpi.l #0x934094AD,d2 + beq.b dma_wide /* wide screen display loop */ + move.w #0x8C00,(a3) /* H32 mode, no lace, no shadow/hilite */ + bra.w dma_narrow /* narrow screen display loop */ + + /* loop - turn on display and wait for vblank */ +dma_wide: + moveq #0,d0 + move.w #0x8154,(a3) /* turn on display (no VB int, V28 mode) */ + move.l #0x40000000,(a3) /* write vram address 0 */ +1: + btst #3,1(a3) + beq.b 1b /* wait for VB */ +2: + btst #3,1(a3) + bne.b 2b /* wait for not VB */ + + move.l d0,(a2) + move.l d0,(a2) + move.l d0,(a2) + move.l d0,(a2) + move.l d0,(a2) + move.l d0,(a2) + move.w d0,(a2) + + nop + nop + nop + nop + + /* Execute DMA */ + nop + nop + move.l d2,(a3) /* DMALEN LO/HI */ + nop + nop + move.l d3,(a3) /* DMA SRC LO/MID */ + nop + nop + move.l d4,(a3) /* DMA SRC HI/MODE, Turn off Display */ + nop + nop + move.l d5,(a3) /* start DMA */ + /* CPU is halted until DMA is complete */ + + /* display finished, check request switch ram banks */ + btst #7,0xA1200F + beq.b 4f + nop + bset #1,0xA12003 /* request switch ram banks */ + nop +3: + btst #1,0xA12003 + bne.b 3b /* wait for bank switch */ +4: + /* do other tasks here */ + pea 0.w + bsr.w get_pad + addq.l #4,sp + move.w d0,0xA12018 + + pea 1.w + bsr.w get_pad + addq.l #4,sp + move.w d0,0xA1201A + + move.l 0xA1201C,d0 + addq.l #1,d0 + move.l d0,0xA1201C /* increment ticks */ + + move.w 0xA12000,d0 + ori.w #0x0100,d0 + move.w d0,0xA12000 /* generate level 2 int for CD */ + + moveq #0x7F,d0 + and.b 0xA1200F,d0 + cmpi.b #MD_CMD_DMA_SCREEN,d0 + bne.b exit_wide /* no longer requesting DMA color display */ + bra.w dma_wide +exit_wide: + bsr.w md_init_hw + movem.l (sp)+,d2-d7/a2-a6 + rts + + /* loop - turn on display and wait for vblank */ +dma_narrow: + moveq #0,d0 + move.w #0x8154,(a3) /* turn on display (no VB int, V28 mode) */ + move.l #0x40000000,(a3) /* write vram address 0 */ +1: + btst #3,1(a3) + beq.b 1b /* wait for VB */ +2: + btst #3,1(a3) + bne.b 2b /* wait for not VB */ + + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + move.w d0,(a2) + + nop + nop + nop + nop + + /* Execute DMA */ + nop + nop + move.l d2,(a3) /* DMALEN LO/HI */ + nop + nop + move.l d3,(a3) /* DMA SRC LO/MID */ + nop + nop + move.l d4,(a3) /* DMA SRC HI/MODE, Turn off Display */ + nop + nop + move.l d5,(a3) /* start DMA */ + /* CPU is halted until DMA is complete */ + + /* display finished, check request switch ram banks */ + btst #7,0xA1200F + beq.b 4f + nop + bset #1,0xA12003 /* request switch ram banks */ + nop +3: + btst #1,0xA12003 + bne.b 3b /* wait for bank switch */ +4: + /* do other tasks here */ + pea 0.w + bsr.w get_pad + addq.l #4,sp + move.w d0,0xA12018 + + pea 1.w + bsr.w get_pad + addq.l #4,sp + move.w d0,0xA1201A + + move.l 0xA1201C,d0 + addq.l #1,d0 + move.l d0,0xA1201C /* increment ticks */ + + move.w 0xA12000,d0 + ori.w #0x0100,d0 + move.w d0,0xA12000 /* generate level 2 int for CD */ + + moveq #0x7F,d0 + and.b 0xA1200F,d0 + cmpi.b #MD_CMD_DMA_SCREEN,d0 + bne.b exit_narrow /* no longer requesting DMA color display */ + bra.w dma_narrow +exit_narrow: + bsr.w md_init_hw + movem.l (sp)+,d2-d7/a2-a6 + rts + +| int init_32x(void *data, int length); +| init 32x, return status +| entry: arg1 = 32X code/data (in word ram), arg2 = length of data +| exit: d0 = status +init_32x: + move.l 0xA130EC,d0 + cmpi.l #0x4D415253,d0 /* 'MARS' */ + beq.b 0f + moveq #-2,d0 /* no 32X detected */ + rts +0: + move.w #0x2700,sr /* disable ints */ + lea 0xA15000,a0 + move.b #1,0x0101(a0) /* activate 32X, assert SH2 RESET */ + + /* wait 10ms for 32X */ + move.w #19170,d1 +1: + dbra d1,1b + + moveq #0,d0 + move.l d0,0x0120(a0) /* clear COMM0 */ + move.l d0,0x0124(a0) /* clear COMM4 */ + move.b #3,0x0101(a0) /* deassert SH2 RESET */ +2: + bclr #7,0x0100(a0) /* get access to 32X VDP resources */ + bne.b 2b + move.w d0,0x0102(a0) /* clear interrupt reg */ + move.w d0,0x0104(a0) /* clear bank reg */ + move.w d0,0x0106(a0) /* clear dreq control reg */ + move.l d0,0x0108(a0) /* clear dreq src reg */ + move.l d0,0x010C(a0) /* clear dreq dst reg */ + move.w d0,0x0110(a0) /* clear dreq length reg */ + move.w d0,0x0130(a0) /* clear pwm control reg */ + move.w d0,0x0132(a0) /* clear pwm cycle reg */ + move.w d0,0x0138(a0) /* clear pwm mono reg */ + move.w d0,0x0180(a0) /* clear vdp mode reg */ + move.w d0,0x0182(a0) /* clear vdp shift reg */ +3: + bclr #0,0x018B(a0) /* switch to frame 0 */ + bne.b 3b + moveq #-1,d1 + lea 0x840000,a1 /* frame buffer */ +4: + move.w d0,(a1)+ /* clear frame buffer */ + dbra d1,4b +5: + bset #0,0x018B(a0) /* switch to frame 1 */ + beq.b 5b + moveq #-1,d1 + lea 0x840000,a1 /* frame buffer */ +6: + move.w d0,(a1)+ /* clear frame buffer */ + dbra d1,6b + + moveq #0x7F,d1 + lea 0xA15200,a1 /* palette cram */ +7: + move.l d0,(a1)+ /* clear palette */ + dbra d1,7b + + move.l 0x0120(a0),d0 /* check Master SH2 */ + cmpi.l #0x53444552,d0 /* 'SDER' */ + bne.b 8f + move.w #0x2000,sr /* enable ints */ + moveq #-3,d0 /* sdram error */ + rts +8: + /* 32X initialized and ready, copy data/code to frame buffer */ + movea.l 4(sp),a0 /* data/code */ + move.l 8(sp),d0 /* length */ + lea 0x840000,a1 /* frame buffer */ +9: + move.l (a0)+,(a1)+ + subq.l #4,d0 + bgt.b 9b + + lea 0xA15000,a0 + move.l #0x5F43445F,0x0120(a0) /* Master SH2 CD handshake '_CD_' */ + +0: + move.l 0x0120(a0),d0 + cmpi.l #0x4D5F4F4B,d0 /* 'M_OK' */ + bne.b 0b +1: + move.l 0x0124(a0),d0 + cmpi.l #0x535F4F4B,d0 /* 'S_OK' */ + bne.b 1b + + moveq #1,d7 /* 32X initialized */ + moveq #0,d0 + move.l d0,0x0120(a0) /* start Master SH2 (which should start Slave SH2) */ + + move.w #0x2000,sr /* enable ints */ + rts + +| int get_comm32x(int offs, int len); +| return communications register value of len at offset offs +| entry: arg1 = offset from 0xA15120, arg2 = length of data (2 or 4 only) +| exit: d0 = communications register value or -1 (error) +get_comm32x: + tst.b d7 + bne.b 0f + moveq #-1,d0 /* 32X not initialized */ + rts +0: + move.l 8(sp),d1 /* length */ + cmpi.w #2,d1 + beq.b 2f + cmpi.w #4,d1 + beq.b 4f + moveq #-1,d0 /* bad parameter */ + rts +2: + move.l 4(sp),d1 /* offset */ + moveq #0,d0 + lea 0xA15120,a0 /* COMM0 */ + move.w 0(a0,d1.w),d0 + rts +4: + move.l 4(sp),d1 /* offset */ + lea 0xA15120,a0 /* COMM0 */ + move.l 0(a0,d1.w),d0 + rts + +| int set_comm32x(int offs, int len, int val); +| set communications register value of len at offset offs +| entry: arg1 = offset from 0xA15120, arg2 = length of data (2 or 4 only), arg3 = value +| exit: d0 = 0 (okay) or -1 (error) +set_comm32x: + tst.b d7 + bne.b 0f + moveq #-1,d0 /* 32X not initialized */ + rts +0: + moveq #0,d0 + move.l 8(sp),d1 /* length */ + cmpi.w #2,d1 + beq.b 2f + cmpi.w #4,d1 + beq.b 4f + moveq #-1,d0 /* bad parameter */ + rts +2: + move.l 4(sp),d1 /* offset */ + lea 0xA15120,a0 /* COMM0 */ + move.w 14(sp),0(a0,d1.w) /* val -> COMM0 + offset */ + rts +4: + move.l 4(sp),d1 /* offset */ + lea 0xA15120,a0 /* COMM0 */ + move.l 12(sp),0(a0,d1.w) /* val -> COMM0 + offset */ + rts + +| int dma_to_32x(short *src, int len); +| DMA data from source to 32X +| entry: arg1 = source pointer, arg2 = length of data (in words) +| exit: d0 = 0 (okay) or -1 (error) or -2 (DMA error) +dma_to_32x: + tst.b d7 + bne.b 0f + moveq #-1,d0 /* 32X not initialized */ + rts +0: + lea 0xA15000,a1 + move.b #0x00,0x0107(a1) /* clear 68S bit - stops SH DREQ */ + + movea.l 4(sp),a0 /* source address */ + move.l 8(sp),d0 /* length in words */ + addq.l #3,d0 + andi.w #0xFFFC,d0 /* FIFO operates on units of four words */ + move.w d0,0x0110(a1) /* SH DREQ Length Reg */ + move.w d0,0x0122(a1) /* COMM2 = # words to dma */ + lsr.l #2,d0 + subq.l #1,d0 /* for dbra */ + + move.b #0x04,0x0107(a1) /* set 68S bit - starts SH DREQ */ + lea 0x0112(a1),a1 +1: + cmpi.w #0x55AA,0xA15120 /* wait for SH2 to start DMA */ + bne.b 1b +2: + move.w (a0)+,(a1) /* FIFO = next word */ + move.w (a0)+,(a1) + move.w (a0)+,(a1) + move.w (a0)+,(a1) +3: + btst #7,0xA15107 /* check FIFO full flag */ + bne.b 3b + dbra d0,2b + + btst #2,0xA15107 + bne.b 4f /* DMA not done? */ + moveq #0,d0 + rts +4: + moveq #-2,d0 + rts + +| int cpy_to_32x(short *src, int len); +| copy data from source to 32X through COMM registers +| entry: arg1 = source pointer, arg2 = length of data (in words) +| exit: d0 = 0 (okay) or -1 (error) +cpy_to_32x: + tst.b d7 + bne.b 0f + moveq #-1,d0 /* 32X not initialized */ + rts +0: + movea.l 4(sp),a0 /* source pointer */ + move.l 8(sp),d0 /* length */ + subq.l #1,d0 /* for dbra */ +1: + move.w (a0)+,0xA15122 + move.w #3,0xA15120 /* xfer next word */ +2: + cmpi.w #1,0xA15120 + bne.b 2b + dbra d0,1b + + move.w #0xFFFF,0xA15120 /* xfer done */ +3: + cmpi.w #0,0xA15120 + bne.b 3b + + moveq #0,d0 + rts + + .align 4 + +| Vertical blank handler +vblank_int: + movem.l d0-d1/a0-a1,-(sp) + + pea 0.w + bsr.w get_pad + addq.l #4,sp + move.w d0,0xA12018 + tst.b d7 + beq.b 0f + move.w d0,0xA15128 +0: + pea 1.w + bsr.w get_pad + addq.l #4,sp + move.w d0,0xA1201A + tst.b d7 + beq.b 1f + move.w d0,0xA1512A +1: + move.l 0xA1201C,d0 + addq.l #1,d0 + move.l d0,0xA1201C /* increment ticks */ + tst.b d7 + beq.b 2f + move.l d0,0xA1512C +2: + move.w 0xA12000,d0 + ori.w #0x0100,d0 + move.w d0,0xA12000 /* generate level 2 int for CD */ + + movem.l (sp)+,d0-d1/a0-a1 + rte + + .align 4 +md_init_end: + + + .data + + .align 4 + + .global global_vars +global_vars: + .long 0 + +iso_pvd_magic: + .asciz "\1CD001\1" + + .align 4 + + .text diff --git a/main.c b/main.c new file mode 100644 index 0000000..3c420d2 --- /dev/null +++ b/main.c @@ -0,0 +1,308 @@ +#include +#include +#include +#include + +#include "hw_md.h" +#include "cdfh.h" +#include "module.h" + +#define MAX_ENTRIES 256 + +typedef struct +{ + int offset; + int length; + char flags; + char name[247]; +} entry_t; + +entry_t gDirTable[MAX_ENTRIES]; + +short gCount = 0; +short gFirst = 0; +short gCurrent = 0; +short gFilter = 1; +short gStage = 0; + +char *temp = (char *)0x0C0000; + +static char *flt_strings[] = { + "running average ", + "sample and hold ", + "exponential dropoff", +}; + +static char *err_strings[] = { + "", + "couldn't load MOD header", + "unrecognized or wrong # channels", + "couldn't allocate pattern buffer", + "couldn't read pattern data" +}; + +//---------------------------------------------------------------------- + +int getCurrDir(void) +{ + int r = 0; + int i = 0; + + while (!r) + { + r = next_dir_entry(); + if (!r && memcmp(global_vars->DENTRY_NAME, ".", 2)) + { + gDirTable[i].offset = global_vars->DENTRY_OFFSET; + gDirTable[i].length = global_vars->DENTRY_LENGTH; + gDirTable[i].flags = global_vars->DENTRY_FLAGS >> 8; + strcpy(gDirTable[i].name, global_vars->DENTRY_NAME); + i++; + } + + if (r == ERR_NO_MORE_ENTRIES) + r = next_dir_sec(); + } + + return i; +} + +int main(void) +{ + int i, j; + unsigned short buttons, changed, previous = 0; + Mod_t module; + CDFileHandle_t *handle; + + strcpy(temp, "MOD Player"); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_WHITE, 15, 1); + + strcpy(temp, "Resample filter: "); + strcat(temp, flt_strings[gFilter]); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 25); + + init_cd(); // init CD + set_cwd("/"); // set current directory to root + gCount = getCurrDir(); + + gStage = 0; + + while (1) + { + + if (gStage == 0) + { + + char *p = temp; + + // update display with directory entries + memset(p, 0, 20*64*2); + if (gCount) + for (i = 0; i < 20; i++) + { + if ((gFirst + i) >= gCount) + break; + + p = &temp[i*64*2 + 2]; // left justified + if (gDirTable[gFirst + i].flags & 2) + { + // directory + *p++ = ((gFirst + i) == gCurrent) ? TEXT_WHITE >> 8 : TEXT_GREEN >> 8; + *p++ = '[' - 0x20; + for (j = 0; j < 36; j++) + { + if (gDirTable[gFirst + i].name[j] == '\0') + break; + *p++ = ((gFirst + i) == gCurrent) ? TEXT_WHITE >> 8 : TEXT_GREEN >> 8; + *p++ = gDirTable[gFirst + i].name[j] - 0x20; + } + *p++ = ((gFirst + i) == gCurrent) ? TEXT_WHITE >> 8 : TEXT_GREEN >> 8; + *p++ = ']' - 0x20; + } + else + { + for (j = 0; j < 38; j++) + { + if (gDirTable[gFirst + i].name[j] == '\0') + break; + *p++ = ((gFirst + i) == gCurrent) ? TEXT_WHITE >> 8 : TEXT_GREEN >> 8; + *p++ = gDirTable[gFirst + i].name[j] - 0x20; + } + } + } + switch_banks(); + sub_delay(1); // wait until vblank to update vram + i = do_md_cmd3(MD_CMD_COPY_VRAM, 0xC000 + 3*64*2, 0x200000, 20*64); // update plane A name table (20 lines of text) + if (i < 0) + { + if (i == -1) + strcpy(temp, "unknown command"); + else if (i == -2) + strcpy(temp, "wait cmd done timeout"); + else if (i == -3) + strcpy(temp, "wait cmd ack timeout"); + else + strcpy(temp, "unknown error"); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_RED, 1, 24); + sub_delay(120); + strcpy(temp, " "); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 24); + } + + sub_delay(7); + + gStage = 1; + + } + else if (gStage == 1) + { + + buttons = GET_PAD(0); + + changed = (buttons & SEGA_CTRL_BUTTONS) ^ previous; + + if (buttons & SEGA_CTRL_DOWN) + { + // DOWN pressed + gCurrent++; + if ((gCurrent - gFirst) >= 20) + gFirst = gCurrent; + + if (gCurrent >= gCount) + gFirst = gCurrent = 0; // wrap around to first entry + + gStage = 0; + + } + + if (buttons & SEGA_CTRL_UP) + { + + // UP pressed + gCurrent--; + if (gCurrent < gFirst) + { + gFirst = gCurrent - 19; + if (gFirst < 0) + gFirst = 0; + } + + if (gCurrent < 0) + { + gCurrent = gCount - 1; // wrap around to last entry + gFirst = gCurrent - 19; + if (gFirst < 0) + gFirst = 0; + } + + gStage = 0; + + } + + if (!changed) + continue; // no change in buttons + previous = buttons & SEGA_CTRL_BUTTONS; + + if (changed & SEGA_CTRL_A) + { + if (!(buttons & SEGA_CTRL_A)) + { + // A just released + if (gDirTable[gCurrent].flags & 2) + { + // directory + global_vars->CWD_OFFSET = gDirTable[gCurrent].offset; + global_vars->CWD_LENGTH = gDirTable[gCurrent].length; + global_vars->CURR_OFFSET = -1; + first_dir_sec(); + gCount = getCurrDir(); + gFirst = gCurrent = 0; + gStage = 0; + } + else + { + + sprintf(temp, "Loading Mod\n"); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 24); + + handle = cd_handle_from_offset(gDirTable[gCurrent].offset, gDirTable[gCurrent].length); + + if (handle == NULL) + { + sprintf(temp, "failed to create cd handle\n"); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_RED, 1, 24); + while (1) ; + } + + /* load module */ + i = InitMOD(handle, &module, gFilter); + + if (!i) + { + /* start module */ + sprintf(temp, "Playing %s - %d chan", module.Title, module.NumberOfChannels); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 24); + + StartMOD(&module, 0); + sub_delay(20); + while (CheckMOD(&module)) + { + sub_delay(2); + buttons = GET_PAD(0); + changed = (buttons & SEGA_CTRL_BUTTONS) ^ previous; + if (!changed) + continue; // no change in buttons + previous = buttons & SEGA_CTRL_BUTTONS; + if (changed & SEGA_CTRL_C) + if (!(buttons & SEGA_CTRL_C)) + break; // C just released - stop playing + } + StopMOD(&module); + ExitMOD(&module); + delete_cd_handle(handle); + + strcpy(temp, " "); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 24); + } + else + { + sprintf(temp, "Error: %s\n", err_strings[i]); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_RED, 1, 24); + sub_delay(300); + strcpy(temp, " "); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 24); + } + } + } + } + + if (changed & SEGA_CTRL_MODE) + { + if (!(buttons & SEGA_CTRL_MODE)) + { + // MODE just released + gFilter = gFilter < 2 ? gFilter + 1: 0; + strcpy(temp, "Resample filter: "); + strcat(temp, flt_strings[gFilter]); + switch_banks(); + do_md_cmd4(MD_CMD_PUT_STR, 0x200000, TEXT_GREEN, 1, 25); + } + } + + } + + } + + return 0; +} + diff --git a/mod-info b/mod-info new file mode 100644 index 0000000..ab02b9d Binary files /dev/null and b/mod-info differ diff --git a/module-exp.c b/module-exp.c new file mode 100644 index 0000000..d7b9f39 --- /dev/null +++ b/module-exp.c @@ -0,0 +1,1429 @@ +/* + SegaCD MOD support code by Chilly Willy, based on + + Ami-PlayMOD_V1_0_20090417.c (c) 2009 Massimiliano Scarano mscarano@libero.it + + which had the following restriction: + If you use the source code, you should always mention me as the original author. +*/ + + +/* Includes ==================================================================================== */ + +#include +#include +#include +#include + +#include "hw_md.h" +#include "cdfh.h" +#include "pcm.h" +#include "module.h" + + +/* Defines ===================================================================================== */ + +/* define if handle 6 or 8 channel mods */ +#define HANDLE_EXTRA_CHANNELS +/* define if handle panning */ +//#define HANDLE_PANNING +/* define if want a little cross-talk between sides (only if no panning) */ +//#define CROSS_TALK 3 + + +/* Function prototypes ========================================================================= */ + +/* + Called by sound sub-system at 50 Hz (or BPM * 2 / 5) + returns 1 if not playing +*/ +uint16_t PlayMOD(Mod_t* Mod_p); + +static void UpdateNote(Mod_t* Mod_p, uint8_t* Data_p); +static void UpdateEffect(Mod_t* Mod_p); +static void DoVibrato(Mod_t* Mod_p, uint8_t Channel); +static void DoTremolo(Mod_t* Mod_p, uint8_t Channel); +static void DoPorta(Mod_t* Mod_p, uint8_t Channel); + +/* + System specific functions +*/ +static Mod_t* SetMOD(Mod_t* Mod_p); +static void SetSample(uint16_t Channel, uint8_t Sample, uint32_t SampleOffset); +static void SetFrequency(uint16_t Channel, uint32_t Period); +static void SetVolume(uint16_t Channel, uint32_t Volume, int32_t Pan); + + +/* Global variables ============================================================================ */ + +/* + From Amiga ProTracker playroutine source code + + - Amiga PERIODS represent delay times before fetching the next sample (in units of system clock ticks @ ~3.5 MHz) +*/ +static const uint16_t AmigaPeriodsTable[ MOD_FINE_TUNE_VALUES ][ MOD_NUMBER_OF_NOTES ] = +{ +/* ; Tuning 0, Normal */ + { 856,808,762,720,678,640,604,570,538,508,480,453, /* ; C-1 to B-1 */ + 428,404,381,360,339,320,302,285,269,254,240,226, /* ; C-2 to B-2 */ + 214,202,190,180,170,160,151,143,135,127,120,113 }, /* ; C-3 to B-3 */ +/* ; Tuning 1 */ + { 850,802,757,715,674,637,601,567,535,505,477,450, /* ; same as above */ + 425,401,379,357,337,318,300,284,268,253,239,225, /* ; but with */ + 213,201,189,179,169,159,150,142,134,126,119,113 }, /* ; finetune +1 */ +/* ; Tuning 2 */ + { 844,796,752,709,670,632,597,563,532,502,474,447, /* ; etc, */ + 422,398,376,355,335,316,298,282,266,251,237,224, /* ; finetune +2 */ + 211,199,188,177,167,158,149,141,133,125,118,112 }, +/* ; Tuning 3 */ + { 838,791,746,704,665,628,592,559,528,498,470,444, + 419,395,373,352,332,314,296,280,264,249,235,222, + 209,198,187,176,166,157,148,140,132,125,118,111 }, +/* ; Tuning 4 */ + { 832,785,741,699,660,623,588,555,524,495,467,441, + 416,392,370,350,330,312,294,278,262,247,233,220, + 208,196,185,175,165,156,147,139,131,124,117,110 }, +/* ; Tuning 5 */ + { 826,779,736,694,655,619,584,551,520,491,463,437, + 413,390,368,347,328,309,292,276,260,245,232,219, + 206,195,184,174,164,155,146,138,130,123,116,109 }, +/* ; Tuning 6 */ + { 820,774,730,689,651,614,580,547,516,487,460,434, + 410,387,365,345,325,307,290,274,258,244,230,217, + 205,193,183,172,163,154,145,137,129,122,115,109 }, +/* ; Tuning 7 */ + { 814,768,725,684,646,610,575,543,513,484,457,431, + 407,384,363,342,323,305,288,272,256,242,228,216, + 204,192,181,171,161,152,144,136,128,121,114,108 }, +/* ; Tuning -8 */ + { 907,856,808,762,720,678,640,604,570,538,508,480, + 453,428,404,381,360,339,320,302,285,269,254,240, + 226,214,202,190,180,170,160,151,143,135,127,120 }, +/* ; Tuning -7 */ + { 900,850,802,757,715,675,636,601,567,535,505,477, + 450,425,401,379,357,337,318,300,284,268,253,238, + 225,212,200,189,179,169,159,150,142,134,126,119 }, +/* ; Tuning -6 */ + { 894,844,796,752,709,670,632,597,563,532,502,474, + 447,422,398,376,355,335,316,298,282,266,251,237, + 223,211,199,188,177,167,158,149,141,133,125,118 }, +/* ; Tuning -5 */ + { 887,838,791,746,704,665,628,592,559,528,498,470, + 444,419,395,373,352,332,314,296,280,264,249,235, + 222,209,198,187,176,166,157,148,140,132,125,118 }, +/* ; Tuning -4 */ + { 881,832,785,741,699,660,623,588,555,524,494,467, + 441,416,392,370,350,330,312,294,278,262,247,233, + 220,208,196,185,175,165,156,147,139,131,123,117 }, +/* ; Tuning -3 */ + { 875,826,779,736,694,655,619,584,551,520,491,463, + 437,413,390,368,347,328,309,292,276,260,245,232, + 219,206,195,184,174,164,155,146,138,130,123,116 }, +/* ; Tuning -2 */ + { 868,820,774,730,689,651,614,580,547,516,487,460, + 434,410,387,365,345,325,307,290,274,258,244,230, + 217,205,193,183,172,163,154,145,137,129,122,115 }, +/* ; Tuning -1 */ + { 862,814,768,725,684,646,610,575,543,513,484,457, + 431,407,384,363,342,323,305,288,272,256,242,228, + 216,203,192,181,171,161,152,144,136,128,121,114 } +}; + + +/* + From Amiga ProTracker playroutine source code + + for Vibrato and Tremolo +*/ +static const uint8_t SineTable[ 32 ] = +{ + 0, 24, 49, 74, 97, 120, 141, 161, + 180, 197, 212, 224, 235, 244, 250, 253, + 255, 253, 250, 244, 235, 224, 212, 197, + 180, 161, 141, 120, 97, 74, 49, 24 +}; + + +/* + Global volume for playing Modules +*/ +static uint8_t gVolume = 16; + +static Mod_t *gMod = NULL; + +typedef struct voice { + uint8_t pending; + uint8_t vol; + uint8_t pan; + uint8_t start; + uint16_t offset; + uint16_t loop; + uint16_t period; +} voice_t; + +#define PENDING_START 1 +#define PENDING_STOP 2 +#define PENDING_VOL 4 +#define PENDING_PER 8 + +voice_t voices[8]; + +static void callback(void); + + +/* Module handler ============================================================================== */ + +/* + Initialize Module structure +*/ +uint8_t InitMOD(CDFileHandle_t *File_p, Mod_t* Mod_p) +{ + const uint8_t ModSignature[ MOD_SIGNATURE_LENGTH ] = {'M', '.', 'K', '.'}; + const uint8_t AltSignature[ MOD_SIGNATURE_LENGTH ] = {'4', 'C', 'H', 'N'}; +#ifdef HANDLE_EXTRA_CHANNELS + const uint8_t Mod6Signature[ MOD_SIGNATURE_LENGTH ] = {'6', 'C', 'H', 'N'}; + const uint8_t Mod8Signature[ MOD_SIGNATURE_LENGTH ] = {'8', 'C', 'H', 'N'}; +#endif + uint8_t Cnt, Iw, Ix, Iy; + uint8_t Sample; + uint8_t Order; + uint32_t Soffset, Iz; + uint8_t *Data_p = (uint8_t *)0x0C0000; /* use word ram for MOD processing */ + + memset( Mod_p, 0, sizeof(Mod_t) ); /* main data structure set to 0 */ + + /* load MOD header from CD */ + if (File_p->Read(File_p, Data_p, 1084) != 1084) + return 1; /* couldn't load MOD header */ + + /* check signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( ModSignature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 4; /* we have a 4 channel mod */ + } + + if ( !Mod_p->NumberOfChannels ) + { + /* check for alternate 4 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( AltSignature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 4; /* we have a 4 channel mod */ + } + } + +#ifdef HANDLE_EXTRA_CHANNELS + if ( !Mod_p->NumberOfChannels ) + { + /* check for 6 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( Mod6Signature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 6; /* we have a 6 channel mod */ + } + } + + if ( !Mod_p->NumberOfChannels ) + { + /* check for 8 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( Mod8Signature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 8; /* we have a 8 channel mod */ + } + } +#endif + + if ( !Mod_p->NumberOfChannels ) + { + return 2; /* not recognized, or not supported number of channels */ + } + + for ( Sample = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + { + uint8_t Byte1, Byte2; + + Byte1 = Data_p[ 20 + 22 + 30 * Sample ]; + Byte2 = Data_p[ 20 + 23 + 30 * Sample ]; + Mod_p->Inst[ Sample ].SampleLength = ( (Byte1 * 0x0100) + Byte2 ) * 2; + + Mod_p->Inst[ Sample ].FineTune = Data_p[ 20 + 24 + 30 * Sample ]; + + Mod_p->Inst[ Sample ].Volume = Data_p[ 20 + 25 + 30 * Sample ]; + + Byte1 = Data_p[ 20 + 26 + 30 * Sample ]; + Byte2 = Data_p[ 20 + 27 + 30 * Sample ]; + Mod_p->Inst[ Sample ].LoopStart = ( (Byte1 * 0x0100) + Byte2 ) * 2; + + Byte1 = Data_p[ 20 + 28 + 30 * Sample ]; + Byte2 = Data_p[ 20 + 29 + 30 * Sample ]; + Mod_p->Inst[ Sample ].LoopLength = ( (Byte1 * 0x0100) + Byte2 ) * 2; + } /* for Sample */ + + Mod_p->SongLength = Data_p[ 950 ]; /* this is the number of orders in a song */ + + /* get NumberOfPatterns */ + Mod_p->NumberOfPatterns = 0; + for ( Order = 0; Order < MOD_NUMBER_OF_ORDERS; Order++ ) + { + Mod_p->Orders[ Order ] = Data_p[ 952 + Order ]; + if ( Mod_p->Orders[ Order ] > Mod_p->NumberOfPatterns ) + { + Mod_p->NumberOfPatterns = Mod_p->Orders[ Order ]; + } + } /* for Order */ + + Mod_p->NumberOfPatterns += 1; /* [ 0 ; Mod_p->NumberOfPatterns ] */ + if (Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4 > 131072) + return 3; /* pattern data too big */ + + /* allocate memory for patterns */ + Mod_p->PatternsBuff_p = (uint8_t*)malloc(Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 6); + if (!Mod_p->PatternsBuff_p) + return 4; /* couldn't allocate pattern buffer */ + + /* Pattern data starts at offset 1084 - read and decode into pattern buffer */ + if ( File_p->Read(File_p, Data_p, Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4) != + (Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4) ) + { + free(Mod_p->PatternsBuff_p); + return 5; /* couldn't read pattern data */ + } + + Iz = 0; + for ( Ix = 0; Ix < Mod_p->NumberOfPatterns; Ix++ ) + { + for ( Iy = 0; Iy < MOD_ROWS_PER_CHANNEL; Iy++ ) + { + uint32_t *Div_p = (uint32_t *)&Data_p[(Ix * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4) + (Iy * Mod_p->NumberOfChannels * 4)]; + uint8_t Channel; + for ( Channel = 0; Channel < Mod_p->NumberOfChannels; Channel++ ) + { + uint32_t division; + uint8_t SampleNumber; + uint8_t EffectNumber; + uint8_t EffectParameter; + uint8_t NoteColumn; + uint16_t PeriodFrequency; + + /* Amiga-fied mess ;-) */ + division = Div_p[ Channel ]; + /* + +-------------------------------------+ + | Byte 0 Byte 1 Byte 2 Byte 3 | + |-------------------------------------| + |aaaaBBBB CCCCCCCCC DDDDeeee FFFFFFFFF| + +-------------------------------------+ + aaaaDDDD = sample number + BBBBCCCCCCCC = sample period value + eeee = effect number + FFFFFFFF = effect parameters + */ + SampleNumber = ((division >> 24) & 0x00F0) | ((division >> 12) & 0x000F); + PeriodFrequency = ((division >> 16) & 0x0FFF); + EffectNumber = (division >> 8) & 0x000F; + EffectParameter = division & 0x00FF; + NoteColumn = 0xFF; // note doesn't exist + /* look up table here */ + for ( Cnt = 0; Cnt < MOD_NUMBER_OF_NOTES; Cnt++ ) + { + if ( AmigaPeriodsTable[ 0 ][ Cnt ] == Mod_p->Notes[ Channel ].PeriodFrequency ) + { + NoteColumn = Cnt; // note exists + break; + } + } /* for Cnt */ + Mod_p->PatternsBuff_p[Iz + 0] = SampleNumber; + Mod_p->PatternsBuff_p[Iz + 1] = EffectNumber; + Mod_p->PatternsBuff_p[Iz + 2] = EffectParameter; + Mod_p->PatternsBuff_p[Iz + 3] = NoteColumn; + Mod_p->PatternsBuff_p[Iz + 4] = PeriodFrequency >> 8; + Mod_p->PatternsBuff_p[Iz + 5] = PeriodFrequency & 0x00FF; + Iz += 6; + } + } + } + + /* fit samples to sound ram */ + Iz = 0; + do + { + if (Iz > MOD_PCM_RAM) + { + // find largest sample and cut it in half + Iy = 0; + Iz = Mod_p->Inst[ 0 ].SampleLength >> Mod_p->Inst[ 0 ].Factor; + for ( Sample = 1; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + if ( (Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor) > Iz ) + { + Iy = Sample; + Iz = Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor; + } + Mod_p->Inst[ Iy ].Factor++; + } + + Iz = 0; + /* find total size of samples given factors */ + for ( Sample = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + Iz += ((Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor) + 32 + 255) & ~255; + } while (Iz > MOD_PCM_RAM); + + /* upload samples to sound ram */ + pcm_reset(); + for ( Sample = 0, Iw = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + { + if ( Mod_p->Inst[ Sample ].SampleLength > 0 ) + { + /* load sample data */ + if (Mod_p->Inst[ Sample ].Factor) + { + /* resample data */ + Iy = 0; + Soffset = Mod_p->Inst[ Sample ].SampleLength; + while (Soffset > 0) + { + for (Ix = 0, Iz = 0; (Ix < (1 << Mod_p->Inst[ Sample ].Factor)) && Soffset; Ix++, Soffset--) + Iz += File_p->Get(File_p); + Data_p[Iy++] = Iz >> Mod_p->Inst[ Sample ].Factor; + } + } + else + File_p->Read(File_p, Data_p, Mod_p->Inst[ Sample ].SampleLength); + + Mod_p->Inst[ Sample ].SampleHandle = Iw; + pcm_load_samples(Iw, (int8_t*)Data_p, Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor); + Iw = pcm_next_block(Iw, Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor); + } + else + { + Mod_p->Inst[ Sample ].SampleHandle = 0xFF; + } + } /* for Sample */ + + memset(voices, 0, sizeof(voices)); + + Mod_p->Speed = 6; /* default speed */ + Mod_p->Tick = 6; /* force immediate processing */ + Mod_p->Order = 0; /* default order */ + Mod_p->Row = 0; /* default row */ + Mod_p->BeatsPerMinute = 125; + Mod_p->IsPlaying = 0; + + return 0; /* no errors */ +} + + +/* + Cleanup MOD - stop playing and release voices +*/ +void ExitMOD(Mod_t* Mod_p) +{ + if (Mod_p) + { + if (Mod_p->IsPlaying) + StopMOD(Mod_p); + + /* quiet sound channels */ + pcm_reset(); + + free(Mod_p->PatternsBuff_p); + + /* make sure struct cannot be reused without init */ + memset(Mod_p, 0, sizeof(Mod_t)); + } +} + + +/* + Start playing Module + loop = bool = TRUE = loop forever, = FALSE = play once + returns the previously set Module, if any +*/ +Mod_t* StartMOD(Mod_t* Mod_p, uint8_t loop) +{ + if (Mod_p) + { + pcm_start_timer(&callback); + Mod_p->IsPlaying = loop ? 0x81 : 1; + return SetMOD(Mod_p); + } + return (Mod_t*)NULL; +} + + +/* + Pause/resume Module + pause = bool = TRUE = pause, = FALSE = resume +*/ +void PauseMOD(Mod_t* Mod_p, uint8_t pause) +{ + uint8_t i; + + if (Mod_p) + { + if ( pause ) + { + asm("move.w #0x2700,%sr"); + Mod_p->IsPlaying &= 0xFE; + for (i=0; iNumberOfChannels; i++) + { + pcm_set_ctrl(0x40 + i); + pcm_set_env(0); + } + pcm_set_ctrl(0xC0); + asm("move.w #0x2000,%sr"); + } + else + { + asm("move.w #0x2700,%sr"); + Mod_p->IsPlaying |= 1; + for (i=0; iNumberOfChannels; i++) + voices[i].pending |= PENDING_VOL; + asm("move.w #0x2000,%sr"); + } + } +} + + +/* + Stop playing Module +*/ +void StopMOD(Mod_t* Mod_p) +{ + if (Mod_p) + { + pcm_stop_timer(); + Mod_p->IsPlaying = 0; + SetMOD(0); + } +} + + +/* + Wait for Module to play through once +*/ +void WaitMOD(Mod_t* Mod_p) +{ + do + { + if (!Mod_p->IsPlaying) + break; + if (Mod_p->IsPlaying & 0x80) + if (!Mod_p->Row && !Mod_p->Order) + break; + } while (1); +} + + +/* + Check Module play status + returns 0 if done playing +*/ +uint8_t CheckMOD(Mod_t* Mod_p) +{ + if (Mod_p->IsPlaying & 0x80) + return (!Mod_p->Row && !Mod_p->Order) ? 0 : 1; + return Mod_p->IsPlaying; +} + + +/* + Set global volume + vol = 0 = off, = 16 = max + returns the previous volume setting +*/ +uint8_t VolumeMOD(uint8_t vol) +{ + uint8_t prev = gVolume; + gVolume = vol > 16 ? 16: vol; + return prev; +} + + +/* ++----------------------------------------------------------------------------+ +| === Playing the MOD === | ++----------------------------------------------------------------------------+ + + timer function, it contains the player logic, it has to be as fast as possible + + The SPEED of a song is the base on how your mod is played. + Each row of a pattern is updated every SPEED number of clock ticks. +*/ +uint16_t PlayMOD(Mod_t* Mod_p) +{ + Mod_p->Pattern = Mod_p->Orders[ Mod_p->Order ]; /* pattern for current order */ + + Mod_p->Tick++; + + if (Mod_p->Tick >= Mod_p->Speed) + { + if ( !Mod_p->PatternDelay ) /* if there is no pattern delay */ + { + UpdateNote( Mod_p, &Mod_p->PatternsBuff_p[ Mod_p->Pattern * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels + Mod_p->Row * Mod_p->NumberOfChannels * 6 ] ); /* process 1 note for every channel */ + Mod_p->Row += 1; /* increment row */ + } + else + { + Mod_p->PatternDelay--; /* else decrement pattern delay */ + } + + Mod_p->Tick = 0; + + if ( Mod_p->Row >= MOD_ROWS_PER_CHANNEL ) + { + /* process next pattern */ + Mod_p->Order++; + if (Mod_p->Order >= Mod_p->SongLength) + { + /* finished playing the mod */ + if (Mod_p->IsPlaying & 0x80) + { + /* loop - reset to defaults */ + Mod_p->Speed = 6; + Mod_p->Tick = 6; /* force immediate processing */ + Mod_p->Order = 0; + Mod_p->Row = 0; + Mod_p->BeatsPerMinute = 125; + } + else + { + Mod_p->IsPlaying = 0; /* song done playing */ + } + } + else + { + Mod_p->Pattern = Mod_p->Orders[ Mod_p->Order ]; + Mod_p->Row = 0; + } + } + } + else + { + Mod_p->Row -= 1; /* decrement row */ + /* update the tick based effects */ + UpdateEffect( Mod_p ); + Mod_p->Row += 1; /* increment row */ + } + + return Mod_p->IsPlaying ? 0 : 1; +} + + +/* + process division - 1 note for every channel +*/ +static void UpdateNote(Mod_t* Mod_p, uint8_t* Data_p) +{ + uint8_t Channel; /* counter */ + uint8_t EParX; /* Effect Parameter X */ + uint8_t EParY; /* Effect Parameter Y */ + uint8_t JumpFlag, BreakFlag; /* boolean flags for effects JUMP_TO_PATTERN and PATTERN_BREAK on the same row */ + + JumpFlag = FALSE; + BreakFlag = FALSE; + + for ( Channel = 0; Channel < Mod_p->NumberOfChannels; Channel++ ) + { + Mod_p->Notes[ Channel ].SampleNumber = Data_p[0]; + Mod_p->Notes[ Channel ].EffectNumber = Data_p[1]; + Mod_p->Notes[ Channel ].EffectParameter = Data_p[2]; + Mod_p->Notes[ Channel ].NoteExists = (Data_p[3] == 0xFF) ? FALSE : TRUE; + Mod_p->Notes[ Channel ].PeriodFrequencyCol = (Data_p[3] == 0xFF) ? 0 : Data_p[3]; + Mod_p->Notes[ Channel ].PeriodFrequency = (Data_p[4] << 8) + Data_p[5]; + + EParX = Mod_p->Notes[ Channel ].EffectParameter >> 4; + EParY = Mod_p->Notes[ Channel ].EffectParameter & 0xF; + + Mod_p->Channels[ Channel ].SampleOffset = 2; + + if ( Mod_p->Notes[ Channel ].SampleNumber > 0 ) + { + Mod_p->Channels[ Channel ].LastInstrument = Mod_p->Notes[ Channel ].SampleNumber - 1; + + if ( !( ( Mod_p->Notes[ Channel ].EffectNumber == EFFECT_EXTENDED_EFFECTS ) && ( EParX == EFFECT_DELAY_NOTE ) ) ) + { + Mod_p->Channels[ Channel ].Volume = Mod_p->Inst[ Mod_p->Notes[ Channel ].SampleNumber - 1 ].Volume; /* default volume of sample */ + } + } + + if ( Mod_p->Notes[ Channel ].NoteExists == TRUE ) + { + if ( ( Mod_p->Channels[ Channel ].WaveformControl & 0x0F ) < 4 ) + { + Mod_p->Channels[ Channel ].VibratoPosition = 0; + } + if ( ( Mod_p->Channels[ Channel ].WaveformControl >> 4 ) < 4 ) + { + Mod_p->Channels[ Channel ].TremoloPosition = 0; + } + + /* if not a porta effect, then set the channels frequency to the looked up amiga value + or - any finetune */ + if ( (Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_TO_NOTE) && + (Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_PLUS_VOL_SLIDE) ) + { + uint8_t FineTune; + + FineTune = Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].FineTune; + Mod_p->Channels[ Channel ].PeriodFrequency = AmigaPeriodsTable[ FineTune ][ Mod_p->Notes[ Channel ].PeriodFrequencyCol ]; + } /* if */ + + //Mod_p->Channels[ Channel ].SampleOffset = 2; + } /* if */ + + /* skip effects ? */ + if ( !( ( Mod_p->Notes[ Channel ].EffectNumber == 0 ) && ( Mod_p->Notes[ Channel ].EffectParameter == 0 ) ) ) + { + /* process the non tick based effects also grab parameters for tick based effects */ + switch ( Mod_p->Notes[ Channel ].EffectNumber ) + { + case EFFECT_ARPEGGIO: /* not processed on tick 0 */ + case EFFECT_PORTA_UP: /* not processed on tick 0 */ + case EFFECT_PORTA_DOWN: /* not processed on tick 0 */ + break; + + case EFFECT_PORTA_TO_NOTE: /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].EffectParameter > 0 ) + { + Mod_p->Channels[ Channel ].PortaSpeed = Mod_p->Notes[ Channel ].EffectParameter; + } + /* no break statement here */ + + case EFFECT_PORTA_PLUS_VOL_SLIDE: /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].NoteExists == TRUE ) + { + Mod_p->Channels[ Channel ].PortaTo = Mod_p->Notes[ Channel ].PeriodFrequency; + } + break; + + case EFFECT_VIBRATO: /* reviewed OK */ + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].VibratoSpeed = EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].VibratoDepth = EParY; + } + break; + + case EFFECT_VIBRATO_PLUS_VOL_SLIDE: /* not processed on tick 0 */ + break; + + case EFFECT_TREMOLO: /* reviewed OK */ + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].TremoloSpeed = EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].TremoloDepth = EParY; + } + break; + + case EFFECT_PAN: /* reviewed OK */ /* not a ProTracker effect */ + Mod_p->Channels[ Channel ].PanValue = Mod_p->Notes[ Channel ].EffectParameter; + break; + + case EFFECT_SAMPLE_OFFSET: /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].EffectParameter > 0 ) + { + Mod_p->Channels[ Channel ].SampleOffset = ( Mod_p->Notes[ Channel ].EffectParameter << 8 ); + } + if ( Mod_p->Channels[ Channel ].SampleOffset > Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].SampleLength ) + { + Mod_p->Channels[ Channel ].SampleOffset = Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].SampleLength; + /* this will result in silence */ + } + break; + + case EFFECT_VOLUME_SLIDE: /* not processed on tick 0 */ + break; + + case EFFECT_JUMP_TO_PATTERN: /* reviewed OK */ + Mod_p->Order = Mod_p->Notes[ Channel ].EffectParameter; + Mod_p->Row = -1; + if ( Mod_p->Order >= Mod_p->SongLength ) + { + Mod_p->Order = 0; + } +// if ( Mod_p->Order == 0 ) +// { + /* loop the mod */ + if (!(Mod_p->IsPlaying & 0x80)) + { + Mod_p->IsPlaying = 0; /* song done playing */ + return; + } +// } + JumpFlag = TRUE; + break; + + case EFFECT_SET_VOLUME: /* reviewed OK */ + Mod_p->Channels[ Channel ].Volume = Mod_p->Notes[ Channel ].EffectParameter; + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + break; + + case EFFECT_PATTERN_BREAK: /* reviewed OK */ + Mod_p->Row = ( EParX * 10 ) + EParY; /* get row number */ + if ( Mod_p->Row == 0 ) + { + Mod_p->Row = -1; + } + else if ( Mod_p->Row >= MOD_ROWS_PER_CHANNEL ) + { + Mod_p->Row = -1; + } + else + { + Mod_p->Row -= 1; + } + + if ( ! JumpFlag && ! BreakFlag ) + { + Mod_p->Order++; + + if ( Mod_p->Order >= Mod_p->SongLength ) + { + Mod_p->Order = 0; + /* repeat the mod */ + if (!(Mod_p->IsPlaying & 0x80)) + { + Mod_p->IsPlaying = 0; /* song done playing */ + return; + } + } + } + BreakFlag = TRUE; + break; + + case EFFECT_EXTENDED_EFFECTS: + switch (EParX) + { + case EFFECT_FINE_PORTA_UP: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency -= EParY; + break; + + case EFFECT_FINE_PORTA_DOWN: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency += EParY; + break; + + case EFFECT_GLISSANDO_CONTROL: /* not implemented not even in fmoddoc2 */ + break; + + case EFFECT_SET_VIBRATO_WAVEFORM: /* reviewed OK */ + Mod_p->Channels[ Channel ].WaveformControl &= 0xF0; + Mod_p->Channels[ Channel ].WaveformControl |= EParY; + break; + + case EFFECT_SET_FINETUNE: /* reviewed OK */ + Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].FineTune = EParY; + break; + + case EFFECT_PATTERN_LOOP: /* reviewed OK */ + if ( EParY == 0 ) + { + Mod_p->Channels[ Channel ].PatLoopRow = Mod_p->Row; + } + else + { + if ( ! Mod_p->Channels[ Channel ].PatLoopNo ) + { + Mod_p->Channels[ Channel ].PatLoopNo = EParY; + } + else + { + Mod_p->Channels[ Channel ].PatLoopNo--; + } + if ( Mod_p->Channels[ Channel ].PatLoopNo ) + { + Mod_p->Row = Mod_p->Channels[ Channel ].PatLoopRow - 1; + } + } + break; + + case EFFECT_SET_TREMOLO_WAVEFORM: /* reviewed OK */ + Mod_p->Channels[ Channel ].WaveformControl &= 0x0F; + Mod_p->Channels[ Channel ].WaveformControl |= (EParY << 4); + break; + + case EFFECT_POSITION_PANNING: /* cw - fixed */ + Mod_p->Channels[ Channel ].PanValue = (EParY << 4) | EParY; + break; + + case EFFECT_RETRIG_NOTE: /* not processed on tick 0 */ + break; + + case EFFECT_FINE_VOLUME_SLIDE_UP: /* reviewed OK */ + Mod_p->Channels[ Channel ].Volume += EParY; + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + break; + + case EFFECT_FINE_VOLUME_SLIDE_DOWN: /* reviewed OK */ + Mod_p->Channels[ Channel ].Volume -= EParY; + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + break; + + case EFFECT_CUT_NOTE: /* not processed on tick 0 */ + break; + + case EFFECT_DELAY_NOTE: /* not processed on tick 0 */ /* reviewed OK */ + continue; /* process next note */ + break; + + case EFFECT_PATTERN_DELAY: /* reviewed OK */ + Mod_p->PatternDelay = EParY; + break; + + default: + break; + } + break; + + case EFFECT_SET_SPEED: /* cw - update for bpm */ + if ( Mod_p->Notes[ Channel ].EffectParameter < 0x20 ) + { + if ( Mod_p->Notes[ Channel ].EffectParameter ) + { + Mod_p->Speed = Mod_p->Notes[ Channel ].EffectParameter; + } + } + else + { + Mod_p->BeatsPerMinute = Mod_p->Notes[ Channel ].EffectParameter; + } + break; + + default: + /* effect not defined */ + break; + } /* switch */ + } /* if */ + + if ( Mod_p->Notes[ Channel ].EffectNumber != EFFECT_SET_TREMOLO_WAVEFORM ) + { + /* set volume */ + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + } + + if ( Mod_p->Channels[ Channel ].PeriodFrequency > 0 ) + { + /* set frequency */ + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + } + + if ( Mod_p->Notes[ Channel ].NoteExists == TRUE ) + { + if ( ( Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_TO_NOTE ) && + ( Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_PLUS_VOL_SLIDE ) ) + { + /* set sample */ + SetSample( Channel, Mod_p->Channels[ Channel ].LastInstrument, Mod_p->Channels[ Channel ].SampleOffset ); + } + } /* if */ + } /* for Channel */ + + return; +} + + +/* + update tick based effects +*/ +static void UpdateEffect(Mod_t* Mod_p) +{ + uint8_t Channel; /* counter */ + uint8_t EParX; /* Effect Parameter X */ + uint8_t EParY; /* Effect Parameter Y */ + uint8_t Tick = Mod_p->Tick; /* current music tick */ + + for ( Channel = 0; Channel < Mod_p->NumberOfChannels; Channel++ ) + { + EParX = Mod_p->Notes[ Channel ].EffectParameter >> 4; + EParY = Mod_p->Notes[ Channel ].EffectParameter & 0xF; + + switch ( Mod_p->Notes[ Channel ].EffectNumber ) + { + + case EFFECT_ARPEGGIO: /* effect used in chip tunes */ /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].EffectParameter > 0 ) + { + switch ( Tick % 3 ) + { + case 0: + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + break; + + case 1: + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency + EParX ); + break; + + case 2: + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency + EParY ); + break; + } /* switch */ + } /* if */ + break; + + case EFFECT_PORTA_UP: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency -= Mod_p->Notes[ Channel ].EffectParameter; + if ( Mod_p->Channels[ Channel ].PeriodFrequency < 113 ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = 113; /* stop at B-3 */ + } + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + break; + + case EFFECT_PORTA_DOWN: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency += Mod_p->Notes[ Channel ].EffectParameter; + if ( Mod_p->Channels[ Channel ].PeriodFrequency > 856 ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = 856; /* stop at C-1 */ + } + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + break; + + case EFFECT_PORTA_TO_NOTE: /* reviewed OK */ + DoPorta( Mod_p, Channel ); + break; + + case EFFECT_VIBRATO: /* reviewed OK */ + DoVibrato( Mod_p, Channel ); + break; + + case EFFECT_PORTA_PLUS_VOL_SLIDE: /* reviewed OK */ + DoPorta( Mod_p, Channel ); + + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].Volume += EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].Volume -= EParY; + } + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + break; + + case EFFECT_VIBRATO_PLUS_VOL_SLIDE: /* reviewed OK */ + DoVibrato( Mod_p, Channel ); + + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].Volume += EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].Volume -= EParY; + } + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + break; + + case EFFECT_TREMOLO: /* reviewed OK */ + DoTremolo( Mod_p, Channel ); + break; + + case EFFECT_VOLUME_SLIDE: /* reviewed OK */ + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].Volume += EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].Volume -= EParY; + } + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + break; + + case EFFECT_EXTENDED_EFFECTS: + switch (EParX) + { + case EFFECT_RETRIG_NOTE: /* I am not 100% sure about this effect */ + if ( !EParY ) + { + break; + } + if ( !( Tick % EParY ) ) + { + SetSample( Channel, Mod_p->Channels[ Channel ].LastInstrument, Mod_p->Channels[ Channel ].SampleOffset ); + } + break; + + case EFFECT_CUT_NOTE: /* reviewed OK */ + if ( Tick == EParY ) + { + Mod_p->Channels[ Channel ].Volume = 0; + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + } + break; + + case EFFECT_DELAY_NOTE: /* I am not 100% sure about this effect */ + if ( Tick == EParY ) + { + if ( Mod_p->Notes[ Channel ].SampleNumber > 0 ) + { + Mod_p->Channels[ Channel ].Volume = Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].Volume; + } + if ( Mod_p->Inst[ Mod_p->Notes[ Channel ].SampleNumber - 1 ].Volume <= 64 ) + { + Mod_p->Channels[ Channel ].Volume = Mod_p->Inst[ Mod_p->Notes[ Channel ].SampleNumber - 1 ].Volume; + } + + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + SetSample( Channel, Mod_p->Channels[ Channel ].LastInstrument, Mod_p->Channels[ Channel ].SampleOffset ); + } /* if */ + break; + + default: + break; + } + break; + + default: + break; + } /* switch */ + } /* for Channel */ + + return; +} + + +/* +*/ +static void DoVibrato(Mod_t* Mod_p, uint8_t Channel) +{ + uint16_t Delta = 0; + uint8_t Temp; + + Temp = ( Mod_p->Channels[ Channel ].VibratoPosition & 31 ); /* Temp will be the index */ + + switch ( Mod_p->Channels[ Channel ].WaveformControl & 3 ) + { + case 0: + Delta = SineTable[ Temp ]; /* look up sine table */ + break; + + case 1: + Temp <<= 3; /* ramp down */ + if ( Mod_p->Channels[ Channel ].VibratoPosition < 0 ) + { + Temp = 255 - Temp; + } + Delta = Temp; + break; + + case 2: + Delta = 255; /* square */ + break; + + case 3: + Delta = rand() & 255; /* random */ + break; + } /* switch */ + + Delta *= Mod_p->Channels[ Channel ].VibratoDepth; + Delta >>= 7; + //Delta <<= 2; /* we use 4*periods so make vibrato 4 times bigger */ + + if ( Mod_p->Channels[ Channel ].VibratoPosition >= 0 ) + { + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency + Delta ); + } + else + { + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency - Delta ); + } + + Mod_p->Channels[ Channel ].VibratoPosition += Mod_p->Channels[ Channel ].VibratoSpeed; + if ( Mod_p->Channels[ Channel ].VibratoPosition > 31 ) + { + Mod_p->Channels[ Channel ].VibratoPosition -= 64; + } + + return; +} + + +/* +*/ +static void DoTremolo(Mod_t* Mod_p, uint8_t Channel) +{ + uint16_t Delta; + uint8_t Temp; + + Temp = ( Mod_p->Channels[ Channel ].TremoloPosition & 31 ); /* Temp will be the index */ + + switch ( ( Mod_p->Channels[ Channel ].WaveformControl >> 4 ) & 3 ) + { + case 0: + Delta = SineTable[ Temp ]; /* look up sine table */ + break; + + case 1: + Temp <<= 3; /* ramp down */ + if ( Mod_p->Channels[ Channel ].TremoloPosition < 0 ) + { + Temp = 255 - Temp; + } + Delta = Temp; + break; + + case 2: + Delta = 255; /* square */ + break; + + case 3: + Delta = rand() & 255; /* random */ + break; + } /* switch */ + + Delta *= Mod_p->Channels[ Channel ].TremoloDepth; + Delta >>= 6; + + if ( Mod_p->Channels[ Channel ].TremoloPosition >= 0 ) + { + if ( Mod_p->Channels[ Channel ].Volume + Delta > 64 ) + { + Delta = 64 - Mod_p->Channels[ Channel ].Volume; + } + + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume + Delta, Mod_p->Channels[ Channel ].PanValue ); + } + else + { + if ( (int16_t) ( Mod_p->Channels[ Channel ].Volume - Delta < 0 ) ) + { + Delta = Mod_p->Channels[ Channel ].Volume; + } + + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume - Delta, Mod_p->Channels[ Channel ].PanValue ); + } + + Mod_p->Channels[ Channel ].TremoloPosition += Mod_p->Channels[ Channel ].TremoloSpeed; + if ( Mod_p->Channels[ Channel ].TremoloPosition > 31 ) + { + Mod_p->Channels[ Channel ].TremoloPosition -= 64; + } + + return; +} + + +/* +*/ +static void DoPorta(Mod_t* Mod_p, uint8_t Channel) +{ + /* slide pitch down if it needs too */ + if ( Mod_p->Channels[ Channel ].PeriodFrequency < Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency += Mod_p->Channels[ Channel ].PortaSpeed; + if ( Mod_p->Channels[ Channel ].PeriodFrequency > Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = Mod_p->Channels[ Channel ].PortaTo; + } + } + + /* slide pitch up if it needs too */ + if ( Mod_p->Channels[ Channel ].PeriodFrequency > Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency -= Mod_p->Channels[ Channel ].PortaSpeed; + if ( Mod_p->Channels[ Channel ].PeriodFrequency < Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = Mod_p->Channels[ Channel ].PortaTo; + } + } + + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + + return; +} + + +/* System specific functions =================================================================== */ + + +static Mod_t* SetMOD(Mod_t* Mod_p) +{ + Mod_t *pmod = gMod; + /* once set, PlayMOD() starts being called */ + gMod = Mod_p; + return pmod; +} + + +static void SetSample(uint16_t Channel, uint8_t Sample, uint32_t SampleOffset) +{ + uint16_t loop = gMod->Inst[ Sample ].LoopStart >> gMod->Inst[ Sample ].Factor; + voices[Channel].start = gMod->Inst[ Sample ].SampleHandle; + voices[Channel].offset = SampleOffset >> gMod->Inst[ Sample ].Factor; + voices[Channel].loop = loop ? loop : (gMod->Inst[ Sample ].SampleLength >> gMod->Inst[ Sample ].Factor) - 1; + voices[Channel].pending |= PENDING_START; + voices[Channel].pending &= ~PENDING_STOP; +} + + +static void SetFrequency(uint16_t Channel, uint32_t Period) +{ + voices[Channel].period = Period; + voices[Channel].pending |= PENDING_PER; +} + + +static void SetVolume(uint16_t Channel, uint32_t Volume, int32_t Pan) +{ + uint16_t left, right; + uint16_t env, pan; + uint16_t chan = Channel & 3; + + env = (Volume * gVolume) >> 2; // 0 to 256 + if (env > 255) + env = 255; // clamp to 255 + +#ifdef HANDLE_PANNING + pan = pcm_lcf(Pan); // Linear Cross Fade pan value +#else + if ( (chan == 0) || (chan == 3) ) + { +#ifdef CROSS_TALK + right = 15 >> CROSS_TALK; +#else + right = 0; +#endif + left = 15; + } + else + { + right = 15; +#ifdef CROSS_TALK + left = 15 >> CROSS_TALK; +#else + left = 0; +#endif + } + pan = (right << 4) | left; +#endif + + voices[Channel].vol = env; + voices[Channel].pan = pan; + voices[Channel].pending |= PENDING_VOL; +} + + +static void callback(void) +{ + static uint16_t tempo = 0; + uint8_t i; + + if (gMod) + PlayMOD(gMod); + + if (tempo != gMod->BeatsPerMinute) + { + tempo = gMod->BeatsPerMinute; + pcm_set_timer(tempo); + } + + /* update channels */ + for (i = 0; i < gMod->NumberOfChannels; i++) + { + pcm_set_ctrl(0x40 + i); // turn off pcm chip briefly and select channel + + if (voices[i].pending & PENDING_STOP) + { + pcm_set_off(i); + pcm_set_env(0); + voices[i].pending &= ~PENDING_STOP; + } + + if (voices[i].pending & PENDING_START) + { + pcm_set_off(i); + pcm_set_start(voices[i].start, voices[i].offset); + pcm_set_loop(voices[i].loop); + voices[i].pending |= PENDING_VOL|PENDING_PER; + } + + if (voices[i].pending & PENDING_VOL) + { + pcm_set_env(voices[i].vol); + pcm_set_pan(voices[i].pan); + voices[i].pending &= ~PENDING_VOL; + } + + if (voices[i].pending & PENDING_PER) + { + pcm_set_env(voices[i].period); + voices[i].pending &= ~PENDING_PER; + } + + if (voices[i].pending & PENDING_START) + { + pcm_set_on(i); + voices[i].pending &= ~PENDING_START; + } + } + pcm_set_ctrl(0xC0); // restart pcm chip +} diff --git a/module.c b/module.c new file mode 100644 index 0000000..0aae225 --- /dev/null +++ b/module.c @@ -0,0 +1,1597 @@ +/* + SegaCD MOD support code by Chilly Willy, based on + + Ami-PlayMOD_V1_0_20090417.c (c) 2009 Massimiliano Scarano mscarano@libero.it + + which had the following restriction: + If you use the source code, you should always mention me as the original author. +*/ + + +/* Includes ==================================================================================== */ + +#include +#include +#include +#include + +#include "hw_md.h" +#include "cdfh.h" +#include "pcm.h" +#include "module.h" + + +/* Defines ===================================================================================== */ + +/* define if handle 6 or 8 channel mods */ +#define HANDLE_EXTRA_CHANNELS +/* define if handle panning */ +#define HANDLE_PANNING +/* define if want a little cross-talk between sides (only if no panning) */ +//#define CROSS_TALK 240 + + +/* Function prototypes ========================================================================= */ + +/* + Called by sound sub-system at 50 Hz (or BPM * 2 / 5) + returns 1 if not playing +*/ +uint16_t PlayMOD(Mod_t* Mod_p); + +static void UpdateNote(Mod_t* Mod_p, uint32_t* Div_p); +static void UpdateEffect(Mod_t* Mod_p); +static void DoVibrato(Mod_t* Mod_p, uint8_t Channel); +static void DoTremolo(Mod_t* Mod_p, uint8_t Channel); +static void DoPorta(Mod_t* Mod_p, uint8_t Channel); + +/* + System specific functions +*/ +static Mod_t* SetMOD(Mod_t* Mod_p); +static void SetSample(uint16_t Channel, uint8_t Sample); +static void SetFrequency(uint16_t Channel, uint32_t Period); +static void SetVolume(uint16_t Channel, uint32_t Volume, int32_t Pan); + + +/* Global variables ============================================================================ */ + +/* + From Amiga ProTracker playroutine source code + + - Amiga PERIODS represent delay times before fetching the next sample (in units of system clock ticks @ ~3.5 MHz) +*/ +static const uint16_t AmigaPeriodsTable[ MOD_FINE_TUNE_VALUES ][ MOD_NUMBER_OF_NOTES ] = +{ +/* ; Tuning 0, Normal */ + { 856,808,762,720,678,640,604,570,538,508,480,453, /* ; C-1 to B-1 */ + 428,404,381,360,339,320,302,285,269,254,240,226, /* ; C-2 to B-2 */ + 214,202,190,180,170,160,151,143,135,127,120,113 }, /* ; C-3 to B-3 */ +/* ; Tuning 1 */ + { 850,802,757,715,674,637,601,567,535,505,477,450, /* ; same as above */ + 425,401,379,357,337,318,300,284,268,253,239,225, /* ; but with */ + 213,201,189,179,169,159,150,142,134,126,119,113 }, /* ; finetune +1 */ +/* ; Tuning 2 */ + { 844,796,752,709,670,632,597,563,532,502,474,447, /* ; etc, */ + 422,398,376,355,335,316,298,282,266,251,237,224, /* ; finetune +2 */ + 211,199,188,177,167,158,149,141,133,125,118,112 }, +/* ; Tuning 3 */ + { 838,791,746,704,665,628,592,559,528,498,470,444, + 419,395,373,352,332,314,296,280,264,249,235,222, + 209,198,187,176,166,157,148,140,132,125,118,111 }, +/* ; Tuning 4 */ + { 832,785,741,699,660,623,588,555,524,495,467,441, + 416,392,370,350,330,312,294,278,262,247,233,220, + 208,196,185,175,165,156,147,139,131,124,117,110 }, +/* ; Tuning 5 */ + { 826,779,736,694,655,619,584,551,520,491,463,437, + 413,390,368,347,328,309,292,276,260,245,232,219, + 206,195,184,174,164,155,146,138,130,123,116,109 }, +/* ; Tuning 6 */ + { 820,774,730,689,651,614,580,547,516,487,460,434, + 410,387,365,345,325,307,290,274,258,244,230,217, + 205,193,183,172,163,154,145,137,129,122,115,109 }, +/* ; Tuning 7 */ + { 814,768,725,684,646,610,575,543,513,484,457,431, + 407,384,363,342,323,305,288,272,256,242,228,216, + 204,192,181,171,161,152,144,136,128,121,114,108 }, +/* ; Tuning -8 */ + { 907,856,808,762,720,678,640,604,570,538,508,480, + 453,428,404,381,360,339,320,302,285,269,254,240, + 226,214,202,190,180,170,160,151,143,135,127,120 }, +/* ; Tuning -7 */ + { 900,850,802,757,715,675,636,601,567,535,505,477, + 450,425,401,379,357,337,318,300,284,268,253,238, + 225,212,200,189,179,169,159,150,142,134,126,119 }, +/* ; Tuning -6 */ + { 894,844,796,752,709,670,632,597,563,532,502,474, + 447,422,398,376,355,335,316,298,282,266,251,237, + 223,211,199,188,177,167,158,149,141,133,125,118 }, +/* ; Tuning -5 */ + { 887,838,791,746,704,665,628,592,559,528,498,470, + 444,419,395,373,352,332,314,296,280,264,249,235, + 222,209,198,187,176,166,157,148,140,132,125,118 }, +/* ; Tuning -4 */ + { 881,832,785,741,699,660,623,588,555,524,494,467, + 441,416,392,370,350,330,312,294,278,262,247,233, + 220,208,196,185,175,165,156,147,139,131,123,117 }, +/* ; Tuning -3 */ + { 875,826,779,736,694,655,619,584,551,520,491,463, + 437,413,390,368,347,328,309,292,276,260,245,232, + 219,206,195,184,174,164,155,146,138,130,123,116 }, +/* ; Tuning -2 */ + { 868,820,774,730,689,651,614,580,547,516,487,460, + 434,410,387,365,345,325,307,290,274,258,244,230, + 217,205,193,183,172,163,154,145,137,129,122,115 }, +/* ; Tuning -1 */ + { 862,814,768,725,684,646,610,575,543,513,484,457, + 431,407,384,363,342,323,305,288,272,256,242,228, + 216,203,192,181,171,161,152,144,136,128,121,114 } +}; + + +/* + From Amiga ProTracker playroutine source code + + for Vibrato and Tremolo +*/ +static const uint8_t SineTable[ 32 ] = +{ + 0, 24, 49, 74, 97, 120, 141, 161, + 180, 197, 212, 224, 235, 244, 250, 253, + 255, 253, 250, 244, 235, 224, 212, 197, + 180, 161, 141, 120, 97, 74, 49, 24 +}; + + +/* + Global volume for playing Modules +*/ +static uint8_t gVolume = 12; + +static Mod_t *gMod = NULL; + +typedef struct voice { + uint8_t pending; + uint8_t vol; + uint8_t pan; + uint8_t start; + uint16_t offset; + uint16_t loop; + uint32_t period; + uint8_t factor; +} voice_t; + +#define PENDING_START 1 +#define PENDING_STOP 2 +#define PENDING_VOL 4 +#define PENDING_PER 8 + +voice_t voices[8]; + +static void callback(void); + + +/* Module handler ============================================================================== */ + +/* + Initialize Module structure +*/ +uint8_t InitMOD(CDFileHandle_t *File_p, Mod_t* Mod_p, uint8_t filter) +{ + const uint8_t ModSignature[ MOD_SIGNATURE_LENGTH ] = {'M', '.', 'K', '.'}; + const uint8_t AltSignature[ MOD_SIGNATURE_LENGTH ] = {'4', 'C', 'H', 'N'}; + const uint8_t FltSignature[ MOD_SIGNATURE_LENGTH ] = {'F', 'L', 'T', '4'}; +#ifdef HANDLE_EXTRA_CHANNELS + const uint8_t Mod6Signature[ MOD_SIGNATURE_LENGTH ] = {'6', 'C', 'H', 'N'}; + const uint8_t Mod8Signature[ MOD_SIGNATURE_LENGTH ] = {'8', 'C', 'H', 'N'}; +#endif + uint8_t Cnt, Iw, Ix; + uint8_t Sample; + uint8_t Order; + uint32_t Soffset, Iy, Iz; + uint8_t *Data_p = (uint8_t *)0x0C0000; /* use word ram for MOD processing */ + + memset( Mod_p, 0, sizeof(Mod_t) ); /* main data structure set to 0 */ + + /* load MOD header from CD */ + if (File_p->Read(File_p, Data_p, 1084) != 1084) + return 1; /* couldn't load MOD header */ + + /* check signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( ModSignature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 4; /* we have a 4 channel mod */ + } + + if ( !Mod_p->NumberOfChannels ) + { + /* check for alternate 4 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( AltSignature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 4; /* we have a 4 channel mod */ + } + } + + if ( !Mod_p->NumberOfChannels ) + { + /* check for StarTrekker 4 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( FltSignature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 4; /* we have a 4 channel mod */ + } + } + +#ifdef HANDLE_EXTRA_CHANNELS + if ( !Mod_p->NumberOfChannels ) + { + /* check for 6 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( Mod6Signature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 6; /* we have a 6 channel mod */ + } + } + + if ( !Mod_p->NumberOfChannels ) + { + /* check for 8 channel signature */ + for ( Cnt = 0; Cnt < MOD_SIGNATURE_LENGTH; Cnt++ ) + { + if ( Mod8Signature[ Cnt ] != Data_p[ Cnt + MOD_SIGNATURE_OFFSET ] ) + { + break; /* no match */ + } + } + if ( MOD_SIGNATURE_LENGTH == Cnt ) + { + Mod_p->NumberOfChannels = 8; /* we have a 8 channel mod */ + } + } +#endif + + if ( !Mod_p->NumberOfChannels ) + { + return 2; /* not recognized, or not supported number of channels */ + } + + memcpy(Mod_p->Title, Data_p, 20); /* copy the module title */ + if (!Mod_p->Title[0]) + strcpy(Mod_p->Title, "untitled"); + + for ( Sample = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + { + uint8_t Byte1, Byte2; + + Byte1 = Data_p[ 20 + 22 + 30 * Sample ]; + Byte2 = Data_p[ 20 + 23 + 30 * Sample ]; + Mod_p->Inst[ Sample ].SampleLength = ( (Byte1 * 0x0100) + Byte2 ) * 2; + + Mod_p->Inst[ Sample ].FineTune = Data_p[ 20 + 24 + 30 * Sample ]; + + Mod_p->Inst[ Sample ].Volume = Data_p[ 20 + 25 + 30 * Sample ]; + + Byte1 = Data_p[ 20 + 26 + 30 * Sample ]; + Byte2 = Data_p[ 20 + 27 + 30 * Sample ]; + Mod_p->Inst[ Sample ].LoopStart = ( (Byte1 * 0x0100) + Byte2 ) * 2; + + Byte1 = Data_p[ 20 + 28 + 30 * Sample ]; + Byte2 = Data_p[ 20 + 29 + 30 * Sample ]; + Mod_p->Inst[ Sample ].LoopLength = ( (Byte1 * 0x0100) + Byte2 ) * 2; + } /* for Sample */ + + Mod_p->SongLength = Data_p[ 950 ]; /* this is the number of orders in a song */ + + /* get NumberOfPatterns */ + Mod_p->NumberOfPatterns = 0; + for ( Order = 0; Order < MOD_NUMBER_OF_ORDERS; Order++ ) + { + Mod_p->Orders[ Order ] = Data_p[ 952 + Order ]; + if ( Mod_p->Orders[ Order ] > Mod_p->NumberOfPatterns ) + { + Mod_p->NumberOfPatterns = Mod_p->Orders[ Order ]; + } + } /* for Order */ + + Mod_p->NumberOfPatterns += 1; /* [ 0 ; Mod_p->NumberOfPatterns ] */ + + /* allocate memory for patterns */ + Mod_p->PatternsBuff_p = (uint32_t*)malloc(Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4); + if (!Mod_p->PatternsBuff_p) + return 3; /* couldn't allocate pattern buffer */ + + /* Pattern data starts at offset 1084 - read into pattern buffer */ + if ( File_p->Read(File_p, Mod_p->PatternsBuff_p, Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4) != (Mod_p->NumberOfPatterns * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels * 4) ) + { + free(Mod_p->PatternsBuff_p); + return 4; /* couldn't read pattern data */ + } + +#ifdef PROC_PATTERNS + { + uint8_t Pattern, Row, Chan; + for (Pattern = 0; Pattern < Mod_p->NumberOfPatterns; Pattern++) + { + for (Row = 0; Row < MOD_ROWS_PER_CHANNEL; Row++) + { + uint32_t *Div_p = &Mod_p->PatternsBuff_p[ Pattern * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels + Row * Mod_p->NumberOfChannels ]; + + for ( Chan = 0; Chan < Mod_p->NumberOfChannels; Chan++ ) + { + uint32_t division; + uint16_t PeriodFrequency; + uint8_t Cnt; + + /* Amiga-fied mess ;-) */ + division = Div_p[ Chan ]; + /* + +-------------------------------------+ + | Byte 0 Byte 1 Byte 2 Byte 3 | + |-------------------------------------| + |aaaaBBBB CCCCCCCCC DDDDeeee FFFFFFFFF| + +-------------------------------------+ + aaaaDDDD = sample number + BBBBCCCCCCCC = sample period value + eeee = effect number + FFFFFFFF = effect parameters + */ + PeriodFrequency = ((division >> 16) & 0x0FFF); + + /* look up table here */ + for ( Cnt = 0; Cnt < MOD_NUMBER_OF_NOTES; Cnt++ ) + { + if ( AmigaPeriodsTable[ 0 ][ Cnt ] == PeriodFrequency ) + { + PeriodFrequency = Cnt + 4060; + /* insert new period */ + division &= 0xF000FFFF; + division |= PeriodFrequency << 16; + Div_p[ Chan ] = division; + break; + } + } + } + } + } + } +#endif + + /* fit samples to sound ram */ + Ix = Iz = 0; + do + { + if (Iz > MOD_PCM_RAM) + { + // find largest sample and cut it in half + Iy = 0xFFFFFFFF; + Iz = 0; + for ( Sample = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + { + if ((Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor) <= (1024 - 32)) + continue; // skip small samples + if (Mod_p->Inst[ Sample ].Factor != Ix) + continue; // skip while some samples still not resampled + if ((Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor) > Iz) + { + Iy = Sample; + Iz = Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor; + } + } + if (Iy == 0xFFFFFFFF) + Ix++; // next level of resampling + else + Mod_p->Inst[ Iy ].Factor++; + } + + Iz = 0; + /* find total size of samples given factors */ + for ( Sample = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + Iz += ((Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor) + 32 + 255) & ~255; + } while (Iz > MOD_PCM_RAM); + + /* upload samples to sound ram */ + pcm_reset(); + for ( Sample = 0, Iw = 0; Sample < MOD_NUMBER_OF_SAMPLES; Sample++ ) + { + if ( Mod_p->Inst[ Sample ].SampleLength > 0 ) + { + /* load sample data */ + File_p->Read(File_p, Data_p, Mod_p->Inst[ Sample ].SampleLength); + if (Mod_p->Inst[ Sample ].Factor) + { + /* resample data */ + if (filter == 1) + { + /* filter coeff = .25, .5, .25 */ + for (Ix = 0; Ix < Mod_p->Inst[ Sample ].Factor; Ix++) + { + int16_t s = 0, a = 0; + Iy = Iz = 0; + Soffset = Mod_p->Inst[ Sample ].SampleLength >> Ix; + while (Soffset <= Mod_p->Inst[ Sample ].SampleLength) + { + s = a + ((int8_t)Data_p[Iy++] >> 1); + a = (int8_t)Data_p[Iy++] >> 2; + Data_p[Iz++] = s + a; + Soffset -= 2; + } + } + } + else if (filter == 2) + { + /* filter coeff = .5, .25, .125 */ + for (Ix = 0; Ix < Mod_p->Inst[ Sample ].Factor; Ix++) + { + int16_t a = 0; + Iy = Iz = 0; + Soffset = Mod_p->Inst[ Sample ].SampleLength >> Ix; + while (Soffset <= Mod_p->Inst[ Sample ].SampleLength) + { + a = (a + (int8_t)Data_p[Iy++]) >> 1; + Data_p[Iz++] = a; + a = (a + (int8_t)Data_p[Iy++]) >> 1; + Soffset -= 2; + } + } + } + else + { + /* regular downsample by N (running average) */ + Iy = Iz = 0; + Soffset = Mod_p->Inst[ Sample ].SampleLength; + while (Soffset > 0) + { + int16_t s = 0; + for (Ix = 0; (Ix < (1 << Mod_p->Inst[ Sample ].Factor)) && Soffset; Ix++, Soffset--) + s += (int8_t)Data_p[Iy++]; + Data_p[Iz++] = s >> Mod_p->Inst[ Sample ].Factor; + } + } + } + + Mod_p->Inst[ Sample ].SampleHandle = Iw; + pcm_load_samples(Iw, (int8_t*)Data_p, Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor); + Iw = pcm_next_block(Iw, Mod_p->Inst[ Sample ].SampleLength >> Mod_p->Inst[ Sample ].Factor); + } + else + { + Mod_p->Inst[ Sample ].SampleHandle = 0xFF; + } + } /* for Sample */ + Mod_p->NextBlock = Iw; /* save last returned next pcm block value */ + + memset(voices, 0, sizeof(voices)); + + Mod_p->Speed = 6; /* default speed */ + Mod_p->Tick = 6; /* force immediate processing */ + Mod_p->Order = 0; /* default order */ + Mod_p->Row = 0; /* default row */ + Mod_p->TimeScale = 0x10; /* 1.0 */ + Mod_p->BeatsPerMinute = 125; + Mod_p->IsPlaying = 0; + + return 0; /* no errors */ +} + + +/* + Cleanup MOD - stop playing and release voices +*/ +void ExitMOD(Mod_t* Mod_p) +{ + if (Mod_p) + { + if (Mod_p->IsPlaying) + StopMOD(Mod_p); + + /* quiet sound channels */ + pcm_reset(); + + free(Mod_p->PatternsBuff_p); + + /* make sure struct cannot be reused without init */ + memset(Mod_p, 0, sizeof(Mod_t)); + } +} + + +/* + Start playing Module + loop = bool = TRUE = loop forever, = FALSE = play once + returns the previously set Module, if any +*/ +Mod_t* StartMOD(Mod_t* Mod_p, uint8_t loop) +{ + if (Mod_p) + { + pcm_start_timer(&callback); + Mod_p->IsPlaying = loop ? 0x81 : 1; + return SetMOD(Mod_p); + } + return (Mod_t*)gMod; +} + + +/* + Pause/resume Module + pause = bool = TRUE = pause, = FALSE = resume +*/ +void PauseMOD(Mod_t* Mod_p, uint8_t pause) +{ + uint8_t i; + + if (Mod_p) + { + if ( pause ) + { + asm("move.w #0x2700,%sr"); + Mod_p->IsPlaying &= 0xFE; + for (i=0; iNumberOfChannels; i++) + { + pcm_set_ctrl(0xC0 + i); + pcm_set_env(0); + } + asm("move.w #0x2000,%sr"); + } + else + { + asm("move.w #0x2700,%sr"); + Mod_p->IsPlaying |= 1; + for (i=0; iNumberOfChannels; i++) + voices[i].pending |= PENDING_VOL; + asm("move.w #0x2000,%sr"); + } + } +} + + +/* + Stop playing Module +*/ +void StopMOD(Mod_t* Mod_p) +{ + if (Mod_p) + { + pcm_stop_timer(); + Mod_p->IsPlaying = 0; + SetMOD(0); + } +} + + +/* + Wait for Module to play through once +*/ +void WaitMOD(Mod_t* Mod_p) +{ + do + { + if (!Mod_p->IsPlaying) + break; + if (Mod_p->IsPlaying & 0x80) + if (!Mod_p->Row && !Mod_p->Order) + break; + } while (1); +} + + +/* + Check Module play status + returns 0 if done playing +*/ +uint8_t CheckMOD(Mod_t* Mod_p) +{ + if (Mod_p->IsPlaying & 0x80) + return (!Mod_p->Row && !Mod_p->Order) ? 0 : 1; + return Mod_p->IsPlaying; +} + + +/* + Set global volume + vol = 0 = off, = 16 = max + returns the previous volume setting +*/ +uint8_t VolumeMOD(uint8_t vol) +{ + uint8_t prev = gVolume; + gVolume = vol > 16 ? 16: vol; + return prev; +} + + +/* + Set current position in Module + order = position to start playing at + returns the current order playing +*/ +uint8_t JumpMOD(Mod_t* Mod_p, uint8_t order) +{ + if (!Mod_p) + return 0; + + uint8_t rv = Mod_p->Order; + + Mod_p->Tick = Mod_p->Speed; /* force immediate processing */ + Mod_p->Order = order; + Mod_p->Row = 0; + + return rv; +} + + +/* + Set current BPM scale for Module + scale = 2.4 fixed point number for scaling BPM + returns the current scale +*/ +uint8_t TimeScaleMOD(Mod_t* Mod_p, uint8_t scale) +{ + if (!Mod_p) + return 0x10; + + uint8_t rv = Mod_p->TimeScale; + + Mod_p->TimeScale = scale < 0x40 ? scale : 0x3F; + + return rv; +} + +/* ++----------------------------------------------------------------------------+ +| === Playing the MOD === | ++----------------------------------------------------------------------------+ + + timer function, it contains the player logic, it has to be as fast as possible + + The SPEED of a song is the base on how your mod is played. + Each row of a pattern is updated every SPEED number of clock ticks. +*/ +uint16_t PlayMOD(Mod_t* Mod_p) +{ + Mod_p->Pattern = Mod_p->Orders[ Mod_p->Order ]; /* pattern for current order */ + + Mod_p->Tick++; + + if (Mod_p->Tick >= Mod_p->Speed) + { + if ( !Mod_p->PatternDelay ) /* if there is no pattern delay */ + { + UpdateNote( Mod_p, &Mod_p->PatternsBuff_p[ Mod_p->Pattern * MOD_ROWS_PER_CHANNEL * Mod_p->NumberOfChannels + Mod_p->Row * Mod_p->NumberOfChannels ] ); /* process 1 note for every channel */ + Mod_p->Row += 1; /* increment row */ + } + else + { + Mod_p->PatternDelay--; /* else decrement pattern delay */ + } + + Mod_p->Tick = 0; + + if ( Mod_p->Row >= MOD_ROWS_PER_CHANNEL ) + { + /* process next pattern */ + Mod_p->Order++; + if (Mod_p->Order >= Mod_p->SongLength) + { + /* finished playing the mod */ + if (Mod_p->IsPlaying & 0x80) + { + /* loop - reset to defaults */ + Mod_p->Speed = 6; + Mod_p->Tick = 6; /* force immediate processing */ + Mod_p->Order = 0; + Mod_p->Row = 0; + Mod_p->TimeScale = 0x10; + Mod_p->BeatsPerMinute = 125; + } + else + { + Mod_p->IsPlaying = 0; /* song done playing */ + } + } + else + { + Mod_p->Pattern = Mod_p->Orders[ Mod_p->Order ]; + Mod_p->Row = 0; + } + } + } + else + { + Mod_p->Row -= 1; /* decrement row */ + /* update the tick based effects */ + UpdateEffect( Mod_p ); + Mod_p->Row += 1; /* increment row */ + } + + return Mod_p->IsPlaying ? 0 : 1; +} + + +/* + process division - 1 note for every channel +*/ +static void UpdateNote(Mod_t* Mod_p, uint32_t* Div_p) +{ + uint8_t Channel; /* counter */ + uint8_t EParX; /* Effect Parameter X */ + uint8_t EParY; /* Effect Parameter Y */ + uint8_t JumpFlag, BreakFlag; /* boolean flags for effects JUMP_TO_PATTERN and PATTERN_BREAK on the same row */ +#ifndef PROC_PATTERNS + uint8_t Cnt; +#endif + + JumpFlag = FALSE; + BreakFlag = FALSE; + + for ( Channel = 0; Channel < Mod_p->NumberOfChannels; Channel++ ) + { + uint32_t division; + + /* Amiga-fied mess ;-) */ + division = Div_p[ Channel ]; + /* + +-------------------------------------+ + | Byte 0 Byte 1 Byte 2 Byte 3 | + |-------------------------------------| + |aaaaBBBB CCCCCCCCC DDDDeeee FFFFFFFFF| + +-------------------------------------+ + aaaaDDDD = sample number + BBBBCCCCCCCC = sample period value + eeee = effect number + FFFFFFFF = effect parameters + */ + Mod_p->Notes[ Channel ].SampleNumber = ((division >> 24) & 0x00F0) | ((division >> 12) & 0x000F); + Mod_p->Notes[ Channel ].PeriodFrequency = ((division >> 16) & 0x0FFF); + Mod_p->Notes[ Channel ].EffectNumber = (division >> 8) & 0x000F; + Mod_p->Notes[ Channel ].EffectParameter = division & 0x00FF; + Mod_p->Notes[ Channel ].NoteExists = FALSE; + +#ifdef PROC_PATTERNS + if ( Mod_p->Notes[ Channel ].PeriodFrequency >= 4060 ) + { + Mod_p->Notes[ Channel ].NoteExists = TRUE; + Mod_p->Notes[ Channel ].PeriodFrequencyCol = Mod_p->Notes[ Channel ].PeriodFrequency - 4060; + Mod_p->Notes[ Channel ].PeriodFrequency = AmigaPeriodsTable[ 0 ][ Mod_p->Notes[ Channel ].PeriodFrequencyCol ]; + } +#else + /* look up table here */ + for ( Cnt = 0; Cnt < MOD_NUMBER_OF_NOTES; Cnt++ ) + { + if ( AmigaPeriodsTable[ 0 ][ Cnt ] == Mod_p->Notes[ Channel ].PeriodFrequency ) + { + Mod_p->Notes[ Channel ].NoteExists = TRUE; + Mod_p->Notes[ Channel ].PeriodFrequencyCol = Cnt; + break; + } + } /* for Cnt */ +#endif + + EParX = Mod_p->Notes[ Channel ].EffectParameter >> 4; + EParY = Mod_p->Notes[ Channel ].EffectParameter & 0xF; + + Mod_p->Channels[ Channel ].SampleOffset = 2; + + if ( Mod_p->Notes[ Channel ].SampleNumber > 0 ) + { + Mod_p->Channels[ Channel ].LastInstrument = Mod_p->Notes[ Channel ].SampleNumber - 1; + + if ( !( ( Mod_p->Notes[ Channel ].EffectNumber == EFFECT_EXTENDED_EFFECTS ) && ( EParX == EFFECT_DELAY_NOTE ) ) ) + { + Mod_p->Channels[ Channel ].Volume = Mod_p->Inst[ Mod_p->Notes[ Channel ].SampleNumber - 1 ].Volume; /* default volume of sample */ + } + } + + if ( Mod_p->Notes[ Channel ].NoteExists == TRUE ) + { + if ( ( Mod_p->Channels[ Channel ].WaveformControl & 0x0F ) < 4 ) + { + Mod_p->Channels[ Channel ].VibratoPosition = 0; + } + if ( ( Mod_p->Channels[ Channel ].WaveformControl >> 4 ) < 4 ) + { + Mod_p->Channels[ Channel ].TremoloPosition = 0; + } + + /* if not a porta effect, then set the channels frequency to the looked up amiga value + or - any finetune */ + if ( (Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_TO_NOTE) && + (Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_PLUS_VOL_SLIDE) ) + { + uint8_t FineTune; + + FineTune = Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].FineTune; + Mod_p->Channels[ Channel ].PeriodFrequency = AmigaPeriodsTable[ FineTune ][ Mod_p->Notes[ Channel ].PeriodFrequencyCol ]; + } /* if */ + + //Mod_p->Channels[ Channel ].SampleOffset = 2; + } /* if */ + + /* skip effects ? */ + if ( !( ( Mod_p->Notes[ Channel ].EffectNumber == 0 ) && ( Mod_p->Notes[ Channel ].EffectParameter == 0 ) ) ) + { + /* process the non tick based effects also grab parameters for tick based effects */ + switch ( Mod_p->Notes[ Channel ].EffectNumber ) + { + case EFFECT_ARPEGGIO: /* not processed on tick 0 */ + case EFFECT_PORTA_UP: /* not processed on tick 0 */ + case EFFECT_PORTA_DOWN: /* not processed on tick 0 */ + break; + + case EFFECT_PORTA_TO_NOTE: /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].EffectParameter > 0 ) + { + Mod_p->Channels[ Channel ].PortaSpeed = Mod_p->Notes[ Channel ].EffectParameter; + } + /* no break statement here */ + + case EFFECT_PORTA_PLUS_VOL_SLIDE: /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].NoteExists == TRUE ) + { + Mod_p->Channels[ Channel ].PortaTo = Mod_p->Notes[ Channel ].PeriodFrequency; + } + break; + + case EFFECT_VIBRATO: /* reviewed OK */ + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].VibratoSpeed = EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].VibratoDepth = EParY; + } + break; + + case EFFECT_VIBRATO_PLUS_VOL_SLIDE: /* not processed on tick 0 */ + break; + + case EFFECT_TREMOLO: /* reviewed OK */ + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].TremoloSpeed = EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].TremoloDepth = EParY; + } + break; + + case EFFECT_PAN: /* reviewed OK */ /* not a ProTracker effect */ + Mod_p->Channels[ Channel ].PanValue = Mod_p->Notes[ Channel ].EffectParameter; + break; + + case EFFECT_SAMPLE_OFFSET: /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].EffectParameter > 0 ) + { + Mod_p->Channels[ Channel ].SampleOffset = ( Mod_p->Notes[ Channel ].EffectParameter << 8 ); + } + if ( Mod_p->Channels[ Channel ].SampleOffset > Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].SampleLength ) + { + Mod_p->Channels[ Channel ].SampleOffset = Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].SampleLength; + /* this will result in silence */ + } + break; + + case EFFECT_VOLUME_SLIDE: /* not processed on tick 0 */ + break; + + case EFFECT_JUMP_TO_PATTERN: /* reviewed OK */ + Mod_p->Order = Mod_p->Notes[ Channel ].EffectParameter; + Mod_p->Row = -1; + if ( Mod_p->Order >= Mod_p->SongLength ) + { + Mod_p->Order = 0; + } +// if ( Mod_p->Order == 0 ) +// { + /* loop the mod */ + if (!(Mod_p->IsPlaying & 0x80)) + { + Mod_p->IsPlaying = 0; /* song done playing */ + return; + } +// } + JumpFlag = TRUE; + break; + + case EFFECT_SET_VOLUME: /* reviewed OK */ + Mod_p->Channels[ Channel ].Volume = Mod_p->Notes[ Channel ].EffectParameter; + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + break; + + case EFFECT_PATTERN_BREAK: /* reviewed OK */ + Mod_p->Row = ( EParX * 10 ) + EParY; /* get row number */ + if ( Mod_p->Row == 0 ) + { + Mod_p->Row = -1; + } + else if ( Mod_p->Row >= MOD_ROWS_PER_CHANNEL ) + { + Mod_p->Row = -1; + } + else + { + Mod_p->Row -= 1; + } + + if ( !JumpFlag && !BreakFlag ) + { + Mod_p->Order++; + + if ( Mod_p->Order >= Mod_p->SongLength ) + { + Mod_p->Order = 0; + /* repeat the mod */ + if (!(Mod_p->IsPlaying & 0x80)) + { + Mod_p->IsPlaying = 0; /* song done playing */ + return; + } + } + } + BreakFlag = TRUE; + break; + + case EFFECT_EXTENDED_EFFECTS: + switch (EParX) + { + case EFFECT_FINE_PORTA_UP: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency -= EParY; + break; + + case EFFECT_FINE_PORTA_DOWN: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency += EParY; + break; + + case EFFECT_GLISSANDO_CONTROL: /* not implemented not even in fmoddoc2 */ + break; + + case EFFECT_SET_VIBRATO_WAVEFORM: /* reviewed OK */ + Mod_p->Channels[ Channel ].WaveformControl &= 0xF0; + Mod_p->Channels[ Channel ].WaveformControl |= EParY; + break; + + case EFFECT_SET_FINETUNE: /* reviewed OK */ + Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].FineTune = EParY; + break; + + case EFFECT_PATTERN_LOOP: /* reviewed OK */ + if ( EParY == 0 ) + { + Mod_p->Channels[ Channel ].PatLoopRow = Mod_p->Row; + } + else + { + if ( ! Mod_p->Channels[ Channel ].PatLoopNo ) + { + Mod_p->Channels[ Channel ].PatLoopNo = EParY; + } + else + { + Mod_p->Channels[ Channel ].PatLoopNo--; + } + if ( Mod_p->Channels[ Channel ].PatLoopNo ) + { + Mod_p->Row = Mod_p->Channels[ Channel ].PatLoopRow - 1; + } + } + break; + + case EFFECT_SET_TREMOLO_WAVEFORM: /* reviewed OK */ + Mod_p->Channels[ Channel ].WaveformControl &= 0x0F; + Mod_p->Channels[ Channel ].WaveformControl |= (EParY << 4); + break; + + case EFFECT_POSITION_PANNING: /* cw - fixed */ + Mod_p->Channels[ Channel ].PanValue = (EParY << 4) | EParY; + break; + + case EFFECT_RETRIG_NOTE: /* not processed on tick 0 */ + break; + + case EFFECT_FINE_VOLUME_SLIDE_UP: /* reviewed OK */ + Mod_p->Channels[ Channel ].Volume += EParY; + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + break; + + case EFFECT_FINE_VOLUME_SLIDE_DOWN: /* reviewed OK */ + Mod_p->Channels[ Channel ].Volume -= EParY; + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + break; + + case EFFECT_CUT_NOTE: /* not processed on tick 0 */ + break; + + case EFFECT_DELAY_NOTE: /* not processed on tick 0 */ /* reviewed OK */ + continue; /* process next note */ + break; + + case EFFECT_PATTERN_DELAY: /* reviewed OK */ + Mod_p->PatternDelay = EParY; + break; + + default: + break; + } + break; + + case EFFECT_SET_SPEED: /* cw - update for bpm */ + if ( Mod_p->Notes[ Channel ].EffectParameter < 0x20 ) + { + if ( Mod_p->Notes[ Channel ].EffectParameter ) + { + Mod_p->Speed = Mod_p->Notes[ Channel ].EffectParameter; + } + } + else + { + Mod_p->BeatsPerMinute = Mod_p->Notes[ Channel ].EffectParameter; + } + break; + + default: + /* effect not defined */ + break; + } /* switch */ + } /* if */ + + if ( Mod_p->Notes[ Channel ].EffectNumber != EFFECT_SET_TREMOLO_WAVEFORM ) + { + /* set volume */ + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + } + + if ( Mod_p->Channels[ Channel ].PeriodFrequency > 0 ) + { + /* set frequency */ + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + } + + if ( Mod_p->Notes[ Channel ].NoteExists == TRUE ) + { + if ( ( Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_TO_NOTE ) && + ( Mod_p->Notes[ Channel ].EffectNumber != EFFECT_PORTA_PLUS_VOL_SLIDE ) ) + { + /* set sample */ + SetSample( Channel, Mod_p->Channels[ Channel ].LastInstrument ); + } + } /* if */ + } /* for Channel */ + + return; +} + + +/* + update tick based effects +*/ +static void UpdateEffect(Mod_t* Mod_p) +{ + uint8_t Channel; /* counter */ + uint8_t EParX; /* Effect Parameter X */ + uint8_t EParY; /* Effect Parameter Y */ + uint8_t Tick = Mod_p->Tick; /* current music tick */ + + for ( Channel = 0; Channel < Mod_p->NumberOfChannels; Channel++ ) + { + EParX = Mod_p->Notes[ Channel ].EffectParameter >> 4; + EParY = Mod_p->Notes[ Channel ].EffectParameter & 0xF; + + switch ( Mod_p->Notes[ Channel ].EffectNumber ) + { + + case EFFECT_ARPEGGIO: /* effect used in chip tunes */ /* reviewed OK */ + if ( Mod_p->Notes[ Channel ].EffectParameter > 0 ) + { + switch ( Tick % 3 ) + { + case 0: + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + break; + + case 1: + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency + EParX ); + break; + + case 2: + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency + EParY ); + break; + } /* switch */ + } /* if */ + break; + + case EFFECT_PORTA_UP: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency -= Mod_p->Notes[ Channel ].EffectParameter; + if ( Mod_p->Channels[ Channel ].PeriodFrequency < 113 ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = 113; /* stop at B-3 */ + } + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + break; + + case EFFECT_PORTA_DOWN: /* reviewed OK */ + Mod_p->Channels[ Channel ].PeriodFrequency += Mod_p->Notes[ Channel ].EffectParameter; + if ( Mod_p->Channels[ Channel ].PeriodFrequency > 856 ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = 856; /* stop at C-1 */ + } + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + break; + + case EFFECT_PORTA_TO_NOTE: /* reviewed OK */ + DoPorta( Mod_p, Channel ); + break; + + case EFFECT_VIBRATO: /* reviewed OK */ + DoVibrato( Mod_p, Channel ); + break; + + case EFFECT_PORTA_PLUS_VOL_SLIDE: /* reviewed OK */ + DoPorta( Mod_p, Channel ); + + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].Volume += EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].Volume -= EParY; + } + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + break; + + case EFFECT_VIBRATO_PLUS_VOL_SLIDE: /* reviewed OK */ + DoVibrato( Mod_p, Channel ); + + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].Volume += EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].Volume -= EParY; + } + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + break; + + case EFFECT_TREMOLO: /* reviewed OK */ + DoTremolo( Mod_p, Channel ); + break; + + case EFFECT_VOLUME_SLIDE: /* reviewed OK */ + if ( EParX > 0 ) + { + Mod_p->Channels[ Channel ].Volume += EParX; + } + if ( EParY > 0 ) + { + Mod_p->Channels[ Channel ].Volume -= EParY; + } + if ( Mod_p->Channels[ Channel ].Volume < 0 ) + { + Mod_p->Channels[ Channel ].Volume = 0; + } + if ( Mod_p->Channels[ Channel ].Volume > 64 ) + { + Mod_p->Channels[ Channel ].Volume = 64; + } + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + break; + + case EFFECT_EXTENDED_EFFECTS: + switch (EParX) + { + case EFFECT_RETRIG_NOTE: /* I am not 100% sure about this effect */ + if ( !EParY ) + { + break; + } + if ( !( Tick % EParY ) ) + { + SetSample( Channel, Mod_p->Channels[ Channel ].LastInstrument ); + } + break; + + case EFFECT_CUT_NOTE: /* reviewed OK */ + if ( Tick == EParY ) + { + Mod_p->Channels[ Channel ].Volume = 0; + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + } + break; + + case EFFECT_DELAY_NOTE: /* I am not 100% sure about this effect */ + if ( Tick == EParY ) + { + if ( Mod_p->Notes[ Channel ].SampleNumber > 0 ) + { + Mod_p->Channels[ Channel ].Volume = Mod_p->Inst[ Mod_p->Channels[ Channel ].LastInstrument ].Volume; + } + if ( Mod_p->Inst[ Mod_p->Notes[ Channel ].SampleNumber - 1 ].Volume <= 64 ) + { + Mod_p->Channels[ Channel ].Volume = Mod_p->Inst[ Mod_p->Notes[ Channel ].SampleNumber - 1 ].Volume; + } + + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume, Mod_p->Channels[ Channel ].PanValue ); + SetSample( Channel, Mod_p->Channels[ Channel ].LastInstrument ); + } /* if */ + break; + + default: + break; + } + break; + + default: + break; + } /* switch */ + } /* for Channel */ + + return; +} + + +/* +*/ +static void DoVibrato(Mod_t* Mod_p, uint8_t Channel) +{ + uint16_t Delta = 0; + uint8_t Temp; + + Temp = ( Mod_p->Channels[ Channel ].VibratoPosition & 31 ); /* Temp will be the index */ + + switch ( Mod_p->Channels[ Channel ].WaveformControl & 3 ) + { + case 0: + Delta = SineTable[ Temp ]; /* look up sine table */ + break; + + case 1: + Temp <<= 3; /* ramp down */ + if ( Mod_p->Channels[ Channel ].VibratoPosition < 0 ) + { + Temp = 255 - Temp; + } + Delta = Temp; + break; + + case 2: + Delta = 255; /* square */ + break; + + case 3: + Delta = rand() & 255; /* random */ + break; + } /* switch */ + + Delta *= Mod_p->Channels[ Channel ].VibratoDepth; + Delta >>= 7; + //Delta <<= 2; /* we use 4*periods so make vibrato 4 times bigger */ + + if ( Mod_p->Channels[ Channel ].VibratoPosition >= 0 ) + { + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency + Delta ); + } + else + { + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency - Delta ); + } + + Mod_p->Channels[ Channel ].VibratoPosition += Mod_p->Channels[ Channel ].VibratoSpeed; + if ( Mod_p->Channels[ Channel ].VibratoPosition > 31 ) + { + Mod_p->Channels[ Channel ].VibratoPosition -= 64; + } + + return; +} + + +/* +*/ +static void DoTremolo(Mod_t* Mod_p, uint8_t Channel) +{ + uint16_t Delta = 0; + uint8_t Temp; + + Temp = ( Mod_p->Channels[ Channel ].TremoloPosition & 31 ); /* Temp will be the index */ + + switch ( ( Mod_p->Channels[ Channel ].WaveformControl >> 4 ) & 3 ) + { + case 0: + Delta = SineTable[ Temp ]; /* look up sine table */ + break; + + case 1: + Temp <<= 3; /* ramp down */ + if ( Mod_p->Channels[ Channel ].TremoloPosition < 0 ) + { + Temp = 255 - Temp; + } + Delta = Temp; + break; + + case 2: + Delta = 255; /* square */ + break; + + case 3: + Delta = rand() & 255; /* random */ + break; + } /* switch */ + + Delta *= Mod_p->Channels[ Channel ].TremoloDepth; + Delta >>= 6; + + if ( Mod_p->Channels[ Channel ].TremoloPosition >= 0 ) + { + if ( Mod_p->Channels[ Channel ].Volume + Delta > 64 ) + { + Delta = 64 - Mod_p->Channels[ Channel ].Volume; + } + + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume + Delta, Mod_p->Channels[ Channel ].PanValue ); + } + else + { + if ( (int16_t) ( Mod_p->Channels[ Channel ].Volume - Delta < 0 ) ) + { + Delta = Mod_p->Channels[ Channel ].Volume; + } + + SetVolume( Channel, Mod_p->Channels[ Channel ].Volume - Delta, Mod_p->Channels[ Channel ].PanValue ); + } + + Mod_p->Channels[ Channel ].TremoloPosition += Mod_p->Channels[ Channel ].TremoloSpeed; + if ( Mod_p->Channels[ Channel ].TremoloPosition > 31 ) + { + Mod_p->Channels[ Channel ].TremoloPosition -= 64; + } + + return; +} + + +/* +*/ +static void DoPorta(Mod_t* Mod_p, uint8_t Channel) +{ + /* slide pitch down if it needs too */ + if ( Mod_p->Channels[ Channel ].PeriodFrequency < Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency += Mod_p->Channels[ Channel ].PortaSpeed; + if ( Mod_p->Channels[ Channel ].PeriodFrequency > Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = Mod_p->Channels[ Channel ].PortaTo; + } + } + + /* slide pitch up if it needs too */ + if ( Mod_p->Channels[ Channel ].PeriodFrequency > Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency -= Mod_p->Channels[ Channel ].PortaSpeed; + if ( Mod_p->Channels[ Channel ].PeriodFrequency < Mod_p->Channels[ Channel ].PortaTo ) + { + Mod_p->Channels[ Channel ].PeriodFrequency = Mod_p->Channels[ Channel ].PortaTo; + } + } + + SetFrequency( Channel, Mod_p->Channels[ Channel ].PeriodFrequency ); + + return; +} + + +/* System specific functions =================================================================== */ + + +static Mod_t* SetMOD(Mod_t* Mod_p) +{ + Mod_t *pmod = gMod; + /* once set, PlayMOD() starts being called */ + gMod = Mod_p; + return pmod; +} + + +static void SetSample(uint16_t Channel, uint8_t Sample) +{ + uint8_t factor = gMod->Inst[ Sample ].Factor; + uint8_t start = gMod->Inst[ Sample ].SampleHandle; + uint32_t offset = gMod->Channels[ Channel ].SampleOffset; + uint32_t loop = gMod->Inst[ Sample ].LoopStart; + uint32_t looplen = gMod->Inst[ Sample ].LoopLength; + uint32_t length = gMod->Inst[ Sample ].SampleLength; + + if (length == 0) + { + voices[Channel].pending |= PENDING_STOP; + return; + } + + voices[Channel].start = start; + voices[Channel].offset = offset >> factor; + voices[Channel].loop = looplen > 2 ? loop >> factor : (length >> factor) - 1; + voices[Channel].factor = factor; + voices[Channel].pending |= PENDING_START; + voices[Channel].pending &= ~PENDING_STOP; +} + + +static void SetFrequency(uint16_t Channel, uint32_t Period) +{ + voices[Channel].period = Period << voices[Channel].factor; + voices[Channel].pending |= PENDING_PER; +} + + +static void SetVolume(uint16_t Channel, uint32_t Volume, int32_t Pan) +{ + uint16_t env, pan; + uint16_t chan = Channel & 3; + + if (Volume == 0) + { + voices[Channel].pending |= PENDING_STOP; + return; + } + + env = (Volume * gVolume) >> 2; // 0 to 256 + if (env > 255) + env = 255; // clamp to 255 + +#ifdef HANDLE_PANNING + pan = Pan; +#else + if ( (chan == 0) || (chan == 3) ) + { +#ifdef CROSS_TALK + pan = ~CROSS_TALK & 255; +#else + pan = 0; +#endif + } + else + { +#ifdef CROSS_TALK + pan = CROSS_TALK; +#else + pan = 255; +#endif + } +#endif + + voices[Channel].vol = env; + voices[Channel].pan = pan; + voices[Channel].pending |= PENDING_VOL; +} + + +static void callback(void) +{ + static uint16_t tempo = 0; + static uint8_t scale = 0; + uint8_t i; + + if (gMod) + PlayMOD(gMod); + + if ((tempo != gMod->BeatsPerMinute) || (scale != gMod->TimeScale)) + { + tempo = gMod->BeatsPerMinute; + scale = gMod->TimeScale; + + uint16_t bpm = 0; + if (scale & 0x20) + bpm += (tempo << 1); + if (scale & 0x10) + bpm += tempo; + if (scale & 0x08) + bpm += (tempo >> 1); + if (scale & 0x04) + bpm += (tempo >> 2); + if (scale & 0x02) + bpm += (tempo >> 3); + if (scale & 0x01) + bpm += (tempo >> 4); + + pcm_set_timer(bpm); + } + + /* update channels */ + for (i = 0; i < gMod->NumberOfChannels; i++) + { + pcm_set_ctrl(0xC0 + i); // turn on pcm chip and select channel + + if (voices[i].pending & PENDING_STOP) + { + pcm_set_off(i); + pcm_set_env(0); + voices[i].pending &= ~PENDING_STOP; + } + + if (voices[i].pending & PENDING_START) + { + pcm_set_off(i); + pcm_set_start(voices[i].start, voices[i].offset); + pcm_set_loop((voices[i].start << 8) + voices[i].loop); + voices[i].pending |= PENDING_VOL|PENDING_PER; + } + + if (voices[i].pending & PENDING_VOL) + { + pcm_set_env(voices[i].vol); + pcm_set_pan(voices[i].pan); + voices[i].pending &= ~PENDING_VOL; + } + + if (voices[i].pending & PENDING_PER) + { + pcm_set_period(voices[i].period); + voices[i].pending &= ~PENDING_PER; + } + + if (voices[i].pending & PENDING_START) + { + pcm_set_on(i); + voices[i].pending &= ~PENDING_START; + } + } +} diff --git a/module.h b/module.h new file mode 100644 index 0000000..15a86eb --- /dev/null +++ b/module.h @@ -0,0 +1,197 @@ +#ifndef _MODULE_H_ +#define _MODULE_H_ + +#include + + +#define AMIGA_PAL_CPU_CLOCK ( 7093789 ) /* Hz unit (~7 MHz) */ + +#ifndef FALSE +#define FALSE ( (uint8_t) 0 ) +#endif +#ifndef TRUE +#define TRUE ( (uint8_t) 1 ) +#endif + +#define MOD_PCM_RAM ( (uint32_t)65536 ) /* change to leave space for sound effects */ + +/* MOD enum Constants */ +#define MOD_SIGNATURE_LENGTH ( (uint8_t) 4 ) +#define MOD_NAME_LENGTH ( (uint8_t) 20 ) +#define MOD_SAMPLE_NAME_LENGTH ( (uint8_t) 22 ) +#define MOD_NUMBER_OF_SAMPLES ( (uint8_t) 31 ) +#define MOD_ROWS_PER_CHANNEL ( (uint8_t) 64 ) +#define MOD_NUMBER_OF_ORDERS ( (uint8_t) 128 ) +#define MOD_SIGNATURE_OFFSET ( (int16_t) 1080 ) /* 0x0438 */ +#define MOD_FINE_TUNE_VALUES ( (uint8_t) 16 ) +#define MOD_NUMBER_OF_NOTES ( (uint8_t) 36 ) +#define MOD_NUMBER_OF_CHANNELS ( (uint8_t) 8 ) +#define MOD_TIMER_SPEED ( (uint8_t) 50 ) /* timer speed, we need a speed of 50 Hz = 50 ticks per second */ + +/* EFFECTS */ +#define EFFECT_ARPEGGIO ( (uint8_t) 0x00 ) /* Effect 0xy (Arpeggio) */ +#define EFFECT_PORTA_UP ( (uint8_t) 0x01 ) /* Effect 1xy (Porta Up) */ +#define EFFECT_PORTA_DOWN ( (uint8_t) 0x02 ) /* Effect 2xy (Porta Down) */ +#define EFFECT_PORTA_TO_NOTE ( (uint8_t) 0x03 ) /* Effect 3xy (Porta To Note) */ +#define EFFECT_VIBRATO ( (uint8_t) 0x04 ) /* Effect 4xy (Vibrato) */ +#define EFFECT_PORTA_PLUS_VOL_SLIDE ( (uint8_t) 0x05 ) /* Effect 5xy (Porta + Vol Slide) */ +#define EFFECT_VIBRATO_PLUS_VOL_SLIDE ( (uint8_t) 0x06 ) /* Effect 6xy (Vibrato + Vol Slide) */ +#define EFFECT_TREMOLO ( (uint8_t) 0x07 ) /* Effect 7xy (Tremolo) */ +#define EFFECT_PAN ( (uint8_t) 0x08 ) /* Effect 8xy (Pan) */ +#define EFFECT_SAMPLE_OFFSET ( (uint8_t) 0x09 ) /* Effect 9xy (Sample Offset) */ +#define EFFECT_VOLUME_SLIDE ( (uint8_t) 0x0A ) /* Effect Axy (Volume Slide) */ +#define EFFECT_JUMP_TO_PATTERN ( (uint8_t) 0x0B ) /* Effect Bxy (Jump To Pattern) */ +#define EFFECT_SET_VOLUME ( (uint8_t) 0x0C ) /* Effect Cxy (Set Volume) */ +#define EFFECT_PATTERN_BREAK ( (uint8_t) 0x0D ) /* Effect Dxy (Pattern Break) */ +#define EFFECT_EXTENDED_EFFECTS ( (uint8_t) 0x0E ) /* Extended Effects */ +#define EFFECT_SET_SPEED ( (uint8_t) 0x0F ) /* Effect Fxy (Set Speed) */ + +#define EFFECT_SET_FILTER ( (uint8_t) 0x00 ) /* Effect E0x (Set Filter) */ +#define EFFECT_FINE_PORTA_UP ( (uint8_t) 0x01 ) /* Effect E1x (Fine Porta Up) */ +#define EFFECT_FINE_PORTA_DOWN ( (uint8_t) 0x02 ) /* Effect E2x (Fine Porta Down) */ +#define EFFECT_GLISSANDO_CONTROL ( (uint8_t) 0x03 ) /* Effect E3x (Glissando Control) */ +#define EFFECT_SET_VIBRATO_WAVEFORM ( (uint8_t) 0x04 ) /* Effect E4x (Set Vibrato Waveform) */ +#define EFFECT_SET_FINETUNE ( (uint8_t) 0x05 ) /* Effect E5x (Set Finetune) */ +#define EFFECT_PATTERN_LOOP ( (uint8_t) 0x06 ) /* Effect E6x (Pattern Loop) */ +#define EFFECT_SET_TREMOLO_WAVEFORM ( (uint8_t) 0x07 ) /* Effect E7x (Set Tremolo WaveForm) */ +#define EFFECT_POSITION_PANNING ( (uint8_t) 0x08 ) /* Effect E8x (Position Panning) */ +#define EFFECT_RETRIG_NOTE ( (uint8_t) 0x09 ) /* Effect E9x (Retrig Note) */ +#define EFFECT_FINE_VOLUME_SLIDE_UP ( (uint8_t) 0x0A ) /* Effect EAx (Fine Volume Slide Up) */ +#define EFFECT_FINE_VOLUME_SLIDE_DOWN ( (uint8_t) 0x0B ) /* Effect EBx (Fine Volume Slide Down) */ +#define EFFECT_CUT_NOTE ( (uint8_t) 0x0C ) /* Effect ECx (Cut Note) */ +#define EFFECT_DELAY_NOTE ( (uint8_t) 0x0D ) /* Effect EDx (Delay Note) */ +#define EFFECT_PATTERN_DELAY ( (uint8_t) 0x0E ) /* Effect EEx (Pattern Delay) */ +#define EFFECT_INVERT_LOOP ( (uint8_t) 0x0F ) /* Effect EFx (Invert Loop) */ + + +typedef struct Note { + uint8_t SampleNumber; + uint8_t EffectNumber; + uint8_t EffectParameter; + uint8_t NoteExists; /* boolean flag */ + uint8_t PeriodFrequencyCol; /* column position in AmigaPeriodsTable[][] */ + uint16_t PeriodFrequency; +} Note_t; + +typedef struct Channel { + uint8_t LastInstrument; + int8_t Volume; /* default volume of sample */ + uint8_t PortaSpeed; + uint8_t VibratoSpeed; + uint8_t VibratoDepth; + uint8_t TremoloSpeed; + uint8_t TremoloDepth; + uint8_t WaveformControl; /* upper 4 bits for the tremolo wavecontrol, and the lower 4 bits for the vibrato wavecontrol */ + int8_t VibratoPosition; + int8_t TremoloPosition; + uint8_t PatLoopRow; + uint8_t PatLoopNo; + int16_t PanValue; /* not used unless HANDLE_PANNING defined */ + int16_t PeriodFrequency; + int16_t PortaTo; /* note to port to value */ + uint32_t SampleOffset; +} Channel_t; + +typedef struct Sample { + uint8_t FineTune; + uint8_t Volume; + uint8_t Factor; /* shift for scaling sample to fit pcm ram */ + + uint32_t SampleHandle; /* associate Sample with data (may be a pointer, or a handle for a loaded sample) */ + uint32_t LoopStart; + uint32_t LoopLength; + uint32_t SampleLength; /* The maximum size for a sample on the Amiga is 128K. */ +} Sample_t; + +/* main data structure */ +typedef struct Module { + uint8_t IsPlaying; /* flags - b0 = 1 = playing, 0 = stopped; b7 = 1 = loop at end */ + uint8_t SongLength; /* song length (1 to 128) */ + uint8_t NumberOfPatterns; /* number of physical patterns (1 to 64) */ + uint8_t NumberOfChannels; /* number of channels (4, 6, or 8) */ + + uint8_t Speed; /* number of ticks (or times the function is called) between each time a new row is processed */ + uint8_t Tick; /* current music tick */ + int8_t Row; /* current row number value 0-63 */ + uint8_t Order; /* current value */ + uint8_t Pattern; /* current value */ + uint8_t PatternDelay; /* number of notes to delay pattern for */ + + uint8_t Pad; + uint8_t TimeScale; /* scale for BPM (2.4 fixed point number) */ + uint16_t BeatsPerMinute; /* beats per minute */ + + uint32_t* PatternsBuff_p; /* start of pattern data (1 to 64 * (64 * 4 * # channels)) */ + + uint8_t Orders[ MOD_NUMBER_OF_ORDERS ]; /* pattern playing orders (128 entries of 0 to 63) */ + + Sample_t Inst[ MOD_NUMBER_OF_SAMPLES ]; /* instrument headers */ + Channel_t Channels[ MOD_NUMBER_OF_CHANNELS ]; /* run time channel info */ + Note_t Notes[ MOD_NUMBER_OF_CHANNELS ]; /* run time note info */ + char Title[22]; /* module title (max of 20 chars) */ + uint8_t NextBlock; /* next block in sram for PCM samples */ +} Mod_t; + + +/* + Initialize Module struct for playing + returns 0 if no error +*/ +uint8_t InitMOD(CDFileHandle_t *File_p, Mod_t* Mod_p, uint8_t filter); + +/* + Cleanup MOD - stop playing and release voices +*/ +void ExitMOD(Mod_t* Mod_p); + +/* + Start playing Module + loop = bool = TRUE = loop forever, = FALSE = play once + returns the previously playing Module, if any +*/ +Mod_t* StartMOD(Mod_t* Mod_p, uint8_t loop); + +/* + Pause/resume Module + pause = bool = TRUE = pause, = FALSE = resume +*/ +void PauseMOD(Mod_t* Mod_p, uint8_t pause); + +/* + Stop playing Module +*/ +void StopMOD(Mod_t* Mod_p); + +/* + Wait for Module to play through once +*/ +void WaitMOD(Mod_t* Mod_p); + +/* + Check Module play status + returns 0 if done playing +*/ +uint8_t CheckMOD(Mod_t* Mod_p); + +/* + Set global volume + vol = 0 = off, = 16 = max + returns the previous volume setting +*/ +uint8_t VolumeMOD(uint8_t vol); + +/* + Set current position in Module + order = position to start playing at + returns the current order playing +*/ +uint8_t JumpMOD(Mod_t* Mod_p, uint8_t order); + +/* + Set current BPM scale for Module + scale = 2.4 fixed point number for scaling BPM + returns the current scale +*/ +uint8_t TimeScaleMOD(Mod_t* Mod_p, uint8_t scale); + +#endif diff --git a/pcm.c b/pcm.c new file mode 100644 index 0000000..cf486a8 --- /dev/null +++ b/pcm.c @@ -0,0 +1,158 @@ + +#include +#include "pcm.h" + +// PCM chip registers +#define PCM_ENV *((volatile uint8_t *)0xFF0001) +#define PCM_PAN *((volatile uint8_t *)0xFF0003) +#define PCM_FDL *((volatile uint8_t *)0xFF0005) +#define PCM_FDH *((volatile uint8_t *)0xFF0007) +#define PCM_LSL *((volatile uint8_t *)0xFF0009) +#define PCM_LSH *((volatile uint8_t *)0xFF000B) +#define PCM_START *((volatile uint8_t *)0xFF000D) +#define PCM_CTRL *((volatile uint8_t *)0xFF000F) +#define PCM_ONOFF *((volatile uint8_t *)0xFF0011) +#define PCM_WAVE *((volatile uint8_t *)0xFF2001) + +static uint8_t loop_markers[32] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +#define BLK_PAD (32 + 255) +#define BLK_SHIFT 8 + +static uint8_t ChanOff; + +static void pcm_cpy(uint16_t doff, void *src, uint16_t len, uint16_t conv) +{ + uint8_t *sptr = (uint8_t *)src; + + while (len > 0) + { + uint8_t *wptr = (uint8_t *)&PCM_WAVE; + uint16_t woff = doff & 0x0FFF; + uint16_t wblen = 0x1000 - woff; + wptr += (woff << 1); + + PCM_CTRL = 0x80 + (doff >> 12); // make sure PCM chip is ON to write wave memory, and set wave bank + pcm_delay(); + + if (wblen > len) + wblen = len; + doff += wblen; + len -= wblen; + while (wblen > 0) + { + int16_t s = (int8_t)*sptr++; + if (conv) + { + // convert from 8-bit signed samples to sign/magnitude samples + if (s < 0) + { + s = -s; + if (s > 127) s = 127; // clamp to -127 + } + else + { + if (s > 126) s = 126; // clamp to 126 + s |= 128; + } + } + *wptr++ = (uint8_t)(s & 255); + wptr++; + wblen--; + } + } +} + +void pcm_load_samples(uint8_t start, int8_t *samples, uint16_t length) +{ + ChanOff = 0xFF; + PCM_ONOFF = 0xFF; // turn off all channels + pcm_cpy(start << BLK_SHIFT, samples, length, 1); + pcm_cpy((start << BLK_SHIFT) + length, loop_markers, 32, 0); +} + +uint16_t pcm_next_block(uint8_t start, uint16_t length) +{ + return start + ((length + BLK_PAD) >> BLK_SHIFT); +} + +void pcm_reset(void) +{ + uint16_t i; + + ChanOff = 0xFF; + PCM_ONOFF = 0xFF; // turn off all channels + pcm_delay(); + + for (i=0; i<8; i++) + { + PCM_CTRL = 0xC0 + i; // turn on pcm chip and select channel + pcm_delay(); + PCM_ENV = 0x00; // channel env off + pcm_delay(); + PCM_PAN = 0x00; + pcm_delay(); + PCM_FDL = 0x00; + pcm_delay(); + PCM_FDH = 0x00; + pcm_delay(); + PCM_LSL = 0x00; + pcm_delay(); + PCM_LSH = 0x00; + pcm_delay(); + PCM_START = 0x00; + pcm_delay(); + } +} + +void pcm_set_ctrl(uint8_t val) +{ + PCM_CTRL = val; + pcm_delay(); +} + +void pcm_set_off(uint8_t index) +{ + ChanOff |= (1 << index); + PCM_ONOFF = ChanOff; + pcm_delay(); +} + +void pcm_set_on(uint8_t index) +{ + ChanOff &= ~(1 << index); + PCM_ONOFF = ChanOff; + pcm_delay(); +} + +void pcm_set_start(uint8_t start, uint16_t offset) +{ + PCM_START = start + (offset >> BLK_SHIFT); + pcm_delay(); +} + +void pcm_set_loop(uint16_t loopstart) +{ + PCM_LSL = loopstart & 0x00FF; // low byte + pcm_delay(); + PCM_LSH = loopstart >> 8; // high byte + pcm_delay(); +} + +void pcm_set_env(uint8_t vol) +{ + PCM_ENV = vol; + pcm_delay(); +} + +void pcm_set_pan(uint8_t pan) +{ + PCM_PAN = pcm_lcf(pan); + pcm_delay(); +} + diff --git a/pcm.h b/pcm.h new file mode 100644 index 0000000..5549810 --- /dev/null +++ b/pcm.h @@ -0,0 +1,34 @@ +#ifndef _PCM_H +#define _PCM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* from pcm.c */ +extern void pcm_load_samples(uint8_t start, int8_t *samples, uint16_t length); +extern uint16_t pcm_next_block(uint8_t start, uint16_t length); +extern void pcm_reset(void); +extern void pcm_set_ctrl(uint8_t val); +extern void pcm_set_off(uint8_t index); +extern void pcm_set_on(uint8_t index); +extern void pcm_set_start(uint8_t start, uint16_t offset); +extern void pcm_set_loop(uint16_t loopstart); +extern void pcm_set_env(uint8_t vol); +extern void pcm_set_pan(uint8_t pan); +/* from pcm-io.s */ +extern uint8_t pcm_lcf(uint8_t pan); +extern void pcm_delay(void); +extern void pcm_set_period(uint32_t period); +extern void pcm_set_freq(uint32_t freq); +extern void pcm_set_timer(uint16_t bpm); +extern void pcm_stop_timer(void); +extern void pcm_start_timer(void (*callback)(void)); + +#ifdef __cplusplus +} +#endif + +#endif