Duty / Noise no longer appears in PN inst. config

This commit is contained in:
scratchminer 2024-01-21 06:47:38 -05:00
parent 85997e55e7
commit 5d2dade036
7 changed files with 956 additions and 0 deletions

19
extern/pwrnoise/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2024 scratchminer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
extern/pwrnoise/README.md vendored Normal file
View file

@ -0,0 +1,4 @@
# pwrnoise
An emulator for the Power Noise fantasy sound chip, part of the [Hexheld](https://github.com/Hexheld/) fantasy console.
Design by [jvsTSX](https://github.com/jvsTSX/), code by scratchminer.

228
extern/pwrnoise/pwrnoise.c vendored Normal file
View file

@ -0,0 +1,228 @@
#include <stdio.h>
#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) {
chan->octave_counter++;
if (chan->enable && !(((chan->octave_counter - 1) >> chan->octave) & 0x0001) && ((chan->octave_counter >> chan->octave) & 0x0001)) {
if ((++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 = chan->period;
}
}
uint8_t out = chan->prev;
if (!chan->enable) out = 0;
else if (out != 0) out = chan->vol;
chan->out_latch = out;
}
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, bool force_zero) {
if (chan->enable && !((chan->octave_counter++ >> chan->octave) & 0x0001) && ((chan->octave_counter >> chan->octave) & 0x0001)) {
if ((++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 & 0x02) ? 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 & 0x01) ? 0x7f : 0x00;
chan->a = 0x00;
chan->portion = false;
}
}
chan->period_counter = 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);
uint8_t out = (left << 4) | right;
if (!chan->enable || force_zero) out = 0;
chan->out_latch = out;
}
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;
printf("%02x %02x\n", reg, val);
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, int16_t *left, int16_t *right) {
int32_t final_left, final_right;
if ((pn->flags & 0x80) != 0) {
pwrnoise_noise_step(&pn->n1);
pwrnoise_noise_step(&pn->n2);
pwrnoise_noise_step(&pn->n3);
pwrnoise_slope_step(&pn->s, (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

76
extern/pwrnoise/pwrnoise.h vendored Normal file
View file

@ -0,0 +1,76 @@
#ifndef PWRNOISE_H
#define PWRNOISE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
typedef struct {
bool enable;
bool am;
uint16_t period;
uint16_t period_counter;
uint8_t octave;
uint16_t octave_counter;
uint8_t tapa;
uint8_t tapb;
bool tapb_enable;
uint16_t lfsr;
uint8_t vol;
uint8_t out_latch;
uint8_t prev;
} noise_channel_t;
typedef struct {
bool enable;
uint8_t flags;
uint16_t period;
uint16_t period_counter;
uint8_t octave;
uint16_t octave_counter;
uint8_t alength;
uint8_t blength;
uint8_t a;
uint8_t b;
bool portion;
uint8_t aoffset;
uint8_t boffset;
uint8_t accum;
uint8_t vol;
uint8_t out_latch;
} slope_channel_t;
typedef struct {
uint8_t flags;
uint8_t gpioa;
uint8_t gpiob;
noise_channel_t n1;
noise_channel_t n2;
noise_channel_t n3;
slope_channel_t s;
} power_noise_t;
void pwrnoise_reset(power_noise_t *pn);
void pwrnoise_step(power_noise_t *pn, int16_t *left, int16_t *right);
void pwrnoise_write(power_noise_t *pn, uint8_t reg, uint8_t val);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,525 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 tildearrow and contributors
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "powernoise.h"
#include "../engine.h"
#include "furIcons.h"
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {regPool[a] = v; pwrnoise_write(&pn, (uint8_t)a, (uint8_t)v); if (dumpWrites) {addWrite(a,v);} }
#define cWrite(c,a,v) rWrite((c << 3) | (a + 1), v)
#define noiseCtl(enable, am, tapB) ((enable ? 0x80 : 0x00) | (am ? 0x02 : 0x00) | (tapB ? 0x01 : 0x00))
#define slopeCtl(enable, rst, a, b) ((enable ? 0x80 : 0x00) | \
(rst ? 0x40 : 0x00) | \
(a.clip ? 0x20 : 0x00) | \
(b.clip ? 0x10 : 0x00) | \
(a.reset ? 0x08 : 0x00) | \
(b.reset ? 0x04 : 0x00) | \
(a.dir ? 0x02 : 0x00) | \
(b.dir ? 0x01 : 0x00))
#define volPan(v, p) (((v * (p >> 4) / 15) << 4) | ((v * (p & 0xf) / 15) & 0xf))
#define mapAmp(a) (((a) * 65535 / 15 - 32768) * (pn.flags & 0x7) / 7)
const char* regCheatSheetPowerNoise[]={
"ACTL", "00",
"N1CTL", "01",
"N1FL", "02",
"N1FH", "03",
"N1SRL", "04",
"N1SRH", "05",
"N1TAP", "06",
"N1V", "07",
"IOA", "08",
"N2CTL", "09",
"N2FL", "0A",
"N2FH", "0B",
"N2SRL", "0C",
"N2SRH", "0D",
"N2TAP", "0E",
"N2V", "0F",
"IOB", "10",
"N3CTL", "11",
"N3FL", "12",
"N3FH", "13",
"N3SRL", "14",
"N3SRH", "15",
"N3TAP", "16",
"N3V", "17",
"SLACC", "18",
"SLCTL", "19",
"SLFL", "1A",
"SLFH", "1B",
"SLPA", "1C",
"SLPB", "1D",
"SLPO", "1E",
"SLV", "1F",
NULL
};
const char** DivPlatformPowerNoise::getRegisterSheet() {
return regCheatSheetPowerNoise;
}
void DivPlatformPowerNoise::acquire(short** buf, size_t len) {
int16_t left, right;
for (size_t h=0; h<len; h++) {
pwrnoise_step(&pn, &left, &right);
oscBuf[0]->data[oscBuf[0]->needle++]=mapAmp(pn.n1.out_latch);
oscBuf[1]->data[oscBuf[1]->needle++]=mapAmp(pn.n2.out_latch);
oscBuf[2]->data[oscBuf[2]->needle++]=mapAmp(pn.n3.out_latch);
oscBuf[3]->data[oscBuf[3]->needle++]=mapAmp(pn.s.out_latch);
buf[0][h] = left;
buf[1][h] = right;
}
}
/* macros:
* EX1 - control (0-63)
* EX2 - portion A length (0-255) - slope only
* EX3 - portion B length (0-255) - slope only
* EX4 - tap A location (0-15) - noise only
* EX5 - tap B location (0-15) - noise only
* EX6 - portion A offset (0-15) - slope only
* EX7 - portion B offset (0-15) - slope only
**/
void DivPlatformPowerNoise::tick(bool sysTick) {
for(int i = 0; i < 4; i++) {
chan[i].std.next();
if (chan[i].std.ex1.had) {
int val = chan[i].std.ex1.val;
if (chan[i].slope) {
chan[i].slopeA.clip = ((val & 0x20) != 0);
chan[i].slopeB.clip = ((val & 0x10) != 0);
chan[i].slopeA.reset = ((val & 0x08) != 0);
chan[i].slopeB.reset = ((val & 0x04) != 0);
chan[i].slopeA.dir = ((val & 0x02) != 0);
chan[i].slopeB.dir = ((val & 0x01) != 0);
cWrite(i, 0x00, slopeCtl(chan[i].active, false, chan[i].slopeA, chan[i].slopeB));
}
else {
chan[i].am = ((val & 0x02) != 0);
chan[i].tapBEnable = ((val & 0x01) != 0);
cWrite(i, 0x00, noiseCtl(chan[i].active, chan[i].am, chan[i].tapBEnable));
}
}
if (chan[i].std.ex2.had && chan[i].slope) {
cWrite(i, 0x03, chan[i].std.ex2.val)
}
if (chan[i].std.ex3.had && chan[i].slope) {
cWrite(i, 0x04, chan[i].std.ex3.val)
}
if ((chan[i].std.ex4.had || chan[i].std.ex5.had) && !chan[i].slope) {
cWrite(i, 0x05, (chan[i].std.ex4.val << 4) | chan[i].std.ex5.val)
}
if ((chan[i].std.ex6.had || chan[i].std.ex7.had) && chan[i].slope) {
cWrite(i, 0x05, (chan[i].std.ex6.val << 4) | chan[i].std.ex7.val)
}
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LINEAR_BROKEN(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
}
chan[i].handleArp();
if (chan[i].std.panL.had) {
chan[i].pan&=0x0f;
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
}
if (chan[i].std.panR.had) {
chan[i].pan&=0xf0;
chan[i].pan|=chan[i].std.panR.val&15;
}
if (chan[i].std.panL.had || chan[i].std.panR.had || chan[i].std.vol.had) {
cWrite(i,0x06,isMuted[i]?0:volPan(chan[i].outVol, chan[i].pan));
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
if (chan[i].slope && chan[i].active) {
cWrite(i, 0x00, slopeCtl(true, true, chan[i].slopeA, chan[i].slopeB));
chan[i].keyOn=true;
}
else if (chan[i].active) {
cWrite(i, 0x03, 0x01);
cWrite(i, 0x04, 0x00);
chan[i].keyOn=true;
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,chan[i].octave);
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095;
cWrite(i,0x01,(4095-chan[i].freq)&0xff);
cWrite(i,0x02,((4095-chan[i].freq)>>8) | (chan[i].octave<<4));
if (chan[i].keyOn) {
if(chan[i].slope) {
cWrite(i, 0x00, slopeCtl(true, false, chan[i].slopeA, chan[i].slopeB));
}
else {
cWrite(i, 0x00, noiseCtl(true, chan[i].am, chan[i].tapBEnable));
cWrite(i, 0x03, 0x01);
cWrite(i, 0x04, 0x00);
}
}
if (chan[i].keyOff) {
if(chan[i].slope) {
cWrite(i, 0x00, slopeCtl(false, false, chan[i].slopeA, chan[i].slopeB));
}
else {
cWrite(i, 0x00, noiseCtl(false, chan[i].am, chan[i].tapBEnable));
}
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
if (chan[i].slope) {
uint8_t counter = pn.s.accum;
regPool[0x18] = counter;
}
else {
uint16_t lfsr;
if (i == 0) lfsr = pn.n1.lfsr;
else if (i == 1) lfsr = pn.n2.lfsr;
else lfsr = pn.n3.lfsr;
regPool[(i << 3) + 0x4] = lfsr & 0xff;
regPool[(i << 3) + 0x5] = lfsr >> 8;
}
}
}
int DivPlatformPowerNoise::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_POWER_NOISE);
if (ins->type==DIV_INS_POWER_NOISE) {
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) {
int baseFreq, divider;
for (divider = 0; divider < 16; divider++) {
baseFreq = round(parent->calcBaseFreq(chipClock,2<<divider,c.value,true));
if(baseFreq < 4096) break;
}
chan[c.chan].octave=divider;
chan[c.chan].baseFreq=baseFreq;
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
chan[c.chan].keyOn=true;
}
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq, divider;
for (divider = 0; divider < 16; divider++) {
destFreq = round(parent->calcBaseFreq(chipClock,2<<divider,c.value2,true));
if(destFreq < 4096) break;
}
bool return2=false;
if (destFreq>chan[c.chan].baseFreq || divider<chan[c.chan].octave) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq > 4095 && chan[c.chan].octave > 0) {
chan[c.chan].octave--;
chan[c.chan].baseFreq %= 4096;
}
if (chan[c.chan].baseFreq>=destFreq || chan[c.chan].octave<divider) {
chan[c.chan].baseFreq=destFreq;
chan[c.chan].octave=divider;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq < 0 && chan[c.chan].octave < 15) {
chan[c.chan].octave++;
chan[c.chan].baseFreq = (chan[c.chan].baseFreq + 4096) % 4096;
}
if (chan[c.chan].baseFreq<=destFreq || chan[c.chan].octave>divider) {
chan[c.chan].baseFreq=destFreq;
chan[c.chan].octave=divider;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_PANNING: {
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
cWrite(c.chan,0x06,isMuted[c.chan]?0:volPan(chan[c.chan].outVol, chan[c.chan].pan));
break;
}
case DIV_CMD_LEGATO: {
int whatAMess = c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0));
int baseFreq, divider;
for (divider = 0; divider < 16; divider++) {
baseFreq = round(parent->calcBaseFreq(chipClock,2<<divider,whatAMess,true));
if(baseFreq < 4096) break;
}
chan[c.chan].baseFreq=baseFreq;
chan[c.chan].octave=divider;
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
}
case DIV_CMD_PRE_PORTA: {
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POWER_NOISE));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) {
int baseFreq, divider;
for (divider = 0; divider < 16; divider++) {
baseFreq = round(parent->calcBaseFreq(chipClock,2<<divider,chan[c.chan].note,true));
if(baseFreq < 4096) break;
}
chan[c.chan].baseFreq=baseFreq;
chan[c.chan].octave=divider;
}
chan[c.chan].inPorta=c.value;
break;
}
case DIV_CMD_GET_VOLMAX:
return 15;
break;
case DIV_CMD_POWER_NOISE_COUNTER_LOAD: {
if (chan[c.chan].slope && c.value == 0) {
rWrite(0x18, c.value2 & 0x7f);
}
else if (!chan[c.chan].slope) {
cWrite(c.chan, 0x03 + c.value, c.value2);
}
break;
}
case DIV_CMD_POWER_NOISE_IO_WRITE:
rWrite(0x08 + (c.value << 3), c.value2);
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);
break;
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_CMD_MACRO_RESTART:
chan[c.chan].std.restart(c.value);
break;
default:
break;
}
return 1;
}
void DivPlatformPowerNoise::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
cWrite(ch,0x06,isMuted[ch]?0:volPan(chan[ch].outVol, chan[ch].pan));
}
void DivPlatformPowerNoise::forceIns() {
for (int i=0; i<6; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
cWrite(i,0x06,isMuted[i]?0:volPan(chan[i].outVol, chan[i].pan));
}
}
void* DivPlatformPowerNoise::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformPowerNoise::getChanMacroInt(int ch) {
return &chan[ch].std;
}
unsigned short DivPlatformPowerNoise::getPan(int ch) {
return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15);
}
DivChannelModeHints DivPlatformPowerNoise::getModeHints(int ch) {
DivChannelModeHints ret;
ret.count=1;
if (ch==3) {
ret.hint[0]=ICON_FUR_SAW;
ret.type[0]=5;
}
else {
ret.hint[0]=ICON_FUR_NOISE;
ret.type[0]=4;
}
return ret;
}
DivSamplePos DivPlatformPowerNoise::getSamplePos(int ch) {
return DivSamplePos();
}
DivDispatchOscBuffer* DivPlatformPowerNoise::getOscBuffer(int ch) {
return oscBuf[ch];
}
int DivPlatformPowerNoise::mapVelocity(int ch, float vel) {
return round(15.0*pow(vel,0.22));
}
unsigned char* DivPlatformPowerNoise::getRegisterPool() {
return regPool;
}
int DivPlatformPowerNoise::getRegisterPoolSize() {
return 32;
}
void DivPlatformPowerNoise::reset() {
memset(regPool,0,32);
for (int i=0; i<4; i++) {
chan[i]=Channel();
chan[i].std.setEngine(parent);
if(i == 3) chan[i].slope = true;
}
pwrnoise_reset(&pn);
// set master volume to full
rWrite(0,0x87);
// set per-channel panning
for (int i=0; i<4; i++) {
cWrite(i,0x06,isMuted[i]?0:volPan(chan[i].outVol, chan[i].pan));
}
addWrite(0xffffffff, 0);
}
int DivPlatformPowerNoise::getOutputCount() {
return 2;
}
bool DivPlatformPowerNoise::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformPowerNoise::notifyWaveChange(int wave) {
}
void DivPlatformPowerNoise::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformPowerNoise::setFlags(const DivConfig& flags) {
chipClock=16777216;
CHECK_CUSTOM_CLOCK;
rate=chipClock;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate;
}
}
void DivPlatformPowerNoise::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformPowerNoise::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformPowerNoise::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 4;
}
void DivPlatformPowerNoise::quit() {
for (int i=0; i<4; i++) {
delete oscBuf[i];
}
}
DivPlatformPowerNoise::~DivPlatformPowerNoise() {
}

View file

@ -0,0 +1,101 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 tildearrow and contributors
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _POWER_NOISE_H
#define _POWER_NOISE_H
#include "../dispatch.h"
#include "../../fixedQueue.h"
#include "../../../extern/pwrnoise/pwrnoise.h"
class DivPlatformPowerNoise: public DivDispatch {
struct SlopePortion {
unsigned char len, offset;
bool clip, reset, dir;
SlopePortion():
len(0),
offset(0),
clip(false),
reset(false),
dir(false) {}
};
struct Channel: public SharedChannel<signed char> {
unsigned char octave, pan, tapA, tapB;
bool slope, am, tapBEnable, keyOn, keyOff;
SlopePortion slopeA, slopeB;
Channel():
SharedChannel<signed char>(15),
octave(0),
pan(255),
tapA(0),
tapB(0),
slope(false),
am(false),
tapBEnable(false),
keyOn(false),
keyOff(false),
slopeA(),
slopeB() {}
};
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
unsigned char regPool[32];
FixedQueue<int16_t,64> queueLeft;
FixedQueue<int16_t,64> queueRight;
power_noise_t pn;
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
unsigned short getPan(int chan);
DivChannelModeHints getModeHints(int chan);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
int mapVelocity(int ch, float vel);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
bool keyOffAffectsArp(int ch);
void setFlags(const DivConfig& flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformPowerNoise();
};
#endif

View file

@ -6902,6 +6902,9 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="OP4 Noise Mode";
dutyMax=3;
}
if (ins->type==DIV_INS_POWER_NOISE) {
dutyMax=0;
}
const char* waveLabel="Waveform";
int waveMax=(ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1));