public void begin(long total_samples, WorkerState state)
        {
            is_running_    = true;
            total_samples_ = total_samples;
            int buffer_length = sample_rate_ / 10;

            double[] left  = new double[buffer_length];
            double[] right = new double[buffer_length];

            if (driver_.getUi(null) == null)
            {
                throw new InvalidOperationException("plugin ui を main view のスレッドで作成した後、このメソッドを呼ばなくてはならない。");
            }

            var eventQueue = generateMidiEvent(sequence_, track_index_);

            foreach (var sequence_item in eventQueue.getSequence())
            {
                var clock = sequence_item.Key;
                var queue = sequence_item.Value;

                long to_sample = (long)(sequence_.getSecFromClock(clock) * sample_rate_);
                if (to_sample >= total_samples_)
                {
                    to_sample = total_samples_;
                }
                doSynthesis(to_sample, left, right, state);
                if (position_ >= total_samples_)
                {
                    break;
                }

                // noteOff, noteOn の順に分けて send する方法も考えられるが、正しく動作しない。
                // このため、いったん一つの配列にまとめてから send する必要がある。
                var events = new List <MidiEvent>();
                events.AddRange(queue.pit);
                events.AddRange(queue.noteoff);
                events.AddRange(queue.noteon);
                driver_.send(events.ToArray());

                //TODO: のこりのイベント送る処理
            }

            doSynthesis(total_samples, left, right, state);

            receiver_.end();
            is_running_ = false;
            state.reportComplete();
        }
        public void testMethod(WorkerState receiver, Object argument)
        {
            int wait_ms = 10;
            int amount  = ((int)argument) / wait_ms;
            int proc    = 0;

            for (int i = 0; i < amount; i++)
            {
                // 中止処理後にreportCompleteを送ってはいけない
                if (receiver.isCancelRequested())
                {
                    sout.println("TestFormWorker#testMethod; cancel requested");
                    return;
                }
                receiver.reportProgress(proc);
                Thread.Sleep(wait_ms);
                proc += wait_ms;
            }
            receiver.reportComplete();
            sout.println("TestFormWorker#testMethod; job done");
        }
        public void begin(long total_samples, WorkerState state)
        {
            var mDriver = getDriver();

#if DEBUG
            sout.println("AquesToneRenderingRunner#begin; (mDriver==null)=" + (mDriver == null));
            String file = System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "AquesToneWaveGenerator.txt");
            log           = new System.IO.StreamWriter(file);
            log.AutoFlush = true;
#endif
            if (mDriver == null)
            {
#if DEBUG
                log.WriteLine("mDriver==null");
                log.Close();
#endif
                exitBegin();
                state.reportComplete();
                return;
            }

#if DEBUG
            sout.println("AquesToneRenderingRunner#begin; mDriver.loaded=" + mDriver.loaded);
#endif
            if (!mDriver.loaded)
            {
#if DEBUG
                log.WriteLine("mDriver.loaded=" + mDriver.loaded);
                log.Close();
#endif
                exitBegin();
                state.reportComplete();
                return;
            }

            mRunning = true;
            //mAbortRequired = false;
            mTotalSamples = total_samples;
#if DEBUG
            sout.println("AquesToneWaveGenerator#begin; mTotalSamples=" + mTotalSamples);
            log.WriteLine("mTotalSamples=" + mTotalSamples);
            log.WriteLine("mTrimRemain=" + mTrimRemain);
#endif

            VsqTrack track       = mVsq.Track.get(mTrack);
            int      BUFLEN      = mSampleRate / 10;
            double[] left        = new double[BUFLEN];
            double[] right       = new double[BUFLEN];
            long     saProcessed = 0;
            int      saRemain    = 0;
            int      lastClock   = 0; // 最後に処理されたゲートタイム

            // 最初にダミーの音を鳴らす
            // (最初に入るノイズを回避するためと、前回途中で再生停止した場合に無音から始まるようにするため)
            mDriver.resetAllParameters();
            mDriver.process(left, right, BUFLEN);
            MidiEvent f_noteon = new MidiEvent();
            f_noteon.firstByte = 0x90;
            f_noteon.data      = new int[] { 0x40, 0x40 };
            f_noteon.clock     = 0;
            mDriver.send(new MidiEvent[] { f_noteon });
            mDriver.process(left, right, BUFLEN);
            MidiEvent f_noteoff = new MidiEvent();
            f_noteoff.firstByte = 0x80;
            f_noteoff.data      = new int[] { 0x40, 0x7F };
            mDriver.send(new MidiEvent[] { f_noteoff });
            for (int i = 0; i < 3; i++)
            {
                mDriver.process(left, right, BUFLEN);
            }
#if DEBUG
            log.WriteLine("pre-process done");
            log.WriteLine("-----------------------------------------------------");
            VsqTrack vsq_track = mVsq.Track.get(mTrack);
            for (Iterator <VsqEvent> itr = vsq_track.getNoteEventIterator(); itr.hasNext();)
            {
                VsqEvent item = itr.next();
                log.WriteLine("c" + item.Clock + "; " + item.ID.LyricHandle.L0.Phrase);
            }
#endif

            // レンダリング開始位置での、パラメータの値をセットしておく
            for (Iterator <VsqEvent> itr = track.getNoteEventIterator(); itr.hasNext();)
            {
                VsqEvent item = itr.next();
#if DEBUG
                sout.println("AquesToneWaveGenerator#begin; item.Clock=" + item.Clock);
                log.WriteLine("*********************************************************");
                log.WriteLine("item.Clock=" + item.Clock);
#endif
                long saNoteStart = (long)(mVsq.getSecFromClock(item.Clock) * mSampleRate);
                long saNoteEnd   = (long)(mVsq.getSecFromClock(item.Clock + item.ID.getLength()) * mSampleRate);
#if DEBUG
                log.WriteLine("saNoteStart=" + saNoteStart + "; saNoteEnd=" + saNoteEnd);
#endif

                EventQueueSequence list = generateMidiEvent(mVsq, mTrack, lastClock, item.Clock + item.ID.getLength());
                lastClock = item.Clock + item.ID.Length + 1;
                for (Iterator <Integer> itr2 = list.keyIterator(); itr2.hasNext();)
                {
                    // まず直前までの分を合成
                    Integer clock = itr2.next();
#if DEBUG
                    log.WriteLine("-------------------------------------------------------");
                    sout.println("AquesToneWaveGenerator#begin;     clock=" + clock);
#endif
                    long saStart = (long)(mVsq.getSecFromClock(clock) * mSampleRate);
                    saRemain = (int)(saStart - saProcessed);
#if DEBUG
                    log.WriteLine("saStart=" + saStart);
                    log.WriteLine("saRemain=" + saRemain);
#endif
                    while (saRemain > 0)
                    {
                        if (state.isCancelRequested())
                        {
                            goto heaven;
                        }
                        int len = saRemain > BUFLEN ? BUFLEN : saRemain;
                        mDriver.process(left, right, len);
                        waveIncoming(left, right, len);
                        saRemain    -= len;
                        saProcessed += len;
                        state.reportProgress(saProcessed);
                        //mTotalAppend += len; <- waveIncomingで計算されるので
                    }

                    // MIDiイベントを送信
                    MidiEventQueue queue = list.get(clock);
                    // まずnoteoff
                    boolean noteoff_send = false;
                    if (queue.noteoff.size() > 0)
                    {
#if DEBUG
                        for (int i = 0; i < queue.noteoff.size(); i++)
                        {
                            String    str   = "";
                            MidiEvent itemi = queue.noteoff.get(i);
                            str += "0x" + PortUtil.toHexString(itemi.firstByte, 2) + " ";
                            for (int j = 0; j < itemi.data.Length; j++)
                            {
                                str += "0x" + PortUtil.toHexString(itemi.data[j], 2) + " ";
                            }
                            sout.println(typeof(AquesToneWaveGenerator) + "#begin;         noteoff; " + str);
                        }
#endif
                        mDriver.send(queue.noteoff.toArray(new MidiEvent[] { }));
                        noteoff_send = true;
                    }
                    // parameterの変更
                    if (queue.param.size() > 0)
                    {
                        for (Iterator <ParameterEvent> itr3 = queue.param.iterator(); itr3.hasNext();)
                        {
                            ParameterEvent pe = itr3.next();
#if DEBUG
                            sout.println(typeof(AquesToneWaveGenerator) + "#begin;         param;   index=" + pe.index + "; value=" + pe.value);
#endif
                            mDriver.setParameter(pe.index, pe.value);
                        }
                    }
                    // ついでnoteon
                    if (queue.noteon.size() > 0)
                    {
                        // 同ゲートタイムにピッチベンドも指定されている場合、同時に送信しないと反映されないようだ!
                        if (queue.pit.size() > 0)
                        {
                            queue.noteon.addAll(queue.pit);
                            queue.pit.clear();
                        }
#if DEBUG
                        for (int i = 0; i < queue.noteon.size(); i++)
                        {
                            String    str   = "";
                            MidiEvent itemi = queue.noteon.get(i);
                            str += "0x" + PortUtil.toHexString(itemi.firstByte, 2) + " ";
                            for (int j = 0; j < itemi.data.Length; j++)
                            {
                                str += "0x" + PortUtil.toHexString(itemi.data[j], 2) + " ";
                            }
                            sout.println(typeof(AquesToneWaveGenerator) + "#begin;         noteon;  " + str);
                        }
#endif
                        mDriver.send(queue.noteon.toArray(new MidiEvent[] { }));
                    }
                    // PIT
                    if (queue.pit.size() > 0 && !noteoff_send)
                    {
#if DEBUG
                        for (int i = 0; i < queue.pit.size(); i++)
                        {
                            String    str   = "";
                            MidiEvent itemi = queue.pit.get(i);
                            str += "0x" + PortUtil.toHexString(itemi.firstByte, 2) + " ";
                            for (int j = 0; j < itemi.data.Length; j++)
                            {
                                str += "0x" + PortUtil.toHexString(itemi.data[j], 2) + " ";
                            }
                            sout.println(typeof(AquesToneWaveGenerator) + "#begin;         pit;     " + str);
                        }
#endif
                        mDriver.send(queue.pit.toArray(new MidiEvent[] { }));
                    }
                    if (mDriver.getUi(mMainWindow) != null)
                    {
                        mDriver.getUi(mMainWindow).invalidateUi();
                    }
                }
            }

            // totalSamplesに足りなかったら、追加してレンダリング
            saRemain = (int)(mTotalSamples - mTotalAppend);
#if DEBUG
            sout.println("AquesToneRenderingRunner#run; totalSamples=" + mTotalSamples + "; mTotalAppend=" + mTotalAppend + "; saRemain=" + saRemain);
#endif
            while (saRemain > 0)
            {
                if (state.isCancelRequested())
                {
                    goto heaven;
                }
                int len = saRemain > BUFLEN ? BUFLEN : saRemain;
                mDriver.process(left, right, len);
                waveIncoming(left, right, len);
                saRemain    -= len;
                saProcessed += len;
                state.reportProgress(saProcessed);
                //mTotalAppend += len;
            }
heaven:
#if DEBUG
            log.Close();
#endif
            exitBegin();
            state.reportComplete();
        }
        public void begin( long total_samples, WorkerState state )
        {
            var mDriver = getDriver();
#if DEBUG
            sout.println( "AquesToneRenderingRunner#begin; (mDriver==null)=" + (mDriver == null) );
            String file = System.IO.Path.Combine( System.Windows.Forms.Application.StartupPath, "AquesToneWaveGenerator.txt" );
            log = new System.IO.StreamWriter( file );
            log.AutoFlush = true;
#endif
            if ( mDriver == null ) {
#if DEBUG
                log.WriteLine( "mDriver==null" );
                log.Close();
#endif
                exitBegin();
                state.reportComplete();
                return;
            }

#if DEBUG
            sout.println( "AquesToneRenderingRunner#begin; mDriver.loaded=" + mDriver.loaded );
#endif
            if ( !mDriver.loaded ) {
#if DEBUG
                log.WriteLine( "mDriver.loaded=" + mDriver.loaded );
                log.Close();
#endif
                exitBegin();
                state.reportComplete();
                return;
            }

            mRunning = true;
            //mAbortRequired = false;
            mTotalSamples = total_samples;
#if DEBUG
            sout.println( "AquesToneWaveGenerator#begin; mTotalSamples=" + mTotalSamples );
            log.WriteLine( "mTotalSamples=" + mTotalSamples );
            log.WriteLine( "mTrimRemain=" + mTrimRemain );
#endif

            VsqTrack track = mVsq.Track.get( mTrack );
            int BUFLEN = mSampleRate / 10;
            double[] left = new double[BUFLEN];
            double[] right = new double[BUFLEN];
            long saProcessed = 0;
            int saRemain = 0;
            int lastClock = 0; // 最後に処理されたゲートタイム

            // 最初にダミーの音を鳴らす
            // (最初に入るノイズを回避するためと、前回途中で再生停止した場合に無音から始まるようにするため)
            mDriver.resetAllParameters();
            mDriver.process( left, right, BUFLEN );
            MidiEvent f_noteon = new MidiEvent();
            f_noteon.firstByte = 0x90;
            f_noteon.data = new int[] { 0x40, 0x40 };
            f_noteon.clock = 0;
            mDriver.send( new MidiEvent[] { f_noteon } );
            mDriver.process( left, right, BUFLEN );
            MidiEvent f_noteoff = new MidiEvent();
            f_noteoff.firstByte = 0x80;
            f_noteoff.data = new int[] { 0x40, 0x7F };
            mDriver.send( new MidiEvent[] { f_noteoff } );
            for ( int i = 0; i < 3; i++ ) {
                mDriver.process( left, right, BUFLEN );
            }
#if DEBUG
            log.WriteLine( "pre-process done" );
            log.WriteLine( "-----------------------------------------------------" );
            VsqTrack vsq_track = mVsq.Track.get( mTrack );
            for ( Iterator<VsqEvent> itr = vsq_track.getNoteEventIterator(); itr.hasNext(); ) {
                VsqEvent item = itr.next();
                log.WriteLine( "c" + item.Clock + "; " + item.ID.LyricHandle.L0.Phrase );
            }
#endif

            // レンダリング開始位置での、パラメータの値をセットしておく
            for ( Iterator<VsqEvent> itr = track.getNoteEventIterator(); itr.hasNext(); ) {
                VsqEvent item = itr.next();
#if DEBUG
                sout.println( "AquesToneWaveGenerator#begin; item.Clock=" + item.Clock );
                log.WriteLine( "*********************************************************" );
                log.WriteLine( "item.Clock=" + item.Clock );
#endif
                long saNoteStart = (long)(mVsq.getSecFromClock( item.Clock ) * mSampleRate);
                long saNoteEnd = (long)(mVsq.getSecFromClock( item.Clock + item.ID.getLength() ) * mSampleRate);
#if DEBUG
                log.WriteLine( "saNoteStart=" + saNoteStart + "; saNoteEnd=" + saNoteEnd );
#endif

                EventQueueSequence list = generateMidiEvent( mVsq, mTrack, lastClock, item.Clock + item.ID.getLength() );
                lastClock = item.Clock + item.ID.Length + 1;
                for ( Iterator<Integer> itr2 = list.keyIterator(); itr2.hasNext(); ) {
                    // まず直前までの分を合成
                    Integer clock = itr2.next();
#if DEBUG
                    log.WriteLine( "-------------------------------------------------------" );
                    sout.println( "AquesToneWaveGenerator#begin;     clock=" + clock );
#endif
                    long saStart = (long)(mVsq.getSecFromClock( clock ) * mSampleRate);
                    saRemain = (int)(saStart - saProcessed);
#if DEBUG
                    log.WriteLine( "saStart=" + saStart );
                    log.WriteLine( "saRemain=" + saRemain );
#endif
                    while ( saRemain > 0 ) {
                        if ( state.isCancelRequested() ) {
                            goto heaven;
                        }
                        int len = saRemain > BUFLEN ? BUFLEN : saRemain;
                        mDriver.process( left, right, len );
                        waveIncoming( left, right, len );
                        saRemain -= len;
                        saProcessed += len;
                        state.reportProgress( saProcessed );
                        //mTotalAppend += len; <- waveIncomingで計算されるので
                    }

                    // MIDiイベントを送信
                    MidiEventQueue queue = list.get( clock );
                    // まずnoteoff
                    boolean noteoff_send = false;
                    if ( queue.noteoff.size() > 0 ) {
#if DEBUG
                        for ( int i = 0; i < queue.noteoff.size(); i++ ) {
                            String str = "";
                            MidiEvent itemi = queue.noteoff.get( i );
                            str += "0x" + PortUtil.toHexString( itemi.firstByte, 2 ) + " ";
                            for ( int j = 0; j < itemi.data.Length; j++ ) {
                                str += "0x" + PortUtil.toHexString( itemi.data[j], 2 ) + " ";
                            }
                            sout.println( typeof( AquesToneWaveGenerator ) + "#begin;         noteoff; " + str );
                        }
#endif
                        mDriver.send( queue.noteoff.toArray( new MidiEvent[] { } ) );
                        noteoff_send = true;
                    }
                    // parameterの変更
                    if ( queue.param.size() > 0 ) {
                        for ( Iterator<ParameterEvent> itr3 = queue.param.iterator(); itr3.hasNext(); ) {
                            ParameterEvent pe = itr3.next();
#if DEBUG
                            sout.println( typeof( AquesToneWaveGenerator ) + "#begin;         param;   index=" + pe.index + "; value=" + pe.value );
#endif
                            mDriver.setParameter( pe.index, pe.value );
                        }
                    }
                    // ついでnoteon
                    if ( queue.noteon.size() > 0 ) {
                        // 同ゲートタイムにピッチベンドも指定されている場合、同時に送信しないと反映されないようだ!
                        if ( queue.pit.size() > 0 ) {
                            queue.noteon.addAll( queue.pit );
                            queue.pit.clear();
                        }
#if DEBUG
                        for ( int i = 0; i < queue.noteon.size(); i++ ) {
                            String str = "";
                            MidiEvent itemi = queue.noteon.get( i );
                            str += "0x" + PortUtil.toHexString( itemi.firstByte, 2 ) + " ";
                            for ( int j = 0; j < itemi.data.Length; j++ ) {
                                str += "0x" + PortUtil.toHexString( itemi.data[j], 2 ) + " ";
                            }
                            sout.println( typeof( AquesToneWaveGenerator ) + "#begin;         noteon;  " + str );
                        }
#endif
                        mDriver.send( queue.noteon.toArray( new MidiEvent[] { } ) );
                    }
                    // PIT
                    if ( queue.pit.size() > 0 && !noteoff_send ) {
#if DEBUG
                        for ( int i = 0; i < queue.pit.size(); i++ ) {
                            String str = "";
                            MidiEvent itemi = queue.pit.get( i );
                            str += "0x" + PortUtil.toHexString( itemi.firstByte, 2 ) + " ";
                            for ( int j = 0; j < itemi.data.Length; j++ ) {
                                str += "0x" + PortUtil.toHexString( itemi.data[j], 2 ) + " ";
                            }
                            sout.println( typeof( AquesToneWaveGenerator ) + "#begin;         pit;     " + str );
                        }
#endif
                        mDriver.send( queue.pit.toArray( new MidiEvent[] { } ) );
                    }
                    if ( mDriver.getUi( mMainWindow ) != null ) {
                        mDriver.getUi( mMainWindow ).invalidateUi();
                    }
                }
            }

            // totalSamplesに足りなかったら、追加してレンダリング
            saRemain = (int)(mTotalSamples - mTotalAppend);
#if DEBUG
            sout.println( "AquesToneRenderingRunner#run; totalSamples=" + mTotalSamples + "; mTotalAppend=" + mTotalAppend + "; saRemain=" + saRemain );
#endif
            while ( saRemain > 0 ) {
                if ( state.isCancelRequested() ) {
                    goto heaven;
                }
                int len = saRemain > BUFLEN ? BUFLEN : saRemain;
                mDriver.process( left, right, len );
                waveIncoming( left, right, len );
                saRemain -= len;
                saProcessed += len;
                state.reportProgress( saProcessed );
                //mTotalAppend += len;
            }
        heaven:
#if DEBUG
            log.Close();
#endif
            exitBegin();
            state.reportComplete();
        }
        public void begin( long total_samples, WorkerState state )
        {
            is_running_ = true;
            total_samples_ = total_samples;
            int buffer_length = sample_rate_ / 10;
            double[] left = new double[buffer_length];
            double[] right = new double[buffer_length];

            if ( driver_.getUi( null ) == null ) {
                throw new InvalidOperationException("plugin ui を main view のスレッドで作成した後、このメソッドを呼ばなくてはならない。");
            }

            var eventQueue = generateMidiEvent( sequence_, track_index_ );
            foreach ( var sequence_item in eventQueue.getSequence() ) {
                var clock = sequence_item.Key;
                var queue = sequence_item.Value;

                long to_sample = (long)(sequence_.getSecFromClock( clock ) * sample_rate_);
                if ( to_sample >= total_samples_ ) {
                    to_sample = total_samples_;
                }
                doSynthesis( to_sample, left, right, state );
                if ( position_ >= total_samples_ ) break;

                // noteOff, noteOn の順に分けて send する方法も考えられるが、正しく動作しない。
                // このため、いったん一つの配列にまとめてから send する必要がある。
                var events = new List<MidiEvent>();
                events.AddRange( queue.pit );
                events.AddRange( queue.noteoff );
                events.AddRange( queue.noteon );
                driver_.send( events.ToArray() );

                //TODO: のこりのイベント送る処理
            }

            doSynthesis( total_samples, left, right, state );

            receiver_.end();
            is_running_ = false;
            state.reportComplete();
        }
        public void patchWork(WorkerState state, Object arg)
        {
#if DEBUG
            sout.println("SynthesizeWorker#patchWork");
#endif
            VsqFileEx             vsq    = AppManager.getVsqFile();
            Object[]              args   = (Object[])arg;
            List <PatchWorkQueue> queue  = (List <PatchWorkQueue>)args[0];
            List <int>            tracks = (List <int>)args[1];
            int    finished = queue.Count;
            string temppath = AppManager.getTempWaveDir();
            for (int k = 0; k < tracks.Count; k++)
            {
                int        track      = tracks[k];
                string     wavePath   = Path.Combine(temppath, track + ".wav");
                List <int> queueIndex = new List <int>();

                for (int i = 0; i < queue.Count; i++)
                {
                    if (queue[i].track == track)
                    {
                        queueIndex.Add(i);
                    }
                }

                if (queueIndex.Count <= 0)
                {
                    // 第trackトラックに対してパッチワークを行う必要無し
                    continue;
                }

#if DEBUG
                sout.println("AppManager#pathWorkToFreeze; wavePath=" + wavePath + "; queue.get( queueIndex.get( 0 ) ).file=" + queue[queueIndex[0]].file);
                sout.println("AppManager#pathWorkToFreeze; queueIndex.size()=" + queueIndex.Count);
#endif
                if (queueIndex.Count == 1 && wavePath.Equals(queue[queueIndex[0]].file))
                {
                    // 第trackトラック全体の合成を指示するキューだった場合.
                    // このとき,パッチワークを行う必要なし.
                    AppManager.mLastRenderedStatus[track - 1] =
                        new RenderedStatus((VsqTrack)vsq.Track[track].clone(), vsq.TempoTable, (SequenceConfig)vsq.config.clone());
                    AppManager.serializeRenderingStatus(temppath, track);
                    AppManager.invokeWaveViewReloadRequiredEvent(track, wavePath, 1, -1);
                    continue;
                }

                WaveWriter writer = null;
                try {
                    int  sampleRate  = vsq.config.SamplingRate;
                    long totalLength = (long)((vsq.getSecFromClock(vsq.TotalClocks) + 1.0) * sampleRate);
                    writer = new WaveWriter(wavePath, vsq.config.WaveFileOutputChannel, 16, sampleRate);
                    int      BUFLEN = 1024;
                    double[] bufl   = new double[BUFLEN];
                    double[] bufr   = new double[BUFLEN];
                    double   total  = 0.0;
                    for (int m = 0; m < queueIndex.Count; m++)
                    {
                        int i = queueIndex[m];
                        if (finished <= i)
                        {
                            break;
                        }

                        // パッチワークの開始秒時
                        double secStart    = vsq.getSecFromClock(queue[i].clockStart);
                        long   sampleStart = (long)(secStart * sampleRate);

                        // パッチワークの終了秒時
                        int clockEnd = queue[i].clockEnd;
                        if (clockEnd == int.MaxValue)
                        {
                            clockEnd = vsq.TotalClocks + 240;
                        }
                        double secEnd    = vsq.getSecFromClock(clockEnd);
                        long   sampleEnd = (long)(secEnd * sampleRate);

                        WaveReader wr = null;
                        try {
                            wr = new WaveReader(queue[i].file);
                            long remain2 = sampleEnd - sampleStart;
                            long proc    = 0;
                            while (remain2 > 0)
                            {
                                int delta = remain2 > BUFLEN ? BUFLEN : (int)remain2;
                                wr.read(proc, delta, bufl, bufr);
                                writer.replace(sampleStart + proc, delta, bufl, bufr);
                                proc    += delta;
                                remain2 -= delta;
                                total   += delta;
                                state.reportProgress(total);
                            }
                        } catch (Exception ex) {
                            Logger.write(typeof(AppManager) + ".patchWorkToFreeze; ex=" + ex + "\n");
                            serr.println("AppManager#patchWorkToFreeze; ex=" + ex);
                        } finally {
                            if (wr != null)
                            {
                                try {
                                    wr.close();
                                } catch (Exception ex2) {
                                    Logger.write(typeof(AppManager) + ".patchWorkToFreeze; ex=" + ex2 + "\n");
                                    serr.println("AppManager#patchWorkToFreeze; ex2=" + ex2);
                                }
                            }
                        }

                        try {
                            PortUtil.deleteFile(queue[i].file);
                        } catch (Exception ex) {
                            Logger.write(typeof(AppManager) + ".patchWorkToFreeze; ex=" + ex + "\n");
                            serr.println("AppManager#patchWorkToFreeze; ex=" + ex);
                        }
                    }

                    VsqTrack vsq_track = vsq.Track[track];
                    if (queueIndex[queueIndex.Count - 1] <= finished)
                    {
                        // 途中で終了せず,このトラックの全てのパッチワークが完了した.
                        AppManager.mLastRenderedStatus[track - 1] =
                            new RenderedStatus((VsqTrack)vsq_track.clone(), vsq.TempoTable, (SequenceConfig)vsq.config.clone());
                        AppManager.serializeRenderingStatus(temppath, track);
                        AppManager.setRenderRequired(track, false);
                    }
                    else
                    {
                        // パッチワークの作成途中で,キャンセルされた
                        // キャンセルされたやつ以降の範囲に、プログラムチェンジ17の歌手変更イベントを挿入する。→AppManager#detectTrackDifferenceに必ず検出してもらえる。
                        VsqTrack copied = (VsqTrack)vsq_track.clone();
                        VsqEvent dumy   = new VsqEvent();
                        dumy.ID.type               = VsqIDType.Singer;
                        dumy.ID.IconHandle         = new IconHandle();
                        dumy.ID.IconHandle.Program = 17;
                        for (int m = 0; m < queueIndex.Count; m++)
                        {
                            int i = queueIndex[m];
                            if (i < finished)
                            {
                                continue;
                            }
                            int      start       = queue[i].clockStart;
                            int      end         = queue[i].clockEnd;
                            VsqEvent singerAtEnd = vsq_track.getSingerEventAt(end);

                            // startの位置に歌手変更が既に指定されていないかどうかを検査
                            int foundStart = -1;
                            int foundEnd   = -1;
                            for (Iterator <int> itr = copied.indexIterator(IndexIteratorKind.SINGER); itr.hasNext();)
                            {
                                int      j  = itr.next();
                                VsqEvent ve = copied.getEvent(j);
                                if (ve.Clock == start)
                                {
                                    foundStart = j;
                                }
                                if (ve.Clock == end)
                                {
                                    foundEnd = j;
                                }
                                if (end < ve.Clock)
                                {
                                    break;
                                }
                            }

                            VsqEvent dumyStart = (VsqEvent)dumy.clone();
                            dumyStart.Clock = start;
                            if (foundStart >= 0)
                            {
                                copied.setEvent(foundStart, dumyStart);
                            }
                            else
                            {
                                copied.addEvent(dumyStart);
                            }

                            if (end != int.MaxValue)
                            {
                                VsqEvent dumyEnd = (VsqEvent)singerAtEnd.clone();
                                dumyEnd.Clock = end;
                                if (foundEnd >= 0)
                                {
                                    copied.setEvent(foundEnd, dumyEnd);
                                }
                                else
                                {
                                    copied.addEvent(dumyEnd);
                                }
                            }

                            copied.sortEvent();
                        }

                        AppManager.mLastRenderedStatus[track - 1] = new RenderedStatus(copied, vsq.TempoTable, (SequenceConfig)vsq.config.clone());
                        AppManager.serializeRenderingStatus(temppath, track);
                    }

                    state.reportComplete();
                } catch (Exception ex) {
                    Logger.write(typeof(AppManager) + ".patchWorkToFreeze; ex=" + ex + "\n");
                    serr.println("AppManager#patchWorkToFreeze; ex=" + ex);
                } finally {
                    if (writer != null)
                    {
                        try {
                            writer.close();
                        } catch (Exception ex2) {
                            Logger.write(typeof(AppManager) + ".patchWorkToFreeze; ex=" + ex2 + "\n");
                            serr.println("AppManager#patchWorkToFreeze; ex2=" + ex2);
                        }
                    }
                }

                // 波形表示用のWaveDrawContextの内容を更新する。

                /*for ( int j = 0; j < queueIndex.size(); j++ ) {
                 *  int i = queueIndex.get( j );
                 *  if ( i >= finished ) {
                 *      continue;
                 *  }
                 *  double secStart = mVsq.getSecFromClock( queue.get( i ).clockStart );
                 *  int clockEnd = queue.get( i ).clockEnd;
                 *  if ( clockEnd == int.MaxValue ) {
                 *      clockEnd = mVsq.TotalClocks + 240;
                 *  }
                 *  double secEnd = mVsq.getSecFromClock( clockEnd );
                 *
                 *  invokeWaveViewReloadRequiredEvent( tracks.get( k ), wavePath, secStart, secEnd );
                 * }*/
                AppManager.invokeWaveViewReloadRequiredEvent(track, wavePath, 1, -1);
            }
#if DEBUG
            sout.println("SynthesizeWorker#patchWork; done");
#endif
            state.reportComplete();
        }