/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2023 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 "waveSynth.h" #include "engine.h" #include "instrument.h" #include "../ta-log.h" inline bool effectOnlyAltersOutput(unsigned char effect) { switch (effect) { case DIV_WS_NONE: case DIV_WS_INVERT: case DIV_WS_ADD: case DIV_WS_SUBTRACT: case DIV_WS_AVERAGE: return true; break; default: return false; break; } return false; } bool DivWaveSynth::activeChanged() { if (activeChangedB) { activeChangedB=false; return true; } if (first) return true; return false; } bool DivWaveSynth::tick(bool skipSubDiv) { bool updated=first; first=false; if (--subDivCounter>0 && !skipSubDiv) { return updated; } subDivCounter=e->tickMult; if (!state.enabled) return updated; if (width<1) return false; if (--divCounter<=0) { // run effect switch (state.effect) { case DIV_WS_INVERT: for (int i=0; i<=state.speed; i++) { output[pos]=height-output[pos]; if (++pos>=width) pos=0; } updated=true; break; case DIV_WS_ADD: for (int i=0; i<=state.speed; i++) { output[pos]+=MIN(height,state.param1); if (output[pos]>=height) output[pos]-=height; if (++pos>=width) pos=0; } updated=true; break; case DIV_WS_SUBTRACT: for (int i=0; i<=state.speed; i++) { output[pos]-=MIN(height,state.param1); if (output[pos]<0) output[pos]+=height; if (++pos>=width) pos=0; } updated=true; break; case DIV_WS_AVERAGE: for (int i=0; i<=state.speed; i++) { int pos1=(pos+1>=width)?0:(pos+1); output[pos]=(128+output[pos]*(256-state.param1)+output[pos1]*state.param1)>>8; if (output[pos]<0) output[pos]=0; if (output[pos]>height) output[pos]=height; if (++pos>=width) pos=0; } updated=true; break; case DIV_WS_PHASE: for (int i=0; i<=state.speed; i++) { output[pos]=wave1[(pos+stage)%width]; if (++pos>=width) { pos=0; if (++stage>=width) stage=0; } } updated=true; break; case DIV_WS_CHORUS: for (int i=0; i<=state.speed; i++) { output[pos]=(wave1[pos]+wave1[(pos+stage)%width])>>1; if (++pos>=width) { pos=0; stage+=state.param1; while (stage>=width) stage-=width; } } updated=true; break; case DIV_WS_WIPE: for (int i=0; i<=state.speed; i++) { output[pos]=(stage&1)?wave1[pos]:wave2[pos]; if (output[pos]<0) output[pos]=0; if (output[pos]>height) output[pos]=height; if (++pos>=width) { pos=0; stage=!stage; } } updated=true; break; case DIV_WS_FADE: for (int i=0; i<=state.speed; i++) { output[pos]=wave1[pos]+(((wave2[pos]-wave1[pos])*stage)>>9); if (++pos>=width) { pos=0; stage+=1+state.param1; if (stage>512) stage=512; } } updated=true; break; case DIV_WS_PING_PONG: for (int i=0; i<=state.speed; i++) { output[pos]=wave1[pos]+(((wave2[pos]-wave1[pos])*stage)>>8); if (++pos>=width) { pos=0; if (stageDir) { stage-=1+state.param1; if (stage<=0) { stageDir=false; stage=0; } } else { stage+=1+state.param1; if (stage>=256) { stageDir=true; stage=256; } } } } updated=true; break; case DIV_WS_OVERLAY: for (int i=0; i<=state.speed; i++) { output[pos]+=wave2[pos]; if (output[pos]>=height) output[pos]-=height; if (++pos>=width) pos=0; } updated=true; break; case DIV_WS_NEGATIVE_OVERLAY: for (int i=0; i<=state.speed; i++) { output[pos]-=wave2[pos]; if (output[pos]<0) output[pos]+=height; if (++pos>=width) pos=0; } updated=true; break; case DIV_WS_SLIDE: for (int i=0; i<=state.speed; i++) { int newPos=(pos+stage)%(width*2); if (newPos>=width) { output[pos]=wave2[newPos-width]; } else { output[pos]=wave1[newPos]; } if (++pos>=width) { pos=0; if (++stage>=width*2) stage=0; } } updated=true; break; case DIV_WS_MIX: for (int i=0; i<=state.speed; i++) { output[pos]=(wave1[pos]+wave2[(pos+stage)%width])>>1; if (++pos>=width) { pos=0; stage+=state.param1; while (stage>=width) stage-=width; } } updated=true; break; case DIV_WS_PHASE_MOD: for (int i=0; i<=state.speed; i++) { int mod=(wave2[pos]*(state.param2-stage)*width)/(64*(height+1)); output[pos]=wave1[(pos+mod)%width]; if (++pos>=width) { pos=0; stage+=state.param1; if (stage>state.param2) stage=state.param2; } } updated=true; break; } divCounter=state.rateDivider; } return updated; } void DivWaveSynth::setWidth(int val) { width=val; if (width<0) width=0; if (width>256) width=256; } #define SHALL_UPDATE_OUT (!state.enabled || force || (state.enabled && effectOnlyAltersOutput(state.effect))) void DivWaveSynth::changeWave1(int num, bool force) { DivWavetable* w1=e->getWave(num); if (width<1) return; for (int i=0; imax<1 || w1->len<1) { wave1[i]=0; if (SHALL_UPDATE_OUT) output[i]=0; } else { int data=w1->data[i*w1->len/width]*height/w1->max; if (data<0) data=0; if (data>height) data=height; wave1[i]=data; if (SHALL_UPDATE_OUT) output[i]=data; } } first=true; } void DivWaveSynth::changeWave2(int num) { DivWavetable* w2=e->getWave(num); if (width<1) return; for (int i=0; imax<1 || w2->len<1) { wave2[i]=0; } else { int data=w2->data[i*w2->len/width]*height/w2->max; if (data<0) data=0; if (data>height) data=height; wave2[i]=data; } } first=true; } void DivWaveSynth::setEngine(DivEngine* engine, int waveFloor) { e=engine; memset(wave1,waveFloor,256); memset(wave2,waveFloor,256); for (int i=0; i<256; i++) { output[i]=waveFloor; } } void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { width=w; height=h; if (width<0) width=0; if (width>256) width=256; if (e==NULL) return; if (which==NULL) { if (state.enabled) activeChangedB=true; state=DivInstrumentWaveSynth(); return; } if (!which->ws.enabled) { if (state.enabled) activeChangedB=true; state=DivInstrumentWaveSynth(); return; } else { if (!state.enabled) activeChangedB=true; } state=which->ws; if (insChanged || !state.global) { pos=0; stage=0; stageDir=false; divCounter=0; subDivCounter=0; changeWave1(state.wave1,true); changeWave2(state.wave2); //tick(true); // ??? first=true; } }