Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Help Required With Program Layout 1

Status
Not open for further replies.

TBaz

Technical User
Jun 6, 2000
65
0
0
ES
I've written quite a few Delphi apps with no real problems, but my latest project has me stumped. I think it's because of too many years using linear programming languages.

It's quite complicated, but I'll try and be as brief as possible...

Essentially, in my project, I create a list of WAV files in a directory and play them all randomly. When all have been played, I jumble them up and play them again. Repeat ad infinitum.

At specific times in the hour, a single WAV file from a different folder must be played - say 10 minutes past each hour (let's call this an 'event').

I have a Timer called every 500ms which updates the on-screen clock and checks to see if an event is due.

A event can be 'important' or 'non-important'.

Important events fire immediately at the time specified, stopping any currently playing WAV file. Non-important events will wait until the currently playing WAV file has finished before it fires.

All of the above is easy enough. The problem I am having is that I'm calling a main loop procedure to play each WAV file on the list using a Repeat..Until. If this main loop is exited, the program ends.

While playing a WAV, I wait until each one has finished before the next one is played using:

// Start WAV Playing
Repeat
Application.ProcessMessages;
Until (PlayNextWAV = True) Or (ProgExit = True);
// Here Is Never Reached

The problem is that after the first WAV is played, even though PlayNextWAV is set to True, the program doesn't drop down to lines immediately following it. In fact, if I put a breakpoint on the Until line, nothing happens and the program doesn't stop, so program control isn't really trapped in the Repeat..Until.

In a nutshell, with languages I'm used to, you always know where the Program Counter is. With Delphi, I'm no longer sure.

So, what's the 'proper' way to do what I'm trying to do?

Thanks in advance...

TB
 
I am going to assume that you are using a TMediaplayer to play the WAV-file.

If that is the case, you will have to use the events OnClick and OnPostClick. Set TMediaPlayer.Wait to False so that OnPostClick doesn't fire until the media has finished playing.

I hope this helps,

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Come to think of it, if you do want to stay linear then maybe (HAVENT TESTED THIS SO KINDA GUESSING HERE) you could use something like:

Code:
while Mediaplayer1.Mode = mpPlaying do
  Application.ProcessMessages;

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Thanks for the input.

First of all, I'm using an audio suite component to play the wav - not media player as I wanted a visual oscilloscope-type display during playback of the wav.

The problem appears to be that if you use the following type of program (pseudo-code):

Note: Listbox 1 contains list of all WAV files in selected directory.

Main Loop Procedure
Repeat
Get Random Entry From Listbox1 (WAV File filename)
If Listbox1 Empty Then Reload all files again
Start Playback Of Current WAV file

// Loop here while wav playing...
Repeat
Application.ProcessMessages;
Until (PlayNextWAV = True) Or (ProgExit = True);
// # Drops to here when current WAV ends
// Check to see if any delayed event on list and play it
// Repeat main loop and play next wav on list
Until ProgExit = True
End Program

In Timer1:

Update on-screen clock
Check current time for event
Save delayed event for playing after current wav... OR
If immediate event, stop current wav and play event
When finished, drop back to Main loop

In Wav playback component:

OnStop event contains code 'PlayNextWAV = True'

So, when wav ends, PlayNextWAV is set to True. Using breakpoints, I've confirmed that this variable IS being set at the point the WAV stops playing, but if I put a breakpoint on the line:

Until (PlayNextWAV = True) Or (ProgExit = True);

..the program never breaks. The only conclusion I have is that program control isn't actually being trapped at the above Repeat..Until loop.

So, basically I need a way to create a loop which plays a list of wav files repeatedly but do nothing while each one is playing - apart from call a timer to update the on-screen clock.

When each wav file ends, another listbox is checked to see if there are any pending events to play and if not, the main loop continues.

I have to admit that this at face value sounds simple enough, though it simply doesn't seem to work,

Quote:

"Come to think of it, if you do want to stay linear then maybe (HAVENT TESTED THIS SO KINDA GUESSING HERE) you could use something like:"

This is actually what I'm doing. :(

What I will try to do is create a very simple project to demonstrate the problem - if that helps.

Cheers...

TBaz
 
just a silly thought, but have you made PlayNextWav a global var rather than a local one?

[bobafett] BobbaFet [bobafett]
Code:
if not Programming = 'Severe Migraine' then
                       ShowMessage('Eureka!');
 
Yes - it's definitely global. :)

TB
 
You need to have a separate thread that handles all of the looping and playing of .wav files. Without one, your timers won't fire, and you can't do any sort of interruption.
 
Quote: You need to have a separate thread that handles all of the looping and playing of .wav files. Without one, your timers won't fire, and you can't do any sort of interruption."

Well the clock display is updating with the correct time, so the Timer is definitely firing.

Or do you mean something else?

TB
 
Ok - I was wrong, Application.ProcessMessages causes TTimer events to fire.

It would be helpful if you could paste your program so we can see exactly how it works. Just make sure you paste it within [ignore]
Code:
[/ignore] tags.
 
Will do.

Tomorrow, I'll knock together a small example which demonstrates the problem I'm having.

Cheers

TB
 
Griffyn really is correct in that you need to think about setting some things in threads. While Delphi is indeed a "linear language", the problem is that what you are wanting to accomplish is not. So you have to interface with the OS so it can see concurrent jobs for the CPU to handle. And while TTimer looks to be concurrent, using those has problems in an environment where there is a lot of processing, like you are dealing with.

That said, I'll post some code later to help you see an example of doing this - those who have followed this forum for a while will know it as the Mediaplayer karaoke problem(s). Remember all things are always linear, but all jobs are necessarily not.

That said, TMediaPlayer is a useful stand-in by description of this problem. While using Wait = true is much more proper and better designed (why doesn't the player you have do this?), this is what the OP is trying:

Code:
...
var
  Form1: TForm1;
  ProgDone: Boolean;
...

MediaPlayer1.FileName := OpenDialog1.FileName;
Mediaplayer1.Open;
MediaPlayer1.Play;
repeat
  Application.ProcessMessages;
  sleep(50);  // remember you got to give the CPU some pause
until (MediaPlayer1.Mode <> mpPlaying) or (ProgDone = true);
MediaPlayer1.Close;
...

While the OP could be mistating what the player package he/she uses requires, this logic works with TMediaPlayer. Now to the interesting part, which illustrates (somewhat) the problem of sticking to the main thread instance when it comes to terminating the app.

Code:
procedure TForm1.FormDestroy(Sender: TObject);
// this never really gets called upon termination if something is playing
begin
  ProgDone := true;
end;

procedure TForm1.Button2Click(Sender: TObject);
// regular application terminate - first line must be present or program will hang
begin
  ProgDone := true;
  Application.Terminate;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
// must be present for closure via X on form.
begin
  ProgDone := true;
end;

end.

Other than that, the correct logic is above - the issue will be corrected either in properly specifying the first boolean in the specification of the player, or making sure that the "done" flag is set properly.

Beyond that, threading really is the proper way to accomplish this task. I'll post some code when I get the chance.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
And here it is (somewhat abbreviated, irrelevant code removed).

Code:
unit mp;
...
type
  TForm1 = class(TForm)
    MediaPlayer1: TMediaPlayer;
    OpenDialog1: TOpenDialog;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Label1: TLabel;
    Edit1: TEdit;
    TrackBar1: TTrackBar;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure ScreenThread;
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
  public
    { Public declarations }
  protected
     procedure UpdateStuff(var WinMsg: TMessage); message WM_USER+1;
  end;
type
  LRecord = record
    offset: integer;
    LyricLine: String;
  end;
var
  Form1: TForm1;
  track_str: string;
  LyricRecord: array[1..100] of LRecord;
  { hopefully there aren't songs this long!}
  LyricPos : integer;
  Playing: Boolean;
  screen_id: DWord;

implementation
...
procedure Tform1.ScreenThread;
  begin
    While Playing do
      begin
        sleep(10);
        PostMessage(Form1.handle, WM_USER+1, 0, 0);
      end;
    EndThread(0);
  end;

procedure TForm1.UpdateStuff(var WinMsg: TMessage);
  begin
    TrackBar1.OnChange := nil;
    Trackbar1.Position := MediaPlayer1.Position;
    TrackBar1.OnChange := TrackBar1Change;
    Label1.Caption := format_str(MediaPlayer1.Position) + track_str;
    if MediaPlayer1.Position >= LyricRecord[LyricPos].Offset then
      begin
        Edit1.Text := LyricRecord[LyricPos].LyricLine;
        inc(LyricPos);
      end;
    if MediaPlayer1.Position = MediaPlayer1.Length then
      begin
        Button2.Enabled := true;
        Button3.Enabled := false;
        LyricPos := 1;
        Playing := false;
      end;
  end;
...
procedure TForm1.Button2Click(Sender: TObject);
begin
  MediaPlayer1.Play;
  Playing := true;
 { TrackBar1.Enabled := false;}
  Button2.Enabled := false;
  Button3.Enabled := true;
  BeginThread(nil, 0, Addr(TForm1.ScreenThread), nil, 0, screen_id);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  MediaPlayer1.Stop;
  Playing := false;
  Button2.Enabled := true;
  TrackBar1.Enabled := true;
  Button3.Enabled := false;
end;
...
procedure TForm1.FormDestroy(Sender: TObject);
begin
  MediaPlayer1.Close;
  { ensure shutting down the thread before the program exits }
  if Playing then
    begin
      Playing := false;
      WaitForSingleObject(screen_id, INFINITE);
    end;
end;

end.

You can also use TThread, but this works as a simple example of threads to allow concurrency.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
@Glenn9999

Many thanks for the input.

To be honest, I've been struggling to create a basic example program showing the problem using TMediaPlayer because I've not used it before in any great depth.

It doesn't have some of the events I'm used to. But I'm getting there... :)

Also, I neglected to mention (because I didn't think it that important at the time) that I'm using two components for playback - a deck 1 and deck 2 if you like.

Whichever deck is currently playing, the other one starts 5 seconds before the first one ends - giving a nice smooth cross-fade effect.

I'll study the code you posted - so thanks for that - and if what you suggest doesn't solve the problem, I'll post my non-working example when it's finished.

Thanks for your help...

TenBaz
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top