/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007-2008 Michael Sevakis (jhMikeS) * Copyright (C) 2006-2007 Adam Gashlin (hcs) * Copyright (C) 2004-2007 Shay Green (blargg) * Copyright (C) 2002 Brad Martin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ /* DSP Based on Brad Martin's OpenSPC DSP emulator */ /* tag reading from sexyspc by John Brawn (John_Brawn@yahoo.com) and others */ #ifndef _SPC_CODEC_H_ #define _SPC_CODEC_H_ /* rather than comment out asserts, just define NDEBUG */ #ifndef NDEBUG #define NDEBUG #endif #include /** Basic configuration options **/ #ifndef ARM_ARCH #define ARM_ARCH 0 #endif #define SPC_DUAL_CORE 1 #if !defined(SPC_DUAL_CORE) || NUM_CORES == 1 #undef SPC_DUAL_CORE #define SPC_DUAL_CORE 0 #endif /* Only some targets are fast enough for gaussian and realtime BRR decode */ #if CONFIG_CPU == S3C2440 || CONFIG_CPU == IMX31L || \ CONFIG_CPU == AS3525 || CONFIG_CPU == AS3525v2 || \ defined(CPU_S5L870X) || \ (CONFIG_PLATFORM & PLATFORM_HOSTED) || MEMORYSIZE <= 2 /* Don't cache BRR waves */ #define SPC_BRRCACHE 0 /* Allow gaussian interpolation */ #define SPC_NOINTERP 0 /* Allow echo processing */ #define SPC_NOECHO 0 #elif defined(CPU_COLDFIRE) /* Cache BRR waves */ #define SPC_BRRCACHE 1 /* Disable gaussian interpolation */ #define SPC_NOINTERP 1 /* Allow echo processing */ #define SPC_NOECHO 0 #elif defined (CPU_PP) && SPC_DUAL_CORE /* Cache BRR waves */ #define SPC_BRRCACHE 1 /* Disable gaussian interpolation */ #define SPC_NOINTERP 1 /* Allow echo processing */ #define SPC_NOECHO 0 #else /* Cache BRR waves */ #define SPC_BRRCACHE 1 /* Disable gaussian interpolation */ #define SPC_NOINTERP 1 /* Disable echo processing */ #define SPC_NOECHO 1 #endif #if (CONFIG_CPU == MCF5250) #define IBSS_ATTR_SPC IBSS_ATTR #define ICODE_ATTR_SPC ICODE_ATTR #define ICONST_ATTR_SPC ICONST_ATTR /* Not enough IRAM available to move further data to it. */ #define IBSS_ATTR_SPC_LARGE_IRAM #elif (CONFIG_CPU == PP5020) /* spc is slower on PP5020 when moving data to IRAM. */ #define IBSS_ATTR_SPC #define ICODE_ATTR_SPC #define ICONST_ATTR_SPC /* Not enough IRAM available to move further data to it. */ #define IBSS_ATTR_SPC_LARGE_IRAM #elif (CONFIG_CPU == PP5022) || (CONFIG_CPU == PP5024) #define IBSS_ATTR_SPC IBSS_ATTR #define ICODE_ATTR_SPC ICODE_ATTR #define ICONST_ATTR_SPC ICONST_ATTR /* Not enough IRAM available to move further data to it. */ #define IBSS_ATTR_SPC_LARGE_IRAM #elif defined(CPU_S5L870X) #define IBSS_ATTR_SPC IBSS_ATTR #define ICODE_ATTR_SPC ICODE_ATTR #define ICONST_ATTR_SPC ICONST_ATTR /* Very large IRAM. Move even more data to it. */ #define IBSS_ATTR_SPC_LARGE_IRAM IBSS_ATTR #else #define IBSS_ATTR_SPC IBSS_ATTR #define ICODE_ATTR_SPC ICODE_ATTR #define ICONST_ATTR_SPC ICONST_ATTR /* Not enough IRAM available to move further data to it. */ #define IBSS_ATTR_SPC_LARGE_IRAM #endif #if SPC_DUAL_CORE #undef SHAREDBSS_ATTR #define SHAREDBSS_ATTR __attribute__ ((section(".ibss"))) #undef SHAREDDATA_ATTR #define SHAREDDATA_ATTR __attribute__((section(".idata"))) #endif /* Samples per channel per iteration */ #if defined(CPU_PP) && NUM_CORES == 1 #define WAV_CHUNK_SIZE 2048 #else #define WAV_CHUNK_SIZE 1024 #endif /**************** Little-endian handling ****************/ static inline unsigned get_le16( void const* p ) { return ((unsigned char const*) p) [1] * 0x100u + ((unsigned char const*) p) [0]; } static inline int get_le16s( void const* p ) { return ((signed char const*) p) [1] * 0x100 + ((unsigned char const*) p) [0]; } static inline void set_le16( void* p, unsigned n ) { ((unsigned char*) p) [1] = (unsigned char) (n >> 8); ((unsigned char*) p) [0] = (unsigned char) n; } #define GET_LE16( addr ) get_le16( addr ) #define GET_LE16A( addr ) get_le16( addr ) #define SET_LE16( addr, data ) set_le16( addr, data ) #define INT16A( addr ) (*(uint16_t*) (addr)) #define INT16SA( addr ) (*(int16_t*) (addr)) #ifdef ROCKBOX_LITTLE_ENDIAN #define GET_LE16SA( addr ) (*( int16_t*) (addr)) #define SET_LE16A( addr, data ) (void) (*(uint16_t*) (addr) = (data)) #else #define GET_LE16SA( addr ) get_le16s( addr ) #define SET_LE16A( addr, data ) set_le16 ( addr, data ) #endif struct Spc_Emu; #define THIS struct Spc_Emu* const this /* The CPU portion (shock!) */ struct cpu_regs_t { long pc; /* more than 16 bits to allow overflow detection */ uint8_t a; uint8_t x; uint8_t y; uint8_t status; uint8_t sp; }; struct src_dir { uint16_t start; uint16_t loop; }; struct cpu_ram_t { union { uint8_t padding1 [0x100]; uint16_t align; } padding1 [1]; union { uint8_t ram [0x10000]; struct src_dir sd [0x10000/sizeof(struct src_dir)]; }; uint8_t padding2 [0x100]; }; #undef RAM #define RAM ram.ram extern struct cpu_ram_t ram; long CPU_run( THIS, long start_time ) ICODE_ATTR_SPC; void CPU_Init( THIS ); /* The DSP portion (awe!) */ enum { VOICE_COUNT = 8 }; enum { REGISTER_COUNT = 128 }; struct raw_voice_t { int8_t volume [2]; uint8_t rate [2]; uint8_t waveform; uint8_t adsr [2]; /* envelope rates for attack, decay, and sustain */ uint8_t gain; /* envelope gain (if not using ADSR) */ int8_t envx; /* current envelope level */ int8_t outx; /* current sample */ int8_t unused [6]; }; struct globals_t { int8_t unused1 [12]; int8_t volume_0; /* 0C Main Volume Left (-.7) */ int8_t echo_feedback; /* 0D Echo Feedback (-.7) */ int8_t unused2 [14]; int8_t volume_1; /* 1C Main Volume Right (-.7) */ int8_t unused3 [15]; int8_t echo_volume_0; /* 2C Echo Volume Left (-.7) */ uint8_t pitch_mods; /* 2D Pitch Modulation on/off for each voice */ int8_t unused4 [14]; int8_t echo_volume_1; /* 3C Echo Volume Right (-.7) */ uint8_t noise_enables; /* 3D Noise output on/off for each voice */ int8_t unused5 [14]; uint8_t key_ons; /* 4C Key On for each voice */ uint8_t echo_ons; /* 4D Echo on/off for each voice */ int8_t unused6 [14]; uint8_t key_offs; /* 5C key off for each voice (instantiates release mode) */ uint8_t wave_page; /* 5D source directory (wave table offsets) */ int8_t unused7 [14]; uint8_t flags; /* 6C flags and noise freq */ uint8_t echo_page; /* 6D */ int8_t unused8 [14]; uint8_t wave_ended; /* 7C */ uint8_t echo_delay; /* 7D ms >> 4 */ char unused9 [2]; }; enum state_t { /* -1, 0, +1 allows more efficient if statements */ state_decay = -1, state_sustain = 0, state_attack = +1, state_release = 2 }; struct cache_entry_t { int16_t const* samples; unsigned end; /* past-the-end position */ unsigned loop; /* number of samples in loop */ unsigned start_addr; }; enum { BRR_BLOCK_SIZE = 16 }; enum { BRR_CACHE_SIZE = 0x20000 + 32} ; struct voice_t { #if SPC_BRRCACHE int16_t const* samples; long wave_end; int wave_loop; #else int16_t samples [3 + BRR_BLOCK_SIZE + 1]; int block_header; /* header byte from current block */ #endif uint8_t const* addr; short volume [2]; long position;/* position in samples buffer, with 12-bit fraction */ short envx; short env_mode; short env_timer; short key_on_delay; }; #if SPC_BRRCACHE /* a little extra for samples that go past end */ extern int16_t BRRcache [BRR_CACHE_SIZE]; #endif enum { FIR_BUF_HALF = 8 }; #if defined(CPU_COLDFIRE) /* global because of the large aligment requirement for hardware masking - * L-R interleaved 16-bit samples for easy loading and mac.w use. */ enum { FIR_BUF_CNT = FIR_BUF_HALF, FIR_BUF_SIZE = FIR_BUF_CNT * sizeof ( int32_t ), FIR_BUF_ALIGN = FIR_BUF_SIZE * 2, FIR_BUF_MASK = ~((FIR_BUF_ALIGN / 2) | (sizeof ( int32_t ) - 1)) }; #elif defined (CPU_ARM) #if ARM_ARCH >= 6 enum { FIR_BUF_CNT = FIR_BUF_HALF * 2, FIR_BUF_SIZE = FIR_BUF_CNT * sizeof ( int32_t ), FIR_BUF_ALIGN = FIR_BUF_SIZE, FIR_BUF_MASK = ~((FIR_BUF_ALIGN / 2) | (sizeof ( int32_t ) - 1)) }; #else enum { FIR_BUF_CNT = FIR_BUF_HALF * 2 * 2, FIR_BUF_SIZE = FIR_BUF_CNT * sizeof ( int32_t ), FIR_BUF_ALIGN = FIR_BUF_SIZE, FIR_BUF_MASK = ~((FIR_BUF_ALIGN / 2) | (sizeof ( int32_t ) * 2 - 1)) }; #endif /* ARM_ARCH */ #endif /* CPU_* */ struct Spc_Dsp { union { struct raw_voice_t voice [VOICE_COUNT]; uint8_t reg [REGISTER_COUNT]; struct globals_t g; int16_t align; } r; unsigned echo_pos; int keys_down; int noise_count; uint16_t noise; /* also read as int16_t */ #if defined(CPU_COLDFIRE) /* FIR history is interleaved. Hardware handles wrapping by mask. * |LR|LR|LR|LR|LR|LR|LR|LR| */ int32_t *fir_ptr; /* wrapped address just behind current position - allows mac.w to increment and mask fir_ptr */ int32_t *last_fir_ptr; /* copy of echo FIR constants as int16_t for use with mac.w */ int16_t fir_coeff [VOICE_COUNT]; #elif defined (CPU_ARM) /* fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code */ int32_t *fir_ptr; #if ARM_ARCH >= 6 /* FIR history is interleaved with guard to eliminate wrap checking * when convolving. * |LR|LR|LR|LR|LR|LR|LR|LR|--|--|--|--|--|--|--|--| */ /* copy of echo FIR constants as int16_t, loaded as int32 for * halfword, packed multiples */ int16_t fir_coeff [VOICE_COUNT]; #else /* FIR history is interleaved with guard to eliminate wrap checking * when convolving. * |LL|RR|LL|RR|LL|RR|LL|RR|LL|RR|LL|RR|LL|RR|LL|RR|... * |--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--| */ /* copy of echo FIR constants as int32_t, for faster access */ int32_t fir_coeff [VOICE_COUNT]; #endif /* ARM_ARCH */ #else /* Unoptimized CPU */ /* fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code */ int fir_pos; /* (0 to 7) */ int fir_buf [FIR_BUF_HALF * 2] [2]; /* copy of echo FIR constants as int, for faster access */ int fir_coeff [VOICE_COUNT]; #endif struct voice_t voice_state [VOICE_COUNT]; #if SPC_BRRCACHE uint8_t oldsize; struct cache_entry_t wave_entry [256]; struct cache_entry_t wave_entry_old [256]; #endif }; void DSP_run_( struct Spc_Dsp* this, long count, int32_t* out_buf ) ICODE_ATTR_SPC; void DSP_reset( struct Spc_Dsp* this ); static inline void DSP_run( struct Spc_Dsp* this, long count, int32_t* out ) { /* Should we just fill the buffer with silence? Flags won't be cleared */ /* during this run so it seems it should keep resetting every sample. */ if ( this->r.g.flags & 0x80 ) DSP_reset( this ); DSP_run_( this, count, out ); } /**************** SPC emulator ****************/ /* 1.024 MHz clock / 32000 samples per second */ enum { CLOCKS_PER_SAMPLE = 32 }; enum { EXTRA_CLOCKS = CLOCKS_PER_SAMPLE / 2 }; /* using this disables timer (since this will always be in the future) */ enum { TIMER_DISABLED_TIME = 127 }; enum { ROM_SIZE = 64 }; enum { ROM_ADDR = 0xFFC0 }; enum { TIMER_COUNT = 3 }; struct Timer { long next_tick; int period; int count; int shift; int enabled; int counter; }; struct Spc_Emu { uint8_t cycle_table [0x100]; struct cpu_regs_t r; int32_t* sample_buf; long next_dsp; int rom_enabled; int extra_cycles; struct Timer timer [TIMER_COUNT]; /* large objects at end */ struct Spc_Dsp dsp; uint8_t extra_ram [ROM_SIZE]; uint8_t boot_rom [ROM_SIZE]; }; enum { SPC_FILE_SIZE = 0x10180 }; struct spc_file_t { char signature [27]; char unused [10]; uint8_t pc [2]; uint8_t a; uint8_t x; uint8_t y; uint8_t status; uint8_t sp; char unused2 [212]; uint8_t ram [0x10000]; uint8_t dsp [128]; uint8_t ipl_rom [128]; }; void SPC_Init( THIS ); int SPC_load_spc( THIS, const void* data, long size ); /**************** DSP interaction ****************/ void DSP_write( struct Spc_Dsp* this, int i, int data ) ICODE_ATTR_SPC; static inline int DSP_read( struct Spc_Dsp* this, int i ) { assert( (unsigned) i < REGISTER_COUNT ); return this->r.reg [i]; } int SPC_read( THIS, unsigned addr, long const time ) ICODE_ATTR_SPC; void SPC_write( THIS, unsigned addr, int data, long const time ) ICODE_ATTR_SPC; /**************** Sample generation ****************/ int SPC_play( THIS, long count, int32_t* out ) ICODE_ATTR_SPC; #endif /* _SPC_CODEC_H_ */