Issues: Creating a very accurate Swing Timer
Asked Answered
P

4

10

In order to make SwingTimer accurate, I like the logic and example suggested by @Tony Docherty On CR. Here is the Link.

In order to highlight the given words, again and again, there is always a few microsecond delays. If I have words to highlight say: "hello how are" and the values for each word are (delays): 200,300,400 ms respectively, then the actual time taken by the timer is always more. Say instead of 200 ms, it takes 216 ms. Like this, if I have many words..in the end, the extra delay is noticeable.

I have to highlight each letter say: 'h''e''l''l''0' each should get 200/length(i.e 5) = 40 ms approx. Set the delay after each letter.

My logic is, take the current time say startTime, just before starting the process. Also, calculate the totalDelay which is totalDelay+=delay/.length().

Now check the condition: (startTime+totalDelay-System.currentTime) if this is -ve, that means the time consumption is more, so skip the letter. Check till there is a positive delay.This means I am adding the timings till now, and overcheck it with the difference in the time taken by the process when it got started.

This may result into skipping to highlight the letters.

But something is wrong. What, it’s difficult for me to make out. It's some problem with the looping thing maybe. I have seen it is entering the loop (to check whether the time is -ve ) just twice. But this should not be the case. And I am also not sure about setting up my next delay. Any ideas?

Here is an SSCCE:

    import java.awt.Color;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.lang.reflect.InvocationTargetException;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JTextPane;
    import javax.swing.SwingUtilities;
    import javax.swing.Timer;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DefaultStyledDocument;
    import javax.swing.text.StyleConstants;
    import javax.swing.text.StyledDocument;

    public class Reminder {
        private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
        private static final String[] WORDS = TEXT.split(" ");
        private JFrame frame;
        private Timer timer;
        private StyledDocument doc;
        private JTextPane textpane;
        private int[] times = new int[100];
      private long totalDelay=0,startTime=0;

        private int stringIndex = 0;
        private int index = 0;

        public void startColoring() {
              times[0]=100;times[9]=200;times[10]=200;times[11]=200;times[12]=200;
              times[1]=400;times[2]=300;times[3]=900;times[4]=1000;times[5]=600;times[6]=200;times[7]=700;times[8]=700;

      ActionListener actionListener = new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent actionEvent) 
       {

       doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);
        stringIndex++;

 try {

 if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")|| doc.getText(stringIndex, 1).equals("\n"))
 {
                            index++;
  }
    if (index < WORDS.length) {

       double delay = times[index];
     totalDelay+=delay/WORDS[index].length();

  /*Check if there is no -ve delay, and you are running according to the time*/
  /*The problem is here I think. It's just entered this twice*/
   while(totalDelay+startTime-System.currentTimeMillis()<0)
      { 
      totalDelay+=delay/WORDS[index].length();
      stringIndex++;
     /*this may result into the end of current word, jump to next word.*/
    if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ") || doc.getText(stringIndex, 1).equals("\n"))
       {
   index += 1;
   totalDelay+=delay/WORDS[index].length();
       }
      }

     timer.setDelay((int)(totalDelay+startTime-System.currentTimeMillis()));

                        } 
else {
         timer.stop();
    System.err.println("Timer stopped");
       }
                    } catch (BadLocationException e) {
                        e.printStackTrace();
                    }
                }
            };

            startTime=System.currentTimeMillis();
            timer = new Timer(times[index], actionListener);
            timer.setInitialDelay(0);
            timer.start();
        }

        public void initUI() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel panel = new JPanel();
            doc = new DefaultStyledDocument();
            textpane = new JTextPane(doc);
            textpane.setText(TEXT);
            javax.swing.text.Style style = textpane.addStyle("Red", null);
            StyleConstants.setForeground(style, Color.RED);
            panel.add(textpane);
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
        }

        public static void main(String args[]) throws InterruptedException, InvocationTargetException {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    Reminder reminder = new Reminder();
                    reminder.initUI();
                    reminder.startColoring();
                }
            });
        }
    }

UPDATE:

For better understanding:

The EG given by @Tony Docherty :

Lets take the word "Test" and say it needs to be highlighted for 1 second, therefore each letter is highlighted for 250ms. Doing things the way you originally, did meant that you set a timer for 250ms for each letter but if each cycle actually took 260ms and lets say the 'e' cycle took 400ms (maybe due to GC or something else using CPU cycles) by the end of the word you would have taken 180ms more than you should have. This error will continue to build for each word until the error is so large highlighting is no longer visually in sync.

The way I am trying, is rather than repeatedly saying this letter needs to be highlighted for x amount of time, calculate the time for each letter relative to the beginning of the sequence ie T = 250, e = 500, s = 750, t = 1000.

So to get the actual time delay you need to add the start time and subtract the current time. To run through the example using the timings I gave above:

StartTime   Letter   Offset    CurrentTime    Delay  ActualTimeTaken   
100000         T       250       100010        240      250  
100000         e       500       100260        240      400  
100000         s       750       100660         90      100  
100000         t      1000       100760        240      250  

So you should be able to see now that the timing for each letter is adjusted to take account of any overrun of time from the previous letter. Of course it is possible that a timing overrun is so great that you have to skip highlighting the next letter (or maybe more than 1) but at least I will remaining broadly in sync.

EDITED SSCCE

Update2

In first phase, I take the timings for each word. That is, when the user hits ESC key, the time is stored for a particular word (he does it as the song is played in background.) When the ESC key is pressed, the current word is highlighted and the time spent on the current word is stored in an array. I keep on storing the timings. When the user ends, now I would like to highlight the words as per the set timings. So here, the timing by the user is important. If the timings are fast, so is the highlighting of words or if slow, vice-versa.

New update: progress

The answers below have different logic, but to my surprise, they work more or less the same. A very very weird problem I have found out with all the logic (including mine) is that they seem to work perfectly for few lines, but after that they gain speed, that's also not slowly, but with a huge difference.

Also if you think I should think in a different way, your suggestions are highly appreciated.

Penumbra answered 23/2, 2013 at 13:21 Comment(21)
The while loop called on the EDT looks suspicious to me. Let me re-read your requirements...Herra
@HovercraftFullOfEels The while loop don't freez any thing,till now.I am trying to check, the extra delay taken by the swing timer, from the starting of the highlighting thing.Will make an update to make it more clear.Penumbra
I still don't fully understand what your problem is: for once, I see that not all letters are red after stopping the timer - is that part of the problem? And: what exactly do you mean by accuracy, any hard numbers and if so, how did you calculate them or what hardware (?) event produces them? You probably won't get below the 15ms of the typical coarse hardware timer produces anyway, plus your action itself takes some time.Northwards
@Northwards 1) Not all the letters are red: That is beacuse of synchronization.I may give up hhighlighting a few letters in between but not loose sync.When i see the time taken by the process is more, i skip that letter. 2)No, not the part of the problem, your logic may be different. 3)Accuracy- I want them to be highlighted in accordance with the background sound/music/song (Same as Sync. in Kataoke apps, As they are Sync. very well).Yes, true, there might be CPU delay, but some logic to overcome, if not 100, then 92% accurate.Penumbra
Continued...I have tried a hell lot of things, also the answer by @Hovercraft Full Of Eels.And i don't have any more ideas, hope will get some good help.Penumbra
still don't get it (silly me ;-) - what is the other end of "synch"? Were/how do you get the "beat" (or whatever) that tells you which letter should be (ideally) highlighted at any single moment?Northwards
forgot 2 thingies: a) what's wrong with @HovercraftFullOfEels approach? b) still no numbers on your required "accuracy" ...Northwards
@Northwards no beat required.In first phase i take the timings for a particular word.Ok will ake another update.b)"still no numbers "may or may not be.The main motive is Sync.The wrong thing is, the method used by -HovercraftFullOfEels, is a bit faster.Not correctly Sync.Penumbra
sync to the minute, micro/milli/nano/femto/second ...?Northwards
@Northwards mili. as milisec is as accurate as anything.See my updated question.Ask for an SSCCE which includes both taking and display the highlited words if Reqd.Penumbra
as already mentioned, the min (best case scenario, no other or only a few timers and a very short actionPerformed itself) resolution is around 15ms .. if that's not good enough you'll have to look for an alternativeNorthwards
@Northwards After all this, i have noticed, this varies as per the set delay and the length of a word.Need not to be 15MS for every thing.For EG for a word delay with 5sec, the delay approx comes out to be 100 MS.Any suggestions for alternative?Penumbra
+1 @Northwards for the average system latency of 15ms for timer (as it most likely uses sleep() and than ofcourse the action... Also if you're measuring elapsed time by subtracting two timestamps, you should use System.nanoTime() to get those timestamps as it uses the "most precise available system timer". As I have suggested before use a Thread with while loop, SwingUtilities.invokeXX and Thread.sleep(time-15) to create your own more accurate timer.Barghest
@DavidKroukamp I did tried both.1)By using System.nanoTime(),Which gives almost 99% same result.2) By using own sleep delay method,which is more accurate than swing timer, but still needs modifications like swing timer.Penumbra
@DavidKroukamp head scratching - I understand your words but not what they are trying to tell me ;-) All I did so far is to try pushing joey into defining (and measuring!) exactly what he needs. The outcome of that will heavily impact the solution.Northwards
@joey rohan on first sight looks like as Swing.Timer is inaccurate, but next period starting when all events are done, sure doesn't preventing for exceptions from RepaintManager,Emulsoid
@Emulsoid "doesn't preventing for exceptions from RepaintManager" because of multiple requests ?Penumbra
@joey rohan not, multiple requests for cycle only multiple of exceptions, any of exception in Java,Emulsoid
@Emulsoid then i think there is only one solution.."prevention".Penumbra
@Northwards no sorry the rest was inteneded for joey, I was just saying what i +1'd for to bring OPs attention to it. My bad for confusion :)Barghest
@joey rohan I can to see difference on 1.5 - 3 seconds per day (between swing.Timer and util.Timer), I see this post as good exampleEmulsoid
B
6

Okay so I have been looking at the some code (the code I posted in your last question about Karaoke timer)

Using that code I put up some measuring system using System.nanoTime() via System.out.println() which will help us to see what is happening:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

public class KaraokeTest {

    private int[] timingsArray = {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};//word/letters timings
    private String[] individualWordsToHighlight = {" \nHello\n", " world\n", " Hello", " world", " Hello", " world", " Hello", " world", " Hello", " world"};//each individual word/letters to highlight
    private int count = 0;
    private final JTextPane jtp = new JTextPane();
    private final JButton startButton = new JButton("Start");
    private final JFrame frame = new JFrame();
    //create Arrays of individual letters and their timings
    final ArrayList<String> chars = new ArrayList<>();
    final ArrayList<Long> charsTiming = new ArrayList<>();

    public KaraokeTest() {
        initComponents();
    }

    private void initComponents() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);

        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        jtp.setEditable(false);

        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                startButton.setEnabled(false);
                count = 0;
                charsTiming.clear();
                chars.clear();

                for (String s : individualWordsToHighlight) {
                    for (int i = 0; i < s.length(); i++) {
                        chars.add(String.valueOf(s.charAt(i)));
                        //System.out.println(String.valueOf(s.charAt(i)));
                    }
                }

                //calculate each letters timings
                for (int x = 0; x < timingsArray.length; x++) {
                    for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
                        individualWordsToHighlight[x] = individualWordsToHighlight[x].replace("\n", " ").replace("\r", " ");//replace line breaks
                        charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));//dont count spaces
                        //System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
                    }
                }

                Timer t = new Timer(1, new AbstractAction() {
                    long startTime = 0;
                    long acum = 0;
                    long timeItTookTotal = 0;
                    long dif = 0, timeItTook = 0, timeToTake = 0;
                    int delay = 0;

                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        if (count < charsTiming.size()) {

                            if (count == 0) {
                                startTime = System.nanoTime();
                                System.out.println("Started: " + startTime);
                            }

                            timeToTake = charsTiming.get(count);
                            acum += timeToTake;

                            //highlight the next word
                            highlightNextWord();

                            //System.out.println("Acum " + acum);
                            timeItTook = (acum - ((System.nanoTime() - startTime) / 1000000));
                            timeItTookTotal += timeItTook;
                            //System.out.println("Elapsed since start: " + (System.nanoTime() - startTime));
                            System.out.println("Time the char should take: " + timeToTake);
                            System.out.println("Time it took: " + timeItTook);
                            dif = (timeToTake - timeItTook);
                            System.out.println("Difference: " + dif);
                            //System.out.println("Difference2 " + (timeToTake - dif));

                            //calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
                            delay = (int) (timeToTake - dif);

                            if (delay < 1) {
                                delay = 1;
                            }

                            //restart timer with new timings
                            ((Timer) ae.getSource()).setInitialDelay((int) timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
                            //((Timer) ae.getSource()).setInitialDelay(delay);
                            ((Timer) ae.getSource()).restart();

                        } else {//we are at the end of the array
                            long timeStopped = System.nanoTime();
                            System.out.println("Stopped: " + timeStopped);
                            System.out.println("Time it should take in total: " + acum);
                            System.out.println("Time it took using accumulator of time taken for each letter: " + timeItTookTotal
                                    + "\nDifference: " + (acum - timeItTookTotal));
                            long timeItTookUsingNanoTime = ((timeStopped - startTime) / 1000000);
                            System.out.println("Time it took using difference (endTime-startTime): " + timeItTookUsingNanoTime
                                    + "\nDifference: " + (acum - timeItTookUsingNanoTime));
                            reset();
                            ((Timer) ae.getSource()).stop();//stop the timer
                        }
                        count++;//increment counter
                    }
                });
                t.setRepeats(false);
                t.start();
            }
        });

        frame.add(jtp, BorderLayout.CENTER);
        frame.add(startButton, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    private void reset() {
        startButton.setEnabled(true);
        jtp.setText("");
        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        JOptionPane.showMessageDialog(frame, "Done");
    }

    private void highlightNextWord() {
        //we still have words to highlight
        int sp = 0;
        for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
            sp += 1;
        }

        while (chars.get(sp - 1).equals(" ")) {
            sp += 1;
            count++;
        }

        //highlight words
        Style style = jtp.addStyle("RED", null);
        StyleConstants.setForeground(style, Color.RED);
        ((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new KaraokeTest();
            }
        });
    }
}

The output on my PC is:

Started: 10289712615974

Time the char should take: 166

Time it took: 165

Difference 1

...

Time the char should take: 166

Time it took: 155

Difference 11

...

Time the char should take: 166

Time it took: 5

Difference 161

Stopped: 10299835063084

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 5542

Difference: 4418

Time it took using difference (endTime-startTime): 10122

Difference: -162

Thus my conclusion is the Swing Timer is actually running faster than we expect as the code in the Timers actionPerformed will not necessarily take as long as the letters expected highlighting time this of course causes an avalanche effect i.e the faster/slower the timer runs the greater/less the difference will become and timers next execution on restart(..) will take be at a different time i.e faster or slower.

in the code do this:

//calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
delay = (int) (timeToTake - dif);


//restart timer with new timings
//((Timer) ae.getSource()).setInitialDelay((int)timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
((Timer) ae.getSource()).setInitialDelay(delay);
((Timer) ae.getSource()).restart();

Produces a more accurate result (maximum latency Ive had is 4ms faster per letter):

Started: 10813491256556

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 162

Difference 4

Stopped: 10823452105363

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 9806

Difference: 154

Time it took using difference (endTime-startTime): 9960

Difference: 0

Barghest answered 27/2, 2013 at 17:30 Comment(19)
@joeyrohan see my latest answer (deleted my old as it had many edits and became community wiki).. Although it no more highlighted the spaces it was still using the spaces when calculating the time for each letter... fixed that now by charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));Barghest
@joeyrohan ahh I hate these edits as I add things I forget.. See my latest edit, previous code was not clearing the arrays of chars and timings when start button is clicked... thus it would work fine the first time, and the next it would take double (as arrays have words added again) and so on and so fourth... Lol this is the last edit I promise :PBarghest
Will debugggg and see.insted of .trim() have to use something else also to remove '\n'.This thing sucks.Penumbra
@joeyrohan perhaps simply replace all \n with white space(s) ... And put that into the array which will than be used to separate into characters excluding white spaces via trim()Barghest
@joeyrohan See this to remove \n and put space instead.Barghest
@joeyrohan Ah you mean replace..hehe nice trick.Thanks. yup and its pleasure see my edited post fixed to omit \n and white space when calculating letter timing and highlightingBarghest
Sorry 2 say:/ a bug. Take timings as {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000} see the differencePenumbra
For so long i was thinking it's something wrong with my logic:P its cause of "\n."Penumbra
@joeyrohan Lol I cant believe a \n would do that to my code :P Hope I helped a little though.... So is it working now that you found the problem?Barghest
yeah it was the - "" but not " " in your replace() !! GOD!!Penumbra
+1 OMG good eye...I see now how stupid I was.. it should have been replaced with a white space (" ") not ""Barghest
@joeyrohan edited my answer to reflect correct changes you have found... so others who use the code will be copying fully corrected codeBarghest
HeHe had 2 cups of coffee for that.Can start my debugging process now.Penumbra
Ok if i have an array having timings say x.And i am reading a file which have n lines.Then what should i change?When i read a line from a file, i store it words by words in an array (individualWordsToHighlight).And so the timings.Creating a problem about the "\n", also the length of both arrays.: what you suggest?PS: please don't update your answer..I want to do myself..suggest some logic please.Penumbra
I am coping the array in which i have stored the timings.And same for string array.But, in jtp, text is set, such that it contains " " and \n.But, we are highlighting according to words in the array not according to text on jtp.Trying to fix it.So, doing something inside highlightNextWord. But i think have to convert the text in text area to charArr then check it with sp.Penumbra
@joeyrohan thanks for the bounty and glad my answer was of help. Highest Ive earned so far :D... Have you gots the code working after your last comment ahh did at last? What was /is the problem?Barghest
Also reading my answer again Swing Timer is running faster than it should is actually not true its running as expected what we forgot is that the timers actionPerformed will only take as long as the task it has to do thereafter it will schedule a new timer restart at nextExecutionTime=nextLetterTime + currentTime, we thus have to make up for the accumulated (or lost) time.Barghest
Now also i don't know what i was doing wrong.But my other complex way did worked LOL.On jtp is was setting text word by word, and not individualWordsToHighlight array(the way you did in your SSCCE), and timings i was taking from some other array.problem was: it was highlightng -1 word after every line.It was cause of "\n".So was increasing SP (was checking "\n" through a char array(my jtp.text was converted toCharArray))But dint worked.So tried nesting of Scanners (cause scanners dont tell end of a line) and then added " " extra to every last word.WORKED :)Penumbra
delay = (int) (timeToTake - dif); -> this line seems redundant. Because delay is simply equivalent to timeItTook. You can use timeItTook instead of delay.Fatality
H
7

I think that to do something like this, you need a Swing Timer that ticks at a constant rate, say 15 msec, as long as it's fast enough to allow the time granularity you require, and then trip the desired behavior inside the timer when the elapsed time is that which you require.

  • In other words, don't change the Timer's delay at all, but just change the required elapse times according to your need.
  • You should not have a while (true) loop on the EDT. Let the "while loop" be the Swing Timer itself.
  • To make your logic more fool proof, you need to check if elapsed time is >= needed time.
  • Again, don't set the Timer's delay. In other words, don't use it as a timer but rather as a poller. Have it beat every xx msec constantly polling the elapsed time, and then reacting if the elapsed time is >= to your need.

The code I'm suggesting would look something like so:

     public void actionPerformed(ActionEvent actionEvent) {

        if (index > WORDS.length || stringIndex >= doc.getLength()) {
           ((Timer)actionEvent.getSource()).stop();
        }

        currentElapsedTime = calcCurrentElapsedTime();
        if (currentElapsedTime >= elapsedTimeForNextChar) {
           setNextCharAttrib(stringIndex);
           stringIndex++;

           if (atNextWord(stringIndex)) {
              stringIndex++; // skip whitespace 
              deltaTimeForEachChar = calcNextCharDeltaForNextWord();
           } else {
              elapsedTimeForNextChar += deltaTimeForEachChar;
           }
        }

        // else -- we haven't reached the next time to change char attribute yet.
        // keep polling.
     }

For example, my SSCCE:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.List;

import javax.swing.*;
import javax.swing.text.*;

public class Reminder3 {
   private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
   private static final String[] WORDS = TEXT.split(" ");
   private static final int[] TIMES = { 100, 400, 300, 900, 1000, 600, 200,
         700, 700, 200, 200, 200, 200 };
   private static final int POLLING_TIME = 12;

   private StyledDocument doc;
   private JTextPane textpane;
   private JPanel mainPanel = new JPanel();
   private List<ReminderWord> reminderWordList = new LinkedList<ReminderWord>();
   private Timer timer;

   // private int stringIndex = 0;

   public Reminder3() {
      doc = new DefaultStyledDocument();
      textpane = new JTextPane(doc);
      textpane.setText(TEXT);
      javax.swing.text.Style style = textpane.addStyle("Red", null);
      StyleConstants.setForeground(style, Color.RED);

      JPanel textPanePanel = new JPanel();
      textPanePanel.add(new JScrollPane(textpane));

      JButton startBtn = new JButton(new AbstractAction("Start") {

         @Override
         public void actionPerformed(ActionEvent arg0) {
            goThroughWords();
         }
      });
      JPanel btnPanel = new JPanel();
      btnPanel.add(startBtn);

      mainPanel.setLayout(new BorderLayout());
      mainPanel.add(textPanePanel, BorderLayout.CENTER);
      mainPanel.add(btnPanel, BorderLayout.SOUTH);
   }

   public void goThroughWords() {
      if (timer != null && timer.isRunning()) {
         return;
      }
      doc = new DefaultStyledDocument();
      textpane.setDocument(doc);
      textpane.setText(TEXT);

      javax.swing.text.Style style = textpane.addStyle("Red", null);
      StyleConstants.setForeground(style, Color.RED);

      int wordStartTime = 0;
      for (int i = 0; i < WORDS.length; i++) {

         if (i > 0) {
            wordStartTime += TIMES[i - 1];
         }
         int startIndexPosition = 0; // set this later
         ReminderWord reminderWord = new ReminderWord(WORDS[i], TIMES[i],
               wordStartTime, startIndexPosition);
         reminderWordList.add(reminderWord);
      }

      int findWordIndex = 0;
      for (ReminderWord word : reminderWordList) {

         findWordIndex = TEXT.indexOf(word.getWord(), findWordIndex);
         word.setStartIndexPosition(findWordIndex);
         findWordIndex += word.getWord().length();
      }

      timer = new Timer(POLLING_TIME, new TimerListener());
      timer.start();
   }

   public JComponent getMainPanel() {
      return mainPanel;
   }


   private void setNextCharAttrib(int textIndex) {
      doc.setCharacterAttributes(textIndex, 1,
            textpane.getStyle("Red"), true);      
   }

   private class TimerListener implements ActionListener {
      private ReminderWord currentWord = null;
      private long startTime = System.currentTimeMillis();

      @Override
      public void actionPerformed(ActionEvent e) {
         if (reminderWordList == null) { 
            ((Timer) e.getSource()).stop();
            return;
         }

         if (reminderWordList.isEmpty() && currentWord.atEnd()) {
            ((Timer) e.getSource()).stop();
            return;
         }

         // if just starting, or if done with current word
         if (currentWord == null || currentWord.atEnd()) {
            currentWord = reminderWordList.remove(0); // get next word
         }

         long totalElapsedTime = System.currentTimeMillis() - startTime;
         if (totalElapsedTime > (currentWord.getStartElapsedTime() + currentWord
               .getIndex() * currentWord.getTimePerChar())) {
            setNextCharAttrib(currentWord.getStartIndexPosition() + currentWord.getIndex());

            currentWord.increment();
         }

      }
   }

   private static void createAndShowGui() {
      Reminder3 reminder = new Reminder3();

      JFrame frame = new JFrame("Reminder");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(reminder.getMainPanel());
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }


   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }

}

class ReminderWord {
   private String word;
   private int totalTime;
   private int timePerChar;
   private int startTime;
   private int startIndexPosition;
   private int index = 0;

   public ReminderWord(String word, int totalTime, int startTime,
         int startIndexPosition) {
      this.word = word;
      this.totalTime = totalTime;
      this.startTime = startTime;
      timePerChar = totalTime / word.length();
      this.startIndexPosition = startIndexPosition;
   }

   public String getWord() {
      return word;
   }

   public int getTotalTime() {
      return totalTime;
   }

   public int getStartElapsedTime() {
      return startTime;
   }

   public int getTimePerChar() {
      return timePerChar;
   }

   public int getStartIndexPosition() {
      return startIndexPosition;
   }

   public int increment() {
      index++;
      return index;
   }

   public int getIndex() {
      return index;
   }

   public boolean atEnd() {
      return index > word.length();
   }

   public void setStartIndexPosition(int startIndexPosition) {
      this.startIndexPosition = startIndexPosition;
   }

   @Override
   public String toString() {
      return "ReminderWord [word=" + word + ", totalTime=" + totalTime
            + ", timePerChar=" + timePerChar + ", startTime=" + startTime
            + ", startIndexPosition=" + startIndexPosition + ", index=" + index
            + "]";
   }

}
Herra answered 23/2, 2013 at 13:39 Comment(17)
I did that all time.Trust me, it din't worked as expected."just change the required elapse times according to your need." This thing varies for me.Even a perfect formula failed.Penumbra
This is beacuse of setDelay() method.I need to set delay for next letter, (but it should be calculated.)That is why have to put inside actionPerformed.Penumbra
@joeyrohan: that's just it. Don't change the Timer's delay. Let the timer act as a poller. See edit above.Herra
@HovercraftFullOfEels whichever granularity he achieves and no matter how fast his actionPerformed will execute, this will lack precision after a while (I think he is trying to make a karoake thing, so he needs to be in perfect sync with music). As this already been suggested, the best way would be synch itself with the audio-player directly (this would take into account pauses/forward/backward, possible latencies of the computer playing the audio)Icsh
I just cannot find any logic related.And what is the exact meaning of "Don't change the Timer's delay."If i don't check inside actionPerformed, where can i?Do i have any option?Penumbra
@GuillaumePolet True, but its tough that way.Have to do it all over again, and here, i just have a little sync. problem.This delay thing is done on the advice of the top answerer on my last post.Penumbra
This thing gives me a very less precision, are you not trying to say calculate the time elapsed for every word rather than from the beginning to end?Penumbra
@joeyrohan: it depends all on the granularity of your timer. Let me see if I can better "grok" your code to show a working example. Again, this code polls, and should poll at a relatively fast frequency, say a 10 to 15 msecs timer delay.Herra
@joeyrohan: still working on it. Making subclasses to show my idea.Herra
+1 wonderful example.Will take some time to absorb, go through and test it.Thank you for your efforts and your valuable time.Will let you know the results.Penumbra
@HovercraftFullOfEels sorry to say, but it goes a bit fast :/Penumbra
@HovercraftFullOfEels But cannot make out where.Not fast, its very fast..any ideas?Penumbra
@joeyrohan: I must have a bug. I will have to get to this later though.Herra
@HovercraftFullOfEels Sorry for this. Please revise again, once i add a Bounty :)Penumbra
@joeyrohan yes I got some but I need to work a bit on it. The basic thing is that I would like to avoid relying on polling mechanisms. I will give it some thoughs during this week but if you have a good answer, don't hesitate to give him your bounty. ONe thing that bothers me is that you only give one array of delays, but I believe you should have 2 of them: one to indicate the begining of each word, and the other for the duration of a word.Icsh
I can't disagree with anything that @GuillaumePolet is suggesting.Herra
@GuillaumePolet i din't noticed, as the highlighting of one word leads to other.Will be very thankful if i can get any help from your side.Penumbra
B
6

Okay so I have been looking at the some code (the code I posted in your last question about Karaoke timer)

Using that code I put up some measuring system using System.nanoTime() via System.out.println() which will help us to see what is happening:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

public class KaraokeTest {

    private int[] timingsArray = {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};//word/letters timings
    private String[] individualWordsToHighlight = {" \nHello\n", " world\n", " Hello", " world", " Hello", " world", " Hello", " world", " Hello", " world"};//each individual word/letters to highlight
    private int count = 0;
    private final JTextPane jtp = new JTextPane();
    private final JButton startButton = new JButton("Start");
    private final JFrame frame = new JFrame();
    //create Arrays of individual letters and their timings
    final ArrayList<String> chars = new ArrayList<>();
    final ArrayList<Long> charsTiming = new ArrayList<>();

    public KaraokeTest() {
        initComponents();
    }

    private void initComponents() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);

        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        jtp.setEditable(false);

        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                startButton.setEnabled(false);
                count = 0;
                charsTiming.clear();
                chars.clear();

                for (String s : individualWordsToHighlight) {
                    for (int i = 0; i < s.length(); i++) {
                        chars.add(String.valueOf(s.charAt(i)));
                        //System.out.println(String.valueOf(s.charAt(i)));
                    }
                }

                //calculate each letters timings
                for (int x = 0; x < timingsArray.length; x++) {
                    for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
                        individualWordsToHighlight[x] = individualWordsToHighlight[x].replace("\n", " ").replace("\r", " ");//replace line breaks
                        charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));//dont count spaces
                        //System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
                    }
                }

                Timer t = new Timer(1, new AbstractAction() {
                    long startTime = 0;
                    long acum = 0;
                    long timeItTookTotal = 0;
                    long dif = 0, timeItTook = 0, timeToTake = 0;
                    int delay = 0;

                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        if (count < charsTiming.size()) {

                            if (count == 0) {
                                startTime = System.nanoTime();
                                System.out.println("Started: " + startTime);
                            }

                            timeToTake = charsTiming.get(count);
                            acum += timeToTake;

                            //highlight the next word
                            highlightNextWord();

                            //System.out.println("Acum " + acum);
                            timeItTook = (acum - ((System.nanoTime() - startTime) / 1000000));
                            timeItTookTotal += timeItTook;
                            //System.out.println("Elapsed since start: " + (System.nanoTime() - startTime));
                            System.out.println("Time the char should take: " + timeToTake);
                            System.out.println("Time it took: " + timeItTook);
                            dif = (timeToTake - timeItTook);
                            System.out.println("Difference: " + dif);
                            //System.out.println("Difference2 " + (timeToTake - dif));

                            //calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
                            delay = (int) (timeToTake - dif);

                            if (delay < 1) {
                                delay = 1;
                            }

                            //restart timer with new timings
                            ((Timer) ae.getSource()).setInitialDelay((int) timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
                            //((Timer) ae.getSource()).setInitialDelay(delay);
                            ((Timer) ae.getSource()).restart();

                        } else {//we are at the end of the array
                            long timeStopped = System.nanoTime();
                            System.out.println("Stopped: " + timeStopped);
                            System.out.println("Time it should take in total: " + acum);
                            System.out.println("Time it took using accumulator of time taken for each letter: " + timeItTookTotal
                                    + "\nDifference: " + (acum - timeItTookTotal));
                            long timeItTookUsingNanoTime = ((timeStopped - startTime) / 1000000);
                            System.out.println("Time it took using difference (endTime-startTime): " + timeItTookUsingNanoTime
                                    + "\nDifference: " + (acum - timeItTookUsingNanoTime));
                            reset();
                            ((Timer) ae.getSource()).stop();//stop the timer
                        }
                        count++;//increment counter
                    }
                });
                t.setRepeats(false);
                t.start();
            }
        });

        frame.add(jtp, BorderLayout.CENTER);
        frame.add(startButton, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    private void reset() {
        startButton.setEnabled(true);
        jtp.setText("");
        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        JOptionPane.showMessageDialog(frame, "Done");
    }

    private void highlightNextWord() {
        //we still have words to highlight
        int sp = 0;
        for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
            sp += 1;
        }

        while (chars.get(sp - 1).equals(" ")) {
            sp += 1;
            count++;
        }

        //highlight words
        Style style = jtp.addStyle("RED", null);
        StyleConstants.setForeground(style, Color.RED);
        ((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new KaraokeTest();
            }
        });
    }
}

The output on my PC is:

Started: 10289712615974

Time the char should take: 166

Time it took: 165

Difference 1

...

Time the char should take: 166

Time it took: 155

Difference 11

...

Time the char should take: 166

Time it took: 5

Difference 161

Stopped: 10299835063084

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 5542

Difference: 4418

Time it took using difference (endTime-startTime): 10122

Difference: -162

Thus my conclusion is the Swing Timer is actually running faster than we expect as the code in the Timers actionPerformed will not necessarily take as long as the letters expected highlighting time this of course causes an avalanche effect i.e the faster/slower the timer runs the greater/less the difference will become and timers next execution on restart(..) will take be at a different time i.e faster or slower.

in the code do this:

//calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
delay = (int) (timeToTake - dif);


//restart timer with new timings
//((Timer) ae.getSource()).setInitialDelay((int)timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
((Timer) ae.getSource()).setInitialDelay(delay);
((Timer) ae.getSource()).restart();

Produces a more accurate result (maximum latency Ive had is 4ms faster per letter):

Started: 10813491256556

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 162

Difference 4

Stopped: 10823452105363

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 9806

Difference: 154

Time it took using difference (endTime-startTime): 9960

Difference: 0

Barghest answered 27/2, 2013 at 17:30 Comment(19)
@joeyrohan see my latest answer (deleted my old as it had many edits and became community wiki).. Although it no more highlighted the spaces it was still using the spaces when calculating the time for each letter... fixed that now by charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));Barghest
@joeyrohan ahh I hate these edits as I add things I forget.. See my latest edit, previous code was not clearing the arrays of chars and timings when start button is clicked... thus it would work fine the first time, and the next it would take double (as arrays have words added again) and so on and so fourth... Lol this is the last edit I promise :PBarghest
Will debugggg and see.insted of .trim() have to use something else also to remove '\n'.This thing sucks.Penumbra
@joeyrohan perhaps simply replace all \n with white space(s) ... And put that into the array which will than be used to separate into characters excluding white spaces via trim()Barghest
@joeyrohan See this to remove \n and put space instead.Barghest
@joeyrohan Ah you mean replace..hehe nice trick.Thanks. yup and its pleasure see my edited post fixed to omit \n and white space when calculating letter timing and highlightingBarghest
Sorry 2 say:/ a bug. Take timings as {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000} see the differencePenumbra
For so long i was thinking it's something wrong with my logic:P its cause of "\n."Penumbra
@joeyrohan Lol I cant believe a \n would do that to my code :P Hope I helped a little though.... So is it working now that you found the problem?Barghest
yeah it was the - "" but not " " in your replace() !! GOD!!Penumbra
+1 OMG good eye...I see now how stupid I was.. it should have been replaced with a white space (" ") not ""Barghest
@joeyrohan edited my answer to reflect correct changes you have found... so others who use the code will be copying fully corrected codeBarghest
HeHe had 2 cups of coffee for that.Can start my debugging process now.Penumbra
Ok if i have an array having timings say x.And i am reading a file which have n lines.Then what should i change?When i read a line from a file, i store it words by words in an array (individualWordsToHighlight).And so the timings.Creating a problem about the "\n", also the length of both arrays.: what you suggest?PS: please don't update your answer..I want to do myself..suggest some logic please.Penumbra
I am coping the array in which i have stored the timings.And same for string array.But, in jtp, text is set, such that it contains " " and \n.But, we are highlighting according to words in the array not according to text on jtp.Trying to fix it.So, doing something inside highlightNextWord. But i think have to convert the text in text area to charArr then check it with sp.Penumbra
@joeyrohan thanks for the bounty and glad my answer was of help. Highest Ive earned so far :D... Have you gots the code working after your last comment ahh did at last? What was /is the problem?Barghest
Also reading my answer again Swing Timer is running faster than it should is actually not true its running as expected what we forgot is that the timers actionPerformed will only take as long as the task it has to do thereafter it will schedule a new timer restart at nextExecutionTime=nextLetterTime + currentTime, we thus have to make up for the accumulated (or lost) time.Barghest
Now also i don't know what i was doing wrong.But my other complex way did worked LOL.On jtp is was setting text word by word, and not individualWordsToHighlight array(the way you did in your SSCCE), and timings i was taking from some other array.problem was: it was highlightng -1 word after every line.It was cause of "\n".So was increasing SP (was checking "\n" through a char array(my jtp.text was converted toCharArray))But dint worked.So tried nesting of Scanners (cause scanners dont tell end of a line) and then added " " extra to every last word.WORKED :)Penumbra
delay = (int) (timeToTake - dif); -> this line seems redundant. Because delay is simply equivalent to timeItTook. You can use timeItTook instead of delay.Fatality
B
4

Have you considered java.util.Timer and scheduleAtFixedRate? You will need a little extra work to do stuff on the EDT, but it should fix the issue of accumulated delays.

Bumgarner answered 23/2, 2013 at 15:17 Comment(9)
1+ but is this guaranteed to be more accurate than the Swing Timer? Also, your suggestion would require polling, same as mine.Herra
Firing times are not necessarily more accurate, but scheduleAtFixedRate will always schedule the next execution based on the starting time rather than the previous execution like the Swing Timer does, so delays will not add up. And I don't see a reason to use polling.Bumgarner
@HovercraftFullOfEels yes, its more accurate, +1, indeed will require polling, as same as yours.Penumbra
Nothing is 100% accurate.Do you guarantee scheduleAtFixedRate won't take any + few MS more?Penumbra
There is no guarantee. But if, for example, you want to fire the task every 100ms, you will probably see firing times like: 110, 201, 350, 415, 500, 620, 716, 805, 912, rather than 105, 230, 330, 455, 560, 672, 790, 903, 1014. In the 2nd case (called "fixed delay", which happens with the swing Timer, or util Timer with the schedule method), each firing time is at least 100ms later than the previous firing time.Bumgarner
The fix delay if for just a word.This delay will change for the next word..then next...Penumbra
Yes, the fixed delay is actually a minimum delay. And the fixed rate is more like an average rate. But I think the fixed rate option is what you are looking for.Bumgarner
Oh, if you mean you need to use different delays for different words, then just cancel the TimerTask and schedule a new one.Bumgarner
Still, i'll be needing a mechanism to cut short the over delay...comes to same thing...+ updating GUIPenumbra
T
4

ScheduledExecutorService tends to be more accurate than Swing's Timer, and it offers the benefit of running more than one thread. In particular, if one tasks gets delayed, it does not affect the starting time of the next tasks (to some extent).

Obviously if the tasks take too long on the EDT, this is going to be your limiting factor.

See below a proposed SSCCE based on yours - I have also slightly refactored the startColoring method and split it in several methods. I have also added some "logging" to get a feedback on the timing of the operations. Don't forget to shutdown the executor when you are done or it might prevent your program from exiting.

Each words starts colouring with a slight delay (between 5 and 20ms on my machine), but the delays are not cumulative. You could actually measure the scheduling overhead and adjust accordingly.

public class Reminder {

    private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
            "arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
            "arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
            "arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
            "arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
            "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
    private static final String[] WORDS = TEXT.split("\\s+");
    private JFrame frame;
    private StyledDocument doc;
    private JTextPane textpane;
    private static final int[] TIMES = {100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200, 
                                        100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
                                        100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
                                        100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
                                        100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
                                        100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200, 200};
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    private int currentLetterIndex;
    private long start; //for logging

    public void startColoring() {
        start = System.currentTimeMillis(); //for logging
        int startTime = TIMES[0];
        for (int i = 0; i < WORDS.length; i++) {
            scheduler.schedule(colorWord(i, TIMES[i + 1]), startTime, TimeUnit.MILLISECONDS);
            startTime += TIMES[i+1];
        }
        scheduler.schedule(new Runnable() {

            @Override
            public void run() {
                scheduler.shutdownNow();
            }
        }, startTime, TimeUnit.MILLISECONDS);
    }

    //Color the given word, one letter at a time, for the given duration
    private Runnable colorWord(final int wordIndex, final int duration) {
        final int durationPerLetter = duration / WORDS[wordIndex].length();
        final int wordStartIndex = currentLetterIndex;
        currentLetterIndex += WORDS[wordIndex].length() + 1;
        return new Runnable() {
            @Override
            public void run() {
                System.out.println((System.currentTimeMillis() - start) + " ms - Word: " + WORDS[wordIndex] + "  - duration = " + duration + "ms");
                for (int i = 0; i < WORDS[wordIndex].length(); i++) {
                    scheduler.schedule(colorLetter(wordStartIndex + i), i * durationPerLetter, TimeUnit.MILLISECONDS);
                }
            }
        };
    }

    //Color the letter on the EDT
    private Runnable colorLetter(final int letterIndex) {
        return new Runnable() {
            @Override
            public void run() {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("\t" + (System.currentTimeMillis() - start) + " ms - letter: " + TEXT.charAt(letterIndex));
                        doc.setCharacterAttributes(letterIndex, 1, textpane.getStyle("Red"), true);
                    }
                });
            }
        };
    }

    public void initUI() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        doc = new DefaultStyledDocument();
        textpane = new JTextPane(doc);
        textpane.setText(TEXT);
        javax.swing.text.Style style = textpane.addStyle("Red", null);
        StyleConstants.setForeground(style, Color.RED);
        panel.add(textpane);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]) throws InterruptedException, InvocationTargetException {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                Reminder reminder = new Reminder();
                reminder.initUI();
                reminder.startColoring();
            }
        });
    }
}
Teahouse answered 25/2, 2013 at 17:39 Comment(15)
Will take some time to debug.Thank you for your answer.Hope it works.Penumbra
To my surprise,it works fine as any thing for the first four lines.After that, the speed increases drastically.My many lines are separated by '\n' so in your for loop which schedules next letter, i added: for (int i = 0; i < WORDS[wordIndex].length(); i++) { if(WORDS[wordIndex].equals("\n"))continue; but still, it counts \n as an extra letter after every end of line.Thats not a problem,by doing this, it should get a bit slow..but it runs fast after some time.Ideas?Penumbra
@joeyrohan Note: I have only changed the text, the splitting to also split newlines and the time array - the rest is the same.Teahouse
It works a lot better now.I have tried to run it around 20 times, and after some time,(not 4 lines) it becomes a bit fast.Say around 3 words ahead.Penumbra
@joeyrohan Have you added the delay between lines to your TIMES array?Teahouse
Hey if i am not wrong, is your logic mising a word?I mean its highlighting for i-1 word insted of i !! please check!!Penumbra
@joeyrohan I have assumed that the first entry in your array is the delay before starting - if it is not then yes, everything will be offset.Teahouse
For example: arey starts after 100ms and lasts 400ms, chod starts at 500ms and lasts 300ms etc. In the end there should be no noticeable delay if the TIMES array is accurate.Teahouse
Then its a huge problem.If give 800 ms to chod, but this delay will be for the previous word..ok I will try to set some dummy first value, and see what happensPenumbra
@joeyrohan Just change the counter in the loop of add a 0 at the beginning of the array.Teahouse
That problem is over, but still, the delay is unnoticeable for few lines.But as the no. of lines are increased, so is the delay.Little less than answer by @Hovercraft Full Of Eels .But much more easier +1Penumbra
@joeyrohan Can you give a sample text so I can test it?Teahouse
The text dosn't matter.Its a long procedure.First take the timings for individual words through a key hit.(Can provide you the class) Then store the timings(note: the song is played in background).Once its over, now comes your logic.Display any lyrics, and insted of your array, take the array in which you have the timings.Same,play the song in background.Now you can exactly know where you are going fast or slow.Can provide you the time setter class, if you need.Penumbra
@joeyrohan When you say there is delay, is that based on the difference between actual time and expected time as printed by the sample code I gave? Or do you simply notice a delay vs. music (in which case it could also be due to the times being inaccurate in the first place)?Teahouse
"which case it could also be due to the times being inaccurate in the first place" True.But that delay will pass on.Till now never happened, but assume the delay is such that (between song played and the highlighting process) it delays a word(more than enough).Than that delay should carry on .EG highlighting actual nth word may lead highlighting n-1 word(according to the song) but here, the case is, it grows more than n+5 or n+7...so on.But, its not the case.The song at starting is with the accordance with the words highlighted.Penumbra

© 2022 - 2024 — McMap. All rights reserved.