Leo's Countdown Timer Source

By request the countdown time source code. I use this to time breaks on the radio show because they’re hard timed - i.e. the network requires me to start and stop exactly on the second for every break.

First the Racket source (requires DrRacket):

Source code

Here are the binaries:


This is the hot clock illustration referred to in the source code:

And here’s the source if you just want to read it without installing Racket. (Excuse the verbose type comments - that’s the style promoted in HtDP and in the fabulous How To Code classes from Gregor Kiczales at UBC based on HtDP.)

;; Show Time
;; a simple countdown timer for the Tech Guy Radio Show

(require 2htdp/universe)
(require 2htdp/image)

;   Design
; ShowTime is an onscreen prompt for radio show hosts.
; The display is based on the traditional "hotclock" used by radio
; stations. The clock represents an hour of programming time with a
; circle divided into wedges representing various kinds of programming
; elements.
; The wedges can represent any arbitrary number of programming elements,
; but from the host's point of view the only things that matter are
; when to start talking (gray on the clock above), and when to stop
; (green and white on the clock above).
; This program features a large timer that counts down the number of
; seconds until the next transition: break to show and show to break.
; For quick comprehension the program uses four colored backgrounds for
; the timer:
; Green - in show, more than two minutes remain
; Yellow - in show, segment will end in under 2 minutes
; Red - in show, segment will end in under 30 seconds
; Blue - in break (ads, promos, news, etc.)
; The transition times for my Tech Guy Radio show are as follows (in seconds):
; Break1 0-344
;  Show1 345-899
; Break2 900-1164
;  Show2 1165-1679
; Break3 1680-1984
;  Show3 1985->2519
; Break4 2520->2784
;  Show4 2785-3409
; Break5 3410-3599

;; --------------------
;; Constants
;; --------------------

; types of segments
(define BREAK "BREAK")
(define SHOW "SHOW")

(define WRAP-TIME 120) ; <2 minutes remaining, yellow background
(define OUT-TIME 30)   ; <30 seconds remaining, red background

(define SECS-IN-HOUR (* 60 60)) ; number of seconds in an hour (0-3600]

;; Image constants

; window info
(define WIDTH 750) ; width of window
(define HEIGHT 300) ; height of window
(define WINDOW-TITLE "Showtime") ; Window name

; countdown text
(define TEXT-SIZE 255)
(define TEXT-COLOR "black")
(define FACE "Fira Code")
(define FAMILY 'default)
(define STYLE 'normal)
(define WEIGHT 'bold)
(define UNDERLINE? #false)
(define TEXT-X (/ WIDTH 2)) ; where to place the text in window (center)
(define TEXT-Y (/ HEIGHT 2))

; background colors
(define BREAK-COLOR "lightblue")
(define SHOW-COLOR "green")
(define WRAP-COLOR "yellow")
(define OUT-COLOR "red")

;; --------------------
;; Data
;; --------------------

(define-struct segment (end type color))
;; Segment is (make-segment Integer String String)
;; interp. a segment of a show clock defined
;; by segment end time in seconds, type, and background color
(define SEG1 (make-segment 350 BREAK BREAK-COLOR))
(define SEG2 (make-segment 915 SHOW SHOW-COLOR))
(define SEG3 (make-segment 1165 BREAK BREAK-COLOR))
(define SEG4 (make-segment 1685 SHOW SHOW-COLOR))
(define SEG5 (make-segment 1970 BREAK BREAK-COLOR))
(define SEG6 (make-segment 2520 SHOW SHOW-COLOR))
(define SEG7 (make-segment 2785 BREAK BREAK-COLOR))
(define SEG8 (make-segment 3415 SHOW SHOW-COLOR))
(define SEG9 (make-segment 3599 BREAK BREAK-COLOR))
(define (fn-for-Segment seg)
  (... (segment-end seg)         ; Integer(0-3600]
       (segment-type seg)        ; String
       (segment-color seg)))     ; String

;; Hotclock is one of:
;; - empty
;; - (cons segment (ListOfSegment)
;; interp. a list of Segments
;; assume segments are sorted by increasing end time
(define clock (list SEG1 SEG2 SEG3 SEG4 SEG5 SEG6 SEG7 SEG8 SEG9))
(define (fn-for-hotclock los)
  (cond [(empty? los) (...)]                        ;BASE CASE
        [else (... (first los)                      ;Segment
                   (fn-for-hotclock (rest los)))])) ;NATURAL RECURSION

(define-struct displayState (bgd countdown))
;; DisplayState is (make-displayState String Integer)
;; interp. the current state of the world reflecting:
;; background color and countdown clock time
(define DS0 (make-displayState "black" 0))
(define DS1 (make-displayState BREAK-COLOR 200))
(define (fn-for-displayState ds)
  (... (displayState-bgd ds)           ; Current background color
       (displayState-countdown ds)))   ; Current countdown time

;; --------------------
;; Functions
;; --------------------

;; displayState -> displayState
;; the event handler for ShowTime
;; called every 1 second
(define (main displayState)
  (big-bang displayState         ; infinite loop, passing displayState to each handler in turn (provided by Racket)
    (on-tick updateState 1)      ; tock updates the displayState once a second
    (to-draw renderState)        ; draw the current displayState on the screen
    (name WINDOW-TITLE)))

;; displayState -> displayState
;; creates next display state 
(define (updateState displayState)
  (local [(define sec (modulo (current-seconds) SECS-IN-HOUR))  ; current second in hour (0-3600]
          (define seg (current-segment sec clock))]             ; current segment

    (make-displayState (get-color sec seg)                      ; current background color
                       (get-countdown sec seg))))               ; seconds left until next transition
;; displayState -> Image 
;; draws the current show state
; (define (renderState displayState) empty-image) ; stub
(define (renderState displayState)
   (text/font (secs->min:sec (displayState-countdown displayState))
   (empty-scene WIDTH HEIGHT (displayState-bgd displayState))))

;; Integer Hotclock -> Segment 
;; given the current time and a hotclock produces the current segment
(check-expect (current-segment 300 clock) SEG1)
(check-expect (current-segment 1600 clock) SEG4)
(check-expect (current-segment 2700 clock) SEG7)

; (define (current-segment sec clock) SEG1) ; stub
(define (current-segment sec clock)
  (cond [(empty? clock) (error (string-append "current-segment: time out of range " (number->string sec)))]                        
        [else (if (<= sec (segment-end (first clock)))    
                  (first clock) 
                  (current-segment sec (rest clock)))]))
;; Integer Segment -> String
;; given the time and current segment, produces the current background color
(check-expect (get-color 200 SEG1) BREAK-COLOR)
(check-expect (get-color 230 SEG1) BREAK-COLOR)
(check-expect (get-color 340 SEG1) BREAK-COLOR)
(check-expect (get-color 600 SEG2) SHOW-COLOR)
(check-expect (get-color 815 SEG2) WRAP-COLOR)
(check-expect (get-color 890 SEG2) OUT-COLOR)

; (define (get-color sec seg) BREAK-COLOR) ; stub
(define (get-color sec seg)
  (cond [(> sec (segment-end seg)) (error "Get-color: Countdown and Segment mismatch")]
        [(equal? (segment-type seg) BREAK) BREAK-COLOR]         ; we're in a break
        [(< sec (- (segment-end seg) WRAP-TIME)) SHOW-COLOR]    ; in a show, but not wrapping
        [(< sec (- (segment-end seg) OUT-TIME)) WRAP-COLOR]     ; wrapping
        [else OUT-COLOR]))                                      ; has to be last 30 seconds of segment

;; Integer Segment -> Integer
;; produces the current countdown given current time and segment
;; (we need this function to handle the exceptional case at the end of the hour
;; we want the countdown to include length of break segments at end of previous hour plus beginning of next hour
(check-expect (get-countdown 200 SEG1) (- (segment-end SEG1) 200))
(check-expect (get-countdown 3500 SEG9) (+ (- (segment-end SEG9) 3500) (segment-end SEG1)))

; (define (get-countdown sec seg) 0) ; stub
(define (get-countdown sec seg)
  (+ (- (segment-end seg) sec)
     (if (equal? seg (first (reverse clock)))     ; in the last segment of the hour?
         (segment-end (first clock))              ; if so add the seconds in the first seg of the next hour
         0)))                                     ; otherwise make no adjustment

;; Integer -> String
;; produces a string in the form of min:sec from seconds
(check-expect (secs->min:sec 120) "2:00")
(check-expect (secs->min:sec 494) "8:14")
(check-expect (secs->min:sec 30) ":30")
(check-expect (secs->min:sec 60) "1:00")
; (define (secs->min:sec secs) "1:00") ;stub
(define (secs->min:sec secs)
  (local [(define m (quotient secs 60))
          (define s (modulo secs 60))]
  (format "~a:~a"
          (if (zero? m)
          (if (< s 10)
              (string-append "0" (number->string s))
(main DS0)

@Leo I heard you say that the outro music right before a CM break has a louder “bong” (for lack of a better term) that covers up a non-audible sound that tell the local stations to play their local content.

Not to be a wise guy, but the sound can’t be heard by humans, does it need to be covered up by that “bong”?

The bong (or stab as it’s called) is for me! so I know when to stop talking.

Thanks for clarifying. I must have misunderstood you. Apologies.

I do like listening to those behind the scenes kind of info on how the show is produced.


thats cool, for folks who like a more simple solution use javascript, i like this one, you can tweak and build uponit the way you want.