package micromod; /** The Synthesizer represents a bunch of Modulators, each with an associated Channel. Methods are provided for setting up/updating the Modulators, and for extracting audio from the channels. */ public class Synthesizer { public static final int CBM_AMIGA_PAL_CLOCK = 7093789; public static final int CBM_AMIGA_NTSC_CLOCK = 7159090; protected Module module; protected Modulator[] modulators; protected Channel[] channels; protected int numberOfChannels, samplingRate, ciaTickSamples, ciaPreCalc; protected boolean pal; /** Constructor. @param mod The module to use initially. @param mixingRate The sampling rate. @param resamp The Resampler to use. */ public Synthesizer( Module mod, int mixingRate ) { samplingRate=mixingRate; setModule(mod); } /** Reconfigure the Synthesizer for a new module. */ public void setModule( Module mod ) { module = mod; numberOfChannels = mod.getSequence().getNumberOfChannels(); modulators = new Modulator[numberOfChannels]; channels = new Channel[numberOfChannels]; for( int n=0; n<numberOfChannels; n++ ) { channels[n] = new Channel( 8000, false ); modulators[n] = new Modulator( channels[n], mod.allowsPanning() ); } usePAL(mod.isPAL()); setSamplingRate(samplingRate); reset(); } /** Set the Synthesizer to produce audio at the specified sampling rate. */ public void setSamplingRate( int rate ) { samplingRate=rate; for( int n=0; n<numberOfChannels; n++ ) { channels[n].configure(rate,pal); } } /** Set whether to decode audio in PAL or NTSC mode. @param p If true, use PAL pitch, else use NTSC pitch and tempo. */ public void usePAL( boolean p ) { pal=p; for( int n=0; n<numberOfChannels; n++ ) { channels[n].configure(samplingRate, pal); } // Set up the CIA timer constant. int ciaClock = CBM_AMIGA_NTSC_CLOCK/10; if( pal ) ciaClock = CBM_AMIGA_PAL_CLOCK/10; ciaPreCalc = (1773447*1200)/ciaClock; } /** @return true if the Synthesizer is configured for PAL, otherwise NTSC */ public boolean isPAL() { return pal; } /** Silence the channels and reset panning to the default values. */ public void reset() { // Configure the default panning. int low=0, high=65536, decider=0; if( !module.allowsPanning() ){ // Reduce the stereo separation a bit. low=13107; high=52428; } for( int n=0; n<numberOfChannels; n++ ) { // Reset the channel modulators[n].reset(); channels[n].reset(); // Set panning. decider = n&3; if(decider==0||decider==3) channels[n].setPanning( high, low ); else channels[n].setPanning( low, high ); } } /** Set the BPM of the playback (which directly affects the number of samples produced per tick of the CIA clock) */ public void setBPM( int bpm ) { ciaTickSamples = (ciaPreCalc*samplingRate)/(bpm*1200); } /** @return The current number of samples of output per tick. */ public int getCiaTickSamples() { return ciaTickSamples; } /** @return the number of channels the Synthesizer is configured for */ public int getNumberOfChannels() { return numberOfChannels; } /** @return The specified Channel object */ public Channel getChannel( int channel ){ return channels[channel]; } /** Call this method at the start of every row to assign instruments and initialise effects */ public void initialiseFX( int channel, int period, int instrument, int effectCommand, int effectValue ) { modulators[channel].initialiseFX( period, module.getInstrument(instrument), effectCommand, effectValue ); } /** This method is called every tick except the first in a row to update the effect currently active on each channel. */ public void updateFX() { for( int n=0; n<numberOfChannels; n++ ) { modulators[n].updateFX(); } } }