paulo@0: #include paulo@0: #include "ljgba.h" paulo@0: #include "pin8gba_sound.h" paulo@0: extern const unsigned short note_freqs[]; paulo@0: paulo@0: static const u16 moveEffect[] = { paulo@0: 0, 3, paulo@0: 0x516D, 0x0000, 0x0100 paulo@0: }; paulo@0: paulo@0: static const u16 rotateEffect[] = { paulo@0: 1, 7, paulo@0: 0x5161, 0x0000, 0x5166, 0x0000, 0x516b, 0x0000, 0x0100 paulo@0: }; paulo@0: paulo@0: static const u16 landEffect[] = { paulo@0: 1, 6, paulo@0: 0x8188, 0x7185, 0x6182, 0x5181, 0x3180, 0x0000 paulo@0: }; paulo@0: paulo@0: static const u16 lockEffect[] = { paulo@0: 3, 1, paulo@0: 0x2138 paulo@0: }; paulo@0: paulo@0: static const u16 lineEffect[] = { paulo@0: 0, 12, paulo@0: 0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9, paulo@0: 0x61A4, 0x51A6, 0x41A9, 0x31A4, 0x21A7, 0x11A9 paulo@0: }; paulo@0: paulo@0: static const u16 holdEffect[] = { paulo@0: 3, 8, paulo@0: 0x3030, 0x0000, 0x4032, 0x0000, 0x3130, 0x0000, 0x202c, 0x2128 paulo@0: }; paulo@0: paulo@0: static const u16 irsRotateEffect[] = { paulo@0: 1, 12, paulo@0: 0x5161, 0x51B1, 0x5166, 0x51B6, 0x516B, 0x51BB, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0100 paulo@0: }; paulo@0: paulo@0: static const u16 homerEffect[] = { paulo@0: 0, 22, paulo@0: 0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9, paulo@0: 0x61A4, 0x51A6, 0x41A9, 0x31A4, paulo@0: 0xB1A7, 0xA1A9, 0x91AC, 0x81A7, 0x71AA, 0x61AC, paulo@0: 0x51A7, 0x41A9, 0x31AC, 0x21A7, 0x11AA, 0x01AC paulo@0: }; paulo@0: paulo@0: static const u16 streakEffect[] = { paulo@0: 0, 30, paulo@0: 0xB1A4, 0xA1A6, 0x91A9, 0x81A4, 0x71A7, 0x61A9, paulo@0: 0x61A4, 0x51A6, 0x41A9, 0x31A4, paulo@0: 0xB1A7, 0xA1A9, 0x91AC, 0x81A7, 0x71AA, 0x61AC, paulo@0: 0x51A7, 0x41A9, 0x31AC, 0x21A7, paulo@0: 0xB1AA, 0xA1AC, 0x91AF, 0x71AA, 0x61AD, 0x51AF, paulo@0: 0x31AA, 0x21AC, 0x11AF, 0x01AA paulo@0: }; paulo@0: paulo@0: static const u16 sectionEffect[] = { paulo@0: 1, 30, paulo@0: 0xB1B0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0xB1B4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0xB1B7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0xB1BC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0xB1B0, 0x0000 paulo@0: }; paulo@0: paulo@0: static const u16 gameOverEffect1[] = { paulo@0: 1, 40, paulo@0: 0xF504, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 paulo@0: }; paulo@0: paulo@0: static const u16 gameOverEffect2[] = { paulo@0: 3, 40, paulo@0: 0xC12C, 0xA725, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 paulo@0: }; paulo@0: paulo@0: static const u16 winEffect1[] = { paulo@0: 1, 30, paulo@0: 0xA564, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, paulo@0: 0xA562, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, paulo@0: 0xA564, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, paulo@0: 0xA566, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000 paulo@0: }; paulo@0: paulo@0: static const u16 winEffect2[] = { paulo@0: 0, 30, paulo@0: 0xA568, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, paulo@0: 0xA566, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, paulo@0: 0xA568, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0000, 0x0000, 0x0000, paulo@0: 0xA56A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, paulo@0: 0x0000, 0x0000 paulo@0: }; paulo@0: paulo@0: paulo@0: static const u16 *const effects[] = { paulo@0: moveEffect, rotateEffect, landEffect, lockEffect, lineEffect, paulo@0: holdEffect, irsRotateEffect, homerEffect, streakEffect, sectionEffect, paulo@0: gameOverEffect1, gameOverEffect2, winEffect1, winEffect2 paulo@0: }; paulo@0: paulo@0: const unsigned short tri_vol_table[5] = { paulo@0: 0, paulo@0: TRILENVOL_25, paulo@0: TRILENVOL_50, paulo@0: TRILENVOL_75, paulo@0: TRILENVOL_100 paulo@0: }; paulo@0: paulo@0: void play_note(unsigned int ch, unsigned int note, unsigned int instrument) { paulo@0: switch (ch) { paulo@0: case 0: paulo@0: SQR1CTRL = instrument; paulo@0: SQR1FREQ = note_freqs[note] | FREQ_RESET; paulo@0: break; paulo@0: case 1: paulo@0: SQR2CTRL = instrument; paulo@0: SQR2FREQ = note_freqs[note] | FREQ_RESET; paulo@0: break; paulo@0: case 2: paulo@0: { paulo@0: int vol = instrument >> 13; paulo@0: TRILENVOL = tri_vol_table[(vol + 1) / 2]; paulo@0: TRIFREQ = note_freqs[note] | FREQ_RESET; paulo@0: } paulo@0: break; paulo@0: case 3: paulo@0: NOISECTRL = instrument; paulo@0: { paulo@0: int octave = (note & 0x3E) << 2; paulo@0: int pitch = note & 0x03; paulo@0: NOISEFREQ = (octave | pitch) ^ (0xF7 | FREQ_RESET); paulo@0: } paulo@0: break; paulo@0: } paulo@0: } paulo@0: paulo@0: void gba_poll_sound(struct LJPCView *v) { paulo@0: for (int i = 0; i < 4; ++i) { paulo@0: if (v->sndLeft[i]) { paulo@0: unsigned int data = *v->sndData[i]; paulo@0: if (data) { paulo@0: play_note(i, data & 0x003F, data & 0xFFC0); paulo@0: } paulo@0: ++v->sndData[i]; paulo@0: --v->sndLeft[i]; paulo@0: } paulo@0: } paulo@0: } paulo@0: paulo@0: void gba_play_sound(struct LJPCView *v, int effect) { paulo@0: const u16 *data = effects[effect]; paulo@0: int ch = data[0]; paulo@0: int len = data[1]; paulo@0: paulo@0: // square waves can be played on either channel 0 or 1; paulo@0: // switch if this channel is occupied and the other is free paulo@0: if (ch < 2 paulo@0: && v->sndLeft[ch] > v->sndLeft[1 - ch]) { paulo@0: ch = 1 - ch; paulo@0: } paulo@0: v->sndData[ch] = data + 2; paulo@0: v->sndLeft[ch] = len; paulo@0: } paulo@0: paulo@0: /** paulo@0: * Sets the sound bias to mid and resolution to 8-bit. paulo@0: * Setting bias is needed because some launchers (such as pogoshell) paulo@0: * set it to a state that mutes the tone generators. paulo@0: */ paulo@0: static void set_bias(void) paulo@0: { paulo@0: asm volatile("mov r2, #2; lsl r2, #8; swi 0x19" ::: "r0", "r1", "r2", "r3"); paulo@0: SETSNDRES(1); paulo@0: } paulo@0: paulo@0: __attribute__((aligned(4))) paulo@0: static const unsigned char triangleWave[16] = { paulo@0: 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, paulo@0: 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 paulo@0: }; paulo@0: unsigned char lastWaveBank = 0; paulo@0: paulo@0: void tri_set_waveform(const void *waveform) { paulo@0: memcpy((void *)TRIWAVERAM, waveform, 16); paulo@0: lastWaveBank = !lastWaveBank; paulo@0: TRICTRL = TRICTRL_2X32 | TRICTRL_BANK(lastWaveBank) | TRICTRL_ENABLE; paulo@0: } paulo@0: paulo@0: void install_sound(struct LJPCView *v) { paulo@0: SNDSTAT = SNDSTAT_ENABLE; paulo@0: DMGSNDCTRL = DMGSNDCTRL_LVOL(7) | DMGSNDCTRL_RVOL(7) paulo@0: | DMGSNDCTRL_LSQR1 | DMGSNDCTRL_RSQR1 paulo@0: | DMGSNDCTRL_LSQR2 | DMGSNDCTRL_RSQR2 paulo@0: | DMGSNDCTRL_LTRI | DMGSNDCTRL_RTRI paulo@0: | DMGSNDCTRL_LNOISE | DMGSNDCTRL_RNOISE; paulo@0: DSOUNDCTRL = DSOUNDCTRL_DMG100; paulo@0: set_bias(); paulo@0: SQR1SWEEP = SQR1SWEEP_OFF; paulo@0: paulo@0: #if 0 paulo@0: TRICTRL = TRICTRL_2X32 | TRICTRL_BANK(0) | TRICTRL_ENABLE; paulo@0: tri_set_waveform(triangleWave); paulo@0: TRILENVOL = 0; paulo@0: TRIFREQ = 1024 | FREQ_RESET; paulo@0: #endif paulo@0: paulo@0: v->sndLeft[0] = 0; paulo@0: v->sndLeft[1] = 0; paulo@0: v->sndLeft[2] = 0; paulo@0: v->sndLeft[3] = 0; paulo@0: } paulo@0: