/** Move the current position to the location clicked.
         *  The music must be in the paused/stopped state.
         *  When we resume in playPause, we start at the currentPulseTime.
         *  So, set the currentPulseTime to the position clicked.
         */
        private void MoveToClicked(object sender, MouseEventArgs evt)
        {
            if (midifile == null || sheet == null)
            {
                return;
            }
            if (playstate != MidiPlayState.Paused && playstate != MidiPlayState.Stopped)
            {
                return;
            }
            playstate = MidiPlayState.Paused;

            /* Remove any highlighted notes */
            lock (sheet)
            {
                sheet.ShadeNotes(-10, (int)currentPulseTime, false);
                piano.ShadeNotes(-10, (int)currentPulseTime);

                currentPulseTime = sheet.PulseTimeForPoint(evt.Location);
                prevPulseTime    = currentPulseTime - midifile.Time.Measure;
                if (currentPulseTime > midifile.TotalPulses)
                {
                    currentPulseTime -= midifile.Time.Measure;
                }
                sheet.ShadeNotes((int)currentPulseTime, (int)prevPulseTime, false);
                piano.ShadeNotes((int)currentPulseTime, (int)prevPulseTime);
            }
        }
        /** Fast forward the midi music by one measure.
         *  The music must be in the paused/stopped state.
         *  When we resume in playPause, we start at the currentPulseTime.
         *  So to fast forward, just increase the currentPulseTime,
         *  and re-shade the sheet music.
         */
        private void FastForward(object sender, EventArgs evt)
        {
            if (midifile == null || sheet == null)
            {
                return;
            }
            if (playstate != MidiPlayState.Paused && playstate != MidiPlayState.Stopped)
            {
                return;
            }
            playstate = MidiPlayState.Paused;

            /* Remove any highlighted notes */
            sheet.ShadeNotes(-10, (int)currentPulseTime, false);
            piano.ShadeNotes(-10, (int)currentPulseTime);

            prevPulseTime     = currentPulseTime;
            currentPulseTime += midifile.Time.Measure;
            if (currentPulseTime > midifile.TotalPulses)
            {
                currentPulseTime -= midifile.Time.Measure;
            }
            sheet.ShadeNotes((int)currentPulseTime, (int)prevPulseTime, false);
            piano.ShadeNotes((int)currentPulseTime, (int)prevPulseTime);
        }
        /** The "Play Measures in a Loop" feature is enabled, and we've reached
         *  the last measure. Stop the sound, unshade the music, and then
         *  start playing again.
         */
        private void RestartPlayMeasuresInLoop()
        {
            timer.Stop();
            playstate = MidiPlayState.Stopped;
            StopSound();

            sheet.ShadeNotes(-10, (int)prevPulseTime, false);
            piano.ShadeNotes(-10, (int)prevPulseTime);
            currentPulseTime = -1;
            prevPulseTime    = 0;
            //System.Threading.Thread.Sleep(300);

            PlayPause(null, null);
        }
        /** Perform the actual stop, by stopping the sound,
         *  removing any shading, and clearing the state.
         */
        void DoStop()
        {
            playstate = MidiPlayState.Stopped;
            StopSound();

            // Remove all shading by redrawing the music
            // JV: set shading instead of invalidating, removes flicker
            sheet.ShadeNotes(-10, (int)currentPulseTime, false);
            piano.ShadeNotes(-10, (int)currentPulseTime);

            startPulseTime   = 0;
            currentPulseTime = 0;
            prevPulseTime    = 0;
            playButton.Image = playImage;
            playTip.SetToolTip(playButton, "Play");
            return;
        }
        /** The callback for the Stop button.
         *  If playing, initiate a stop and wait for the timer to finish.
         *  Then do the actual stop.
         */
        public void Stop(object sender, EventArgs args)
        {
            if (midifile == null || sheet == null || playstate == MidiPlayState.Stopped)
            {
                return;
            }

            if (playstate == MidiPlayState.Pausing || playstate == MidiPlayState.Stopping || playstate == MidiPlayState.Playing)
            {
                /* Wait for timer to finish */
                playstate = MidiPlayState.Stopping;
                System.Threading.Thread.Sleep(300);
                DoStop();
            }
            else if (playstate == MidiPlayState.Paused)
            {
                DoStop();
            }
        }
        /** The callback for the timer. If the midi is still playing,
         *  update the currentPulseTime and shade the sheet music.
         *  If a stop or pause has been initiated (by someone clicking
         *  the stop or pause button), then stop the timer.
         */
        void TimerCallback(object sender, EventArgs args)
        {
            if (midifile == null || sheet == null)
            {
                timer.Stop();
                playstate = MidiPlayState.Stopped;
                return;
            }
            else if (playstate == MidiPlayState.Stopped || playstate == MidiPlayState.Paused)
            {
                /* This case should never happen */
                timer.Stop();
                return;
            }
            else if (playstate == MidiPlayState.Stopping)
            {
                timer.Stop();
                return;
            }
            else if (playstate == MidiPlayState.Playing)
            {
                TimeSpan diff = DateTime.Now.TimeOfDay.Subtract(startTime);
                long     msec = diff.Minutes * 60 * 1000 +
                                diff.Seconds * 1000 + diff.Milliseconds;
                prevPulseTime    = currentPulseTime;
                currentPulseTime = startPulseTime + msec * pulsesPerMsec;

                /* If we're playing in a loop, stop and restart */
                if (options.playMeasuresInLoop)
                {
                    double nearEndTime = currentPulseTime + pulsesPerMsec * 10;
                    int    measure     = (int)(nearEndTime / midifile.Time.Measure);
                    if (measure > options.playMeasuresInLoopEnd)
                    {
                        RestartPlayMeasuresInLoop();
                        return;
                    }
                }

                /* Stop if we've reached the end of the song */
                if (currentPulseTime > midifile.TotalPulses)
                {
                    timer.Stop();
                    DoStop();
                    return;
                }

                sheet.ShadeNotes((int)currentPulseTime, (int)prevPulseTime, true);
                piano.ShadeNotes((int)currentPulseTime, (int)prevPulseTime);
                return;
            }
            else if (playstate == MidiPlayState.Pausing)
            {
                timer.Stop();
                TimeSpan diff = DateTime.Now.TimeOfDay.Subtract(startTime);
                long     msec = diff.Minutes * 60 * 1000 +
                                diff.Seconds * 1000 + diff.Milliseconds;

                StopSound();

                prevPulseTime    = currentPulseTime;
                currentPulseTime = startPulseTime + msec * pulsesPerMsec;
                sheet.ShadeNotes((int)currentPulseTime, (int)prevPulseTime, false);
                piano.ShadeNotes((int)currentPulseTime, (int)prevPulseTime);
                playstate        = MidiPlayState.Paused;
                playButton.Image = playImage;
                playTip.SetToolTip(playButton, "Play");
                return;
            }
        }
        /** The callback for the play/pause button (a single button).
         *  If we're stopped or pause, then play the midi file.
         *  If we're currently playing, then initiate a pause.
         *  (The actual pause is done when the timer is invoked).
         */
        private void PlayPause(object sender, EventArgs args)
        {
            if (midifile == null || sheet == null || numberTracks() == 0)
            {
                return;
            }
            else if (playstate == MidiPlayState.Stopping || playstate == MidiPlayState.Pausing)
            {
                return;
            }
            else if (playstate == MidiPlayState.Playing)
            {
                playstate = MidiPlayState.Pausing;
                return;
            }
            else if (playstate == MidiPlayState.Stopped || playstate == MidiPlayState.Paused)
            {
                /* The startPulseTime is the pulse time of the midi file when
                 * we first start playing the music.  It's used during shading.
                 */
                if (options.playMeasuresInLoop)
                {
                    /* If we're playing measures in a loop, make sure the
                     * currentPulseTime is somewhere inside the loop measures.
                     */
                    int measure = (int)(currentPulseTime / midifile.Time.Measure);
                    if ((measure < options.playMeasuresInLoopStart) ||
                        (measure > options.playMeasuresInLoopEnd))
                    {
                        currentPulseTime = options.playMeasuresInLoopStart * midifile.Time.Measure;
                    }
                    startPulseTime    = currentPulseTime;
                    options.pauseTime = (int)(currentPulseTime - options.shifttime);
                }
                else if (playstate == MidiPlayState.Paused)
                {
                    startPulseTime    = currentPulseTime;
                    options.pauseTime = (int)(currentPulseTime - options.shifttime);
                }
                else
                {
                    options.pauseTime = 0;
                    startPulseTime    = options.shifttime;
                    currentPulseTime  = options.shifttime;
                    prevPulseTime     = options.shifttime - midifile.Time.Quarter;
                }

                if (Type.GetType("Mono.Runtime") != null)
                {
                    SkipLeadingSilence();
                }

                CreateMidiFile();
                playstate = MidiPlayState.Playing;
                Volume.SetVolume(volumeBar.Value);
                PlaySound(tempSoundFile);
                startTime = DateTime.Now.TimeOfDay;
                timer.Start();
                playButton.Image = pauseImage;
                playTip.SetToolTip(playButton, "Pause");
                sheet.ShadeNotes((int)currentPulseTime, (int)prevPulseTime, true);
                piano.ShadeNotes((int)currentPulseTime, (int)prevPulseTime);
                return;
            }
        }
        /** Create a new MidiPlayer, displaying the play/stop buttons, the
         *  speed bar, and volume bar.  The midifile and sheetmusic are initially null.
         */
        public MidiPlayer()
        {
            this.Font = new Font("Arial", 10, FontStyle.Bold);
            loadButtonImages();
            int buttonheight = this.Font.Height * 2;

            this.midifile    = null;
            this.options     = null;
            this.sheet       = null;
            playstate        = MidiPlayState.Stopped;
            startTime        = DateTime.Now.TimeOfDay;
            startPulseTime   = 0;
            currentPulseTime = 0;
            prevPulseTime    = -10;
            errormsg         = new StringBuilder(256);
            ToolTip tip;

            /* Create the rewind button */
            rewindButton            = new Button();
            rewindButton.Parent     = this;
            rewindButton.Image      = rewindImage;
            rewindButton.ImageAlign = ContentAlignment.MiddleCenter;
            rewindButton.Size       = new Size(buttonheight, buttonheight);
            rewindButton.Location   = new Point(buttonheight / 2, buttonheight / 2);
            rewindButton.Click     += new EventHandler(Rewind);
            tip = new ToolTip();
            tip.SetToolTip(rewindButton, Strings.rewind);

            /* Create the play button */
            playButton            = new Button();
            playButton.Parent     = this;
            playButton.Image      = playImage;
            playButton.ImageAlign = ContentAlignment.MiddleCenter;
            playButton.Size       = new Size(buttonheight, buttonheight);
            playButton.Location   = new Point(buttonheight / 2, buttonheight / 2);
            playButton.Location   = new Point(rewindButton.Location.X + rewindButton.Width + buttonheight / 2,
                                              rewindButton.Location.Y);
            playButton.Click += new EventHandler(PlayPause);
            playTip           = new ToolTip();
            playTip.SetToolTip(playButton, Strings.play);

            /* Create the stop button */
            stopButton            = new Button();
            stopButton.Parent     = this;
            stopButton.Image      = stopImage;
            stopButton.ImageAlign = ContentAlignment.MiddleCenter;
            stopButton.Size       = new Size(buttonheight, buttonheight);
            stopButton.Location   = new Point(playButton.Location.X + playButton.Width + buttonheight / 2,
                                              playButton.Location.Y);
            stopButton.Click += new EventHandler(Stop);
            tip = new ToolTip();
            tip.SetToolTip(stopButton, Strings.stop);

            /* Create the fastFwd button */
            fastFwdButton            = new Button();
            fastFwdButton.Parent     = this;
            fastFwdButton.Image      = fastFwdImage;
            fastFwdButton.ImageAlign = ContentAlignment.MiddleCenter;
            fastFwdButton.Size       = new Size(buttonheight, buttonheight);
            fastFwdButton.Location   = new Point(stopButton.Location.X + stopButton.Width + buttonheight / 2,
                                                 stopButton.Location.Y);
            fastFwdButton.Click += new EventHandler(FastForward);
            tip = new ToolTip();
            tip.SetToolTip(fastFwdButton, Strings.fastForward);



            /* Create the Speed bar */
            speedLabel           = new Label();
            speedLabel.Parent    = this;
            speedLabel.Text      = Strings.speed + " : 100%";
            speedLabel.TextAlign = ContentAlignment.MiddleLeft;
            speedLabel.Height    = buttonheight;
            speedLabel.Width     = buttonheight * 3;
            speedLabel.Location  = new Point(fastFwdButton.Location.X + fastFwdButton.Width + buttonheight / 2,
                                             fastFwdButton.Location.Y);

            speedBar               = new TrackBar();
            speedBar.Parent        = this;
            speedBar.Minimum       = 1;
            speedBar.Maximum       = 150;
            speedBar.TickFrequency = 10;
            speedBar.TickStyle     = TickStyle.BottomRight;
            speedBar.LargeChange   = 10;
            speedBar.Value         = 100;
            speedBar.Width         = buttonheight * 6;
            speedBar.Location      = new Point(speedLabel.Location.X + speedLabel.Width + 2,
                                               speedLabel.Location.Y);
            speedBar.Scroll += new EventHandler(SpeedBarChanged);

            tip = new ToolTip();
            tip.SetToolTip(speedBar, Strings.speed);

            /* Create the Volume bar */
            Label volumeLabel = new Label();

            volumeLabel.Parent     = this;
            volumeLabel.Image      = volumeImage;
            volumeLabel.ImageAlign = ContentAlignment.MiddleRight;
            volumeLabel.Height     = buttonheight;
            volumeLabel.Width      = buttonheight * 2;
            volumeLabel.Location   = new Point(speedBar.Location.X + speedBar.Width + buttonheight / 2,
                                               speedBar.Location.Y);

            volumeBar               = new TrackBar();
            volumeBar.Parent        = this;
            volumeBar.Minimum       = 1;
            volumeBar.Maximum       = 100;
            volumeBar.TickFrequency = 10;
            volumeBar.TickStyle     = TickStyle.BottomRight;
            volumeBar.LargeChange   = 10;
            volumeBar.Value         = Volume.GetVolume();
            volumeBar.Width         = buttonheight * 6;
            volumeBar.Location      = new Point(volumeLabel.Location.X + volumeLabel.Width + 2,
                                                volumeLabel.Location.Y);
            volumeBar.Scroll += new EventHandler(ChangeVolume);
            tip = new ToolTip();
            tip.SetToolTip(volumeBar, Strings.volume);

            Height = buttonheight * 2;

            /* Initialize the timer used for playback, but don't start
             * the timer yet (enabled = false).
             */
            timer          = new Timer();
            timer.Enabled  = false;
            timer.Interval = 100;  /* 100 millisec */
            timer.Tick    += new EventHandler(TimerCallback);

            tempSoundFile = "";
        }