From 5d2dade0364b6646154a74465d0cb59333222eb4 Mon Sep 17 00:00:00 2001 From: scratchminer Date: Sun, 21 Jan 2024 06:47:38 -0500 Subject: [PATCH] Duty / Noise no longer appears in PN inst. config --- extern/pwrnoise/LICENSE | 19 ++ extern/pwrnoise/README.md | 4 + extern/pwrnoise/pwrnoise.c | 228 +++++++++++++ extern/pwrnoise/pwrnoise.h | 76 +++++ src/engine/platform/powernoise.cpp | 525 +++++++++++++++++++++++++++++ src/engine/platform/powernoise.h | 101 ++++++ src/gui/insEdit.cpp | 3 + 7 files changed, 956 insertions(+) create mode 100644 extern/pwrnoise/LICENSE create mode 100644 extern/pwrnoise/README.md create mode 100644 extern/pwrnoise/pwrnoise.c create mode 100644 extern/pwrnoise/pwrnoise.h create mode 100644 src/engine/platform/powernoise.cpp create mode 100644 src/engine/platform/powernoise.h diff --git a/extern/pwrnoise/LICENSE b/extern/pwrnoise/LICENSE new file mode 100644 index 000000000..3f7589ab1 --- /dev/null +++ b/extern/pwrnoise/LICENSE @@ -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. diff --git a/extern/pwrnoise/README.md b/extern/pwrnoise/README.md new file mode 100644 index 000000000..0366f408a --- /dev/null +++ b/extern/pwrnoise/README.md @@ -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. \ No newline at end of file diff --git a/extern/pwrnoise/pwrnoise.c b/extern/pwrnoise/pwrnoise.c new file mode 100644 index 000000000..114848a7f --- /dev/null +++ b/extern/pwrnoise/pwrnoise.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +#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 diff --git a/extern/pwrnoise/pwrnoise.h b/extern/pwrnoise/pwrnoise.h new file mode 100644 index 000000000..c20da5b47 --- /dev/null +++ b/extern/pwrnoise/pwrnoise.h @@ -0,0 +1,76 @@ +#ifndef PWRNOISE_H +#define PWRNOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +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 \ No newline at end of file diff --git a/src/engine/platform/powernoise.cpp b/src/engine/platform/powernoise.cpp new file mode 100644 index 000000000..ef14c06d3 --- /dev/null +++ b/src/engine/platform/powernoise.cpp @@ -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 + +#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; hdata[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<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<chan[c.chan].baseFreq || divider 4095 && chan[c.chan].octave > 0) { + chan[c.chan].octave--; + chan[c.chan].baseFreq %= 4096; + } + if (chan[c.chan].baseFreq>=destFreq || chan[c.chan].octavedivider) { + 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<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<rate=rate; + } +} + +void DivPlatformPowerNoise::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformPowerNoise::poke(std::vector& 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() { +} diff --git a/src/engine/platform/powernoise.h b/src/engine/platform/powernoise.h new file mode 100644 index 000000000..cc0e1d741 --- /dev/null +++ b/src/engine/platform/powernoise.h @@ -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 { + unsigned char octave, pan, tapA, tapB; + bool slope, am, tapBEnable, keyOn, keyOff; + SlopePortion slopeA, slopeB; + + Channel(): + SharedChannel(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 queueLeft; + FixedQueue 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& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformPowerNoise(); +}; + +#endif diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index bd8a3660a..301b7ac58 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -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));