- Iterate through a list of
(seconds, text)pairs to find the current lyric - Explain the lookahead technique and why it improves the experience
- Use
show_lyrics()to display text in the lyrics area - Combine everything from all six lessons into a fully working karaoke player
In Lesson 3 you parsed each lyrics file into a list like:
[(4, "[Intro]"), (79, "Well, oh, they might wearβ¦"), (82, "Or knackered Converseβ¦")]
In Lesson 5 you wrote an on_tick handler that fires every 500 ms with the
current elapsed time. Now you will combine those two pieces.
Part 1 β Finding the right lyric line
Given that the song is at elapsed = 81 seconds, which lyric should be
showing? We need to find the last entry in the list whose timestamp is
less than or equal to 81:
lyrics = [
(4, "[Intro]"),
(79, "Well, oh, they might wear classic Reeboks"),
(82, "Or knackered Converse, or track bottoms stucked in socks"),
]
elapsed = 81
current = ""
for timestamp, text in lyrics:
if timestamp <= elapsed:
current = text # keep updating β last one that fits wins
print(current)
# "Well, oh, they might wear classic Reeboks"
# (82 is too late β we are only at 81)
The trick is that we keep updating current as long as
the timestamp fits. By the end of the loop, current holds the
most recent lyric that has been reached.
Part 2 β The lookahead trick
Showing the lyric at the exact moment it is sung is a little too late β the viewer needs a second or two to read the words before singing them!
The solution is simple: pretend you are a few seconds further ahead when you search the list. We call this value the lookahead:
LOOKAHEAD = 2 # show each lyric 2 seconds before it is sung
current = ""
for timestamp, text in lyrics:
if timestamp <= elapsed + LOOKAHEAD:
current = text
With LOOKAHEAD = 2 and elapsed = 80, the search looks for
entries up to second 82. That means the line at second 82 will already appear at
second 80 β two seconds early β giving the singer time to read it.
Try changing LOOKAHEAD to 0, then to 4.
Which feels most natural when you are singing along?
Part 3 β Wrapping it in a function
As always, if you are going to call the same logic more than once, put it in a function:
def get_current_lyric(lyrics, elapsed):
"""Return the lyric that should be showing at 'elapsed' seconds."""
current = ""
for timestamp, text in lyrics:
if timestamp <= elapsed + LOOKAHEAD:
current = text
return current
Part 4 β Displaying the lyric on screen
show_lyrics(text) puts any string into the lyrics area on the Now Playing page.
Call it inside your tick handler:
from pypod import on_tick, show_timestamp, show_progress, show_lyrics
LOOKAHEAD = 2
@on_tick
def update_display(elapsed):
show_timestamp(elapsed)
show_progress(elapsed, 400)
if current_track is not None and current_track.lyrics:
lyric = get_current_lyric(current_track.lyrics, elapsed)
show_lyrics(lyric)
Part 5 β The finished main.py
Here is the complete, finished student code β the result of all six lessons. Every line should feel familiar now.
main.pyfrom pypod import (
Track, add_track,
play, next_track,
on_song_selected, on_track_ended, on_tick,
show_timestamp, show_progress, show_lyrics,
list_music_files, music_path, lyrics_path,
start,
)
# ββ Lesson 3: load timestamped lyrics ββββββββββββββββββββββββββββ
def load_lyrics(path):
entries = []
try:
with open(path) as f:
for line in f:
parts = line.strip().split(None, 1)
if len(parts) == 2:
minutes, seconds = parts[0].split(":")
total = int(minutes) * 60 + int(seconds)
entries.append((total, parts[1]))
except Exception:
pass
return entries
# ββ Lesson 2: parse filenames from the SD card βββββββββββββββββββ
def parse_filename(filename):
stem = filename[:-4]
position = stem.rfind("(")
title = stem[:position].strip()
artist = stem[position + 1 : -1].strip()
return title, artist
for filename in list_music_files():
title, artist = parse_filename(filename)
track = Track(
title = title,
artist = artist,
file_path = music_path(filename),
lyrics = load_lyrics(lyrics_path(title)),
)
add_track(track)
# ββ Lesson 4: play music when a song is selected βββββββββββββββββ
current_track = None
@on_song_selected
def handle_selection(track):
global current_track
current_track = track
play(track)
@on_track_ended
def handle_end():
next_track()
# ββ Lessons 5 & 6: update display and lyrics every tick ββββββββββ
LOOKAHEAD = 2
def get_current_lyric(lyrics, elapsed):
current = ""
for timestamp, text in lyrics:
if timestamp <= elapsed + LOOKAHEAD:
current = text
return current
@on_tick
def update_display(elapsed):
show_timestamp(elapsed)
show_progress(elapsed, 400)
if current_track is not None and current_track.lyrics:
show_lyrics(get_current_lyric(current_track.lyrics, elapsed))
# ββ Always last ββββββββββββββββββββββββββββββββββββββββββββββββββ
start()
- Add
LOOKAHEAD,get_current_lyric, and theshow_lyricscall to yourupdate_displayfunction. - Play a song that has a lyrics file β the lyrics area should now scroll as the song plays.
- Try adjusting
LOOKAHEADto0and4β notice the difference. - Try playing a song with no lyrics file β the app should still work, just showing nothing in the lyrics area.
-
Show a window of upcoming lyrics. Instead of showing just one line,
collect the next three lines after the current one and display them all β
joined with
"\n". (Hint: you will need to track the index of the current lyric, not just the text.) -
Clear the lyrics when a song ends. Register an additional action
inside
handle_endthat callsshow_lyrics("")before moving to the next track. -
Stretch goal: Add a
set_volume()call somewhere in your code to start playback at a specific volume level. Can you read the starting volume from the arc widget before the song begins?
π You have built a karaoke player!
Look back at what you have learned across six sessions:
- Lesson 1 β Classes,
__init__, object instances,self - Lesson 2 β String slicing,
rfind,strip, functions,forloops, tuples - Lesson 3 β File reading,
split(),int(), nested lists,try/except - Lesson 4 β Callbacks, the decorator syntax (
@),global - Lesson 5 β The tick, time formatting with
//and%, string formatting - Lesson 6 β Searching a sorted list, the lookahead trick, putting it all together