239 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include <stdint.h>
 | |
| #include <stdbool.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "pwrnoise.h"
 | |
| 
 | |
| #ifdef __cplusplus
 | |
| extern "C" {
 | |
| #endif
 | |
| 
 | |
| void pwrnoise_noise_write(noise_channel_t *chan, uint8_t reg, uint8_t val) {
 | |
| 	switch (reg & 0x1f) {
 | |
| 		case 1:
 | |
| 			chan->enable = (val & 0x80) != 0;
 | |
| 			chan->am = (val & 0x02) != 0;
 | |
| 			chan->tapb_enable = (val & 0x01) != 0;
 | |
| 			break;
 | |
| 		case 2:
 | |
| 			chan->period = (chan->period & 0xf00) | val;
 | |
| 			break;
 | |
| 		case 3:
 | |
| 			chan->period = (chan->period & 0xff) | (((uint16_t)val << 8) & 0xf00);
 | |
| 			chan->octave = val >> 4;
 | |
| 			break;
 | |
| 		case 4:
 | |
| 			chan->lfsr = (chan->lfsr & 0xff00) | val;
 | |
| 			break;
 | |
| 		case 5:
 | |
| 			chan->lfsr = (chan->lfsr & 0x00ff) | ((uint16_t)val << 8);
 | |
| 			break;
 | |
| 		case 6:
 | |
| 			chan->tapa = val >> 4;
 | |
| 			chan->tapb = val & 0x0f;
 | |
| 			break;
 | |
| 		case 7:
 | |
| 			chan->vol = val;
 | |
| 			break;
 | |
| 		default: break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void pwrnoise_noise_step(noise_channel_t *chan, uint16_t cycles) {
 | |
| 	if (!chan->enable) {
 | |
| 		chan->out_latch = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	chan->octave_counter += cycles;
 | |
| 	if (((cycles >= 2) && ((cycles >> (chan->octave + 1)) != 0)) || (!(((chan->octave_counter - 1) >> chan->octave) & 0x0001) && ((chan->octave_counter >> chan->octave) & 0x0001))) {
 | |
| 		chan->period_counter += (cycles >> (chan->octave + 1));
 | |
| 		if ((cycles >> (chan->octave + 1)) == 0) ++chan->period_counter;
 | |
| 		
 | |
| 		while (chan->period_counter >= 4096) {
 | |
| 			chan->prev = (uint8_t)(chan->lfsr >> 15);
 | |
| 			uint16_t in = ((chan->lfsr >> chan->tapa) ^ (chan->tapb_enable ? (chan->lfsr >> chan->tapb) : 0)) & 0x0001;
 | |
| 			chan->lfsr = (chan->lfsr << 1) | in;
 | |
| 			chan->period_counter -= 4096 - chan->period;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	chan->out_latch = chan->prev ? chan->vol : 0;
 | |
| }
 | |
| 
 | |
| void pwrnoise_slope_write(slope_channel_t *chan, uint8_t reg, uint8_t val) {
 | |
| 	switch (reg & 0x1f) {
 | |
| 		case 0:
 | |
| 			chan->accum = val & 0x7f;
 | |
| 			break;
 | |
| 		case 1:
 | |
| 			chan->enable = (val & 0x80) != 0;
 | |
| 			if ((val & 0x40) != 0) {
 | |
| 				chan->a = 0;
 | |
| 				chan->b = 0;
 | |
| 				chan->portion = false;
 | |
| 			}
 | |
| 			chan->flags = val & 0x3f;
 | |
| 			break;
 | |
| 		case 2:
 | |
| 			chan->period = (chan->period & 0xf00) | val;
 | |
| 			break;
 | |
| 		case 3:
 | |
| 			chan->period = (chan->period & 0xff) | (((uint16_t)val << 8) & 0xf00);
 | |
| 			chan->octave = val >> 4;
 | |
| 			break;
 | |
| 		case 4:
 | |
| 			chan->alength = val;
 | |
| 			break;
 | |
| 		case 5:
 | |
| 			chan->blength = val;
 | |
| 			break;
 | |
| 		case 6:
 | |
| 			chan->aoffset = val >> 4;
 | |
| 			chan->boffset = val & 0x0f;
 | |
| 			break;
 | |
| 		case 7:
 | |
| 			chan->vol = val;
 | |
| 			break;
 | |
| 		default: break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void pwrnoise_slope_step(slope_channel_t *chan, uint16_t cycles, bool force_zero) {
 | |
| 	if (!chan->enable) {
 | |
| 		chan->out_latch = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	chan->octave_counter += cycles;
 | |
| 	if (((cycles >= 2) && ((cycles >> (chan->octave + 1)) != 0)) || (!(((chan->octave_counter - 1) >> chan->octave) & 0x0001) && ((chan->octave_counter >> chan->octave) & 0x0001))) {
 | |
| 		chan->period_counter += (cycles >> (chan->octave + 1));
 | |
| 		if ((cycles >> (chan->octave + 1)) == 0) ++chan->period_counter;
 | |
| 		
 | |
| 		while (chan->period_counter >= 4096) {
 | |
| 			if (!chan->portion) {
 | |
| 				if ((chan->flags & 0x02) != 0) chan->accum -= chan->aoffset;
 | |
| 				else chan->accum += chan->aoffset;
 | |
| 				
 | |
| 				if ((chan->flags & 0x20) != 0 && chan->accum > 0x7f) chan->accum = (chan->flags & 0x02) ? 0x00 : 0x7f;
 | |
| 				chan->accum &= 0x7f;
 | |
| 				
 | |
| 				if (++chan->a > chan->alength) {
 | |
| 					if ((chan->flags & 0x04) != 0) chan->accum = (chan->flags & 0x01) ? 0x7f : 0x00;
 | |
| 					chan->b = 0x00;
 | |
| 					chan->portion = true;
 | |
| 				}
 | |
| 			}
 | |
| 			else {
 | |
| 				if ((chan->flags & 0x01) != 0) chan->accum -= chan->boffset;
 | |
| 				else chan->accum += chan->boffset;
 | |
| 				
 | |
| 				if ((chan->flags & 0x10) != 0 && chan->accum > 0x7f) chan->accum = (chan->flags & 0x01) ? 0x00 : 0x7f;
 | |
| 				chan->accum &= 0x7f;
 | |
| 				
 | |
| 				if (++chan->b > chan->blength) {
 | |
| 					if ((chan->flags & 0x08) != 0) chan->accum = (chan->flags & 0x02) ? 0x7f : 0x00;
 | |
| 					chan->a = 0x00;
 | |
| 					chan->portion = false;
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			chan->period_counter -= 4096 - chan->period;
 | |
| 			
 | |
| 			uint8_t left = chan->accum >> 3;
 | |
| 			uint8_t right = chan->accum >> 3;
 | |
| 	
 | |
| 			switch (chan->vol >> 4) {
 | |
| 				case 0:
 | |
| 				case 1:
 | |
| 					left >>= 1;
 | |
| 				case 2:
 | |
| 				case 3:
 | |
| 					left >>= 1;
 | |
| 				case 4:
 | |
| 				case 5:
 | |
| 				case 6:
 | |
| 				case 7:
 | |
| 					left >>= 1;
 | |
| 				default: break;
 | |
| 			}
 | |
| 			switch (chan->vol & 0xf) {
 | |
| 				case 0:
 | |
| 				case 1:
 | |
| 					right >>= 1;
 | |
| 				case 2:
 | |
| 				case 3:
 | |
| 					right >>= 1;
 | |
| 				case 4:
 | |
| 				case 5:
 | |
| 				case 6:
 | |
| 				case 7:
 | |
| 					right >>= 1;
 | |
| 				default: break;
 | |
| 			}
 | |
| 	
 | |
| 			left &= (chan->vol >> 4);
 | |
| 			right &= (chan->vol & 0xf);
 | |
| 			chan->prev = (left << 4) | right;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	chan->out_latch = force_zero ? 0 : chan->prev;
 | |
| }
 | |
| 
 | |
| void pwrnoise_reset(power_noise_t *pn) {
 | |
| 	memset(pn, 0, sizeof(power_noise_t));
 | |
| }
 | |
| 
 | |
| void pwrnoise_write(power_noise_t *pn, uint8_t reg, uint8_t val) {
 | |
| 	reg &= 0x1f;
 | |
| 	
 | |
| 	if (reg == 0x00) {
 | |
| 		pn->flags = val;
 | |
| 	}
 | |
| 	else if (reg == 0x08 && !(pn->flags & 0x20)) {
 | |
| 		pn->gpioa = val;
 | |
| 	}
 | |
| 	else if (reg == 0x10 && !(pn->flags & 0x40)) {
 | |
| 		pn->gpiob = val;
 | |
| 	}
 | |
| 	else if (reg < 0x08) {
 | |
| 		pwrnoise_noise_write(&pn->n1, reg % 8, val);
 | |
| 	}
 | |
| 	else if (reg < 0x10) {
 | |
| 		pwrnoise_noise_write(&pn->n2, reg % 8, val);
 | |
| 	}
 | |
| 	else if (reg < 0x18) {
 | |
| 		pwrnoise_noise_write(&pn->n3, reg % 8, val);
 | |
| 	}
 | |
| 	else {
 | |
| 		pwrnoise_slope_write(&pn->s, reg % 8, val);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void pwrnoise_step(power_noise_t *pn, uint16_t cycles, int16_t *left, int16_t *right) {
 | |
| 	int32_t final_left, final_right;
 | |
| 	
 | |
| 	if ((pn->flags & 0x80) != 0) {
 | |
| 		pwrnoise_noise_step(&pn->n1, cycles);
 | |
| 		pwrnoise_noise_step(&pn->n2, cycles);
 | |
| 		pwrnoise_noise_step(&pn->n3, cycles);
 | |
| 		pwrnoise_slope_step(&pn->s, cycles, (pn->n1.am && !(pn->n1.prev)) || (pn->n2.am && !(pn->n2.prev)) || (pn->n3.am && !(pn->n3.prev)));
 | |
| 		
 | |
| 		final_left = (pn->n1.out_latch >> 4) + (pn->n2.out_latch >> 4) + (pn->n3.out_latch >> 4) + (pn->s.out_latch >> 4);
 | |
| 		final_right = (pn->n1.out_latch & 0xf) + (pn->n2.out_latch & 0xf) + (pn->n3.out_latch & 0xf) + (pn->s.out_latch & 0xf);
 | |
| 	}
 | |
| 	else {
 | |
| 		final_left = 0;
 | |
| 		final_right = 0;
 | |
| 	}
 | |
| 	
 | |
| 	*left = (int16_t)((final_left * 65535 / 63 - 32768) * (pn->flags & 0x7) / 7);
 | |
| 	*right = (int16_t)((final_right * 65535 / 63 - 32768) * (pn->flags & 0x7) / 7);
 | |
| }
 | |
| 
 | |
| #ifdef __cplusplus
 | |
| }
 | |
| #endif
 | 
