Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 1 | /*************************************************************************** |
| 2 | * __________ __ ___. |
| 3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| 4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| 5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| 6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| 7 | * \/ \/ \/ \/ \/ |
| 8 | * $Id$ |
| 9 | * |
Michael Sevakis | ef1f7d3 | 2008-01-24 13:35:13 +0000 | [diff] [blame] | 10 | * Copyright (C) 2007-2008 Michael Sevakis (jhMikeS) |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 11 | * Copyright (C) 2006-2007 Adam Gashlin (hcs) |
| 12 | * Copyright (C) 2004-2007 Shay Green (blargg) |
| 13 | * Copyright (C) 2002 Brad Martin |
| 14 | * |
Daniel Stenberg | 2acc0ac | 2008-06-28 18:10:04 +0000 | [diff] [blame^] | 15 | * This program is free software; you can redistribute it and/or |
| 16 | * modify it under the terms of the GNU General Public License |
| 17 | * as published by the Free Software Foundation; either version 2 |
| 18 | * of the License, or (at your option) any later version. |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 19 | * |
| 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| 21 | * KIND, either express or implied. |
| 22 | * |
| 23 | ****************************************************************************/ |
| 24 | |
| 25 | /* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ |
| 26 | /* DSP Based on Brad Martin's OpenSPC DSP emulator */ |
| 27 | /* tag reading from sexyspc by John Brawn (John_Brawn@yahoo.com) and others */ |
| 28 | |
| 29 | #ifndef _SPC_CODEC_H_ |
| 30 | #define _SPC_CODEC_H_ |
| 31 | |
| 32 | /* rather than comment out asserts, just define NDEBUG */ |
| 33 | #define NDEBUG |
| 34 | #include <assert.h> |
| 35 | |
| 36 | /** Basic configuration options **/ |
| 37 | |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 38 | #define SPC_DUAL_CORE 1 |
| 39 | |
| 40 | #if !defined(SPC_DUAL_CORE) || NUM_CORES == 1 |
| 41 | #undef SPC_DUAL_CORE |
| 42 | #define SPC_DUAL_CORE 0 |
| 43 | #endif |
| 44 | |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 45 | /* TGB is the only target fast enough for gaussian and realtime BRR decode */ |
| 46 | /* echo is almost fast enough but not quite */ |
Michael Sevakis | 8a04f62 | 2008-05-03 16:11:39 +0000 | [diff] [blame] | 47 | #if defined(TOSHIBA_GIGABEAT_F) || defined(TOSHIBA_GIGABEAT_S) ||\ |
| 48 | defined(SIMULATOR) |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 49 | /* Don't cache BRR waves */ |
| 50 | #define SPC_BRRCACHE 0 |
| 51 | |
| 52 | /* Allow gaussian interpolation */ |
| 53 | #define SPC_NOINTERP 0 |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 54 | |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 55 | /* Allow echo processing */ |
| 56 | #define SPC_NOECHO 0 |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 57 | #elif defined(CPU_COLDFIRE) |
| 58 | /* Cache BRR waves */ |
| 59 | #define SPC_BRRCACHE 1 |
| 60 | |
| 61 | /* Disable gaussian interpolation */ |
| 62 | #define SPC_NOINTERP 1 |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 63 | |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 64 | /* Allow echo processing */ |
| 65 | #define SPC_NOECHO 0 |
| 66 | #elif defined (CPU_PP) && SPC_DUAL_CORE |
| 67 | /* Cache BRR waves */ |
| 68 | #define SPC_BRRCACHE 1 |
| 69 | |
| 70 | /* Disable gaussian interpolation */ |
| 71 | #define SPC_NOINTERP 1 |
| 72 | |
| 73 | /* Allow echo processing */ |
| 74 | #define SPC_NOECHO 0 |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 75 | #else |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 76 | /* Cache BRR waves */ |
| 77 | #define SPC_BRRCACHE 1 |
| 78 | |
| 79 | /* Disable gaussian interpolation */ |
| 80 | #define SPC_NOINTERP 1 |
| 81 | |
| 82 | /* Disable echo processing */ |
| 83 | #define SPC_NOECHO 1 |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 84 | #endif |
| 85 | |
| 86 | #ifdef CPU_ARM |
Michael Sevakis | a124e12 | 2007-11-26 08:02:18 +0000 | [diff] [blame] | 87 | |
| 88 | #if CONFIG_CPU != PP5002 |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 89 | #undef ICODE_ATTR |
| 90 | #define ICODE_ATTR |
| 91 | |
| 92 | #undef IDATA_ATTR |
| 93 | #define IDATA_ATTR |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 94 | |
| 95 | #undef ICONST_ATTR |
| 96 | #define ICONST_ATTR |
| 97 | |
| 98 | #undef IBSS_ATTR |
| 99 | #define IBSS_ATTR |
Michael Sevakis | a124e12 | 2007-11-26 08:02:18 +0000 | [diff] [blame] | 100 | #endif |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 101 | |
| 102 | #if SPC_DUAL_CORE |
Michael Sevakis | 0509914 | 2008-04-06 04:34:57 +0000 | [diff] [blame] | 103 | #undef SHAREDBSS_ATTR |
| 104 | #define SHAREDBSS_ATTR __attribute__ ((section(".ibss"))) |
| 105 | #undef SHAREDDATA_ATTR |
| 106 | #define SHAREDDATA_ATTR __attribute__((section(".idata"))) |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 107 | #endif |
| 108 | #endif |
| 109 | |
| 110 | /* Samples per channel per iteration */ |
| 111 | #if defined(CPU_PP) && NUM_CORES == 1 |
| 112 | #define WAV_CHUNK_SIZE 2048 |
| 113 | #else |
| 114 | #define WAV_CHUNK_SIZE 1024 |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 115 | #endif |
| 116 | |
| 117 | /**************** Little-endian handling ****************/ |
| 118 | |
| 119 | static inline unsigned get_le16( void const* p ) |
| 120 | { |
| 121 | return ((unsigned char const*) p) [1] * 0x100u + |
| 122 | ((unsigned char const*) p) [0]; |
| 123 | } |
| 124 | |
| 125 | static inline int get_le16s( void const* p ) |
| 126 | { |
| 127 | return ((signed char const*) p) [1] * 0x100 + |
| 128 | ((unsigned char const*) p) [0]; |
| 129 | } |
| 130 | |
| 131 | static inline void set_le16( void* p, unsigned n ) |
| 132 | { |
| 133 | ((unsigned char*) p) [1] = (unsigned char) (n >> 8); |
| 134 | ((unsigned char*) p) [0] = (unsigned char) n; |
| 135 | } |
| 136 | |
| 137 | #define GET_LE16( addr ) get_le16( addr ) |
| 138 | #define SET_LE16( addr, data ) set_le16( addr, data ) |
| 139 | #define INT16A( addr ) (*(uint16_t*) (addr)) |
| 140 | #define INT16SA( addr ) (*(int16_t*) (addr)) |
| 141 | |
| 142 | #ifdef ROCKBOX_LITTLE_ENDIAN |
| 143 | #define GET_LE16A( addr ) (*(uint16_t*) (addr)) |
| 144 | #define GET_LE16SA( addr ) (*( int16_t*) (addr)) |
| 145 | #define SET_LE16A( addr, data ) (void) (*(uint16_t*) (addr) = (data)) |
| 146 | #else |
| 147 | #define GET_LE16A( addr ) get_le16 ( addr ) |
| 148 | #define GET_LE16SA( addr ) get_le16s( addr ) |
| 149 | #define SET_LE16A( addr, data ) set_le16 ( addr, data ) |
| 150 | #endif |
| 151 | |
| 152 | struct Spc_Emu; |
| 153 | #define THIS struct Spc_Emu* const this |
| 154 | |
| 155 | /* The CPU portion (shock!) */ |
| 156 | |
| 157 | struct cpu_regs_t |
| 158 | { |
| 159 | long pc; /* more than 16 bits to allow overflow detection */ |
| 160 | uint8_t a; |
| 161 | uint8_t x; |
| 162 | uint8_t y; |
| 163 | uint8_t status; |
| 164 | uint8_t sp; |
| 165 | }; |
| 166 | |
| 167 | struct cpu_ram_t |
| 168 | { |
| 169 | union { |
| 170 | uint8_t padding1 [0x100]; |
| 171 | uint16_t align; |
| 172 | } padding1 [1]; |
| 173 | uint8_t ram [0x10000]; |
| 174 | uint8_t padding2 [0x100]; |
| 175 | }; |
| 176 | |
| 177 | #undef RAM |
| 178 | #define RAM ram.ram |
| 179 | extern struct cpu_ram_t ram; |
| 180 | |
| 181 | long CPU_run( THIS, long start_time ) ICODE_ATTR; |
| 182 | void CPU_Init( THIS ); |
| 183 | |
| 184 | /* The DSP portion (awe!) */ |
| 185 | enum { VOICE_COUNT = 8 }; |
| 186 | enum { REGISTER_COUNT = 128 }; |
| 187 | |
| 188 | struct raw_voice_t |
| 189 | { |
| 190 | int8_t volume [2]; |
| 191 | uint8_t rate [2]; |
| 192 | uint8_t waveform; |
| 193 | uint8_t adsr [2]; /* envelope rates for attack, decay, and sustain */ |
| 194 | uint8_t gain; /* envelope gain (if not using ADSR) */ |
| 195 | int8_t envx; /* current envelope level */ |
| 196 | int8_t outx; /* current sample */ |
| 197 | int8_t unused [6]; |
| 198 | }; |
| 199 | |
| 200 | struct globals_t |
| 201 | { |
| 202 | int8_t unused1 [12]; |
| 203 | int8_t volume_0; /* 0C Main Volume Left (-.7) */ |
| 204 | int8_t echo_feedback; /* 0D Echo Feedback (-.7) */ |
| 205 | int8_t unused2 [14]; |
| 206 | int8_t volume_1; /* 1C Main Volume Right (-.7) */ |
| 207 | int8_t unused3 [15]; |
| 208 | int8_t echo_volume_0; /* 2C Echo Volume Left (-.7) */ |
| 209 | uint8_t pitch_mods; /* 2D Pitch Modulation on/off for each voice */ |
| 210 | int8_t unused4 [14]; |
| 211 | int8_t echo_volume_1; /* 3C Echo Volume Right (-.7) */ |
| 212 | uint8_t noise_enables; /* 3D Noise output on/off for each voice */ |
| 213 | int8_t unused5 [14]; |
| 214 | uint8_t key_ons; /* 4C Key On for each voice */ |
| 215 | uint8_t echo_ons; /* 4D Echo on/off for each voice */ |
| 216 | int8_t unused6 [14]; |
| 217 | uint8_t key_offs; /* 5C key off for each voice |
| 218 | (instantiates release mode) */ |
| 219 | uint8_t wave_page; /* 5D source directory (wave table offsets) */ |
| 220 | int8_t unused7 [14]; |
| 221 | uint8_t flags; /* 6C flags and noise freq */ |
| 222 | uint8_t echo_page; /* 6D */ |
| 223 | int8_t unused8 [14]; |
| 224 | uint8_t wave_ended; /* 7C */ |
| 225 | uint8_t echo_delay; /* 7D ms >> 4 */ |
| 226 | char unused9 [2]; |
| 227 | }; |
| 228 | |
| 229 | enum state_t |
| 230 | { /* -1, 0, +1 allows more efficient if statements */ |
| 231 | state_decay = -1, |
| 232 | state_sustain = 0, |
| 233 | state_attack = +1, |
| 234 | state_release = 2 |
| 235 | }; |
| 236 | |
| 237 | struct cache_entry_t |
| 238 | { |
| 239 | int16_t const* samples; |
| 240 | unsigned end; /* past-the-end position */ |
| 241 | unsigned loop; /* number of samples in loop */ |
| 242 | unsigned start_addr; |
| 243 | }; |
| 244 | |
| 245 | enum { BRR_BLOCK_SIZE = 16 }; |
| 246 | enum { BRR_CACHE_SIZE = 0x20000 + 32} ; |
| 247 | |
| 248 | struct voice_t |
| 249 | { |
| 250 | #if SPC_BRRCACHE |
| 251 | int16_t const* samples; |
| 252 | long wave_end; |
| 253 | int wave_loop; |
| 254 | #else |
| 255 | int16_t samples [3 + BRR_BLOCK_SIZE + 1]; |
| 256 | int block_header; /* header byte from current block */ |
| 257 | #endif |
| 258 | uint8_t const* addr; |
| 259 | short volume [2]; |
| 260 | long position;/* position in samples buffer, with 12-bit fraction */ |
| 261 | short envx; |
| 262 | short env_mode; |
| 263 | short env_timer; |
| 264 | short key_on_delay; |
| 265 | }; |
| 266 | |
| 267 | #if SPC_BRRCACHE |
| 268 | /* a little extra for samples that go past end */ |
| 269 | extern int16_t BRRcache [BRR_CACHE_SIZE]; |
| 270 | #endif |
| 271 | |
| 272 | enum { FIR_BUF_HALF = 8 }; |
| 273 | |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 274 | #if defined(CPU_COLDFIRE) |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 275 | /* global because of the large aligment requirement for hardware masking - |
| 276 | * L-R interleaved 16-bit samples for easy loading and mac.w use. |
| 277 | */ |
| 278 | enum |
| 279 | { |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 280 | FIR_BUF_CNT = FIR_BUF_HALF, |
| 281 | FIR_BUF_SIZE = FIR_BUF_CNT * sizeof ( int32_t ), |
| 282 | FIR_BUF_ALIGN = FIR_BUF_SIZE * 2, |
| 283 | FIR_BUF_MASK = ~((FIR_BUF_ALIGN / 2) | (sizeof ( int32_t ) - 1)) |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 284 | }; |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 285 | #elif defined (CPU_ARM) |
| 286 | enum |
| 287 | { |
| 288 | FIR_BUF_CNT = FIR_BUF_HALF * 2 * 2, |
| 289 | FIR_BUF_SIZE = FIR_BUF_CNT * sizeof ( int32_t ), |
| 290 | FIR_BUF_ALIGN = FIR_BUF_SIZE, |
| 291 | FIR_BUF_MASK = ~((FIR_BUF_ALIGN / 2) | (sizeof ( int32_t ) * 2 - 1)) |
| 292 | }; |
| 293 | #endif /* CPU_* */ |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 294 | |
| 295 | struct Spc_Dsp |
| 296 | { |
| 297 | union |
| 298 | { |
| 299 | struct raw_voice_t voice [VOICE_COUNT]; |
| 300 | uint8_t reg [REGISTER_COUNT]; |
| 301 | struct globals_t g; |
| 302 | int16_t align; |
| 303 | } r; |
| 304 | |
| 305 | unsigned echo_pos; |
| 306 | int keys_down; |
| 307 | int noise_count; |
| 308 | uint16_t noise; /* also read as int16_t */ |
| 309 | |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 310 | #if defined(CPU_COLDFIRE) |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 311 | /* circularly hardware masked address */ |
| 312 | int32_t *fir_ptr; |
| 313 | /* wrapped address just behind current position - |
| 314 | allows mac.w to increment and mask fir_ptr */ |
| 315 | int32_t *last_fir_ptr; |
| 316 | /* copy of echo FIR constants as int16_t for use with mac.w */ |
Michael Sevakis | 1a41c8a | 2007-11-18 19:03:45 +0000 | [diff] [blame] | 317 | int16_t fir_coeff [VOICE_COUNT]; |
| 318 | #elif defined (CPU_ARM) |
| 319 | /* fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code */ |
| 320 | int32_t *fir_ptr; |
| 321 | /* copy of echo FIR constants as int32_t, for faster access */ |
| 322 | int32_t fir_coeff [VOICE_COUNT]; |
Michael Sevakis | 8450149 | 2007-07-16 21:16:52 +0000 | [diff] [blame] | 323 | #else |
| 324 | /* fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code */ |
| 325 | int fir_pos; /* (0 to 7) */ |
| 326 | int fir_buf [FIR_BUF_HALF * 2] [2]; |
| 327 | /* copy of echo FIR constants as int, for faster access */ |
| 328 | int fir_coeff [VOICE_COUNT]; |
| 329 | #endif |
| 330 | |
| 331 | struct voice_t voice_state [VOICE_COUNT]; |
| 332 | |
| 333 | #if SPC_BRRCACHE |
| 334 | uint8_t oldsize; |
| 335 | struct cache_entry_t wave_entry [256]; |
| 336 | struct cache_entry_t wave_entry_old [256]; |
| 337 | #endif |
| 338 | }; |
| 339 | |
| 340 | struct src_dir |
| 341 | { |
| 342 | char start [2]; |
| 343 | char loop [2]; |
| 344 | }; |
| 345 | |
| 346 | void DSP_run_( struct Spc_Dsp* this, long count, int32_t* out_buf ) ICODE_ATTR; |
| 347 | void DSP_reset( struct Spc_Dsp* this ); |
| 348 | |
| 349 | static inline void DSP_run( struct Spc_Dsp* this, long count, int32_t* out ) |
| 350 | { |
| 351 | /* Should we just fill the buffer with silence? Flags won't be cleared */ |
| 352 | /* during this run so it seems it should keep resetting every sample. */ |
| 353 | if ( this->r.g.flags & 0x80 ) |
| 354 | DSP_reset( this ); |
| 355 | |
| 356 | DSP_run_( this, count, out ); |
| 357 | } |
| 358 | |
| 359 | /**************** SPC emulator ****************/ |
| 360 | /* 1.024 MHz clock / 32000 samples per second */ |
| 361 | enum { CLOCKS_PER_SAMPLE = 32 }; |
| 362 | |
| 363 | enum { EXTRA_CLOCKS = CLOCKS_PER_SAMPLE / 2 }; |
| 364 | |
| 365 | /* using this disables timer (since this will always be in the future) */ |
| 366 | enum { TIMER_DISABLED_TIME = 127 }; |
| 367 | |
| 368 | enum { ROM_SIZE = 64 }; |
| 369 | enum { ROM_ADDR = 0xFFC0 }; |
| 370 | |
| 371 | enum { TIMER_COUNT = 3 }; |
| 372 | |
| 373 | struct Timer |
| 374 | { |
| 375 | long next_tick; |
| 376 | int period; |
| 377 | int count; |
| 378 | int shift; |
| 379 | int enabled; |
| 380 | int counter; |
| 381 | }; |
| 382 | |
| 383 | void Timer_run_( struct Timer* t, long time ) ICODE_ATTR; |
| 384 | |
| 385 | static inline void Timer_run( struct Timer* t, long time ) |
| 386 | { |
| 387 | if ( time >= t->next_tick ) |
| 388 | Timer_run_( t, time ); |
| 389 | } |
| 390 | |
| 391 | struct Spc_Emu |
| 392 | { |
| 393 | uint8_t cycle_table [0x100]; |
| 394 | struct cpu_regs_t r; |
| 395 | |
| 396 | int32_t* sample_buf; |
| 397 | long next_dsp; |
| 398 | int rom_enabled; |
| 399 | int extra_cycles; |
| 400 | |
| 401 | struct Timer timer [TIMER_COUNT]; |
| 402 | |
| 403 | /* large objects at end */ |
| 404 | struct Spc_Dsp dsp; |
| 405 | uint8_t extra_ram [ROM_SIZE]; |
| 406 | uint8_t boot_rom [ROM_SIZE]; |
| 407 | }; |
| 408 | |
| 409 | enum { SPC_FILE_SIZE = 0x10180 }; |
| 410 | |
| 411 | struct spc_file_t |
| 412 | { |
| 413 | char signature [27]; |
| 414 | char unused [10]; |
| 415 | uint8_t pc [2]; |
| 416 | uint8_t a; |
| 417 | uint8_t x; |
| 418 | uint8_t y; |
| 419 | uint8_t status; |
| 420 | uint8_t sp; |
| 421 | char unused2 [212]; |
| 422 | uint8_t ram [0x10000]; |
| 423 | uint8_t dsp [128]; |
| 424 | uint8_t ipl_rom [128]; |
| 425 | }; |
| 426 | |
| 427 | void SPC_Init( THIS ); |
| 428 | |
| 429 | int SPC_load_spc( THIS, const void* data, long size ); |
| 430 | |
| 431 | /**************** DSP interaction ****************/ |
| 432 | void DSP_write( struct Spc_Dsp* this, int i, int data ) ICODE_ATTR; |
| 433 | |
| 434 | static inline int DSP_read( struct Spc_Dsp* this, int i ) |
| 435 | { |
| 436 | assert( (unsigned) i < REGISTER_COUNT ); |
| 437 | return this->r.reg [i]; |
| 438 | } |
| 439 | |
| 440 | void SPC_run_dsp_( THIS, long time ) ICODE_ATTR; |
| 441 | |
| 442 | static inline void SPC_run_dsp( THIS, long time ) |
| 443 | { |
| 444 | if ( time >= this->next_dsp ) |
| 445 | SPC_run_dsp_( this, time ); |
| 446 | } |
| 447 | |
| 448 | int SPC_read( THIS, unsigned addr, long const time ) ICODE_ATTR; |
| 449 | void SPC_write( THIS, unsigned addr, int data, long const time ) ICODE_ATTR; |
| 450 | |
| 451 | /**************** Sample generation ****************/ |
| 452 | int SPC_play( THIS, long count, int32_t* out ) ICODE_ATTR; |
| 453 | |
| 454 | #endif /* _SPC_CODEC_H_ */ |