Introduction
A break has a time limit. Your music library contains complete recordings with a preference score, a duration in seconds and a stable track ID. You will make a reusable C++ program that considers tracks in a public order, keeps each recording that fits, and saves an actual playlist you can play.
You need basic terminal navigation and C++ strings, classes, vector, file streams and exceptions. We will introduce the const references, comparison function and relative paths used here. The parser, original synthetic audio and browser player are prepared. You will write the selection and export functions, not an audio converter or an optimal packing algorithm.
Use the Terminal Tab for commands and the nano text editor, and the Player Tab for listening. You will learn how to open, change and save a small part of a source file before compiling it. Both use this one environment. Edit only selection.cpp and export.cpp; create playlists directly in outputs/. Keep the catalog, supplied framework and original outputs/audio/ files unchanged. These original synthetic compositions contain no outside samples.
Fit Complete Tracks into a Break
In this step, you will hear a normal example, then use a remaining-time counter to choose complete recordings. Start in Player. Select example-120.m3u8, click Load playlist, then Play 1. The list contains Morning Light (50 seconds) and Window Seat (70 seconds). Use the audio controls to pause or move the playback position. Select example-90.m3u8, load it and play its only track. The shorter break keeps the complete first recording; it does not cut the second one.
Switch to Terminal. If stray characters or an unfinished command appear at the prompt, press Ctrl+C to cancel that input. cd changes directory. This absolute path also recovers from an ordinary wrong-directory error:
cd /home/labex/project/short-break
cat displays a text file. sample.tsv is a two-track catalog; TSV means tab-separated values. Its columns are ID, title, whole seconds, preference and audio path. A higher preference is considered first. An ID identifies a recording; titles can repeat.
cat sample.tsv
playlist.hpp declares the Track object and the two function interfaces. Each Track groups the five fields. A vector<Track> holds several objects in order. Read the short definition:
cat playlist.hpp
Build the supplied starter. g++ compiles C++; -std=c++17 selects C++17, -Wall -Wextra request useful warnings, and -o playlist names the executable. The three .cpp files are compiled together. A warning about the starter's unused budget is expected.
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist runs the executable in this directory. Its arguments are catalog, budget in whole seconds from 1 through 86400 inclusive, and output path. The prepared parser uses that same bound for catalog durations and preference values; you do not need to implement that parser. Run the incomplete starter on the 90-second sample:
./playlist sample.tsv 90 outputs/sample-90.m3u8
It prints both recordings and Total: 120s / 90s. That is the bug you will fix. The starter's output file is only a title list, so loading it in Player currently reports a missing #EXTM3U header. That is an expected format failure, separate from a missing audio file. Step 3 will teach the export format.
Read the function you are about to change:
cat selection.cpp
The #include lines make the interface and algorithm library available. Keep those lines, the function signature and its outer braces. The starter body simply returns every track. Before editing, predict the sample: after accepting the 50-second recording, how much of 90 remains, and can the 70-second recording fit? You should have 40 seconds left and skip the second recording.
Now give chooseTracks a remaining budget. Passing tracks by value makes a local copy; later sorting will not change the catalog. const auto& track reads each existing object without copying or modifying it. Start an empty selected vector, visit each track, append it with push_back only when it fits, and subtract its duration with -=. Use <= so an exact fit is accepted. Keep looping after a track does not fit; return the selected objects after the loop.
You will write just that function body in nano, a terminal text editor. Open the existing source:
nano selection.cpp
The bottom of nano shows shortcuts: ^ means hold Ctrl while pressing the following letter. Use arrow keys to move without changing text. Press Ctrl+W, type TODO and press Enter to find the starter comment. Move to the beginning of that line with Ctrl+A. Ctrl+K cuts the current line; press it twice to remove the comment and return tracks;. Leave the closing } below the cursor in place. If you cut the wrong line, Ctrl+U pastes back the cut text before you continue.
Type the following body at that position, ending with a newline before the existing closing brace. Read each statement as you enter it: the vector collects the result, the loop visits all candidates, and the if changes both the result and remaining time together.
std::vector<Track> selected;
for (const auto& track : tracks) {
if (track.seconds <= budget) {
selected.push_back(track);
budget -= track.seconds;
}
}
return selected;
Press Ctrl+O to write the file, Enter to confirm the existing name selection.cpp, then Ctrl+X to exit to the shell prompt. If you exit while changes are unsaved, nano asks whether to save: Y, then Enter, keeps them; N discards only changes since the last save. Ctrl+C cancels a prompt so you can return to editing. Do not rerun setup to recover an edit.
Rebuild after a source edit, then rerun the same sample:
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist sample.tsv 90 outputs/sample-90.m3u8
Now only t01 | Morning Light | 50s appears, with Total: 50s / 90s. Forty seconds remain; the 70-second recording cannot fit. This version respects input order. Next, you will make order independent of the catalog's row arrangement.
If compilation reports an error such as selection.cpp:7:..., the first number names the source line. +7 asks nano to open at line 7; use the actual line number in your error:
nano +7 selection.cpp
Read the first error and nearby braces or punctuation, correct that local text, then save with Ctrl+O, Enter and exit with Ctrl+X. Rebuild before rerunning the sample. A failed build can leave the previous executable in place; running it does not test your edit.
Apply a Predictable Preference Order
In this step, you will sort the whole library before choosing tracks. Begin in Terminal, in /home/labex/project/short-break. Inspect the full catalog:
cat catalog.tsv
The rows are deliberately shuffled. Consider preference from largest to smallest, then track ID alphabetically from smallest to largest when scores tie. Thus t02 comes before t03, even though it is longer. These are simple preference decisions, not a search for the greatest possible total duration.
Before editing, work out the preference/ID order and trace a 90-second budget on paper or mentally. Which later recording can still fit after the first oversized one? Also predict whether 120 seconds leaves room for t03.
For a 90-second budget, t01 uses 50 seconds. Both t02 (95) and t03 (70) do not fit the remaining 40. Continue to t04 (30), which leaves 10. t05 (20) does not fit. The result is t01, t04, totaling 80 seconds. Stopping at the first oversized track would lose a valid later choice.
std::sort uses the half-open range from begin() to end(). The comparison returns true when a must precede b. [] introduces a small anonymous function, called a lambda; its two const Track& arguments read the compared objects. The != test chooses the primary comparison, and the final return breaks a tie by ID.
Open the function you completed in Step 1:
nano selection.cpp
Press Ctrl+W, search for std::vector<Track> selected;, then Enter and Ctrl+A. Insert the following sort call before that line, ending with a newline. Keep your existing selection loop and return statement. Sorting must happen before the first selection; sorting only the selected result cannot reconsider a recording that was skipped earlier.
std::sort(tracks.begin(), tracks.end(),
[](const Track& a, const Track& b) {
if (a.preference != b.preference)
return a.preference > b.preference;
return a.id < b.id;
});
Check the two decisions separately: different scores use > for descending preference; equal scores reach < for ascending ID. Save with Ctrl+O, Enter, then exit with Ctrl+X. You have added ordering without replacing the budget logic you already wrote.
Rebuild and run both budgets:
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist catalog.tsv 90 outputs/break-90.m3u8
./playlist catalog.tsv 120 outputs/break-120.m3u8
The 90-second result is t01, t04, totaling 80. The 120-second result is t01, t03, totaling exactly 120: t02 is skipped, then the 70-second track fits the remaining 70. Both choices use whole recordings. Output is still a title list until the next step.
Export a Playlist You Can Play
In this step, you will save the chosen objects as UTF-8 extended M3U. Begin in Terminal. This .m3u8 file is a text playlist, not audio itself. The first line is #EXTM3U. Each selected track then has a #EXTINF:seconds,title metadata line followed by its audio path.
A relative path is resolved from the playlist's directory, not the catalog's directory or the process's current directory. Save generated playlists directly in outputs/; the originals live in outputs/audio/. Therefore an entry such as audio/Morning Light.wav reaches the recording beside that playlist. Keep that folder relationship when downloading or sharing files. Spaces and Unicode are supported; keep the supplied paths, with no newline, leading #, absolute path, parent traversal or extra playlist directives.
std::ofstream opens an output file. << writes text or numbers; \n writes a newline. This function receives the selected vector and output name through const references, so it does not copy or change them. File failures throw an exception that the prepared main program reports. The input parser validates a positive integer budget, the catalog and all referenced audio before this function opens an output, preserving existing output on those input failures.
Read the prepared exporter before opening the editor:
cat export.cpp
It already includes the libraries, declares the function, opens file and refuses an unsuccessful open. Keep that framework. Its last loop writes only titles, which explains the earlier format failure. Predict the first three lines needed for Morning Light: a playlist header, its 50-second metadata line, then the relative path. Compare your prediction with #EXTINF:seconds,title; the separator after seconds is an ASCII comma ,.
Open the existing file:
nano export.cpp
Search for TODO with Ctrl+W, Enter, then press Ctrl+A. Press Ctrl+K twice to remove the comment and the one-line title loop. Keep the opening code and final function brace. At this position, write the header once, then write each selected object's two lines. Finally check the stream after writing, so a write failure is reported too. Insert only this local section, ending with a newline:
file << "#EXTM3U\n";
for (const auto& track : tracks) {
file << "#EXTINF:" << track.seconds << ',' << track.title << '\n';
file << track.path << '\n';
}
if (!file) throw std::runtime_error("Playlist write failed.");
Check that the header is outside the loop, each metadata line is followed by its own path, and every line ends with \n. Save with Ctrl+O, Enter, then exit with Ctrl+X. If compilation identifies an error, reopen at its line as in Step 1, repair the local edit and rebuild.
Rebuild and recreate both lists with the updated exporter:
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist catalog.tsv 90 outputs/break-90.m3u8
./playlist catalog.tsv 120 outputs/break-120.m3u8
Inspect the saved 90-second file:
cat outputs/break-90.m3u8
The saved UTF-8 file contains:
#EXTM3U
#EXTINF:50,Morning Light
audio/Morning Light.wav
#EXTINF:30,夜の散歩
audio/夜の散歩.wav
The Japanese title and filename remain UTF-8 text. In some terminal sessions, the display shows placeholders for these characters even though the saved bytes are intact. Do not rename the originals to fix a display issue; Player shows the original title and path. The duration lines describe the whole original files; changing a number here would not shorten the audio.
Switch to Player. Click Refresh files, select break-90.m3u8 and click Load playlist. The result should show two tracks and 80 seconds declared. Click Play 1, move the audio position, pause, then Play 2 to hear the second recording. The browser reads your saved M3U8 and exactly those audio paths. A missing source produces Cannot play, not a replacement track. Use Download playlist and Download current track to save the actual files; the playlist alone does not contain the audio.

Look at the two rows and their durations, then the audio controls below them. Switch back to Terminal after checking playback.
Reuse the Program and Read a Rejection
In this step, you will distinguish a valid short result from invalid input. Begin in Terminal. A positive budget smaller than every track is valid: no full recording fits. Generate a one-second list:
./playlist catalog.tsv 1 outputs/tiny.m3u8
It reports Total: 0s / 1s. The file contains only #EXTM3U. Loading it in Player should show zero tracks. This is a correct empty result, not a connection or parsing failure.
A budget such as 90s is invalid because the CLI expects digits in whole seconds. The next command intentionally exits with status 1; it must not replace your existing 90-second playlist. echo $? immediately displays the preceding command's exit status (0 means success). Run these as two consecutive commands:
./playlist catalog.tsv 90s outputs/break-90.m3u8
echo $?
The error says that budget and durations must be positive integer seconds, followed by status 1. Do not reset the environment: remove the suffix and use 90 when retrying. If an input path is mistyped, the error Cannot open catalog points to the catalog argument. Recover with the absolute cd from Step 1 and the real name catalog.tsv. Missing audio means a catalog path does not reach a file; preserve the prepared originals and check the path before trying again.
Confirm the existing good file still contains both proper metadata/path pairs:
cat outputs/break-90.m3u8
You can now use the same program for different positive budgets, always writing directly in outputs/. It orders by preference and ID, skips oversized choices and never trims a recording. Physical speaker output and every possible third-party player's behavior are outside this exercise; the provided browser decodes these real WAV files.
Summary
You read a working example, edited local C++ function logic in nano, and rebuilt to test each change. You used objects and a vector to order recordings, select complete tracks within a remaining budget and write UTF-8 M3U8 files. You played the actual output, understood relative paths and distinguished a valid empty playlist from invalid input. The next challenge changes the library while keeping these rules.



