Author Topic: 60 Or Less Pasted Sentences At-Once Text To Speech Reader  (Read 4244 times)

0 Members and 1 Guest are viewing this topic.

This topic contains a post which is marked as Best Answer. Press here if you would like to see it.

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
60 Or Less Pasted Sentences At-Once Text To Speech Reader
« on: January 25, 2022, 08:06:32 pm »
Using B+'s and Steve's text-to-speech code, I added it with Steve's text pasting code and now we got a text-to-speech reader that reads almost any text that you can paste. There is a 60 sentence, or so, limit, because I didn't want people to accidentally copy a whole book and have it read all at once. There really isn't a way to stop the speech either because it uses Windows Power Shell, besides turning your computer off. This is probably a Windows-only app. I tested it with about 58 sentences from the Bible and it worked fine. I did run into a small problem that I fixed, which was that it stopped working altogether when it saw a quotation mark. So before it runs, I have it scan all of the text and delete anything before or after the text and number ASCII code numbers. Between the numbers and text there's a couple of punctuation marks and a few I added like the question mark, period, and comma, and a couple others. I haven't tried every possibility but it should work OK. Feel free to play around with it. Thank you to B+ and Steve (SMCNeil) for their very hard work. Hopefully people can make use of this app. Have fun!

Code: QB64: [Select]
  1. 'Thanks to B+ for the audio code and Steve (SMCNeil) for the paste code!
  2. _Title "Text Reader"
  3. Screen _NewImage(800, 600, 32)
  4. start:
  5. Print "CTRL-V to paste text and then press Enter to read through your speakers (Around a 60 sentence limit at a time.): "
  6. ExtendedInput information$
  7. Dim t(3000) As String
  8. For x = 1 To Len(information$)
  9.     If Asc(Mid$(information$, x, x)) < 40 Or Asc(Mid$(information$, x, x)) > 122 Then Mid$(information$, x, x) = " "
  10. t(i) = information$
  11. If Len(information$) > 10000 Then Print: Print "Too much text to read at once. Try again with less.": GoTo start:
  12. Print t(i)
  13. speak t(i)
  14. Print "Again (Y/N)";
  15.     ag$ = InKey$
  16.     If ag$ = "Y" Or ag$ = "y" Then Cls: GoTo start:
  17.     If ag$ = "N" Or ag$ = "n" Then End
  18.  
  19. Sub speak (message As String)
  20.     Shell _Hide "Powershell -Command " + Chr$(34) + "Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('" + message + "');" + Chr$(34)
  21.  
  22. Sub ExtendedInput (out$)
  23.     PCopy 0, 1
  24.     A = _AutoDisplay: X = Pos(0): Y = CsrLin
  25.     CP = 0: OldCP = 0 'Cursor Position
  26.     _KeyClear
  27.     Do
  28.         PCopy 1, 0
  29.         If _KeyDown(100307) Or _KeyDown(100308) Then AltDown = -1 Else AltDown = 0
  30.         k = _KeyHit
  31.         If AltDown Then
  32.             Select Case k 'ignore all keypresses except ALT-number presses
  33.                 Case 48 TO 57: AltWasDown = -1: alt$ = alt$ + Chr$(k)
  34.             End Select
  35.         Else
  36.             Select Case k 'without alt, add any keypresses to our input
  37.                 Case 8
  38.                     oldin$ = in$
  39.                     If CP > 0 Then OldCP = CP: CP = CP - 1
  40.                     in$ = Left$(in$, CP) + Mid$(in$, CP + 2) 'backspace to erase input
  41.                 Case 9
  42.                     oldin$ = in$
  43.                     in$ = Left$(in$, CP) + Space$(4) + Mid$(in$, CP + 1) 'four spaces for any TAB entered
  44.                     OldCP = CP
  45.                     CP = CP + 4
  46.                 Case 32 TO 128
  47.                     If _KeyDown(100305) Or _KeyDown(100306) Then
  48.                         If k = 118 Or k = 86 Then
  49.                             oldin$ = in$
  50.                             in$ = Left$(in$, CP) + _Clipboard$ + Mid$(in$, CP + 1) 'ctrl-v paste
  51.                             'CTRL-V leaves cursor in position before the paste, without moving it after.
  52.                             'Feel free to modify that behavior here, if you want it to move to after the paste.
  53.                             'CP = CP + LEN(_CLIPBOARD$)
  54.                         End If
  55.                         If k = 122 Or k = 90 Then Swap in$, oldin$: Swap OldCP, CP 'ctrl-z undo
  56.                     Else
  57.                         oldin$ = in$
  58.                         in$ = Left$(in$, CP) + Chr$(k) + Mid$(in$, CP + 1) 'add input to our string
  59.                         OldCP = CP
  60.                         CP = CP + 1
  61.                     End If
  62.                 Case 18176 'Home
  63.                     CP = 0
  64.                 Case 20224 'End
  65.                     CP = Len(in$)
  66.                 Case 21248 'Delete
  67.                     oldin$ = in$
  68.                     in$ = Left$(in$, CP) + Mid$(in$, CP + 2)
  69.                 Case 19200 'Left
  70.                     CP = CP - 1
  71.                     If CP < 0 Then CP = 0
  72.                 Case 19712 'Right
  73.                     CP = CP + 1
  74.                     If CP > Len(in$) Then CP = Len(in$)
  75.             End Select
  76.         End If
  77.         alt$ = Right$(alt$, 3)
  78.         If AltWasDown = -1 And AltDown = 0 Then
  79.             v = Val(alt$)
  80.             If v >= 0 And v <= 255 Then in$ = in$ + Chr$(v)
  81.             alt$ = "": AltWasDown = 0
  82.         End If
  83.         blink = (blink + 1) Mod 30
  84.         Locate Y, X
  85.         Print Left$(in$, CP);
  86.         If blink \ 15 Then Print " "; Else Print "_";
  87.         Print Mid$(in$, CP + 1)
  88.  
  89.         _Display
  90.         _Limit 30
  91.     Loop Until k = 13
  92.  
  93.     PCopy 1, 0
  94.     Locate Y, X: Print in$
  95.     out$ = in$
  96.  
  97.  
« Last Edit: January 26, 2022, 12:15:19 am by SierraKen »

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #1 on: January 25, 2022, 08:23:03 pm »
By the way, I just guessed on the DIM. I don't know if I needed so much of the 3000, but I figured the memory needed to come from somewhere. Since I don't know the SUBs, I wouldn't know what they mean though. So I just guessed. It might just need 100 for all I know.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #2 on: January 25, 2022, 08:28:40 pm »
I'm fairy certain that you can pause or stop the text to speech engine anytime you want, without having to turn the power off.  It's just a call to System.Speech.Synthesis.Stop (Pause), if I remember correctly.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #3 on: January 25, 2022, 09:36:51 pm »
Hey @SierraKen

That is a great combo of Steve's code helpers.

I think Steve made his code filter out the quotes you mentioned Plus you can use different voices, so hey maybe take a script for a play, modify it to call different voices including a narrator to fill in the scenes with words. see this: https://qb64forum.alephc.xyz/index.php?topic=4598.msg140073#msg140073

That would also keep speeches shorter and the user could press escape in between lines if they want to quit.

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #4 on: January 26, 2022, 01:16:17 pm »
I tried for 2 hours last night to add the Stop and Pause codes but nothing seemed to work. So I either have the codes wrong or doing something wrong. I deleted what I made but what I tried was making a separate SUB for the close code and putting the command to the SUB right after the command to play the audio. It seemed like it just wouldn't get to that other SUB command. Just now I tried a different way with still no luck. It seems like the way BASIC goes from one thing to another, it doesn't flow away from the audio text when it plays, it stays there until it's done. Unless I have the wrong code. I looked online for codes last night but couldn't really find anything besides C language stuff in which I am not familiar with. Can anyone give me any ideas? Feel free to fix this also if you have the time and want to. Thanks. 

Code: QB64: [Select]
  1. 'Thanks to B+ for the audio code and Steve (SMCNeill) for the paste code!
  2. _Title "Text Reader"
  3. Screen _NewImage(800, 600, 32)
  4. start:
  5. Print "CTRL-V to paste text and then press Enter to read through your speakers (Around a 60 sentence limit at a time.): "
  6. ExtendedInput information$
  7. Dim t(3000) As String
  8. For x = 1 To Len(information$)
  9.     If Asc(Mid$(information$, x, x)) < 40 Or Asc(Mid$(information$, x, x)) > 122 Then Mid$(information$, x, x) = " "
  10. t(i) = information$
  11. ll = Len(information$)
  12. If ll > 10000 Then Print: Print "Too much text to read at once. Try again with less.": GoTo start:
  13. Print t(i)
  14.  
  15. speak t(i)
  16.     a$ = InKey$
  17.     If a$ <> "" Then closing t(i)
  18.     Locate 1, 1
  19.     Print "TEST"
  20. Loop While a$ <> ""
  21.  
  22. again:
  23. Print "Again (Y/N)";
  24.     ag$ = InKey$
  25.     If ag$ = "Y" Or ag$ = "y" Then Cls: GoTo start:
  26.     If ag$ = "N" Or ag$ = "n" Then End
  27.  
  28. Sub speak (message As String)
  29.     Shell _Hide "Powershell -Command " + Chr$(34) + "Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('" + message + "');" + Chr$(34)
  30.  
  31. Sub closing (message As String)
  32.     Locate 1, 1: Print "12345"
  33.     Shell _Hide "Powershell -Command " + Chr$(34) + "Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.Stop)('" + message + "');" + Chr$(34)
  34.  
  35. Sub ExtendedInput (out$)
  36.     PCopy 0, 1
  37.     A = _AutoDisplay: X = Pos(0): Y = CsrLin
  38.     CP = 0: OldCP = 0 'Cursor Position
  39.     _KeyClear
  40.     Do
  41.         PCopy 1, 0
  42.         If _KeyDown(100307) Or _KeyDown(100308) Then AltDown = -1 Else AltDown = 0
  43.         k = _KeyHit
  44.         If AltDown Then
  45.             Select Case k 'ignore all keypresses except ALT-number presses
  46.                 Case 48 TO 57: AltWasDown = -1: alt$ = alt$ + Chr$(k)
  47.             End Select
  48.         Else
  49.             Select Case k 'without alt, add any keypresses to our input
  50.                 Case 8
  51.                     oldin$ = in$
  52.                     If CP > 0 Then OldCP = CP: CP = CP - 1
  53.                     in$ = Left$(in$, CP) + Mid$(in$, CP + 2) 'backspace to erase input
  54.                 Case 9
  55.                     oldin$ = in$
  56.                     in$ = Left$(in$, CP) + Space$(4) + Mid$(in$, CP + 1) 'four spaces for any TAB entered
  57.                     OldCP = CP
  58.                     CP = CP + 4
  59.                 Case 32 TO 128
  60.                     If _KeyDown(100305) Or _KeyDown(100306) Then
  61.                         If k = 118 Or k = 86 Then
  62.                             oldin$ = in$
  63.                             in$ = Left$(in$, CP) + _Clipboard$ + Mid$(in$, CP + 1) 'ctrl-v paste
  64.                             'CTRL-V leaves cursor in position before the paste, without moving it after.
  65.                             'Feel free to modify that behavior here, if you want it to move to after the paste.
  66.                             'CP = CP + LEN(_CLIPBOARD$)
  67.                         End If
  68.                         If k = 122 Or k = 90 Then Swap in$, oldin$: Swap OldCP, CP 'ctrl-z undo
  69.                     Else
  70.                         oldin$ = in$
  71.                         in$ = Left$(in$, CP) + Chr$(k) + Mid$(in$, CP + 1) 'add input to our string
  72.                         OldCP = CP
  73.                         CP = CP + 1
  74.                     End If
  75.                 Case 18176 'Home
  76.                     CP = 0
  77.                 Case 20224 'End
  78.                     CP = Len(in$)
  79.                 Case 21248 'Delete
  80.                     oldin$ = in$
  81.                     in$ = Left$(in$, CP) + Mid$(in$, CP + 2)
  82.                 Case 19200 'Left
  83.                     CP = CP - 1
  84.                     If CP < 0 Then CP = 0
  85.                 Case 19712 'Right
  86.                     CP = CP + 1
  87.                     If CP > Len(in$) Then CP = Len(in$)
  88.             End Select
  89.         End If
  90.         alt$ = Right$(alt$, 3)
  91.         If AltWasDown = -1 And AltDown = 0 Then
  92.             v = Val(alt$)
  93.             If v >= 0 And v <= 255 Then in$ = in$ + Chr$(v)
  94.             alt$ = "": AltWasDown = 0
  95.         End If
  96.         blink = (blink + 1) Mod 30
  97.         Locate Y, X
  98.         Print Left$(in$, CP);
  99.         If blink \ 15 Then Print " "; Else Print "_";
  100.         Print Mid$(in$, CP + 1)
  101.  
  102.         _Display
  103.         _Limit 30
  104.     Loop Until k = 13
  105.  
  106.     PCopy 1, 0
  107.     Locate Y, X: Print in$
  108.     out$ = in$
  109.  
  110.  

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #5 on: January 26, 2022, 02:40:12 pm »
First thing you'd have to change is the SHELL _HIDE statements.  As they exist, program execution waits until the SHELL call is completed before moving on.

You'd need a SHELL _DONTWAIT to let execution run in the background and not stop program flow.

Second thing you'd probably want is to swap your powershell routine to assign a return variable to store the handle of your sound process.  From just the line reading "New Object System.Speech...." to "$VariableName = New Object System.Speech....".  Reference everything via that variable from that point on.  (See my latest version for an example.)

Now that you have a variable to hold the process, and you're no lnger waiting for your SHELL call to terminate before continuing program execution, you should be able to SHELL _DONTWAIT and:

$VariableName.Pause

and

$VariableName.Resume
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Marked as best answer by SierraKen on January 26, 2022, 11:37:06 am

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #6 on: January 26, 2022, 03:27:30 pm »
Well I overhauled the code to see what kind of line delimiter was used for lines / sentences.

Knowing this I split the glob of text into lines and read each line individually, and then looked to see if user pressed escape to quit the reading, still might get an extra line read but not the whole long blurb.

I have commented most of crucial steps to say what I was doing and I used Steve's Speak version that already had characters removed and easy enough to add to the remove$ string new ones you find.

Code: QB64: [Select]
  1. Option _Explicit ' no typos please!
  2.  
  3. ' b+ modification of Ken code, replace with Steve's Speak sub because Steve's Speak sub removes stumbling characters.
  4.  
  5. _Title "Text Reader"
  6. Screen _NewImage(800, 600, 32)
  7.  
  8. Dim information$, nl$, sentence$, again$   ' nl$ is common variable name for Next Line signal with Control Char.
  9. Dim As Long textIndex, nSentences
  10.  
  11. start:
  12. Print "CTRL-V to paste text and then press Enter to read through your speakers: "
  13. ExtendedInput information$
  14.  
  15.  
  16. ' Kem here we figure out what separtes lines in big copy/paste to input 3 choices
  17. ' now figure out if information has line delimiters either Chr$(10) or Chr$(13) or both?
  18. If InStr(information$, Chr$(10)) Then nl$ = Chr$(10) ' lines from typical WP
  19. If InStr(information$, Chr$(13)) Then nl$ = Chr$(13) ' rarer
  20. If InStr(information$, Chr$(13) + Chr$(10)) Then nl$ = Chr$(13) + Chr$(10) ' lines from bas source
  21.  
  22. ' test with copy of first 2 lines here! nl$ = Chr$(13) + Chr$(10 for Basic IDE
  23. 'Print Len(nl$), Asc(nl$, 1)
  24. 'End
  25.  
  26. ' Ken now we know how to split that massive input into single lines
  27. ReDim text(1 To 1) As String ' set up dynamice array with REDIM instead on DIM
  28.  
  29. 'now we can split out all the lines in info to single lines
  30. Split information$, nl$, text() ' splits to an array of text strings, single sentences usually
  31.  
  32. ' Now that we have out sentences, we will read them one at a time until we finish or the user says Enough!
  33. ' How many sentences do we have?
  34. nSentences = UBound(text)
  35.  
  36. ' going through the sentences, press escape to exit th eread
  37. For textIndex = 1 To nSentences
  38.     sentence$ = text(textIndex)
  39.     speak sentence$, 0, 1
  40.     If _KeyHit = 27 Then Exit For ' if user pressed the escape key then bug out
  41.  
  42. 'go again?
  43. _KeyClear ' for some reason regualar input is not working????
  44. Input "Would you like to do another reading, y for yes "; again$
  45. If again$ = "y" Then GoTo start
  46.  
  47.  
  48.  
  49. Sub speak (text As String, Speaker As Integer, Speed)
  50.     Dim message As String, remove$, out$
  51.     Dim As Long i, j
  52.     message = text
  53.     'some symbols and such can't be used with Powershell like this, as they're command symbols
  54.     'we need to strip them out of our text.  (Like apostrophes!)
  55.     remove$ = "'" + Chr$(34) 'add to remove$ here, if more symbols need to be removed as future testing showcases problems
  56.     For j = 1 To Len(remove$)
  57.         Do
  58.             i = InStr(message, Mid$(remove$, j, 1))
  59.             If i Then message = Left$(message, i - 1) + Mid$(message, i + 1)
  60.         Loop Until i = 0
  61.     Next
  62.     out$ = "Powershell -Command " + Chr$(34)
  63.     out$ = out$ + "Add-Type -AssemblyName System.Speech; "
  64.     out$ = out$ + "$Speech = New-Object System.Speech.Synthesis.SpeechSynthesizer; "
  65.     If Speaker = 0 Then out$ = out$ + "$Speech.SelectVoice('Microsoft David Desktop'); "
  66.     If Speaker = 1 Then out$ = out$ + "$Speech.SelectVoice('Microsoft Zira Desktop'); "
  67.     If Speed Then out$ = out$ + "$Speech.Rate =" + Str$(Speed) + "; "
  68.     out$ = out$ + "$Speech.Speak('" + message + "');" + Chr$(34)
  69.     Shell _Hide out$
  70.  
  71. Sub ExtendedInput (out$)
  72.     Dim alt$, oldIn$, in$
  73.     Dim As Long A, X, Y, CP, OldCP, AltDown, k, AltWasDown, v, blink
  74.     PCopy 0, 1
  75.     A = _AutoDisplay: X = Pos(0): Y = CsrLin
  76.     CP = 0: OldCP = 0 'Cursor Position
  77.     _KeyClear
  78.     Do
  79.         PCopy 1, 0
  80.         If _KeyDown(100307) Or _KeyDown(100308) Then AltDown = -1 Else AltDown = 0
  81.         k = _KeyHit
  82.         If AltDown Then
  83.             Select Case k 'ignore all keypresses except ALT-number presses
  84.                 Case 48 TO 57: AltWasDown = -1: alt$ = alt$ + Chr$(k)
  85.             End Select
  86.         Else
  87.             Select Case k 'without alt, add any keypresses to our input
  88.                 Case 8
  89.                     oldIn$ = in$
  90.                     If CP > 0 Then OldCP = CP: CP = CP - 1
  91.                     in$ = Left$(in$, CP) + Mid$(in$, CP + 2) 'backspace to erase input
  92.                 Case 9
  93.                     oldIn$ = in$
  94.                     in$ = Left$(in$, CP) + Space$(4) + Mid$(in$, CP + 1) 'four spaces for any TAB entered
  95.                     OldCP = CP
  96.                     CP = CP + 4
  97.                 Case 32 TO 128
  98.                     If _KeyDown(100305) Or _KeyDown(100306) Then
  99.                         If k = 118 Or k = 86 Then
  100.                             oldIn$ = in$
  101.                             in$ = Left$(in$, CP) + _Clipboard$ + Mid$(in$, CP + 1) 'ctrl-v paste
  102.                             'CTRL-V leaves cursor in position before the paste, without moving it after.
  103.                             'Feel free to modify that behavior here, if you want it to move to after the paste.
  104.                             'CP = CP + LEN(_CLIPBOARD$)
  105.                         End If
  106.                         If k = 122 Or k = 90 Then Swap in$, oldIn$: Swap OldCP, CP 'ctrl-z undo
  107.                     Else
  108.                         oldIn$ = in$
  109.                         in$ = Left$(in$, CP) + Chr$(k) + Mid$(in$, CP + 1) 'add input to our string
  110.                         OldCP = CP
  111.                         CP = CP + 1
  112.                     End If
  113.                 Case 18176 'Home
  114.                     CP = 0
  115.                 Case 20224 'End
  116.                     CP = Len(in$)
  117.                 Case 21248 'Delete
  118.                     oldIn$ = in$
  119.                     in$ = Left$(in$, CP) + Mid$(in$, CP + 2)
  120.                 Case 19200 'Left
  121.                     CP = CP - 1
  122.                     If CP < 0 Then CP = 0
  123.                 Case 19712 'Right
  124.                     CP = CP + 1
  125.                     If CP > Len(in$) Then CP = Len(in$)
  126.             End Select
  127.         End If
  128.         alt$ = Right$(alt$, 3)
  129.         If AltWasDown = -1 And AltDown = 0 Then
  130.             v = Val(alt$)
  131.             If v >= 0 And v <= 255 Then in$ = in$ + Chr$(v)
  132.             alt$ = "": AltWasDown = 0
  133.         End If
  134.         blink = (blink + 1) Mod 30
  135.         Locate Y, X
  136.         Print Left$(in$, CP);
  137.         If blink \ 15 Then Print " "; Else Print "_";
  138.         Print Mid$(in$, CP + 1)
  139.  
  140.         _Display
  141.         _Limit 30
  142.     Loop Until k = 13
  143.  
  144.     PCopy 1, 0
  145.     Locate Y, X: Print in$
  146.     out$ = in$
  147.  
  148. Sub Split (SplitMeString As String, delim As String, loadMeArray() As String)
  149.     Dim curpos As Long, arrpos As Long, LD As Long, dpos As Long 'fix use the Lbound the array already has
  150.     curpos = 1: arrpos = LBound(loadMeArray): LD = Len(delim)
  151.     dpos = InStr(curpos, SplitMeString, delim)
  152.     Do Until dpos = 0
  153.         loadMeArray(arrpos) = Mid$(SplitMeString, curpos, dpos - curpos)
  154.         arrpos = arrpos + 1
  155.         If arrpos > UBound(loadMeArray) Then ReDim _Preserve loadMeArray(LBound(loadMeArray) To UBound(loadMeArray) + 1000) As String
  156.         curpos = dpos + LD
  157.         dpos = InStr(curpos, SplitMeString, delim)
  158.     Loop
  159.     loadMeArray(arrpos) = Mid$(SplitMeString, curpos)
  160.     ReDim _Preserve loadMeArray(LBound(loadMeArray) To arrpos) As String 'get the ubound correct
  161.  
  162.  
  163.  

No limit to amount of lines you want to copy / paste in except memory.

Offline SierraKen

  • Forum Resident
  • Posts: 1454
    • View Profile
Re: 60 Or Less Pasted Sentences At-Once Text To Speech Reader
« Reply #7 on: January 26, 2022, 04:35:34 pm »
WOW I sorta see what you did there B+. You broke up the text to different lines and used a KeyHit command to stop it between lines. Perfect!!! I don't know anything about ReDim and a couple other things you did there, but I am really happy you fixed it! Thank you! Amazing that it has no limit either, besides memory limitations. This app can be used with books, articles, websites, whatever you need to be read to and can copy text from.
Thanks also to Steve, I tried your ways again today but for some reason the Stop and Pause commands just won't work for me. I will try to remember the _DONTWAIT command though, that is very important.