Problem Description
How would you write a program the plays a specified
MP3 audio file when a sentence in a TRichEdit rich edit control is clicked?
Here's a little program that answers that question.
Background & Techniques
A program like this might be good for letting a reader learning a foreign
language hear the pronunciation by a native speaker, for example. MP3
files are generally more compact than .Wav files with the same content, so might be a
good choice.
Using my usual "Divide and Conquer" technique of problem solving, I
divided the job into 4 smaller problems.
- Identifying a clicked location. in the text.
- Identifying the position range of each sentence in the text so a
clicked location can be associated with a sentence.
- Associating a specific MP3 file with each sentence..
- Playing the MP3 files.
For the 1st problem, there is no specific OnClick event exit but
either the
OnMouseDown
or
OnMouseUp
exits will do, I chose OnMouseUp. Edit controls like rich edits identify the
selected text with SelStart and SelLength properties.
SelStart is the character position within the entire rich edit text where a mouse button was pressed and
SelStart + SelLength is the character position where the mouse button was
released. For a normal mouse "click", SelLength will
be 0 because the mouse down and mouse up positions are the same.
For the 2nd problem, associating the clicked position with a sentence we
can look at two sub problems. Problem 2A, for we need to
count the sentences and identify the character position within the text
where they start and end. Scanning and building a table based on
sentence ending periods should do that. (This program doesn't handle
cases where text contains numbers with embedded decimal points or
abbreviations with periods, we'll leave that problem for version 2
J.) Once we have the clicked position from
step one, just need to find which of the sentence ranges contains that
clicked position value (problem 2B). Since we can assume that each
sentence begins one position after the previous sentence ends, all we need to
retain in a table is the end positions. The clicked sentence is then
the first end position that is greater than or equal to the clicked
position. For example, if three sentences ended
at character positions 100, 200, and 300, a click on position 150
would belong to sentence #2.
Problems 3 is solved simply by making a list of file names to be played
for each sentence. It is probably best to make the list complete so
that every click plays something. In that case, then sentence number
is simply an index into a string list or array of MP3 file names.
Clicking on the 4th sentence lays the 4th MP3 file, etc.
On to problem 4, actually playing the file. My first attempt was to use the ShellExecute API call to open the file. This
automatically starts the program , associated with that file. For
Windows, that normally is Windows Media Player (WMP) and the problem
is that it opens in whatever default mode is configured. We really
don't need a full blown animated skin WMP opening each time we play a file.
Changing ShellExecute to open WMP.exe directly and passing the file name
as a parameter and using the SW_HIDE parameter, works much better.
If a program using this technique were to be distributed, it would probably
be necessary to have name of the MP3 player to be executed set in a
parameter file that the user could change for their system.
Addendum September 30, 2010: Here's Version 2 which turned out to
be a fairly major rewrite of the original. The sentence start and end
positions within the text are no longer significant. A young
lady wrote asking how to to respond to a user click on a sentence by playing
an MP3 or WAV file identified by text which is enclosed in parentheses
within the sentence. The file identification text is to be
highlighted while the file plays. Clicks are ignored for sentences with
invalid or missing file identification text. For text that might
require future maintenance, tying the file to such special text would be
more practical than tying the file to a character position range as
implemented in the original version.
In this version, a setup page allows the user to maintain the default
text and to load other text files for testing. She may also save, load, and
maintain a "File Association" table which connects the file identification
text with the actual audio file names. Each line of the File Association
table has the format "FileId=Filename" . "FileId" is the text string which
appears within the last set of parentheses in the sentence to trigger the
playback. "Filename" is the name of the file to play.
The code to trigger audio playback performs these tasks:
- Recognize the user click. A RichEdit OnMouseUp event
exit performs the search.
- The SelStart property identifies the location within the text
where the click occurred.
- Uses PosEx function to find the period at the end of the
sentence.
- Scans back character by character looking for a ) character.
If found scan, back from there to the open paren ( and copy the
enclosed text to a TagId string.
- We use the Name=Value string list facility to look for
the TagId as a Name field and verify that the Value
field is the name of an existing file.
- If we have a valid filename, use the RichEdit SelAttributes
property to highlight the TagId text, play Filename
file using a TMediaPlayer control, and then reset the text
attributes back to the DefAttribute values.
I'll retain links to both the original and the revised version downloads
for future reference.
Running/Exploring the Program