package turgen.generator;

import java.io.*;
import turgen.*;
import turgen.playlist.AbstractPlayListItem;
import turgen.config.*;
import static turgen.generator.ProgressSource.CANCEL_NO;

/** Electric signal genetator
 * 
 * 
 */
public class SignalGenerator implements Runnable, ProgressSource, SampleConsumer {

    /*Output types*/
    public static final int TYPE_WAVE = 0;
    public static final int TYPE_AUDIO = 1;
    
    /*Instruction constants*/
    public static final int INSTR_NARROW = 0;
    public static final int INSTR_WIDE = 1;
    public static final int INSTR_PILOT = 2;
    public static final int INSTR_DATA = 3;
    public static final int INSTR_PILOTTONE = 4;
    public static final int INSTR_END = 7;
    public static final int INSTR_SHORTSILENCE = 8;
    public static final int INSTR_SYNC = 9;
    public static final int INSTR_LONGSILENCE = 10;
    public static final int INSTR_SETUP = 11;
    public static final int INSTR_BLOCKSEP = 12;
    public static final int INSTR_STOP = 13;
    public static final int INSTR_PWMS = 14;
    public static final int INSTR_PWMC = 15;
    public static final int INSTR_PWMD = 16;
    public static final int INSTR_PWML = 17;
    public static final int INSTR_BAUD = 18;
    public static final int INSTR_STDDATA = 19;
    public static final int INSTR_FSK=20;
    public static final int INSTR_FUJI=21;

    /*Flag constants*/
    public static final int FLAG_POLARITY_10 = 1;
    public static final int FLAG_POLARITY_01 = 0;
    public static final int FLAG_ORDER_LH = 0;
    public static final int FLAG_ORDER_HL = 1;
    
    /*Configuration entries - WAVE*/
    private static ConfigClass cfcWaveGenerator;
    private static IntegerConfigEntry cfgWavePulsesAmplitude;
    private static StringConfigEntry cfgWavePostProcessingString;
    private static IntegerConfigEntry cfgWaveAudioBits;
    private static IntegerConfigEntry cfgWaveAudioChannels;
    private static BooleanConfigEntry cfgWaveSignalInRightChannelOnly;
    private static BooleanConfigEntry cfgWaveDoNotModulateStandardRecords;
    private static BooleanConfigEntry cfgWaveUseHarmonicPulses;
    
    /*Configuration entries - Audio*/
    private static ConfigClass cfcAudioGenerator;
    private static IntegerConfigEntry cfgAudioPulsesAmplitude;
    private static IntegerConfigEntry cfgAudioBufferSize;
    private static IntegerConfigEntry cfgAudioBits;
    private static BooleanConfigEntry cfgAudioSigned;
    private static IntegerConfigEntry cfgAudioPreSilence;
    private static IntegerConfigEntry cfgAudioPostSilence;
    private static IntegerConfigEntry cfgAudioChannels;
    private static BooleanConfigEntry cfgAudioSignalInRightChannelOnly;
    private static BooleanConfigEntry cfgAudioDoNotModulateStandardRecords;
    private static BooleanConfigEntry cfgAudioUseDoubleBuffering;
    private static BooleanConfigEntry cfgAudioUseHarmonicPulses;
    
    
    /*Postprocessing enabled flag*/
    private final boolean wavePostProcessingEnabled;

    /*Total number of instructions*/
    private final int genLength;

    private int ip; /*Index of current instruction*/
    private int cx; /*Loop counter*/
    private int op; /*Current instruction*/

    private final int[] mem;   /*Memory*/

    private final AbstractPlayListItem plr; /*Current playlist item*/

    /** Wide pulse*/
    private byte[] WIDE_PULSE;
    /** Short pulse*/
    private byte[] NARROW_PULSE;
    /** Pilot tone pulse*/
    private byte[] PILOTTONE_PULSE;
    /** Sync pulse*/
    private byte[] SYNC_PULSE;
    /** Silence 0.1 seconds*/
    private byte[] SILENCE_SHORT;
    /** Block separator*/
    private byte[] BLOCKSEP;
    /** Stop pulse*/
    private byte[] STOP_PULSE;
    
    /*Auxiliary samples*/
    private byte[] LOW_SAMPLE;
    private byte[] HIGH_SAMPLE;
    private byte[] SILENCE_SAMPLE;
    
    
    /*Order of bits*/
    private boolean loHiOrder=false;
    
    /*Pwm instructions*/
    private int pwmPolarity;
    private boolean pwmLoHiOrder;
    private int pwmSampleRate;
    
    /*FSK related*/
    private FSKGenerator fskGenerator;
    
    
    /*Output file*/
    private String outFile;
    /*Cancel type*/
    private volatile int cancelType;
    /*Postprocessing run flag*/
    private boolean postProcessRunning = false;
    /*Rename to WAV flag*/
    private boolean waveRenameToWav = false;

    /*Configuration copy*/
    private boolean cSigned;
    private int cBits;
    private String cPostProcessingString;
    private int cPulseVolume;
    private int cChannels;
    private boolean cSignalInRightChannelOnly;
    private int cBufferSize;
    private int cPreSilence;
    private int cPostSilence;
    private boolean cDoNotModulateStandardRecords;
    private boolean cUseDoubleBuffering;
    private boolean cUseHarmonicPulses;

    /*Error message*/
    private String errorMessage;
    /**Signal writer*/
    private SignalWriter signalWriter;
    
    /*Output type, WAVE or AUDIO*/
    private final int outputType;

    /** Create new SignalGenerator
     * 
     * @param p Playlist Item
     * @param dta Generator instructions
     * @param renToWav Chane file extension to WAVE
     * @param postProEnabled Postprocessing enabled
     * @param outputType Output type WAVE or AUDIO
     */
    public SignalGenerator(AbstractPlayListItem p, int[] dta, boolean renToWav,boolean postProEnabled,int outputType) {
        plr = p;
        mem = dta;
        cancelType = CANCEL_NO;
        genLength = mem.length;
        outFile = p.outFile;
        errorMessage = null;
        waveRenameToWav = renToWav;
        wavePostProcessingEnabled=postProEnabled;
        this.outputType=outputType;
    }

    /**Basic initialization*/
    public static void init() {

        /* General configuration options*/
        int[] bts = {16, 8};
        int[] chans = {1, 2};

        /*WAVE - Initialize configuration class*/
        cfcWaveGenerator = new ConfigClass("WaveGenerator", "WAVE generator");

        /*WAVE  - Initialize configuration entries*/
        cfgWavePulsesAmplitude = new IntegerConfigEntry("pulsesAmplitude","Amplitude [%]", 90, 0, 100);
        cfgWaveAudioChannels = new IntegerConfigEntry("channels","Channels", chans, 1);
        cfgWaveAudioBits = new IntegerConfigEntry("bitsPerSample","Bits per sample", bts, 0);
        cfgWavePostProcessingString = new StringConfigEntry("postProcessingCommand","Postprocessing command", "");
        cfgWaveSignalInRightChannelOnly = new BooleanConfigEntry("signalInRightChannelOnly","Signal in right channel only",false);
        cfgWaveDoNotModulateStandardRecords = new BooleanConfigEntry("doNotModulateStandardRecords","Do not modulate standard records",false);
        cfgWaveUseHarmonicPulses = new BooleanConfigEntry("userHarmonicPulses","Use harmonic pulses",false);

        /*WAVE - entries to the class*/
        cfcWaveGenerator.addEntry(cfgWavePulsesAmplitude);
        cfcWaveGenerator.addEntry(cfgWaveAudioChannels);
        cfcWaveGenerator.addEntry(cfgWaveAudioBits);
        cfcWaveGenerator.addEntry(cfgWavePostProcessingString);
        cfcWaveGenerator.addEntry(cfgWaveSignalInRightChannelOnly);
        cfcWaveGenerator.addEntry(cfgWaveDoNotModulateStandardRecords);
        cfcWaveGenerator.addEntry(cfgWaveUseHarmonicPulses);

        /*WAVE - Register configuration class*/
        Configuration.registerClass(cfcWaveGenerator);
        
        
        /*AUDIO - Initialize configuration classes*/
        cfcAudioGenerator = new ConfigClass("AudioGenerator","Audio generator");

        /*AUDIO - Initialize configuration entries*/
        cfgAudioPulsesAmplitude = new IntegerConfigEntry("pulsesAmplitude","Amplitude [%]", 90, 0, 100);
        cfgAudioChannels = new IntegerConfigEntry("channels","Channels", chans, 1);
        cfgAudioBits = new IntegerConfigEntry("bitsPerSample","Bits per sample", bts, 0);
        cfgAudioSigned = new BooleanConfigEntry("signed","Signed samples", true);

        cfgAudioPreSilence = new IntegerConfigEntry("preSilence","Initial silence [0.1 s]", 10, 0, 20);
        cfgAudioPostSilence = new IntegerConfigEntry("postSilence","Terminal silence [0.1 s]", 10, 0, 20);
        cfgAudioBufferSize = new IntegerConfigEntry("bufferSize","Buffer size [bytes]", 176400, 32, 1411200);
        cfgAudioSignalInRightChannelOnly = new BooleanConfigEntry("signalInRightChannelOnly","Signal in right channel only",false);
        cfgAudioDoNotModulateStandardRecords = new BooleanConfigEntry("doNotModulateStandardRecords","Do not modulate standard records",false);
        cfgAudioUseDoubleBuffering = new BooleanConfigEntry("doubleBuffering","Use double buffering",true);
        cfgAudioUseHarmonicPulses = new BooleanConfigEntry("userHarmonicPulses","Use harmonic pulses",false);
        
        /*AUDIO - Add entries to the class*/
        cfcAudioGenerator.addEntry(cfgAudioPulsesAmplitude);
        cfcAudioGenerator.addEntry(cfgAudioChannels);
        cfcAudioGenerator.addEntry(cfgAudioBits);
        cfcAudioGenerator.addEntry(cfgAudioSigned);
        cfcAudioGenerator.addEntry(cfgAudioPreSilence);
        cfcAudioGenerator.addEntry(cfgAudioPostSilence);
        cfcAudioGenerator.addEntry(cfgAudioBufferSize);
        cfcAudioGenerator.addEntry(cfgAudioSignalInRightChannelOnly);
        cfcAudioGenerator.addEntry(cfgAudioDoNotModulateStandardRecords);
        cfcAudioGenerator.addEntry(cfgAudioUseDoubleBuffering);
        cfcAudioGenerator.addEntry(cfgAudioUseHarmonicPulses);
        
        /*AUDIO - Register class*/
        Configuration.registerClass(cfcAudioGenerator);

    }
    
    private void copyConfiguration () {
     
        if (outputType==TYPE_WAVE) {
            cBits = cfgWaveAudioBits.getAsInt();
            cSigned = (cBits != 8);
            cPostProcessingString = cfgWavePostProcessingString.getAsString();
            cChannels = cfgWaveAudioChannels.getAsInt();
            cPulseVolume = cfgWavePulsesAmplitude.getAsInt();
            cSignalInRightChannelOnly = cfgWaveSignalInRightChannelOnly.getAsBoolean();
            cDoNotModulateStandardRecords = cfgWaveDoNotModulateStandardRecords.getAsBoolean();
            cUseHarmonicPulses = cfgWaveUseHarmonicPulses.getAsBoolean();
        }
        else {
            cBits = cfgAudioBits.getAsInt();
            cBufferSize = cfgAudioBufferSize.getAsInt();
            cPreSilence = cfgAudioPreSilence.getAsInt();
            cSigned = cfgAudioSigned.getAsBoolean();
            cChannels = cfgAudioChannels.getAsInt();
            cPulseVolume = cfgAudioPulsesAmplitude.getAsInt();
            cSignalInRightChannelOnly = cfgAudioSignalInRightChannelOnly.getAsBoolean();
            cDoNotModulateStandardRecords = cfgAudioDoNotModulateStandardRecords.getAsBoolean();
            cUseDoubleBuffering = cfgAudioUseDoubleBuffering.getAsBoolean();
            cUseHarmonicPulses = cfgAudioUseHarmonicPulses.getAsBoolean();
        
            /*Correct post silence if necessary*/
            cPostSilence = cfgAudioPostSilence.getAsInt();
            double postSilenceSeconds=cPostSilence/10.0f;
            double bufferSeconds=cBufferSize/(44100.0*cChannels*(cBits>>3));
            if (!(postSilenceSeconds>bufferSeconds*1.5)) {
                postSilenceSeconds=bufferSeconds*1.5;
                cPostSilence=(int)Math.round(Math.ceil(postSilenceSeconds*10));
            }
        }
    }

    
    /** Prepare to generate signal
     * 
     * @return Error message or null when OK
     */ 
    public String prepare() {

        /*Copy configuration*/
        copyConfiguration();
        
        if (outputType==TYPE_WAVE) {
            signalWriter = new WaveSignalWriter(cBits, cChannels);
        }
        else {
            if (cUseDoubleBuffering==true) {
                signalWriter = new AudioSignalBufferedWriter(cBits,cChannels,cSigned,cBufferSize);
            }
            else {
                signalWriter = new AudioSignalWriter(cBits,cChannels,cSigned,cBufferSize);
            }
        }
               
        /*Prepare writer*/
        String message = signalWriter.prepare(outFile);
        if (message != null) return message;

        /*Create silence*/
        SILENCE_SHORT = PulseCreator.createPulse(cChannels, cPulseVolume, 4410, cBits, cSigned, 1, 1,cSignalInRightChannelOnly,false);
        BLOCKSEP = PulseCreator.createPulse(cChannels, cPulseVolume, 1000, cBits, cSigned, 1, 1,cSignalInRightChannelOnly,false);

        /*No error message*/
        return null;

    }

    /*Main Thread -----------------------------------------------------------*/
    /*************************************************************************/
    public void run() {

        /*Change extension of output file if requested*/
        outFile = plr.outFile;
        if (waveRenameToWav == true) {
            outFile = Utils.changeFileExtension(outFile, ".wav");
        }

        /*Register playlist item*/
        if (!(Turgen.sync.registerProgress(this, Thread.currentThread())) == true) {
            return;
        }

        /*Initialize output file*/
        String pm = prepare();

        /*In case of failure, bail out*/
        if (pm != null) {
            errorMessage = pm;
            Turgen.sync.unregisterProgress(this);
            return;
        } 


        try {
            
            /*Initial signal*/
            for (int k = 0; k < cPreSilence; k++) {
                signalWriter.writeInitialSignal(SILENCE_SHORT);
            }
            
            ip = 0;
            op = 0;  
            
            do {

                op = mem[ip];
                

                /*Execution of instructions*/
                switch (op) {

                    /*Narrow pulse*/
                    case SignalGenerator.INSTR_NARROW: {
                        generateNarrow();
                        ip++;
                        break;
                    }
                    /*Wide pulse*/
                    case SignalGenerator.INSTR_WIDE: {
                        generateWide();
                        ip++;
                        break;
                    }
                    /*Pilot tone pulse*/
                    case SignalGenerator.INSTR_PILOT: {
                        generatePilotTone(1);
                        ip++;
                        break;
                    }
                    /*Sync pulse*/
                    case SignalGenerator.INSTR_SYNC: {
                        generateSync();
                        ip++;
                        break;
                    }
                    /*Data*/
                    case SignalGenerator.INSTR_DATA: {
                        ip++;
                        cx=mem[ip];
                        ip++;
                        
                        for(int i=0;i<cx && cancelType==CANCEL_NO;i++) {
                            generateByte(mem[ip]);
                            ip++;
                        }
                        break;

                    }
                    /*Pilot tone*/
                    case SignalGenerator.INSTR_PILOTTONE: {
                        ip++;
                        cx=mem[ip];
                        generatePilotTone(cx);
                        ip++;
                        break;
                    }

                    /*End*/
                    case SignalGenerator.INSTR_END: {
                        break;
                    }

                    /*Silence 0.1 seconds*/
                    case SignalGenerator.INSTR_SHORTSILENCE: {
                        generateSilence(1);
                        ip++;
                        break;
                    }

                    /*Silence 1 seconds*/
                    case SignalGenerator.INSTR_LONGSILENCE: {
                        ip++;
                        generateSilence(mem[ip] * 10);
                        ip++;
                        break;
                    }
                    
                    /*Setup*/
                    case SignalGenerator.INSTR_SETUP: {
                        handleSetup();
                        ip+=13;
                        break;
                    }

                    /*Block separator*/
                    case SignalGenerator.INSTR_BLOCKSEP: {
                        generateBlockSep();
                        ip++;
                        break;
                    }

                    /*Stop pulse*/
                    case SignalGenerator.INSTR_STOP: {
                        generateStop();
                        ip++;
                        break;
                    }
                        
                        
                    /*PWMS*/    
                    case SignalGenerator.INSTR_PWMS: {
                        ip++;
                        pwmPolarity=mem[ip];
                        ip++;
                        pwmLoHiOrder=(mem[ip] != SignalGenerator.FLAG_ORDER_HL);
                        ip++;
                        pwmSampleRate=mem[ip];
                        ip++;
                        
                        /*Create levels*/
                        LOW_SAMPLE=PulseCreator.createPulse(cChannels, cPulseVolume, 1, cBits, cSigned, PulseCreator.SPECIAL_LOW, pwmPolarity,cSignalInRightChannelOnly,false);
                        HIGH_SAMPLE=PulseCreator.createPulse(cChannels, cPulseVolume, 1, cBits, cSigned, PulseCreator.SPECIAL_HIGH, pwmPolarity,cSignalInRightChannelOnly,false);
                        SILENCE_SAMPLE=PulseCreator.createPulse(cChannels, cPulseVolume, 1, cBits, cSigned, PulseCreator.SPECIAL_SILENCE, pwmPolarity,cSignalInRightChannelOnly,false);
                        break;
                    }
                        
                    /*PWMC*/
                    case SignalGenerator.INSTR_PWMC: {
                        ip++;
                        int silence=getPWMMilis2Samples(mem[ip]);
                        ip++;
                        for(int i=0;i<silence;i++) {
                            signalWriter.write(SILENCE_SAMPLE);
                        }
                        
                        cx=mem[ip]; /*Number of pairs*/
                        ip++;
                        for(int i=0;i<cx;i++) {
                            byte[] pulse =  PulseCreator.createPulse(cChannels, cPulseVolume, getPWMLength(mem[ip]), cBits, cSigned, 0, pwmPolarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
                            ip++;
                            for (int j=0;j<mem[ip];j++) {
                                signalWriter.write(pulse);
                            }
                            ip++;
                        }
                        break;
                    }
                        
                    case SignalGenerator.INSTR_PWMD: {
                        ip++;
                        cx=mem[ip]; /*Bytes of data*/
                        
                        /*Create pulses*/
                        ip++;
                        NARROW_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, getPWMLength(mem[ip]), cBits, cSigned, 0, pwmPolarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
                        ip++;
                        WIDE_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, getPWMLength(mem[ip]), cBits, cSigned, 0, pwmPolarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
                        ip++;
                        /*Set order*/
                        loHiOrder=pwmLoHiOrder;
                        
                        /*Generate data itself*/
                        for(int i=0;i<cx && cancelType==CANCEL_NO;i++) {
                            generateByte(mem[ip]);
                            ip++;
                        }
                        
                        break;
                    }
                        
                    case SignalGenerator.INSTR_PWML: {
                        ip++;
                        /*Silence*/
                        int silence=getPWMMilis2Samples(mem[ip]);
                        ip++;
                        for(int i=0;i<silence;i++) {
                            signalWriter.write(SILENCE_SAMPLE);
                        }
                        /*Number of lengths*/
                        cx=mem[ip]; 
                        ip++;
                        
                        int sampleCount;
                        boolean high;
                        high = pwmPolarity==SignalGenerator.FLAG_ORDER_HL;
                        
                        /*States*/
                        for(int i=0;i<cx;i++) {
                            sampleCount=getPWMLength(mem[ip]);
                            ip++;
                            for(int j=0;j<sampleCount;j++) {
                                signalWriter.write(high==true?HIGH_SAMPLE:LOW_SAMPLE);
                            }
                            high = !high;
                        }
                        
                        break;
                    }    
                    case SignalGenerator.INSTR_BAUD: {
                        generateBAUD();
                        break;
                    }
                    case SignalGenerator.INSTR_STDDATA: {
                        generateSTDDATA();
                        break;
                    }
                    case SignalGenerator.INSTR_FSK: {
                        generateFSK();
                        break;
                    }
                    case SignalGenerator.INSTR_FUJI: {
                        /*Skip opcode*/
                        ip++;
                        /*Get length*/
                        cx = mem[ip];
                        /*Skip data*/
                        ip++;
                        ip+=cx;
                        break;
                    }
                        

                }/*End of switch*/

            }
            while (op != SignalGenerator.INSTR_END && cancelType == CANCEL_NO);

            /*Generator canceled*/
            if (cancelType != CANCEL_NO) {
                signalWriter.prepareForClose();
                signalWriter.close();
            }
            /*Generator ended normally*/
            else {
                
                /*Five seconds of silence to prevent truncation*/
                generateTerminalSilence(50);

                signalWriter.prepareForClose();
                signalWriter.close();
                //dumpInstructions();

                /*PostProcessing*/
                if (outputType==TYPE_WAVE) {
                    postProcessWaveFile();
                }
                

            }

            /*Everything OK*/
            if (cancelType == CANCEL_NO) {
                Turgen.dlm.frmTurgen.getStatusObject().addGeneratedWaves();
            }
        }
        catch (Exception e) {

            errorMessage = Utils.getExceptionMessage(e);
            Turgen.dlm.frmTurgen.getStatusObject().addFailedWaves();
            System.out.println(plr.inFile);
            System.out.println(plr.pluginClass);

            try {
                e.printStackTrace();
                signalWriter.close();
            }
            catch (Exception e1) {
                errorMessage = Utils.getExceptionMessage(e1);
                e1.printStackTrace();
            }
            finally {
            }
        }
        finally {
            Turgen.sync.unregisterProgress(this);
        }

    }

    
    /**Generate pilot tone
     *
     * @param num Number of pilot tone pulses
     * @throws Exception
     */
    private void generatePilotTone(int num) throws Exception {

        for (int i = 0; i < num && cancelType==CANCEL_NO; i++) {
            signalWriter.write(PILOTTONE_PULSE);
        }

    }

    /**Generate sync pulse*/
    private void generateSync() throws Exception {
        signalWriter.write(SYNC_PULSE);
    }

    /**Generate silence
     *
     * @param ticks Number of 0.1 second ticks
     * @throws Exception
     */
    private void generateSilence(int ticks) throws Exception {
        for (int p = 0; p < ticks && cancelType==CANCEL_NO; p++) {
            signalWriter.write(SILENCE_SHORT);
        }
    }
    
    /**Generate terminal silence
     *
     * @param ticks Number of 0.1 second ticks
     * @throws Exception
     */
    private void generateTerminalSilence(int ticks) throws Exception {
        for (int p = 0; p < ticks && cancelType==CANCEL_NO; p++) {
            signalWriter.writeTerminationSignal(SILENCE_SHORT);
        }
    }

    /**Generate block separator*/
    private void generateBlockSep() throws Exception{
        signalWriter.write(BLOCKSEP);
    }

    /**Generate stop pulse*/
    private void generateStop() throws Exception {
        signalWriter.write(STOP_PULSE);
    }

    /**Generuje 0 nebo 1*/
    private void generateWide() throws Exception {
        signalWriter.write(WIDE_PULSE);
    }

    private void generateNarrow() throws Exception {
        signalWriter.write(NARROW_PULSE);
    }
        

    /** Generate byte
     *
     * @param i Byte to be generated
     * @throws Exception
     */
    private void generateByte(int i) throws Exception {

        /*Bit order from highest to lowest*/
        if (loHiOrder==false) {

            for (int k = 0; k < 8; k++) {
                if ((i & 0x00000080) == 0) {
                    signalWriter.write(NARROW_PULSE);
                } else {
                    signalWriter.write(WIDE_PULSE);
                }

                /*Left shift*/
                i = i << 1;
            }
        }
        /*Bit order from lowest to highest*/
        else {
            for(int k=0;k<8;k++) {
                if ((i & 0x00000001) == 0) {
                    signalWriter.write(NARROW_PULSE);
                }
                else {
                    signalWriter.write(WIDE_PULSE);
                }

                /*Shift to right*/
                i = i >> 1;
            }
        }
    }

    /*Implementing Generator*/
    public boolean isDirect() {
        return false;
    }
    
    /** Handle setup instruction*/
    private void handleSetup() {
        
        int dab = cBits;
        boolean das = cSigned;
        
        int tmp = ip+1;

        /*Polarity and byte order*/
        int polarity=mem[tmp+5];
        loHiOrder = mem[tmp+6] != SignalGenerator.FLAG_ORDER_HL;

        /*Vytvorit nove pulzy podle nastaveni*/
        PILOTTONE_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, mem[tmp+0], dab, das, 0, polarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
        WIDE_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, mem[tmp+1], dab, das, 0, polarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
        NARROW_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, mem[tmp+2], dab, das, 0, polarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
        SYNC_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, mem[tmp+3], dab, das, 0, polarity,cSignalInRightChannelOnly,cUseHarmonicPulses);
        STOP_PULSE = PulseCreator.createPulse(cChannels, cPulseVolume, mem[tmp+4], dab, das, 0, polarity,cSignalInRightChannelOnly,cUseHarmonicPulses);

    }
    
    /*Cancel generator*/
    public void cancelOperation(int cncType) {
        cancelType = cncType;
    }

    /**Return progress
     *
     * @return Progress in percent
     */

    public int getStatusPercent() {
        double d;
        d = ip;
        d = d / genLength;
        d = d * 100;
        if (d > 100) {
            d = 100.0;
        }
        return (int) Math.round(d);
    }

    public String getStatusString() {
        if (!postProcessRunning) {
            return null;
        }
        return "POSTPRO";
    }

    public boolean isPartOfSequence() {
        return (outputType!=TYPE_WAVE);
    }

    public String getOutputFile() {
        return outFile;
    }

    public int getCancelledType() {
        return cancelType;
    }

    public String getInputFile() {
        return plr.inFile;
    }
    
    

    public String getErrorMessage() {
        return errorMessage;
    }

    private int getPWMLength(int i) {
        return (int)Math.round((double)i*(double)44100/(double)pwmSampleRate);
    }

    private int getPWMMilis2Samples(int i) {
        return (int)Math.round( (double)i*44.1d);
    }
    
    /** Generate BAUD instruction 
     *  00 OPCODE
     *  01 BAUDRATE
     */
    
    private void generateBAUD() {
        ip++;
        if (cDoNotModulateStandardRecords==false) {
            fskGenerator = new FSKGenerator(mem[ip],cSigned,cChannels,cBits,this,cPulseVolume,cSignalInRightChannelOnly);
        }
        else {
            fskGenerator = new SerialPortSignalGenerator(mem[ip],cSigned,cChannels,cBits,this,cPulseVolume,cSignalInRightChannelOnly);
        }
        ip++;
    }
    
    /** Generate STDDATA instruction
     * 00 OPCODE
     * 01 IRG LENGTH
     * 02 DATA LENGTH
     * 03 ... DATA
     */
    private void generateSTDDATA() throws Exception {
        ip++;
        
        /*Get IRG length*/
        int irgLen=mem[ip];
        ip++;
        /*Ged data length*/
        int dataLen=mem[ip];
        ip++;
        
        /*Prepare data for fsk genetator*/
        int data[] = new int[dataLen];
        System.arraycopy(mem, ip, data, 0, dataLen);
        
        /*Increase instruction pointer*/
        ip+=dataLen;
        
        fskGenerator.resetAngle();
        
        int numPieces = irgLen/2000;
        int remainder = irgLen%2000;
        
        /*Generate IRG in pieces of 2 seconds*/
        for(int i=0;i<numPieces;i++) {
            fskGenerator.generateIRG(2000);
            if (cancelType!=CANCEL_NO) {
                return;
            }
        }
        /*Remainder of IRG*/
        fskGenerator.generateIRG(remainder);
        if (cancelType!=CANCEL_NO) {
           return;
        }
        
        /*Generate data*/
        fskGenerator.generateData(data);
        
    }
    
    /** Generate FSK instruction
     * 00 IRG
     * 01 LENGTH
     * 02 ... DATA (0 and 1 durations in 0.1 miliseconds)
     * @throws Exception 
     */
    private void generateFSK() throws Exception {
        ip++;
        int irgLen=mem[ip];
        ip++;
        int dataLen=mem[ip];
        ip++;
        
        /*To be on the safe side*/
        if (fskGenerator==null) {
            if (cDoNotModulateStandardRecords==false) {
                fskGenerator = new FSKGenerator(600,cSigned,cChannels,cBits,this,cPulseVolume,cSignalInRightChannelOnly);
            }
            else {
                fskGenerator = new SerialPortSignalGenerator(600,cSigned,cChannels,cBits,this,cPulseVolume,cSignalInRightChannelOnly);
            }
        }
        fskGenerator.resetAngle();
        fskGenerator.generateIRG(irgLen);
        fskGenerator.generateFSK(mem,ip,dataLen);
        ip+=dataLen;
    }


    public void consumeSamples(byte[] b) throws Exception {
        signalWriter.write(b);
    }

    private void postProcessWaveFile() {
        if (cPostProcessingString.equals("") || wavePostProcessingEnabled == false) {
            return;
        }

        File f = new File(outFile);
        if (f.exists() && f.isFile()) {
            f = f.getAbsoluteFile();
            String outDir = f.getParent();
            String outFileName = f.getName();
            int j = outFileName.lastIndexOf('.');
            String outFileNameNoExt = (j < 0) ? outFileName : outFileName.substring(0, j);
            String s1 = cPostProcessingString;
            boolean dlt = false;
            if (s1.startsWith("!")) {
                dlt = true;
                s1 = s1.substring(1);
            }

            s1 = Utils.replaceElements(s1, "%OD%", outDir);
            s1 = Utils.replaceElements(s1, "%ODS%", outDir.concat(System.getProperty("file.separator")));
            s1 = Utils.replaceElements(s1, "%OFNE%", outFileName);
            s1 = Utils.replaceElements(s1, "%OFN%", outFileNameNoExt);

            String dsp = Turgen.sp + Turgen.sp;
            s1 = Utils.replaceElements(s1, dsp, Turgen.sp);

            System.out.println("Postprocessing: ");

            String[] cmdArray = Utils.getCommandArrayFromCommandLine(s1);
            Utils.printCommandLine(cmdArray);

            try {
                postProcessRunning = true;
                Process proc = Runtime.getRuntime().exec(cmdArray);
                proc.waitFor();
                if (dlt == true) {
                    f.delete();
                }
            } catch (InterruptedException ie) {
                /*No problem*/
            } catch (Exception xxe) {
                xxe.printStackTrace();
            } finally {
                postProcessRunning = false;
            }

        }

    }
    
    private void dumpInstructions() {
        int wrap=0;
        int ip=0;
        
        /* Main dump loop*/
        while (ip<mem.length) {
            
            System.out.flush();
            
            switch(mem[ip]) {
                case INSTR_NARROW: {
                    System.out.format("%d,                /*Narrow pulse*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_WIDE: {
                    System.out.format("%d,                  /*Wide pulse*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_PILOT: {
                    System.out.format("%d,                  /*Pilot tone pulse*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_DATA: {
                    System.out.format("%d,%d,             /*Data*/\n",mem[ip],mem[ip+1]);
                    int count = mem[ip+1];
                    ip+=2;
                    dumpDataBytes(mem, ip, count);
                    ip+=count;
                    break;
                }
                case INSTR_PILOTTONE: {
                    System.out.format("%d,%d,            /*Pilot tone*/\n",mem[ip],mem[ip+1]);
                    ip+=2;
                    break;
                }
                case INSTR_END: {
                    System.out.format("%d,                 /*Program End*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_SHORTSILENCE: {
                    System.out.format("%d,                 /*Short silence*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_SYNC: {
                    System.out.format("%d,                 /*Sync*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_LONGSILENCE: {
                    System.out.format("%d,%d,            /*Long silence*/\n",mem[ip],mem[ip+1]);
                    ip+=2; 
                    break;
                }
                case INSTR_SETUP: {
                    System.out.format("%d,                /*Setup*/\n",mem[ip]);
                    ip++;
                    for(int i=0;i<12;i++) {
                        System.out.format("%d,",mem[ip+i]);
                    }
                    System.out.println();
                    ip+=12;
                    break;
                }
                case INSTR_BLOCKSEP: {
                    System.out.format("%d,                /*Block separator*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_STOP: {
                    System.out.format("%d,                /*Stop pulse*/\n",mem[ip]);
                    ip++;
                    break;
                }
                case INSTR_PWMS: {
                    System.out.format("%d,                /*PWMS*/\n",mem[ip]);
                    System.out.format("%d,%d,%d,\n",mem[ip+1],mem[ip+2],mem[ip+3]);
                    ip+=4;
                    break;
                }
                case INSTR_PWMC: {
                    System.out.format("%d,%d,%d,      /*PWMC*/\n",mem[ip],mem[ip+1],mem[ip+2]);                            
                    int numPairs=mem[ip+2];
                    ip+=3;
                    dumpDataBytes(mem, ip, numPairs*2);
                    ip+=(numPairs*2);
                    break;
                }
                case INSTR_PWMD: {
                    System.out.format("%d,%d,%d,%d,  /*PWMD*/\n",mem[ip],mem[ip+1],mem[ip+2],mem[ip+3]);                            
                    int count=mem[ip+1];
                    ip+=4;
                    dumpDataBytes(mem, ip, count);
                    ip+=count;
                    break;
                }
                case INSTR_PWML: {
                    System.out.format("%d,%d,%d,      /*PWML*/\n",mem[ip],mem[ip+1],mem[ip+2]);                            
                    int count=mem[ip+2];
                    ip+=3;
                    dumpDataBytes(mem, ip, count);
                    ip+=count;
                    break;
                }
                case INSTR_BAUD: {
                    System.out.format("%d,%d,            /*Baud*/\n",mem[ip],mem[ip+1]);
                    ip+=2; 
                    break;
                }
                case INSTR_STDDATA: {
                    System.out.format("%d,%d,%d,      /*Standard data*/\n",mem[ip],mem[ip+1],mem[ip+2]);                            
                    int count=mem[ip+2];
                    ip+=3;
                    dumpDataBytes(mem, ip, count);
                    ip+=count;
                    break; 
                }
                case INSTR_FSK: {
                    System.out.format("%d,%d,%d,       /*FSK*/\n",mem[ip],mem[ip+1]);
                    int count=mem[ip+2];
                    ip+=3;
                    dumpDataBytes(mem, ip, count);
                    ip+=count;
                    break; 
                }
                case INSTR_FUJI: {
                    System.out.format("%d,%d,         /*FUJI*/\n",mem[ip],mem[ip+1]);
                    int count = mem[ip+1];
                    ip+=2;
                    dumpDataBytes(mem, ip, count);
                    ip+=count;
                    break;
                }
                
                
                
            }
            
        }
        
    }
    
    private void dumpDataBytes(int[] mem,int ip,int count) {
        
        int wrap = 0;
        for (int k = 0; k < count; k++) {
            System.out.format("0x%02X,", mem[ip]);
            if (wrap == 19) {
                System.out.println();
                wrap = 0;
            }
            else {
                wrap++;
            }
            ip++;
        }
        if (wrap != 0) {
            System.out.println();
        }
    }
}
