How to get around “say” command bug when setting volume

applescriptbugcommand linesound-volumetext to speech

Context:

I have an AppleScript .scpt file that speaks the selected text in the System Voice. The selected text is copied to the clipboard, and then the text is spoken via the say command. The script is triggered by keystroke via FastScripts.

Because the macOS Text-to-Speech volume is too loud by default (and there is no global volume control for the Speech), I use AppleScript to set the volume as a fraction of the current volume. I learned how to achieve this from this post:

How to change volume of “say” in AppleScript?

Here is the part of my code that is relevant to my question:

set the clipboard to ("[[volm 0.35]] " & (the clipboard))
set this_say_Pid to do shell script "LANG=en_US.UTF-8 pbpaste -Prefer txt | say > /dev/null 2>&1 & echo $!"

The above code speaks the clipboard text at a volume that is 35% of the current system volume, which is what I desire. (Yes, the audible Speech is really that loud by default.)

The code works well, except I've discovered a bug.


How to reproduce the bug:

To observe this bug, copy the following text to the clipboard, and then run the above code:

 

- This is a test.

The problem-text is difficult to format in Markdown (which is what Stack Exchange uses) because 1) blank newlines are automatically removed, and 2) a hyphen-space combination is converted to a list bullet point. This is why the above text formatting looks wonky; I had to use a blockquote to get around issue one, and I had to add a code block to get around issue two.

Here is the problem-text, literally spelled out in words (if you want to open a text file and recreate the text):

newline
hyphen space Text

Make sure that you include the blank newline when you copy the text onto the clipboard. Make sure that there is a space between the hyphen and the first word of the text. Otherwise, the bug will not surface.

The problem-text must be the very beginning of the clipboard text. That is, if the problem-text is in the middle or at the end of the clipboard text, the bug will not surface.


Additional ways to reproduce the bug:

The hyphen (-) can be substituted for an en dash (–) (⌥ option + -) or an em dash (—) (⌥ option + ⇧ shift + -), and the bug will still surface. As far as I have found in my tests, there are no other punctuation symbols that can be substituted for the hyphen that will trigger the bug.


There is another way to trigger the bug, which does not involve a hyphen or dash. If the text is preceded by at least 2 newline characters, the bug will surface.

For example:

  • If there is 1 newline character, the bug will not surface.

  • If there are 47 newline characters above the text, the bug will surface.

Whether there are any spaces between the 2+ newline characters and the text does not matter; the bug surfaces either way.


The effect of the bug:

  • When running the AppleScript code with the problem text on your clipboard, the volume is not decreased one bit; the clipboard will be spoken at the system volume level.

  • Also, there is a noticeable delay before the text is spoken.


To be clear, while the context of my question is an .scpt file, this is not technically an AppleScript bug. The bug exists when using say directly in Terminal.app. For example, type the following in Terminal to reproduce the bug:

say "[[volm 0.35]]
- This is a test."

Is it possible to remedy this bug, so that, when the clipboard contents begin with any type of problem-text, my script remains sensitive to the custom volume, and a delay is not introduced?

Best Answer

The best is to put '[[volm 0.35]] ' into the shell (not in the clipboard), like this:

set this_say_Pid to do shell script "say '[[volm 0.35]]\\ '$(LANG=en_US.UTF-8 pbpaste -Prefer txt) > /dev/null 2>&1 & echo $!"

Or, if you want to put "[[volm 0.35]] " into the clipboard, use the single quotes, like this:

set the clipboard to ("'[[volm 0.35]] '" & (the clipboard))
set this_say_Pid to do shell script "LANG=en_US.UTF-8 pbpaste -Prefer txt | say > /dev/null 2>&1 & echo $!"