package micromod;
/**
The Modulator is intended to "modulate" the pitch/volume of a Channel
over time. It can be configured to perform arpeggios, vibratos, tremolos, portamentos etc.
*/
public class Modulator {
public static final int
FX_ARPEGGIO=0, FX_SLIDEUP=1, FX_SLIDEDOWN=2, FX_TONEPORTA=3,
FX_VIBRATO=4, FX_TONEPORTAVOLSLIDE=5, FX_VIBRATOVOLSLIDE=6,
FX_TREMOLO=7, FX_PANNING=8, FX_SETSAMPLEOFFSET=9, FX_VOLSLIDE=0xA,
FX_SETVOLUME=0xC, FX_LOWPASS=0xE0, FX_FINESLIDEUP=0xE1,
FX_FINESLIDEDOWN=0xE2, FX_SETGLISSANDO=0xE3, FX_SETVIBRATOWAVE=0xE4,
FX_SETFINETUNE=0xE5, FX_SETTREMOLOWAVE=0xE7, FX_EXTPAN=0xE8, FX_RETRIG=0xE9,
FX_FINEVOLUP=0xEA,FX_FINEVOLDOWN=0xEB, FX_NOTECUT=0xEC, FX_NOTEDELAY=0xED,
FX_INVERTLOOP=0xEF;
protected Channel channel;
protected boolean supportsPanning;
protected ProTrackerLFO vibratoLFO, tremoloLFO;
protected int currentFXCommand, currentFXValue, fxSubValue1, fxSubValue2;
protected int currentFXPeriod, currentFXCounter;
protected int tonePortaDestination, tonePortaSpeed, sampleOffset;
protected int vibSpeed, vibDepth, tremSpeed, tremDepth;
protected int[] arpeggio = new int[3];
protected static int[] sinTable = new int[] {
0, 24 , 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253,
255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24 };
// TODO: Find out the period limits of FTK style mods. Have found some using periods as low as 45.
protected static int[] periodTable = new int[] {
856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 };
/**
@param chan the channel to be associated with this Modulator
@param supportsPanning If false, will ignore extended panning commands.
Old Protracker modules do not have panning, but may still issue the
panning commands.
*/
public Modulator( Channel chan, boolean supportsPanning ) {
channel = chan;
vibratoLFO = new ProTrackerLFO();
tremoloLFO = new ProTrackerLFO();
this.supportsPanning = supportsPanning;
}
/**
Reset the modulator to "power on" defaults.
*/
public void reset() {
currentFXCommand=currentFXValue=currentFXPeriod=0;
tonePortaDestination=tonePortaSpeed=sampleOffset=0;
vibSpeed=vibDepth=0;
tremSpeed=tremDepth=0;
vibratoLFO.reset();
tremoloLFO.reset();
}
/**
Initialise both this object and the channel associated with it with the data from a row
in a ProTracker-style sequence.
*/
public void initialiseFX( int fxPeriod, Instrument fxInstrument, int fxCommand, int fxValue ) {
currentFXPeriod=fxPeriod;
currentFXCommand=fxCommand;
currentFXValue=fxValue;
// Set up the porta destination if not zero.
if( fxPeriod != 0 ) tonePortaDestination = fxPeriod;
// Trigger the instrument(if any) on the channel.
if( fxCommand == FX_TONEPORTA || fxCommand == FX_TONEPORTAVOLSLIDE ) {
// If it's a tone porta, instruct the channel to switch to it.
channel.trigger( fxInstrument, -1 );
}
else if ( fxCommand == FX_NOTEDELAY ) {
/// Assign the instrument but defer trigger
channel.trigger( fxInstrument, 0 );
}
else {
// Do a normal trigger.
channel.trigger( fxInstrument, fxPeriod );
}
switch(fxCommand) {
case FX_ARPEGGIO:
if( fxValue != 0 ) initialiseArpeggio();
break;
case FX_TONEPORTA:
if( fxValue !=0 ) tonePortaSpeed = fxValue;
break;
case FX_VIBRATO:
initialiseVibrato(fxPeriod!=0);
break;
case FX_TONEPORTAVOLSLIDE:
getFXSubValues();
break;
case FX_VIBRATOVOLSLIDE:
getFXSubValues();
break;
case FX_TREMOLO:
initialiseTremolo(fxPeriod!=0);
break;
case FX_PANNING:
if( !supportsPanning ) break;
if( fxValue<=128 )
channel.setPanning( (128-fxValue)<<9, fxValue<<9 );
else if( fxValue==0xA4 )
channel.setPanning( -32768, 32768 );
break;
case FX_SETSAMPLEOFFSET:
if( currentFXValue != 0 ) sampleOffset = currentFXValue;
channel.setSamplePosition( sampleOffset << 8 );
break;
case FX_VOLSLIDE:
getFXSubValues();
break;
case FX_SETVOLUME:
channel.setVolume( fxValue, true );
break;
case FX_FINESLIDEUP:
// Research needed. Docs say add fxValue. Mods say add fxValue*2
channel.setPeriod( channel.getPeriod()-(fxValue<<1), true );
break;
case FX_FINESLIDEDOWN:
channel.setPeriod( channel.getPeriod()+(fxValue<<1), true );
break;
case FX_SETVIBRATOWAVE:
setVibratoWave(fxValue);
break;
case FX_SETFINETUNE:
adjustFineTune();
break;
case FX_SETTREMOLOWAVE:
setTremoloWave(fxValue);
break;
case FX_EXTPAN:
if( !supportsPanning ) break;
channel.setPanning( (15-fxValue)*4369, fxValue*4369 );
break;
case FX_RETRIG:
currentFXCounter = fxValue;
break;
case FX_NOTECUT:
currentFXCounter = fxValue;
updateNoteCut();
break;
case FX_NOTEDELAY:
currentFXCounter = fxValue;
updateNoteDelay();
break;
case FX_FINEVOLUP:
channel.setVolume( channel.getVolume()+fxValue, true );
break;
case FX_FINEVOLDOWN:
channel.setVolume( channel.getVolume()-fxValue, true );
break;
}
}
/**
Update the pitch/volume for a CIA tick of the tracker.
*/
public void updateFX() {
switch( currentFXCommand ) {
case FX_ARPEGGIO:
if( currentFXValue != 0 )
updateArpeggio();
break;
case FX_SLIDEUP:
channel.setPeriod( channel.getPeriod()-currentFXValue, true );
break;
case FX_SLIDEDOWN:
channel.setPeriod( channel.getPeriod()+currentFXValue, true );
break;
case FX_TONEPORTA:
updateTonePorta();
break;
case FX_VIBRATO:
updateVibrato(false);
break;
case FX_TONEPORTAVOLSLIDE:
updateTonePorta();
updateVolSlide();
break;
case FX_VIBRATOVOLSLIDE:
updateVibrato(false);
updateVolSlide();
break;
case FX_TREMOLO:
updateTremolo(false);
break;
case FX_VOLSLIDE:
updateVolSlide();
break;
case FX_RETRIG:
currentFXCounter--;
if( currentFXCounter==0 ) {
channel.trigger( null, channel.getPeriod() );
currentFXCounter = currentFXValue;
}
break;
case FX_NOTECUT:
updateNoteCut();
break;
case FX_NOTEDELAY:
updateNoteDelay();
break;
}
}
protected void getFXSubValues() {
fxSubValue1 = (currentFXValue&0xF0) >> 4;
fxSubValue2 = currentFXValue&0xF;
}
protected void initialiseArpeggio() {
currentFXCounter=0;
int period = channel.getPeriod();
int firstAdd = (currentFXValue&0xF0) >> 4;
int secondAdd = currentFXValue&0x0F;
for(int n=0; n<36; n++ )
if( periodTable[n] <= period ) {
arpeggio[0]=periodTable[n];
if( n+firstAdd > 35 ) firstAdd = 35-n;
if( n+secondAdd > 35 ) secondAdd = 35-n;
arpeggio[1] = periodTable[n+firstAdd];
arpeggio[2] = periodTable[n+secondAdd];
break;
}
}
protected void updateArpeggio() {
currentFXCounter++;
if( currentFXCounter > 2 ) currentFXCounter=0;
channel.setPeriod(arpeggio[currentFXCounter], false);
}
protected void updateTonePorta() {
int source = channel.getPeriod();
if( source==0 ) return;
if( source > tonePortaDestination ) {
if( (source-=tonePortaSpeed) < tonePortaDestination )
source=tonePortaDestination;
}
else {
if( (source+=tonePortaSpeed) > tonePortaDestination )
source=tonePortaDestination;
}
channel.setPeriod( source, true );
}
protected void updateVolSlide() {
channel.setVolume( channel.getVolume()+fxSubValue1-fxSubValue2, true );
}
protected void initialiseVibrato( boolean newNote ) {
getFXSubValues();
if( fxSubValue1 != 0 ) vibSpeed = fxSubValue1;
if( fxSubValue2 != 0 ) vibDepth = fxSubValue2;
updateVibrato(newNote);
}
protected void updateVibrato( boolean trig ){
int pdelta = vibratoLFO.update(vibSpeed,trig)*vibDepth >> 7;
channel.setPeriod( pdelta+channel.getPeriod(), false );
}
protected void setVibratoWave( int waveform ) {
boolean retrig=true;
if(waveform>3){
waveform-=4;
retrig=false;
}
switch(waveform){
case 0:
vibratoLFO.setWaveform(ProTrackerLFO.WF_SINUS,retrig);
break;
case 1:
vibratoLFO.setWaveform(ProTrackerLFO.WF_SAWDN,retrig);
break;
case 2:
vibratoLFO.setWaveform(ProTrackerLFO.WF_SQUARE,retrig);
break;
case 3:
vibratoLFO.setWaveform(ProTrackerLFO.WF_RANDOM,retrig);
break;
}
}
protected void initialiseTremolo( boolean newNote ) {
getFXSubValues();
if( fxSubValue1 != 0 ) tremSpeed = fxSubValue1;
if( fxSubValue2 != 0 ) tremDepth = fxSubValue2;
updateTremolo(newNote);
}
protected void updateTremolo( boolean trig ) {
int vdelta = tremoloLFO.update(tremSpeed,trig)*tremDepth >> 7;
channel.setVolume( vdelta+channel.getVolume(), false );
}
protected void setTremoloWave( int waveform ) {
boolean retrig=true;
if(waveform>3){
waveform-=4;
retrig=false;
}
switch(waveform){
case 0:
tremoloLFO.setWaveform(ProTrackerLFO.WF_SINUS,retrig);
break;
case 1:
tremoloLFO.setWaveform(ProTrackerLFO.WF_SAWDN,retrig);
break;
case 2:
tremoloLFO.setWaveform(ProTrackerLFO.WF_SQUARE,retrig);
break;
case 3:
tremoloLFO.setWaveform(ProTrackerLFO.WF_RANDOM,retrig);
break;
}
}
protected void updateNoteCut() {
if( currentFXCounter==0 ) channel.setVolume(0, true);
currentFXCounter--;
}
protected void updateNoteDelay() {
if( currentFXCounter==0 ) channel.trigger( null, currentFXPeriod );
currentFXCounter--;
}
protected void adjustFineTune() {
int fine;
if( currentFXValue < 8 )
fine = currentFXValue;
else
fine = -16 + currentFXValue;
channel.setFineTune( fine );
}
}