Here's my attempt:
At the moment it only prints text to the screen.  I have an
interesting idea about generating a wav file directly, but I'm going
to have trouble getting that done before the summary deadline.  The
script gets the exercise plan from a text file with a simple format.
It doesn't do any error checking of arguments or file format.

Usage:  ruby -d coach.rb weekly_plan.txt
Use the -d switch unless you want to wait 20 minutes for all the output.

-----week3.txt-----
run 90
walk 90
run 180
walk 180
run 90
walk 90
run 180
walk 180
----------

-----coach.rb-----
$CheerThreshold = 6   #decrease to get more random encouragement
$LongThreshold = 120  #minimum time to be considered a "long" run

class Phase
  attr_reader :action, :seconds
  def initialize action, time
    @action = action.downcase
    @seconds = time.to_i
  end
end

class Coach
  def initialize filename
    File.open(filename) {|f|
      @rawdata = f.read.split("\n")
    }
    @duration = 0
    @runs = @longs = @walks = 0
    @encouragometer = 0
    @step = [30,15,10,5,5]
  end

  def coach
    build_timeline
    say summarize(2)
    say start_prompt
    @time = Time.now
    @target_time = @time
    while (phase = @phases.shift)
      update_summary phase
      narrate_phase phase
      if @phases.size > 0
        say transition(@phases[0].action)
        say summarize(rand(2))
      end
    end
    say finish_line
  end

  def narrate_phase phase
    say what_to_do_for(phase)
    @target_time += phase.seconds
    delta = (@target_time - Time.now).to_i
    stepidx = 0
    while (delta > 0)
      stepidx+=1 if delta < @step[stepidx]+1
      wait_time = delta%@step[stepidx]
      wait_time += @step[stepidx] if wait_time <= 0
      wait(wait_time)
      delta = (@target_time - Time.now).to_i
      encourage_maybe
      say whats_left(phase.action,delta) if delta > 0
    end
  end

  def update_summary phase
    @duration -= phase.seconds
    @runs -= 1 if phase.action == 'run'
    @longs -= 1 if phase.action == 'run' and phase.seconds >= $LongThreshold
    @walks -= 1 if phase.action == 'walk'
  end


  def build_timeline
    @phases = @rawdata.map {|command|
      p = Phase.new(*command.split)
      @duration += p.seconds
      @runs += 1 if p.action == 'run'
      @longs += 1 if p.action == 'run' and p.seconds >= $LongThreshold
      @walks += 1 if p.action == 'walk'
      p
    }
  end

  def say s
    puts s
    #todo: replace with speech
  end
  def wait n
    if $DEBUG
      puts "...waiting #{n} seconds..."
      @target_time -= n
    else
      $stdout.flush
      sleep(n)
    end
  end

  def encourage_maybe
    @encouragometer += rand(3)
    if (@encouragometer > $CheerThreshold)
      say cheer
      @encouragometer = 0
    end
  end

  def timesay secs
    secs = secs.to_i
    s = ""
    if secs > 60
      min = secs/60
      secs -= min*60
      s += "#{min} minute"
      s += 's' if min > 1
      s += ' and ' if secs > 0
    end
    if secs > 0
      s += "#{secs} second"
      s += 's' if secs > 1
    end
    s
  end

  # All the phrases should be below this line, not mixed up in the logic
  def what_to_do_for phase
    s = "#{phase.action} for #{timesay(phase.seconds)} \n"
    s += "You are almost done" if @phases.size == 1
    s
  end
  def whats_left act, time
    timestr = timesay(time)
    s = [
      "You have #{timestr} more to #{act}",
      "#{act} for #{timestr} more",
      "only #{timestr} left of #{act}ing",
      "You have #{timestr} more to #{act}",
      "#{timestr} left in this phase",
      "There are #{timestr} until the next activity"
    ]
    s[rand(s.size)]
  end
  def start_prompt
    "are you ready, go!"
  end
  def transition next_act
    s = ["OK, you can #{next_act} now",
         "get ready to #{next_act}"]
    s[rand(s.size)]
  end
  def finish_line
    "you are done, rest now."
  end
  def cheer
    c = ["Keep it up!", "Way to go!", "Good Job!"]
    c[rand(c.size)]
  end
  def summarize degree
    shorts = @runs - @longs
    s = "you have #{timesay(@duration)}"
    if degree > 0
      if degree > 1
        s+= " for "
      else
        s+= " to go and there are " if @runs > 0
      end
      s+="#{@longs} long run" if @longs > 0
      s+="s" if @longs > 1
      s+=" and" if @longs > 0 and shorts > 0 and degree <=1
      s+=" #{shorts} short run" if shorts > 0
      s+="s" if shorts > 1
      if degree >1
        s+=" and" if @longs+shorts > 0
        s+=" #{@walks} walk" if @walks > 0
        s+="s" if @walks > 1
      else
        s+=" left"
      end
    else
      s+= " left to exercise"
    end
    s
  end
end

Coach.new(ARGV[0]||"week3.txt").coach
----------


-Adam