<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Articles on Perl.com - programming news, code and culture</title>
    <link>https://www.perl.com/article/</link>
    <description>Recent content in Articles on Perl.com - programming news, code and culture</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 13 Apr 2026 22:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.perl.com/article/" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Making an Asynchronous Clocking Drum Machine App in Perl</title>
      <link>https://www.perl.com/article/making-an-asynchronous-clocking-drum-machine-in-perl/</link>
      <pubDate>Mon, 13 Apr 2026 22:00:00 +0000</pubDate>
      <guid>https://www.perl.com/article/making-an-asynchronous-clocking-drum-machine-in-perl/</guid>
      <description>&lt;p&gt;Let&amp;rsquo;s Make a Drum Machine application! Yeah! :D&lt;/p&gt;
&lt;p&gt;There are basically two important things to handle: A MIDI &amp;ldquo;clock&amp;rdquo; and a groove to play.&lt;/p&gt;
&lt;p&gt;Why asynchronous? Well, a simple &lt;code&gt;while (1) { Time::HiRes::sleep($interval); ... }&lt;/code&gt; will not do because the time between ticks will fluctuate, often dramatically. &lt;code&gt;IO::Async::Timer::Periodic&lt;/code&gt; is a great timer for this purpose. Its default scheduler uses system time, so intervals happen as close to the correct real-world time as possible.&lt;/p&gt;
&lt;h2 id=&#34;clocks&#34;&gt;Clocks&lt;/h2&gt;
&lt;p&gt;A MIDI clock tells a MIDI device about the tempo. This can be handed to a drum machine or a sequencer. Each clock tick tells the device to advance a step of a measured interval. Usually this is very short, and is often 24 pulses per quarter-note (four quarter-notes to a measure of four beats).&lt;/p&gt;
&lt;p&gt;Here is code to do that, followed by an explanation of the parts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; v5&lt;span style=&#34;color:#ae81ff&#34;&gt;.36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; feature &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;try&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; IO::Async::Loop ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; IO::Async::Timer::Periodic ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::RtMidi::FFI::Device ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;usb&amp;#39;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# MIDI sequencer device&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $bpm  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# beats per minute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $interval &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/ $bpm /&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# time / bpm / clocks-per-beat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# open the named midi device for output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $midi_out &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RtMidiOut&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;try { &lt;span style=&#34;color:#75715e&#34;&gt;# this will die on Windows but is needed for Mac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;open_virtual_port(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;RtMidiOut&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;catch ($e) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;open_port_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;qr/\Q$name/&lt;/span&gt;i);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;start; &lt;span style=&#34;color:#75715e&#34;&gt;# start the sequencer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$SIG{INT} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#75715e&#34;&gt;# halt gracefully&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    say &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\nStop&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    try {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;stop; &lt;span style=&#34;color:#75715e&#34;&gt;# stop the sequencer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;panic; &lt;span style=&#34;color:#75715e&#34;&gt;# make sure all notes are off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    catch ($e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        warn &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Can&amp;#39;t halt the MIDI out device: $e\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $loop &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; IO::Async::Loop&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $timer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; IO::Async::Timer::Periodic&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   interval &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; $interval,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   on_tick  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; { $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;clock }, &lt;span style=&#34;color:#75715e&#34;&gt;# send a clock tick!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$timer&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;start;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$loop&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;add($timer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$loop&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;run;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above code does a few things. First it uses modern Perl, then the modules that will make execution asynchronous, and finally the module that makes real-time MIDI possible.&lt;/p&gt;
&lt;p&gt;Next up, a &lt;code&gt;$name&lt;/code&gt; variable is captured for a unique MIDI device. (And to see what the names of MIDI devices on the system are, use &lt;a href=&#34;https://metacpan.org/author/JBARRETT&#34;&gt;JBARRETT&lt;/a&gt;&amp;rsquo;s little &lt;a href=&#34;https://metacpan.org/release/JBARRETT/MIDI-RtMidi-FFI-0.10/source/examples/list_devices.pl&#34;&gt;list_devices&lt;/a&gt; script.) Also, the beats per minute is taken from the command-line. If neither is given, &lt;code&gt;usb&lt;/code&gt; is used for the name, and the BPM is set to &amp;ldquo;dance tempo.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The clock needs a time interval to tick off. For us, this is a fraction of a second based on the beats per minute, and is assigned to the &lt;code&gt;$interval&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;To get the job done, we will need to open the named MIDI device for sending output messages to. This is done with the &lt;code&gt;$name&lt;/code&gt; provided.&lt;/p&gt;
&lt;p&gt;In order to not just die when we want to stop, &lt;code&gt;$SIG{INT}&lt;/code&gt; is redefined to gracefully halt. This also sends a &lt;code&gt;stop&lt;/code&gt; message to the open MIDI device. This stops the sequencer from playing.&lt;/p&gt;
&lt;p&gt;Now for the meat and potatoes: The asynchronous loop and periodic timer. These tell the program to do its thing, in a non-blocking and event-driven manner. The periodic timer ticks off a clock message every &lt;code&gt;$interval&lt;/code&gt;. Pretty simple!&lt;/p&gt;
&lt;p&gt;As an example, here is the above code controlling my &lt;a href=&#34;https://www.korg.com/us/products/dj/volca_drum/&#34;&gt;Volca Drum&lt;/a&gt; drum machine on a stock, funky groove. We invoke it on the command-line like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perl clock-gen-async.pl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/making-an-asynchronous-clocking-drum-machine-in-perl/clocked-sequence.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;h2 id=&#34;grooves&#34;&gt;Grooves&lt;/h2&gt;
&lt;p&gt;What we really want is to make our drum machine actually play something of our own making. So it&amp;rsquo;s refactor time&amp;hellip; Let&amp;rsquo;s make a 4/4 time groove, with 16th-note resolution, that alternates between two different parts. &amp;ldquo;4/4&amp;rdquo; is a &amp;ldquo;time signature&amp;rdquo; in music jargon and means that there are four beats per measure (numerator), and a quarter note equals one beat (denominator). Other time signatures like the waltz&amp;rsquo;s 3/4 are simple, while odd meters like 7/8 are not.&lt;/p&gt;
&lt;p&gt;In order to generate syncopated patterns, &lt;a href=&#34;https://metacpan.org/pod/Math::Prime::XS&#34;&gt;Math::Prime::XS&lt;/a&gt; and &lt;a href=&#34;https://metacpan.org/pod/Music::CreatingRhythms&#34;&gt;Music::CreatingRhythms&lt;/a&gt; are added to the &lt;code&gt;use&lt;/code&gt; statements. &amp;ldquo;What are syncopated patterns?&amp;rdquo;, you may ask. Good question! &amp;ldquo;Syncopated&amp;rdquo; means, &amp;ldquo;characterized by displaced beats.&amp;rdquo; That is, every beat does not happen evenly, at exactly the same time. Instead, some are displaced. For example, a repeated &lt;code&gt;[1 1 1 1]&lt;/code&gt; is even and boring. But when it becomes a repeated &lt;code&gt;[1 1 0 1]&lt;/code&gt; things get spicier and more syncopated.&lt;/p&gt;
&lt;p&gt;The desired MIDI channel is added to the command-line inputs. Most commonly, this will be channel &lt;code&gt;9&lt;/code&gt; (in zero-based numbering). But some drum machines and sequencers are &amp;ldquo;multi-timbral&amp;rdquo; and use multiple channels simultaneously for individual sounds.&lt;/p&gt;
&lt;p&gt;Next we define the drums to use. This is a hash-reference that includes the MIDI patch number, the channel it&amp;rsquo;s on, and the pattern to play. The combined patterns of all the drums, when played together at tempo, make a groove.&lt;/p&gt;
&lt;p&gt;Now we compute intervals and friends. Previously, there was one &lt;code&gt;$interval&lt;/code&gt;. Now there are a whole host of measurements to make before sending MIDI messages.&lt;/p&gt;
&lt;p&gt;Then, as before, a named MIDI output device is opened, and a graceful stop is defined.&lt;/p&gt;
&lt;p&gt;Next, a &lt;a href=&#34;https://metacpan.org/pod/Music::CreatingRhythms&#34;&gt;Music::CreatingRhythms&lt;/a&gt; object is created. And then, again as before, an asynchronous loop and periodic timer are instantiated and set in motion.&lt;/p&gt;
&lt;p&gt;The meaty bits are in the timer&amp;rsquo;s &lt;code&gt;on_tick&lt;/code&gt; callback. This contains all the logic needed to trigger our drum grooves.&lt;/p&gt;
&lt;p&gt;As was done in the previous clock code, a clock message is sent, but also we keep track of the number of clock ticks that have passed. This number of ticks is used to trigger the drums. We care about 16 beats. So every 16th beat, we construct and play a queue of events.&lt;/p&gt;
&lt;p&gt;Adjusting the drum patterns is where &lt;a href=&#34;https://metacpan.org/pod/Math::Prime::XS&#34;&gt;Math::Prime::XS&lt;/a&gt; and &lt;a href=&#34;https://metacpan.org/pod/Music::CreatingRhythms&#34;&gt;Music::CreatingRhythms&lt;/a&gt; come into play. The subroutine that does that is &lt;code&gt;adjust_drums()&lt;/code&gt; and is fired every 4th measure. A measure is equal to four quarter-notes, and we use four pulses for each, to make 16 beats per measure. This routine reassigns either Euclidean or manual patterns of 16 beats to each drum pattern.&lt;/p&gt;
&lt;p&gt;Managing the queue is next. If a drum is to be played at the current beat (as tallied by the &lt;code&gt;$beat_count&lt;/code&gt; variable), it is added to the queue at full velocity (&lt;code&gt;127&lt;/code&gt;). Then, after all the drums have been accounted for, the queue is played with &lt;code&gt;$midi_out-&amp;gt;note_on()&lt;/code&gt; messages. Lastly, the queue is &amp;ldquo;drained&amp;rdquo; by sending &lt;code&gt;$midi_out-&amp;gt;note_off()&lt;/code&gt; messages.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; v5&lt;span style=&#34;color:#ae81ff&#34;&gt;.36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; feature &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;try&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; IO::Async::Loop ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; IO::Async::Timer::Periodic ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Math::Prime::XS &lt;span style=&#34;color:#e6db74&#34;&gt;qw(primes)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::RtMidi::FFI::Device ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;usb&amp;#39;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# MIDI sequencer device&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $bpm  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# beats-per-minute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chan &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift &lt;span style=&#34;color:#e6db74&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# 0-15, 9=percussion, -1=multi-timbral&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $drums &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    kick  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; { num &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;, chan &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; $chan &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ? &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; : $chan, pat &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    snare &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; { num &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;38&lt;/span&gt;, chan &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; $chan &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ? &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; : $chan, pat &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hihat &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; { num &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;, chan &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; $chan &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ? &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; : $chan, pat &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $beats &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# beats in a measure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $divisions &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# divisions of a quarter-note into 16ths&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $clocks_per_beat &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# PPQN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $clock_interval &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/ $bpm /&lt;/span&gt; $clocks_per_beat; &lt;span style=&#34;color:#75715e&#34;&gt;# time / bpm / ppqn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $sixteenth &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $clocks_per_beat &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; $divisions; &lt;span style=&#34;color:#75715e&#34;&gt;# clocks per 16th-note&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; %primes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ( &lt;span style=&#34;color:#75715e&#34;&gt;# for computing the pattern&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    all  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [ primes($beats) ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    to_5 &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [ primes(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    to_7 &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [ primes(&lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;) ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $ticks &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# clock ticks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $beat_count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# how many beats?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $toggle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# part A or B?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @queue; &lt;span style=&#34;color:#75715e&#34;&gt;# priority queue for note_on/off messages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# open the named midi output device&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $midi_out &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RtMidiOut&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;try { &lt;span style=&#34;color:#75715e&#34;&gt;# this will die on Windows but is needed for Mac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;open_virtual_port(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;RtMidiOut&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;catch ($e) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;open_port_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;qr/\Q$name/&lt;/span&gt;i);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$SIG{INT} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#75715e&#34;&gt;# halt gracefully&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    say &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\nStop&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    try {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;stop; &lt;span style=&#34;color:#75715e&#34;&gt;# stop the sequencer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;panic; &lt;span style=&#34;color:#75715e&#34;&gt;# make sure all notes are off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    catch ($e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        warn &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Can&amp;#39;t halt the MIDI out device: $e\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# for computing the pattern&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $loop &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; IO::Async::Loop&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $timer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; IO::Async::Timer::Periodic&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    interval &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; $clock_interval,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    on_tick  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;clock;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $ticks&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($ticks % &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;sixteenth &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# adjust the drum pattern every 4th measure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($beat_count % &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;($&lt;/span&gt;beats &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; $divisions) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                adjust_drums($mcr, $drums, &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;%primes, &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;$toggle);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# add simultaneous drums to the queue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $drum (keys %$drums) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{$drum}{pat}[ $beat_count % &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;beats ]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    push @queue, { drum &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; $drum, velocity &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;127&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# play the queue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $drum (@queue) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;note_on(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{ $drum&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{drum} }{chan},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{ $drum&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{drum} }{num},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    $drum&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{velocity}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $beat_count&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# drain the queue with note_off messages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $drum &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pop @queue) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                $midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;note_off(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{ $drum&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{drum} }{chan},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{ $drum&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{drum} }{num},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            @queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (); &lt;span style=&#34;color:#75715e&#34;&gt;# ensure the queue is empty&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$timer&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;start;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$loop&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;add($timer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$loop&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;run;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;adjust_drums&lt;/span&gt;($mcr, $drums, $primes, $toggle) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# choose random primes to use by the hihat, kick, and snare&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; ($p, $q, $r) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $primes&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{$_}[ int rand $primes&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{$_}&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;@&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;}&lt;/span&gt; sort keys %$primes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($$toggle &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        say &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;part A&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{hihat}{pat} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid($p, $beats);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{kick}{pat}  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid($q, $beats);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{snare}{pat} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;rotate_n($r, $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, $beats));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $$toggle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# set to part B&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        say &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;part B&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{hihat}{pat} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid($p, $beats);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{kick}{pat}  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;qw(1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1)&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $drums&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{snare}{pat} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;qw(0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0)&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $$toggle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# set to part A&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(You may notice the inefficiency of attempting to drain an empty queue 23 times every 16th note. Oof! Fortunately, this doesn&amp;rsquo;t fire anything other than a single while loop condition. A more efficient solution would be to only drain the queue once, but this requires a bit more complexity that we won&amp;rsquo;t be adding, for brevity&amp;rsquo;s sake.)&lt;/p&gt;
&lt;p&gt;On Windows, this works fine:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perl clocked-euclidean-drums.pl &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gs wavetable&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To run with &lt;code&gt;fluidsynth&lt;/code&gt; and hear the General MIDI percussion sounds, open a fresh new terminal session, and start up &lt;code&gt;fluidsynth&lt;/code&gt; like so (mac syntax):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fluidsynth -a coreaudio -m coremidi -g 2.0 ~/Music/soundfont/FluidR3_GM.sf2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;FluidR3_GM.sf2&lt;/code&gt; is a MIDI &amp;ldquo;soundfont&amp;rdquo; file and can be downloaded for free.&lt;/p&gt;
&lt;p&gt;Next, enter this on the command-line (back in the &lt;strong&gt;previous&lt;/strong&gt; terminal session):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perl clocked-euclidean-drums.pl fluid &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You will hear standard kick, snare, and closed hihat cymbal. And here is a poor recording of this with my phone:&lt;/p&gt;

&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/making-an-asynchronous-clocking-drum-machine-in-perl/clocked-euclidean-drums-GM.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;To run the code with my multi-timbral drum machine, I enter this on the command-line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perl clocked-euclidean-drums.pl usb &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt; -1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here is what that sounds like:&lt;/p&gt;

&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/making-an-asynchronous-clocking-drum-machine-in-perl/clocked-euclidean-drums.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;h2 id=&#34;the-module&#34;&gt;The Module&lt;/h2&gt;
&lt;p&gt;I have coded this logic, and a bit more, into a friendly &lt;a href=&#34;https://metacpan.org/pod/Music::SimpleDrumMachine&#34;&gt;CPAN module&lt;/a&gt;. Check out the &lt;code&gt;eg/euclidean.pl&lt;/code&gt; example program in the distribution. It is a work in progress. YMMV.&lt;/p&gt;
&lt;h2 id=&#34;credits&#34;&gt;Credits&lt;/h2&gt;
&lt;p&gt;Thank you to Andrew Rodland (hobbs), who helped me wrap my head around the &amp;ldquo;no-sleeping asynchronous&amp;rdquo; algorithm.&lt;/p&gt;
&lt;h2 id=&#34;to-do-challenges&#34;&gt;To-do Challenges&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Make patterns other than prime number based Euclidean phrases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Toggle more than two groove parts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add snare fills to the (end of the) 4th bars. (&lt;a href=&#34;https://github.com/ology/Music/blob/master/clocked-euclidean-drum-fills.pl&#34;&gt;here&amp;rsquo;s my version&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make this code handle odd meter grooves.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://metacpan.org/pod/IO::Async::Loop&#34;&gt;IO::Async::Loop&lt;/a&gt; module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://metacpan.org/pod/IO::Async::Timer::Periodic&#34;&gt;IO::Async::Timer::Periodic&lt;/a&gt; module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://metacpan.org/pod/Math::Prime::XS&#34;&gt;Math::Prime::XS&lt;/a&gt; module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://metacpan.org/pod/MIDI::RtMidi::FFI::Device&#34;&gt;MIDI::RtMidi::FFI::Device&lt;/a&gt; module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://metacpan.org/pod/Music::CreatingRhythms&#34;&gt;Music::CreatingRhythms&lt;/a&gt; module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://metacpan.org/pod/Music::SimpleDrumMachine&#34;&gt;Music::SimpleDrumMachine&lt;/a&gt; WIP module based on this logic&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The cross-platform &lt;a href=&#34;https://www.fluidsynth.org/&#34;&gt;fluidsynth&lt;/a&gt; application&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My original music: &lt;a href=&#34;https://www.youtube.com/@GeneBoggs&#34;&gt;https://www.youtube.com/@GeneBoggs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Musical Rhythms with Math in Perl</title>
      <link>https://www.perl.com/article/musical-rhythms-with-math-in-perl/</link>
      <pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://www.perl.com/article/musical-rhythms-with-math-in-perl/</guid>
      <description>&lt;p&gt;Let&amp;rsquo;s talk about music programming! There are a million aspects to this subject, but today, we&amp;rsquo;ll touch on generating rhythmic patterns with mathematical and combinatorial techniques. These include the generation of partitions, necklaces, and Euclidean patterns.&lt;/p&gt;
&lt;p&gt;Stefan and J. Richard Hollos wrote an &lt;a href=&#34;https://abrazol.com/books/rhythm1/&#34;&gt;excellent little book&lt;/a&gt; called &amp;ldquo;Creating Rhythms&amp;rdquo; that has been turned into &lt;a href=&#34;https://abrazol.com/books/rhythm1/software.html&#34;&gt;C, Perl, and Python&lt;/a&gt;. It features a number of algorithms that produce or modify lists of numbers or bit-vectors (of ones and zeroes). These can be beat onsets (the ones) and rests (the zeroes) of a rhythm. We&amp;rsquo;ll check out these concepts with Perl.&lt;/p&gt;
&lt;p&gt;For each example, we&amp;rsquo;ll save the MIDI with the &lt;a href=&#34;https://metacpan.org/pod/MIDI::Util&#34;&gt;MIDI::Util&lt;/a&gt; module. Also, in order to actually &lt;em&gt;hear&lt;/em&gt; the rhythms, we will need a MIDI synthesizer. For these illustrations, &lt;a href=&#34;https://www.fluidsynth.org/&#34;&gt;fluidsynth&lt;/a&gt; will work. Of course, any MIDI capable synth will do! I often control my eurorack analog synthesizer with code (and a MIDI interface module).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I start &lt;code&gt;fluidsynth&lt;/code&gt; on my mac in the terminal, in a &lt;em&gt;separate&lt;/em&gt; session. It uses a generic soundfont file (&lt;code&gt;sf2&lt;/code&gt;) that can be downloaded &lt;a href=&#34;https://keymusician01.s3.amazonaws.com/FluidR3_GM.zip&#34;&gt;here&lt;/a&gt; (124MB zip).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fluidsynth -a coreaudio -m coremidi -g 2.0 ~/Music/soundfont/FluidR3_GM.sf2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, how does Perl know what output port to use? There are a few ways, but with &lt;a href=&#34;https://metacpan.org/author/JBARRETT&#34;&gt;JBARRETT&lt;/a&gt;&amp;rsquo;s &lt;a href=&#34;https://metacpan.org/pod/MIDI::RtMidi::FFI::Device&#34;&gt;MIDI::RtMidi::FFI::Device&lt;/a&gt;, you can do this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::RtMidi::FFI::Device ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $midi_in &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RtMidiIn&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $midi_out &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RtMidiOut&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Input devices:\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$midi_in&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;print_ports;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Output devices:\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$midi_out&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;print_ports;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This shows that &lt;code&gt;fluidsynth&lt;/code&gt; is alive and ready for interaction.&lt;/p&gt;
&lt;p&gt;Okay, on with the show!&lt;/p&gt;
&lt;p&gt;First-up, let&amp;rsquo;s look at partition algorithms. With the &lt;code&gt;part()&lt;/code&gt; function, we can generate all partitions of &lt;code&gt;n&lt;/code&gt;, where &lt;code&gt;n&lt;/code&gt; is &lt;code&gt;5&lt;/code&gt;, and the &amp;ldquo;parts&amp;rdquo; all add up to &lt;code&gt;5&lt;/code&gt;. Then taking one of these (say, the third element), we convert it to a binary sequence that can be interpreted as a rhythmic phrase, and play it 4 times.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; strict;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; warnings;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $parts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;part(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# [ [ 1, 1, 1, 1, 1 ], [ 1, 1, 1, 2 ], [ 1, 2, 2 ], [ 1, 1, 3 ], [ 2, 3 ], [ 1, 4 ], [ 5 ] ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $parts&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]; &lt;span style=&#34;color:#75715e&#34;&gt;# [ 1, 2, 2 ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;int2b([$p]); &lt;span style=&#34;color:#75715e&#34;&gt;# [ [ 1, 1, 0, 1, 0 ] ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we render and save the rhythm:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(setup_score)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; setup_score(bpm &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, channel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $bit ($seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;@&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($bit) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;n(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;r(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;write_score(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;perldotcom-1.mid&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In order to play the MIDI file that is produced, we can use &lt;code&gt;fluidsynth&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fluidsynth -i ~/Music/soundfont/FluidR3_GM.sf2 perldotcom-1.mid
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/musical-rhythms-with-math-in-perl/perldotcom-1.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;Not terribly exciting yet.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see what the &amp;ldquo;compositions&amp;rdquo; of a number reveal. According to the &lt;a href=&#34;https://metacpan.org/pod/Music::CreatingRhythms&#34;&gt;Music::CreatingRhythms&lt;/a&gt; docs, a composition of a number is &amp;ldquo;the set of combinatorial variations of the partitions of &lt;code&gt;n&lt;/code&gt; with the duplicates removed.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Okay. Well, the 7 partitions of &lt;code&gt;5&lt;/code&gt; are:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[[1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 4], [2, 3], [5]]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And the 16 compositions of &lt;code&gt;5&lt;/code&gt; are:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[[1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 1], [1, 1, 3], [1, 2, 1, 1], [1, 2, 2], [1, 3, 1], [1, 4], [2, 1, 1, 1], [2, 1, 2], [2, 2, 1], [2, 3], [3, 1, 1], [3, 2], [4, 1], [5]]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That is, the list of compositions has, not only the partition &lt;code&gt;[1, 2, 2]&lt;/code&gt;, but also its variations: &lt;code&gt;[2, 1, 2]&lt;/code&gt; and &lt;code&gt;[2, 2, 1]&lt;/code&gt;. Same with the other partitions. Selections from this list will produce possibly cool rhythms.&lt;/p&gt;
&lt;p&gt;Here are the compositions of &lt;code&gt;5&lt;/code&gt; turned into sequences, played by a snare drum, and written to the disk:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(setup_score)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $comps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;compm(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;# compositions of 5 with 3 elements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;int2b($comps);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; setup_score(bpm &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, channel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $pattern ($seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;@&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $bit (@$pattern) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($bit) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;n(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;# snare patch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;r(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;write_score(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;perldotcom-2.mid&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/musical-rhythms-with-math-in-perl/perldotcom-2.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;A little better. Like a syncopated snare solo.&lt;/p&gt;
&lt;h2 id=&#34;sidebar&#34;&gt;Sidebar&lt;/h2&gt;
&lt;p&gt;Another way to play the MIDI file is to use &lt;a href=&#34;https://wiki.archlinux.org/title/Timidity++&#34;&gt;timidity&lt;/a&gt;. On my mac, with the soundfont specified in the &lt;code&gt;timidity.cfg&lt;/code&gt; configuration file, this would be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;timidity -c ~/timidity.cfg -Od perldotcom-2.mid
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To convert a MIDI file to an mp3 (or other audio formats), I do this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;timidity -c ~/timidity.cfg perldotcom-2.mid -Ow -o - | ffmpeg -i - -acodec libmp3lame -ab 64k perldotcom-2.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay. Enough technical details! What if we want a kick bass drum and hi-hat cymbals, too? Refactor time…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(setup_score)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $s_comps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;compm(&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;# snare&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $s_seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;int2b($s_comps);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $k_comps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;compm(&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;# kick&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $k_seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;int2b($k_comps);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; setup_score(bpm &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, channel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) { &lt;span style=&#34;color:#75715e&#34;&gt;# repeats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $s_choice &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $s_seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[ int rand @$s_seq ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $k_choice &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $k_seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[ int rand @$k_seq ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $i (&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; $#$s_choice) { &lt;span style=&#34;color:#75715e&#34;&gt;# pattern position&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @notes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;# hi-hat every time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($s_choice&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($k_choice&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;n(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;, @notes);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;write_score(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;perldotcom-3.mid&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/musical-rhythms-with-math-in-perl/perldotcom-3.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;Here we play generated kick and snare patterns, along with a steady hi-hat.&lt;/p&gt;
&lt;p&gt;Next up, let&amp;rsquo;s look at rhythmic &amp;ldquo;necklaces.&amp;rdquo; Here we find many grooves of the world.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/musical-rhythms-with-math-in-perl/rhythm-necklaces.png&#34; alt=&#34;World rhythms&#34;&gt;&lt;/p&gt;
&lt;p&gt;Image from &lt;a href=&#34;https://cgm.cs.mcgill.ca/~godfried/publications/geometry-of-rhythm.pdf&#34;&gt;The Geometry of Musical Rhythm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Rhythm necklaces are circular diagrams of equally spaced, connected nodes. A necklace is a lexicographical ordering with no rotational duplicates. For instance, the necklaces of &lt;code&gt;3&lt;/code&gt; beats are &lt;code&gt;[[1, 1, 1], [1, 1, 0], [1, 0, 0], [0, 0, 0]]&lt;/code&gt;. Notice that there is no &lt;code&gt;[1, 0, 1]&lt;/code&gt; or &lt;code&gt;[0, 1, 1]&lt;/code&gt;. Also, there are no rotated versions of &lt;code&gt;[1, 0, 0]&lt;/code&gt;, either.&lt;/p&gt;
&lt;p&gt;So, how many 16 beat rhythm necklaces are there?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $necklaces &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;neck(&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; scalar @$necklaces, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# 4116 of &amp;#39;em!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay. Let&amp;rsquo;s generate necklaces of &lt;code&gt;8&lt;/code&gt; instead, pull a random choice, and play the pattern with a percussion instrument.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(setup_score)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $patch &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;75&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# claves&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $necklaces &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;neck(&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $choice &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $necklaces&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[ int rand @$necklaces ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; setup_score(bpm &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, channel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) { &lt;span style=&#34;color:#75715e&#34;&gt;# repeats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $bit (@$choice) { &lt;span style=&#34;color:#75715e&#34;&gt;# pattern position&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($bit) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;n(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;, $patch);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;r(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;write_score(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;perldotcom-4.mid&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/musical-rhythms-with-math-in-perl/perldotcom-4.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;Here we choose from &lt;strong&gt;all&lt;/strong&gt; necklaces. But note that this also includes the sequence with all ones and the sequence with all zeroes. More sophisticated code might skip these.&lt;/p&gt;
&lt;p&gt;More interesting would be playing simultaneous beats.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(setup_score)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $necklaces &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;neck(&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $x_choice &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $necklaces&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[ int rand @$necklaces ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $y_choice &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $necklaces&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[ int rand @$necklaces ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $z_choice &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $necklaces&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[ int rand @$necklaces ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; setup_score(bpm &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, channel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) { &lt;span style=&#34;color:#75715e&#34;&gt;# repeats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $i (&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; $#$x_choice) { &lt;span style=&#34;color:#75715e&#34;&gt;# pattern position&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @notes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($x_choice&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;75&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# claves&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($y_choice&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;63&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# hi_conga&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($z_choice&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# low_conga&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;n(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;, @notes);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;write_score(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;perldotcom-5.mid&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that sounds like:&lt;/p&gt;

&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/musical-rhythms-with-math-in-perl/perldotcom-5.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;How about Euclidean patterns? What are they, and why are they named for a geometer?&lt;/p&gt;
&lt;p&gt;Euclidean patterns are a set number of positions &lt;code&gt;P&lt;/code&gt; that are filled with a number of beats &lt;code&gt;Q&lt;/code&gt; that is less than or equal to &lt;code&gt;P&lt;/code&gt;. They are named for Euclid because they are generated by applying the &amp;ldquo;Euclidean algorithm,&amp;rdquo; which was originally designed to find the greatest common divisor (GCD) of two numbers, to distribute musical beats as evenly as possible.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; MIDI::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(setup_score)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Music::CreatingRhythms ();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $mcr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Music::CreatingRhythms&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $beats &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $s_seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;rotate_n(&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, $beats)); &lt;span style=&#34;color:#75715e&#34;&gt;# snare&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $k_seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, $beats); &lt;span style=&#34;color:#75715e&#34;&gt;# kick&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $h_seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $mcr&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;euclid(&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, $beats); &lt;span style=&#34;color:#75715e&#34;&gt;# hi-hats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; setup_score(bpm &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, channel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) { &lt;span style=&#34;color:#75715e&#34;&gt;# repeats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $i (&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; $beats &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) { &lt;span style=&#34;color:#75715e&#34;&gt;# pattern position&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @notes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($s_seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# snare&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($k_seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# kick&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($h_seq&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;[$i]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @notes, &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;# hi-hats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (@notes) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;n(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;, @notes);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;r(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$score&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;write_score(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;perldotcom-6.mid&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;audio controls&gt;
  &lt;source src=&#34;https://www.perl.com/media/musical-rhythms-with-math-in-perl/perldotcom-6.mp3&#34; type=&#34;audio/mpeg&#34;&gt;
  Your browser does not support the audio element.
&lt;/audio&gt;


&lt;p&gt;Now we&amp;rsquo;re talkin&amp;rsquo; - an actual drum groove! To reiterate, the &lt;code&gt;euclid()&lt;/code&gt; method distributes a number of beats, like &lt;code&gt;2&lt;/code&gt; or &lt;code&gt;11&lt;/code&gt;, over the number of beats, &lt;code&gt;16&lt;/code&gt;. The kick and snare use the same arguments, but the snare pattern is rotated by 4 beats, so that they alternate.&lt;/p&gt;
&lt;h2 id=&#34;so-what-have-we-learned-today&#34;&gt;So what have we learned today?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;That you can use mathematical functions to generate sequences to represent rhythmic patterns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;That you can play an entire sequence or simultaneous notes with MIDI.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;references&#34;&gt;References:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/ology/Music/tree/master/mrwmip/&#34;&gt;Article repository&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://abrazol.com/books/rhythm1/&#34;&gt;Creating Rhythms book&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/MIDI::RtMidi::FFI::Device&#34;&gt;MIDI::RtMidi::FFI::Device&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/MIDI::Util&#34;&gt;MIDI::Util&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/Music::CreatingRhythms&#34;&gt;Music::CreatingRhythms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.fluidsynth.org/&#34;&gt;fluidsynth&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://wiki.archlinux.org/title/Timidity++&#34;&gt;timidity&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Fastmail Donates USD 10,000 to The Perl and Raku Foundation</title>
      <link>https://www.perl.com/article/fastmail-donates-usd-10-000-to-the-perl-and-raku-foundation/</link>
      <pubDate>Wed, 04 Feb 2026 00:45:55 +0000</pubDate>
      <guid>https://www.perl.com/article/fastmail-donates-usd-10-000-to-the-perl-and-raku-foundation/</guid>
      <description>&lt;p&gt;2025 was a tough year for &lt;a href=&#34;https://perlfoundation.org/&#34;&gt;The Perl and Raku Foundation&lt;/a&gt; (TPRF). Funds were sorely needed. The community grants program had been paused due to budget constraints and we were in danger of needing to pause the Perl 5 core maintenance grants. &lt;a href=&#34;https://www.fastmail.com/&#34;&gt;Fastmail&lt;/a&gt; stepped up with a USD 10,000 donation and helped TPRF to continue to support Perl 5 core maintenance. Ricardo Signes explains why Fastmail helped keep this very important work on track.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Perl has served us quite well since Fastmail&amp;rsquo;s inception.  We&amp;rsquo;ve built up a large code base that has continued to work, grow, and improve over twenty years.  We&amp;rsquo;ve stuck with Perl because Perl stuck with us:  it kept working and growing and improving, and very rarely did those improvements require us to stop the world and adapt to onerous changes.  We know that kind of stability is, in part, a function of the developers of Perl, whose time is spent figuring out how to make Perl better without also making it worse.  The money we give toward those efforts is well-spent, because it keeps the improvements coming and the language reliable.&lt;/p&gt;
&lt;p&gt;— Ricardo Signes, Director &amp;amp; Chief Developer Experience Officer, Fastmail&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the reasons that you don&amp;rsquo;t hear about Perl in the headlines is its reliability. Upgrading your Perl from one version to the next? That can be a very boring deployment. You code worked before and it continues to &amp;ldquo;just work&amp;rdquo; after the upgrade. You don&amp;rsquo;t need to rant about short deprecation cycles, performance degradation or dependencies which no longer install. The Perl 5 core maintainers take great care to ensure that you don&amp;rsquo;t have to care very much about upgrading your Perl. Backwards compatibility is top of mind. If your deployment is boring, it&amp;rsquo;s because a lot of care and attention has been given to this matter by the people who love Perl and love to work on it.&lt;/p&gt;
&lt;p&gt;As we moved to secure TPRF&amp;rsquo;s 2025 budget, we reached out to organizations which rely on Perl. A number of these companies immediately offered to help. Fastmail has already been a supporter of TPRF for quite some time. In addition to this much needed donation, Fastmail has been providing rock solid free email hosting to the foundation for many years.&lt;/p&gt;
&lt;p&gt;While Fastmail&amp;rsquo;s donation has been allocated towards Perl 5 Core maintenance, TPRF is now in the position to re-open &lt;a href=&#34;https://perlfoundation.org/grants.html&#34;&gt;the community grants program&lt;/a&gt;, funding it with USD 10,000 for 2026. There is also an opportunity to increase the community grants funding if sponsor participation increases. As we begin our 2026 fundraising, we are looking to cast a wider net and bring more sponsor organizations on board to help support healthy Perl and Raku ecosystems.&lt;/p&gt;
&lt;p&gt;Maybe your organization will be the one to help us double our community grants budget in 2026. To &lt;a href=&#34;https://perlfoundation.org/how-do-sponsors-benefit.html&#34;&gt;become a sponsor&lt;/a&gt;, contact:
&lt;a href=&#34;mailto:olaf@perlfoundation.org&#34;&gt;olaf@perlfoundation.org&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Cast-Iron Community: Your Chance to Sponsor TPRC 2026</title>
      <link>https://www.perl.com/article/cast-iron-community-your-chance-to-sponsor-tprc-2026/</link>
      <pubDate>Tue, 03 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://www.perl.com/article/cast-iron-community-your-chance-to-sponsor-tprc-2026/</guid>
      <description>&lt;p&gt;&amp;ldquo;Perl is my cast-iron pan - reliable, versatile, durable, and continues to be
ever so useful.&amp;rdquo; &lt;a href=&#34;https://tprc.us/tprc-2026-gsp/&#34;&gt;TPRC 2026&lt;/a&gt; brings together a
community that embodies all of these qualities, and we&amp;rsquo;re looking for sponsors
to help make this special gathering possible.&lt;/p&gt;
&lt;h3 id=&#34;about-the-conference&#34;&gt;About the Conference&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://tprc.us/tprc-2026-gsp/&#34;&gt;The Perl and Raku Conference 2026&lt;/a&gt; is a
community-organized gathering of developers, enthusiasts, and industry
professionals. It takes place from June 26-28, 2026, in Greenville, South Carolina.
The conference will feature an intimate, single-track format that promises high sponsor visibility.
We look forward to approximately 80 participants with some of those staying in town for the shoulder days (June 25-29) and a Monday
workshop.&lt;/p&gt;
&lt;h3 id=&#34;why-sponsor&#34;&gt;Why Sponsor?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Give back to the language and communities which have already given so much to you&lt;/li&gt;
&lt;li&gt;Connect with the developers and craftspeople who build your tools &amp;ndash; the ones that are built to last&lt;/li&gt;
&lt;li&gt;Help to ensure that The Perl and Raku Foundation can continue to fund Perl 5 core maintenance and Community Grants&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;sponsorship-tiers&#34;&gt;Sponsorship Tiers&lt;/h3&gt;
&lt;h4 id=&#34;platinum-sponsor-6000&#34;&gt;Platinum Sponsor ($6,000)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Only 1 sponsorship is available at this level&lt;/li&gt;
&lt;li&gt;Premium logo placement on conference website&lt;/li&gt;
&lt;li&gt;This donation qualifies your organization to be a Bronze Level Sponsor of The Perl and Raku Foundation&lt;/li&gt;
&lt;li&gt;5-minute speaking slot during opening ceremony&lt;/li&gt;
&lt;li&gt;2 complimentary conference passes&lt;/li&gt;
&lt;li&gt;Priority choice of rollup banner placement&lt;/li&gt;
&lt;li&gt;Logo prominently displayed on conference badges&lt;/li&gt;
&lt;li&gt;First choice of major named sponsorship (Conference Dinner, T-shirts, or Swag Bags)&lt;/li&gt;
&lt;li&gt;Logo on main stage backdrop and conference banners&lt;/li&gt;
&lt;li&gt;Social media promotion&lt;/li&gt;
&lt;li&gt;All benefits of lower tiers&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;gold-sponsor-4000&#34;&gt;Gold Sponsor ($4,000)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Logo on all conference materials&lt;/li&gt;
&lt;li&gt;One complimentary conference pass&lt;/li&gt;
&lt;li&gt;Rollup banner on display&lt;/li&gt;
&lt;li&gt;Choice of named sponsorship (Lunch or Snacks)&lt;/li&gt;
&lt;li&gt;Logo on backdrop and banners&lt;/li&gt;
&lt;li&gt;Dedicated social media recognition&lt;/li&gt;
&lt;li&gt;All benefits of lower tiers&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;silver-sponsor-2000&#34;&gt;Silver Sponsor ($2,000)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Logo on conference website&lt;/li&gt;
&lt;li&gt;Logo on backdrop and banners&lt;/li&gt;
&lt;li&gt;Choice of smaller named sponsorship (Beverage Bars)&lt;/li&gt;
&lt;li&gt;Social media mention&lt;/li&gt;
&lt;li&gt;All benefits of lower tier&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;bronze-sponsor-1000&#34;&gt;Bronze Sponsor ($1,000)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Name/logo on conference website&lt;/li&gt;
&lt;li&gt;Name/logo on backdrop and banners&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;all-sponsors-receive&#34;&gt;All Sponsors Receive&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Logo/name in Update::Daily conference newsletter sidebar&lt;/li&gt;
&lt;li&gt;Opportunity to provide materials for conference swag bags&lt;/li&gt;
&lt;li&gt;Recognition during opening and closing ceremonies&lt;/li&gt;
&lt;li&gt;Listed on conference website sponsor page&lt;/li&gt;
&lt;li&gt;Mentioned in conference social media&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;named-sponsorship-opportunities&#34;&gt;Named Sponsorship Opportunities&lt;/h3&gt;
&lt;p&gt;Exclusive naming rights available for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Conference Dinner ($2,000) - Signage on tables and buffet&lt;/li&gt;
&lt;li&gt;Conference Swag Bags ($1,500) - Logo on bags&lt;/li&gt;
&lt;li&gt;Conference T-Shirts ($1,500) - Logo on sleeve&lt;/li&gt;
&lt;li&gt;Lunches ($1,500) - Signage at pickup and on menu tickets&lt;/li&gt;
&lt;li&gt;Snacks ($1,000) - Signage at snack bar&lt;/li&gt;
&lt;li&gt;Update::Daily Printing ($200) - Logo on masthead&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;about-the-perl-and-raku-foundation&#34;&gt;About The Perl and Raku Foundation&lt;/h3&gt;
&lt;p&gt;Proceeds beyond conference expenses support The Perl and Raku Foundation, a non-profit organization dedicated to advancing the Perl and Raku programming languages through open source development, education, and community building.&lt;/p&gt;
&lt;h3 id=&#34;contact-information&#34;&gt;Contact Information&lt;/h3&gt;
&lt;p&gt;For more information on how to become a sponsor, please contact:
&lt;a href=&#34;mailto:olaf@perlfoundation.org&#34;&gt;olaf@perlfoundation.org&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Podlite comes to Perl: a lightweight block-based markup language for everyday use</title>
      <link>https://www.perl.com/article/podlite-comes-to-perl-a-lightweight-block-based-markup-language-for-everyday-use/</link>
      <pubDate>Tue, 27 Jan 2026 18:13:16 +0000</pubDate>
      <guid>https://www.perl.com/article/podlite-comes-to-perl-a-lightweight-block-based-markup-language-for-everyday-use/</guid>
      <description>&lt;p&gt;My name is Alex. Over the last years I’ve implemented several versions of the Raku&amp;rsquo;s documentation format (Synopsys 26 / Raku&amp;rsquo;s Pod) in Perl and JavaScript.&lt;/p&gt;
&lt;p&gt;At an early stage, I shared the idea of creating a lightweight version of Raku&amp;rsquo;s Pod, with Damian Conway, the original author of the Synopsys 26 Documentation specification (S26).
He was supportive of the concept and offered several valuable insights that helped shape the vision of what later became Podlite.&lt;/p&gt;
&lt;p&gt;Today, Podlite is a small block-based markup language that is easy to read as plain text, simple to parse, and flexible enough to be used everywhere — in code, notes, technical documents, long-form writing, and even full documentation systems.&lt;/p&gt;
&lt;p&gt;This article is an introduction for the Perl community — what Podlite is, how it looks, how you can already use it in Perl via a source filter, and what’s coming next.&lt;/p&gt;
&lt;h2 id=&#34;the-block-structure-of-podlite&#34;&gt;The Block Structure of Podlite&lt;/h2&gt;
&lt;p&gt;One of the core ideas behind Podlite is its consistent block-based structure.
Every meaningful element of a document — a heading, a paragraph, a list item, a table, a code block, a callout — is represented as a block. This makes documents both readable for humans and predictable for tools.&lt;/p&gt;
&lt;p&gt;Podlite supports three interchangeable block styles: &lt;code&gt;delimited&lt;/code&gt;, &lt;code&gt;paragraph&lt;/code&gt;, and &lt;code&gt;abbreviated&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;abbreviated-blocks-block&#34;&gt;Abbreviated blocks (&lt;code&gt;=BLOCK&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;This is the most compact form.
A block starts with &lt;code&gt;=&lt;/code&gt; followed by the block name.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;head1 Installation Guide
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;item Perl &lt;span style=&#34;color:#ae81ff&#34;&gt;5.8&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; newer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;para This tool automates the process&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;ends on the next directive or a blank line&lt;/li&gt;
&lt;li&gt;best used for simple one-line blocks&lt;/li&gt;
&lt;li&gt;cannot include configuration options (attributes)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;paragraph-blocks-for-block&#34;&gt;Paragraph blocks (&lt;code&gt;=for BLOCK&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;Use this form when you want a multi-line block or need attributes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; code :lang&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;perl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;say &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello from Podlite!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;ends when a blank line appears&lt;/li&gt;
&lt;li&gt;can include complex content&lt;/li&gt;
&lt;li&gt;allows attributes such as &lt;code&gt;:lang&lt;/code&gt;, &lt;code&gt;:id&lt;/code&gt;, &lt;code&gt;:caption&lt;/code&gt;, &lt;code&gt;:nested&lt;/code&gt;, …&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;delimited-blocks-begin-block--end-block&#34;&gt;Delimited blocks (&lt;code&gt;=begin BLOCK&lt;/code&gt; … &lt;code&gt;=end BLOCK&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;The most expressive form. Useful for large sections, nested blocks, or structures that require clarity.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;begin nested :notify&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;important&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Make sure you have administrator privileges&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;end nested
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;explicit start and end markers&lt;/li&gt;
&lt;li&gt;perfect for code, lists, tables, notifications, markdown, formulas&lt;/li&gt;
&lt;li&gt;can contain other blocks, including nested ones&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These block styles differ in syntax convenience, but all produce the same internal structure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/podlite-comes-to-perl-a-lightweight-block-based-markup-language-for-everyday-use/podlite-blocktypes.png&#34; alt=&#34;diagram here showing the three block styles and how they map to the same internal structure&#34;&gt;&lt;/p&gt;
&lt;p&gt;Regardless of which syntax you choose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;all three forms represent the same &lt;strong&gt;block type&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;attributes apply the same way (&lt;code&gt;:lang&lt;/code&gt;, &lt;code&gt;:caption&lt;/code&gt;, &lt;code&gt;:id&lt;/code&gt;, …)&lt;/li&gt;
&lt;li&gt;tools and renderers treat them uniformly&lt;/li&gt;
&lt;li&gt;nested blocks work identically&lt;/li&gt;
&lt;li&gt;you can freely mix styles inside a document&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;example-comparing-pod-and-podlite&#34;&gt;Example: Comparing POD and Podlite&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s see how the same document looks in traditional POD versus Podlite:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/podlite-comes-to-perl-a-lightweight-block-based-markup-language-for-everyday-use/pod-podlite.png&#34; alt=&#34;POD vs Podlite&#34;&gt;&lt;/p&gt;
&lt;p&gt;Each block has clear boundaries, so you don&amp;rsquo;t need blank lines between them. This makes your documentation more compact and easier to read.
This is one of the reasons Podlite remains compact yet powerful:
the syntax stays flexible, while the underlying document model stays clean and consistent.&lt;/p&gt;
&lt;p&gt;This Podlite example rendered as on the following screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/podlite-comes-to-perl-a-lightweight-block-based-markup-language-for-everyday-use/podlite-renderexample.png&#34; alt=&#34;Podlite example&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;inside-the-podlite-specification-10&#34;&gt;Inside the Podlite Specification 1.0&lt;/h2&gt;
&lt;p&gt;One important point about Podlite is that it is first and foremost a specification.
It does not belong to any particular programming language, platform, or tooling ecosystem.
The specification defines the document model, syntax rules, and semantics.&lt;/p&gt;
&lt;p&gt;From the Podlite 1.0 specification, notable features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;headings (&lt;code&gt;=head1&lt;/code&gt;, &lt;code&gt;=head2&lt;/code&gt;, …)&lt;/li&gt;
&lt;li&gt;lists and definition lists, and including task lists&lt;/li&gt;
&lt;li&gt;tables (simple and advanced)&lt;/li&gt;
&lt;li&gt;CSV-backed tables&lt;/li&gt;
&lt;li&gt;callouts / notifications (&lt;code&gt;=nested :notify&amp;lt;tip|warning|important|note|caution&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;table of contents (&lt;code&gt;=toc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;includes (&lt;code&gt;=include&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;embedded data (&lt;code&gt;=data&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;pictures (&lt;code&gt;=picture&lt;/code&gt; and inline &lt;code&gt;P&amp;lt;&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;formulas (&lt;code&gt;=formula&lt;/code&gt; and inline &lt;code&gt;F&amp;lt;&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;user defined blocks and markup codes&lt;/li&gt;
&lt;li&gt;Markdown integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;=markdown&lt;/code&gt; block is part of the standard block set defined by the Podlite Specification 1.0.
This means Markdown is not an add-on or optional plugin — it is a fully integrated, first-class component of the language.&lt;/p&gt;
&lt;p&gt;Markdown content becomes part of Podlite’s unified document structure, and its headings merge naturally with Podlite headings inside the TOC and document outline.&lt;/p&gt;
&lt;p&gt;Below is a screenshot showing how Markdown inside Perl is rendered in the in-development VS Code extension, demonstrating both the block structure and live preview:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/podlite-comes-to-perl-a-lightweight-block-based-markup-language-for-everyday-use/podlite_for_perl.png&#34; alt=&#34;Podlite source, including =markdown block&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;using-podlite-in-perl-via-the-source-filter&#34;&gt;Using Podlite in Perl via the source filter&lt;/h2&gt;
&lt;p&gt;To make Podlite directly usable in Perl code, there is a module on CPAN:
&lt;a href=&#34;https://metacpan.org/pod/Podlite&#34;&gt;Podlite&lt;/a&gt;  — Use Podlite markup language in Perl programs&lt;/p&gt;
&lt;p&gt;A minimal example could look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Podlite; &lt;span style=&#34;color:#75715e&#34;&gt;# enable Podlite blocks inside Perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;head1 Quick Example
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;begin markdown
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Podlite can live inside your Perl programs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;end markdown
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Podlite active\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;roadmap-whats-next-for-podlite&#34;&gt;Roadmap: what&amp;rsquo;s next for Podlite&lt;/h2&gt;
&lt;p&gt;Podlite continues to grow, and the Specification 1.0 is only the beginning.
Several areas are already in active development, and more will evolve with community feedback.&lt;/p&gt;
&lt;p&gt;Some of the things currently planned or in progress:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLI tools
&lt;ul&gt;
&lt;li&gt;command-line utilities for converting Podlite to HTML, PDF, man pages, etc.&lt;/li&gt;
&lt;li&gt;improve pipelines for building documentation sites from Podlite sources&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;VS Code integration&lt;/li&gt;
&lt;li&gt;Ecosystem growth
&lt;ul&gt;
&lt;li&gt;develop comprehensive documentation and tutorials&lt;/li&gt;
&lt;li&gt;community-driven block types and conventions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;try-podlite-and-share-feedback&#34;&gt;Try Podlite and share feedback&lt;/h2&gt;
&lt;p&gt;If this resonates with you, I’d be very happy to hear from you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ideas for useful block types&lt;/li&gt;
&lt;li&gt;suggestions for tools or integrations&lt;/li&gt;
&lt;li&gt;feedback on the syntax and specification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/podlite/podlite-specs/discussions&#34;&gt;https://github.com/podlite/podlite-specs/discussions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Even small contributions — a comment, a GitHub star, or trying an early tool — help shape the future of the specification and encourage further development.&lt;/p&gt;
&lt;p&gt;Useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPAN: &lt;a href=&#34;https://metacpan.org/pod/Podlite&#34;&gt;https://metacpan.org/pod/Podlite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub:&lt;a href=&#34;https://github.com/podlite&#34;&gt;https://github.com/podlite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Specification
&lt;ul&gt;
&lt;li&gt;(HTML): &lt;a href=&#34;https://podlite.org/specification&#34;&gt;https://podlite.org/specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;source: &lt;a href=&#34;https://github.com/podlite/podlite-specs&#34;&gt;https://github.com/podlite/podlite-specs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Project site: &lt;a href=&#34;https://podlite.org&#34;&gt;https://podlite.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Roadmap: &lt;a href=&#34;https://podlite.org/#Roadmap&#34;&gt;https://podlite.org/#Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for reading,
&lt;strong&gt;Alex&lt;/strong&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Announcing the Perl Toolchain Summit 2026</title>
      <link>https://www.perl.com/article/announcing-the-perl-toolchain-summit-2026/</link>
      <pubDate>Fri, 23 Jan 2026 12:00:00 +0000</pubDate>
      <guid>https://www.perl.com/article/announcing-the-perl-toolchain-summit-2026/</guid>
      <description>&lt;h1 id=&#34;announcing-the-perl-toolchain-summit-2026&#34;&gt;Announcing the Perl Toolchain Summit 2026!&lt;/h1&gt;
&lt;p&gt;The organizers have been working behind the scenes since last September,
and today I&amp;rsquo;m happy to announce that the 16th Perl Toolchain Summit will
be held in Vienna, Austria, from Thursday April 23rd till Sunday April
26th, 2026.&lt;/p&gt;
&lt;p&gt;This post is brought to you by &lt;a href=&#34;https://www.simplelists.com/&#34;&gt;Simplelists&lt;/a&gt;, a group email and
mailing list service provider, and a recurring sponsor of the Perl
Toolchain Summit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/announcing-the-perl-toolchain-summit-2026/simplelists-logo.png&#34; alt=&#34;Simplelists logo&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Started in 2008 as the Perl QA Hackathon in Oslo, the Perl Toolchain
Summit is an annual event that brings together the key developers
working on the Perl toolchain. Each year (except for 2020-2022), the
event moves from country to country all over Europe, organised by local
teams of volunteers. The surplus money from previous summits helps fund
the next one.&lt;/p&gt;
&lt;p&gt;Since 2023, the organizing team is formally split between a &amp;ldquo;global&amp;rdquo;
team and a &amp;ldquo;local&amp;rdquo; team (although this setup has been informally
used before).&lt;/p&gt;
&lt;p&gt;The global team is made up of veteran PTS organizers, who deal with
invitations, finding sponsors, paying bills and communications. They are
Laurent Boivin (&lt;a href=&#34;https://metacpan.org/author/ELBEHO&#34;&gt;ELBEHO&lt;/a&gt;), Philippe
Bruhat (&lt;a href=&#34;https://metacpan.org/author/BOOK&#34;&gt;BOOK&lt;/a&gt;), Thibault Duponchelle
(&lt;a href=&#34;https://metacpan.org/author/CONTRA&#34;&gt;CONTRA&lt;/a&gt;), Tina Müller
(&lt;a href=&#34;https://metacpan.org/author/TINITA&#34;&gt;TINITA&lt;/a&gt;) and Breno de Oliveira
(&lt;a href=&#34;https://metacpan.org/author/GARU&#34;&gt;GARU&lt;/a&gt;), supported by &lt;a href=&#34;https://www.mongueurs.net/&#34;&gt;Les Mongueurs
de Perl&lt;/a&gt;&amp;rsquo;s bank account.&lt;/p&gt;
&lt;p&gt;The local team members for this year have organized several events in
Vienna (including the Perl QA Hackathon 2010!) and deal with finding the
venue, the hotel, the catering and welcoming our attendees in Vienna in
April. They are Alexander Hartmaier
(&lt;a href=&#34;https://metacpan.org/author/ABRAXXA&#34;&gt;ABRAXXA&lt;/a&gt;), Thomas Klausner
(&lt;a href=&#34;https://metacpan.org/author/DOMM&#34;&gt;DOMM&lt;/a&gt;), Maroš Kollár
(&lt;a href=&#34;https://metacpan.org/author/MAROS&#34;&gt;MAROS&lt;/a&gt;), Michael Kröll and Helmut
Wollmersdorfer (&lt;a href=&#34;https://metacpan.org/author/WOLLMERS&#34;&gt;WOLLMERS&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The developers who maintain CPAN and associated tools and services are
all volunteers, scattered across the globe. This event is the one time
in the year when they can get together.&lt;/p&gt;
&lt;p&gt;The summit provides dedicated time to work on the critical systems and
tools, with all the right people in the same room. The attendees hammer
out solutions to thorny problems and discuss new ideas to keep the
toolchain moving forward. This year, about 40 people have been invited,
with 35 participants expected to join us in Vienna.&lt;/p&gt;
&lt;p&gt;If you want to find out more about the work being done at the Toolchain
Summit, and hear the teams and people involved, you can listen to
several episodes of &lt;a href=&#34;https://underbar.cpan.io/&#34;&gt;The Underbar&lt;/a&gt; podcast,
which were recorded during the 2025 edition in Leipzig, Germany:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://underbar.cpan.io/episodes/3/&#34;&gt;MetaCPAN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://underbar.cpan.io/episodes/5/&#34;&gt;Test::Smoke&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://underbar.cpan.io/episodes/6/&#34;&gt;CPAN Testers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://underbar.cpan.io/episodes/7/&#34;&gt;CPAN Security Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://underbar.cpan.io/episodes/8/&#34;&gt;A tangent about the Perl Toolchain Summit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given the important nature of the attendees&amp;rsquo; work and their volunteer
status, we try to pay for most expenses (travel, lodging, food, etc.)
through sponsorship. If you&amp;rsquo;re interested in helping sponsor the summit,
please get in touch with the global team at
&lt;a href=&#34;mailto:pts2026@perltoolchainsummit.org&#34;&gt;pts2026@perltoolchainsummit.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.simplelists.com/&#34;&gt;Simplelists&lt;/a&gt; has been sponsoring the Perl Toolchain Summit for several
years now. We are very grateful for their continued support.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Simplelists is proud to sponsor the 2026 Perl Toolchain Summit, as Perl
forms the core of our technology stack. We are grateful that we can rely
on the robust and comprehensive Perl ecosystem, from the core of Perl
itself to a whole myriad of CPAN modules. We are glad that the PTS
continues its unsung work, ensuring that Simplelists can continue to
rely on these many tools.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>DuckDuckGo Donates $25,000 to The Perl and Raku Foundation v2025</title>
      <link>https://www.perl.com/article/duckduckgo-donates-25-000-to-the-perl-and-raku-foundation-v2025/</link>
      <pubDate>Wed, 01 Oct 2025 12:00:00 +0000</pubDate>
      <guid>https://www.perl.com/article/duckduckgo-donates-25-000-to-the-perl-and-raku-foundation-v2025/</guid>
      <description>&lt;p&gt;For the second consecutive year, The Perl and Raku Foundation (TPRF) is
overjoyed to announce &lt;a href=&#34;https://spreadprivacy.com/2025-duckduckgo-charitable-donations/&#34;&gt;a donation of USD 25,000 from
DuckDuckGo&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DuckDuckGo has demonstrated how Perl and its ecosystem can deliver power and
scale to drive the DuckDuckGo core systems, plug-in framework and Instant
Answers. The Foundation is grateful that DuckDuckGo recognises the importance
of Perl, and for their generous funding support for a second year through
their charitable donations programme.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;ndash; Stuart J Mackintosh, President of The Perl and Raku Foundation&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.perl.com/article/duckduckgo-donates-25-000-to-the-perl-and-raku-foundation/&#34;&gt;Last year&amp;rsquo;s donation of USD 25,000 from
DuckDuckGo&lt;/a&gt;
was instrumental in helping to fund the foundation&amp;rsquo;s Core Perl Maintenance Fund
and this year&amp;rsquo;s donation will help to fund more of the same crucial work that
keeps the Perl language moving forward.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/duck-duck-go/fireworks.jpg&#34; alt=&#34;Fireworks celebration&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org/author/PEVANS&#34;&gt;Paul &amp;ldquo;LeoNerd&amp;rdquo; Evans&lt;/a&gt; is one of the
developers who gets regular funding from the Perl Core Maintenance Fund. Here
is a short list of just some of the many contributions which Paul has made to
core Perl as part of the maintenance fund work:&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&#34;https://perldoc.perl.org/builtin&#34;&gt;builtin&lt;/a&gt; module (5.36), making
available many new useful language-level utilities that were previously
loaded from modules like &lt;a href=&#34;https://metacpan.org/pod/Scalar::Util&#34;&gt;Scalar::Util&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The complete &lt;a href=&#34;https://perldoc.perl.org/feature#The-&#39;class&#39;-feature&#34;&gt;feature
&amp;lsquo;class&amp;rsquo;&lt;/a&gt; system
(5.38), adding proper object-orientation syntax and abilities&lt;/li&gt;
&lt;li&gt;Lexical method support (5.42), adding &lt;code&gt;my method&lt;/code&gt; and
the &lt;code&gt;$obj-&amp;gt;&amp;amp;method&lt;/code&gt; invocation syntax for better object encapsulation&lt;/li&gt;
&lt;li&gt;Stabilising some of the recent experiments - signatures (5.36),
try/catch (5.40), foreach on multiple vars (5.40)&lt;/li&gt;
&lt;li&gt;Ability to use the //= and ||= operators in signatures (5.38),
performance improvements and named parameters (upcoming in next
release)&lt;/li&gt;
&lt;li&gt;The new &lt;a href=&#34;https://perldoc.perl.org/functions/any&#34;&gt;any&lt;/a&gt; and
&lt;a href=&#34;https://perldoc.perl.org/functions/all&#34;&gt;all&lt;/a&gt; keywords (5.42)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;We look forward to many more innovative contributions from Paul over the coming
year.&lt;/p&gt;
&lt;p&gt;While TPRF never takes continued support for granted, when it does arrive, it
allows the foundation to plan for the future with much greater confidence.
Multi-year partnerships with our sponsors allow us to continue to prioritize
important work, knowing that we will have the runway that we need to fund the
work which helps to sustain the Perl Language and its associated communities.&lt;/p&gt;
&lt;p&gt;For more information on how to become a sponsor, please contact:
&lt;a href=&#34;mailto:olaf@perlfoundation.org&#34;&gt;olaf@perlfoundation.org&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;&lt;a href=&#34;https://www.flickr.com/photos/67458569@N00/7722577066&#34;&gt;Fireworks&lt;/a&gt;&amp;quot; by &lt;a href=&#34;https://www.flickr.com/photos/67458569@N00&#34;&gt;colink.&lt;/a&gt; is licensed under &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/2.0/?ref=openverse&#34;&gt;CC BY-SA 2.0&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Geizhals Preisvergleich Donates USD 10,000 to The Perl and Raku Foundation</title>
      <link>https://www.perl.com/article/geizhals-donates-to-tprf/</link>
      <pubDate>Thu, 18 Sep 2025 09:30:00 +0000</pubDate>
      <guid>https://www.perl.com/article/geizhals-donates-to-tprf/</guid>
      <description>&lt;p&gt;Today The Perl and Raku Foundation is thrilled to announce a donation of USD
10,000 from &lt;a href=&#34;https://geizhals.at&#34;&gt;Geizhals Preisvergleich&lt;/a&gt;. This gift helps to
secure the future of The Perl 5 Core Maintenance Fund.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Perl has been an integral part of our product price comparison platform
from the start of the company 25 years ago. Supporting the Perl 5 Core
Maintenance Fund means supporting both present and future of a
substantial pillar of Modern Open Source Computing, for us and other
current or prospective users.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;ndash; Michael Kröll of Geizhals Preisvergleich&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Geizhals is not only providing core funding for the Perl ecosystem, but also
supporting developers, actively contributing to European conferences, and
employing Perl coders. Their interest in the strategic maintenance and
development of Perl and CPAN is of great value to us all, and their
investment is very much appreciated.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;ndash; Stuart J Mackintosh, President of The Perl and Raku Foundation&lt;/p&gt;
&lt;p&gt;But who exactly is Geizhals, and why does their support matter so much to the
Perl community?&lt;/p&gt;
&lt;p&gt;Geizhals Preisvergleich began in July of 1997 as a hobby project—and yes,
&amp;ldquo;Geizhals&amp;rdquo; literally translates to &amp;ldquo;skinflint&amp;rdquo; in English (they even operate
&lt;a href=&#34;https://skinflint.co.uk/&#34;&gt;skinflint.co.uk&lt;/a&gt; for UK users!). From those humble
beginnings, they&amp;rsquo;ve leveraged the power of Perl to scale up to serving &lt;a href=&#34;https://unternehmen.geizhals.at/&#34;&gt;4.3
million monthly users&lt;/a&gt;. With Perl being a key
part of their infrastructure, they have generously decided to support the Perl
5 Core Maintenance Fund.&lt;/p&gt;
&lt;p&gt;While many of us know about the Core Maintenance Fund, the specific problems it
addresses often remain invisible to users. I reached out to the maintainers
whose work is supported by this fund. This is what core maintainer Tony Cook
had to say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My work tends to be little things, I review other people&amp;rsquo;s work which I think
improves quality and velocity, and fix more minor issues, some examples would
be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a fix to signal handling where perl could crash where an external library
created threads (&lt;a href=&#34;https://github.com/perl/perl5/issues/22487&#34;&gt;#22487&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fix a segmentation fault in smartmatch against a sub if the sub exited via a
loop exit op (such as last)
(&lt;a href=&#34;https://github.com/perl/perl5/issues/16608&#34;&gt;#16608&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fixed a bug where a regexp warning could leak memory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;prevent a confusing undefined warning message when accessing a sub
parameter that was placeholder for a hash element indexed by an
undef key (&lt;a href=&#34;https://github.com/perl/perl5/issues/22423&#34;&gt;#22423&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;What Tony has highlighted are the kinds of bug fixes which collectively help to
ensure that Perl remains stable, secure and reliable for the many organisations
and individuals who depend on it.&lt;/p&gt;
&lt;p&gt;With organizations like Geizhals Preisvergleich funding the work which Tony and
others put into maintaining the Perl 5 core, we can work together to ensure that
the Perl core continues to receive the maintenance which it deserves, for many
years to come. Whether you&amp;rsquo;re a startup using Perl for rapid prototyping or an
enterprise running mission-critical systems, your support helps ensure Perl
remains reliable for everyone. Please join us on this journey.&lt;/p&gt;
&lt;p&gt;For more information on how to become a sponsor, please contact:
&lt;a href=&#34;mailto:olaf@perlfoundation.org&#34;&gt;olaf@perlfoundation.org&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Announcing Dancer2 2.0.0</title>
      <link>https://www.perl.com/article/announcing-dancer2-2-0-0/</link>
      <pubDate>Mon, 15 Sep 2025 18:00:00 +0000</pubDate>
      <guid>https://www.perl.com/article/announcing-dancer2-2-0-0/</guid>
      <description>&lt;h2 id=&#34;your-favorite-perl-web-framework-now-even-better&#34;&gt;Your Favorite Perl Web Framework, Now Even Better&lt;/h2&gt;
&lt;p&gt;The Dancer Core Team project is proud to announce the release of &lt;strong&gt;Dancer2 2.0.0&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;This release has been a long time coming, and while open source sometimes takes longer
than we’d like, we believe the wait has been worth it. With fresh documentation, architectural
improvements, and developer-friendly new features, version 2.0.0 represents a significant
evolution of Dancer2 and the Perl web ecosystem.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like a more extensive overview, I gave a talk at the &lt;a href=&#34;https://perlconference.us/tprc-2025-gsp/&#34;&gt;Perl and Raku Conference 2025&lt;/a&gt;,
about Dancer2 2.0.0, covering new features and where we’re headed next. You can watch the full presentation here:
&lt;a href=&#34;https://www.youtube.com/watch?v=pCTj-lT2Y40&amp;amp;list=PLA9_Hq3zhoFxvyYYyf9P2eYxitFRyEGza&amp;amp;index=4&amp;amp;pp=iAQB&#34;&gt;Dancer2 2.0.ohh myyy on YouTube →&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;release-highlights&#34;&gt;Release Highlights&lt;/h2&gt;
&lt;p&gt;Every major release is an opportunity to take stock of where a project stands and where it’s going. For Dancer2, 2.0.0 represents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A renewed commitment to documentation and developer experience&lt;/li&gt;
&lt;li&gt;A modernization of the framework’s core&lt;/li&gt;
&lt;li&gt;A better foundation for web development with Perl&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some of the most important changes included in Dancer2 2.0.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Brand New Documentation&lt;/strong&gt;
Thanks to a grant from the Perl and Raku Foundation, Dancer2’s documentation has been
completely rewritten. Clearer guides and reference materials will make it easier than
ever to get started and to master advanced features.
&lt;a href=&#34;https://perldancer.org/documentation&#34;&gt;Read the docs →&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Extendable Configuration System&lt;/strong&gt;
Thanks to first-time contributor &lt;strong&gt;Mikko Koivunalho&lt;/strong&gt;, the new configuration system
allows for greater flexibility in how Dancer2 applications are configured and extended.
Developers can build new configuration modules, and even integrate configuration systems
from other applications or frameworks.&lt;/p&gt;
&lt;p&gt;Building on this work, long-time core team member &lt;strong&gt;Yanick Champoux&lt;/strong&gt; enhanced the new
configuration system even further, enabling additional configuration readers to be
bootstrapped by the default one.
&lt;a href=&#34;https://metacpan.org/dist/Dancer2/view/lib/Dancer2/Manual/Config.pod&#34;&gt;Learn more in the config guide →&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A Leaner Core Distribution&lt;/strong&gt;
Why have two templating systems in the core framework when you can have zero?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Dancer2::Template::Simple&lt;/code&gt; has been removed from the core. It is now available
as a separate distribution on &lt;a href=&#34;https://metacpan.org/pod/Dancer2::Template::Simple&#34;&gt;MetaCPAN&lt;/a&gt;
for migration projects from Dancer 1.&lt;/li&gt;
&lt;li&gt;Our fork of &lt;code&gt;Template::Tiny&lt;/code&gt; has been retired, with its improvements merged upstream (thanks,
Karen Etheridge!), and &lt;code&gt;Dancer2::Template::TemplateTiny&lt;/code&gt; is now just an adapter for the official
version. &lt;a href=&#34;https://metacpan.org/pod/Dancer2::Template::Tiny&#34;&gt;See the documentation →&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Smarter Data Handling&lt;/strong&gt;
Dancer2 now supports configurable data/secrets censoring using &lt;code&gt;Data::Censor&lt;/code&gt;, helping developers
protect sensitive information in logs and debug pages. &lt;a href=&#34;https://metacpan.org/pod/Data::Censor&#34;&gt;Learn more about Data::Censor →&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Better Logging and Debugging&lt;/strong&gt;
Hooks are now logged as they are executed, and a brand-new hook — &lt;code&gt;on_hook_exception&lt;/code&gt; — provides
a way to handle unexpected issues more gracefully. &lt;a href=&#34;https://metacpan.org/dist/Dancer2/view/lib/Dancer2/Manual.pod#Hooks&#34;&gt;See the hooks documentation →&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CLI Improvements&lt;/strong&gt;
The command-line interface also received some attention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allows new Dancer2 applications to be scaffolded from the &lt;a href=&#34;https://metacpan.org/dist/Dancer2/view/lib/Dancer2/Manual/Tutorial.pod&#34;&gt;Dancer2 Tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Behind-the-scenes improvements to allow future scaffolding of plugins and extensions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;thank-you-to-our-contributors&#34;&gt;Thank You to Our Contributors&lt;/h2&gt;
&lt;p&gt;Dancer2 is what it is because of its community. A heartfelt thank you goes out to everyone who made this release possible, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mikko Koivunalho&lt;/strong&gt;, for his first-time contribution of the extendable configuration system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yanick Champoux&lt;/strong&gt;, for building on Mikko’s work and extending what&amp;rsquo;s possible with the new configuration system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Karen Etheridge&lt;/strong&gt;, for integrating improvements back into &lt;code&gt;Template::Tiny&lt;/code&gt;, helping us to streamline the Dancer2 core.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Perl and Raku Foundation&lt;/strong&gt;, for supporting the documentation grant that gave us our brand-new docs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sawyer X&lt;/strong&gt;, for being a great grant manager, and his endless patience, great suggestions, and encouragement throughout the grant process.&lt;/li&gt;
&lt;li&gt;And of course, everyone who tested, reported issues, submitted patches, and offered feedback along the way.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your contributions and support are what keep the project moving forward. ❤️&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;Dancer2 remains an active, community-driven project, and version 2.0.0 shows our continued dedication
to advancing the framework and supporting our community.&lt;/p&gt;
&lt;p&gt;We invite you to try out Dancer2 2.0.0, explore the new documentation, and join the conversation on
&lt;a href=&#34;https://github.com/PerlDancer/Dancer2&#34;&gt;GitHub&lt;/a&gt; or the project’s mailing list.&lt;/p&gt;
&lt;h2 id=&#34;keep-dancing&#34;&gt;Keep Dancing!&lt;/h2&gt;
&lt;p&gt;We’re excited about this release and can’t wait to see what the Perl community builds with it.&lt;/p&gt;
&lt;p&gt;Jason (CromeDome) and the Dancer2 Core Team&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My Guilty Perl Obsession</title>
      <link>https://www.perl.com/article/my-guilty-perl-obsession/</link>
      <pubDate>Tue, 26 Aug 2025 11:09:09 +0000</pubDate>
      <guid>https://www.perl.com/article/my-guilty-perl-obsession/</guid>
      <description>&lt;!-- markdownlint-disable MD033 MD013 MD012 --&gt;
&lt;h2 id=&#34;the-guilt&#34;&gt;The Guilt&lt;br&gt;&lt;/h2&gt;
&lt;p&gt;I consider myself successful.&lt;br&gt;
I&amp;rsquo;m 45, with a sportscar, a house, a family, and a small business now 30 years old.&lt;br&gt;
I made good decisions.&lt;br&gt;
My car is 15-years old, my monitors are 20-years old, my chair is 25-years old, my desk is 25-years old.&lt;br&gt;
My computers historically last 10-years; I just tossed a mouse that survived 8-years.&lt;br&gt;
Perl.&lt;br&gt;
My first venture into server-side web development was Lotus Notes&amp;hellip;that lasted 5 days.&lt;br&gt;
Perl has lasted me nearly 30 years, and we sure as hell ain&amp;rsquo;t done yet!&lt;br&gt;&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t meet the perl community until the Toronto conference in 2023.&lt;br&gt;
That&amp;rsquo;s where I saw the faces.&lt;br&gt;
That&amp;rsquo;s when I saw the humanity.&lt;br&gt;
That&amp;rsquo;s why I felt the guilt.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d paid for my car, for my house, for my computers, my desk, and my chair.&lt;br&gt;
Perl came for free.&lt;br&gt;
I didn&amp;rsquo;t pay for it; I didn&amp;rsquo;t work for it.&lt;br&gt;
But at the conference, before me stood the people who did.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve worked hard for my success.&lt;br&gt;
It&amp;rsquo;s clear to me now that I wasn&amp;rsquo;t the only one working hard for my success.&lt;br&gt;
How much of your success is because of all of their hard work?&lt;br&gt;
And how much have you contributed in return?&lt;br&gt;
Me? I contributed absolutely nothing.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s my guilt.&lt;br&gt;
But this is the very best kind of guilt.&lt;br&gt;
Because it&amp;rsquo;s not too late!&lt;br&gt;
In fact, now is the perfect time.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve hired members of the perl community.&lt;br&gt;
That&amp;rsquo;s a start.&lt;br&gt;
I&amp;rsquo;m donating directly to the foundation.&lt;br&gt;
And I intend to continue doing so.&lt;br&gt;
If my success depends on perl, then perl depends on my success.&lt;br&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-pleasure&#34;&gt;The Pleasure&lt;/h2&gt;
&lt;p&gt;Perl was always a perfect fit for me.&lt;br&gt;
As a syntax, it was concise, yet flexible.&lt;br&gt;
My code&amp;rsquo;s form could mirror its function.&lt;br&gt;
How perfectly splendid.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Perl is the basis of Holophrastic&amp;rsquo;s web development platform: Pipelines.&lt;br&gt;
As new popular languages have come along, they&amp;rsquo;re touted as the best new amazing modern.&lt;br&gt;
And then they vanish, supplanted by the very next amazing.&lt;br&gt;
Had I invested in each, I&amp;rsquo;d have more archives of code than formats of music albums.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Instead, I rest easy in the knowledge that perl will always keep up.&lt;br&gt;
xls, xlsx, pdf, mysql, mariadb, imagemagick, json, curl&lt;br&gt;
There will always be a cpan module waiting for me when I need it.&lt;br&gt;
My clients have no idea the power that I wield with my fingertips.&lt;br&gt;
AI translations, image processing craziness, survey systems built for 100&#39;000 concurrent test-takers&amp;hellip;&lt;br&gt;
And while all of that definitely took some expertise on my end,&lt;br&gt;
Perl created exactly zero hurdles,&lt;br&gt;
It never got in the way at all.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Longevity.&lt;br&gt;
Perl&amp;rsquo;s got it.&lt;br&gt;
The Perl Foundation&amp;rsquo;s got it.&lt;br&gt;
The Perl Community&amp;rsquo;s got it.&lt;br&gt;
I&amp;rsquo;ve got it.&lt;br&gt;
The lines between?  All blurry.&lt;br&gt;&lt;/p&gt;
&lt;h2 id=&#34;holophrastic-enterprises-inc&#34;&gt;Holophrastic Enterprises Inc.&lt;/h2&gt;
&lt;p&gt;Custom Business Web Development&lt;br&gt;&lt;/p&gt;
&lt;p&gt;I work as what I&amp;rsquo;d call an inside-contractor.&lt;br&gt;
I&amp;rsquo;m a speed-dial (is that still a thing?) phone call away.&lt;br&gt;
Occasionally, a client will call me a dozen times in a day.&lt;br&gt;
I&amp;rsquo;m closer than their colleague in the next office.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;It usually starts with &amp;ldquo;a website&amp;rdquo;&lt;br&gt;
the typical sales- or marketing-oriented something-pretty&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Then business takes over.&lt;br&gt;
A product-configuration wizard&lt;br&gt;
A sales-commission calculator&lt;br&gt;
Can you connect to our accounting software and provide the reports that it can&amp;rsquo;t?&lt;br&gt;
Can you replace our warehouse-production backend?&lt;br&gt;
What about a portal?&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Yes; yes I can.&lt;br&gt;
One business obstacle at a time.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Now, 30 years later, I often encounter code &amp;amp; comments, decades old.&lt;br&gt;
The feeling I get from seeing a line that&amp;rsquo;s now older than the me who wrote it&amp;hellip;&lt;br&gt;
I used to feel alone.&lt;br&gt;
I now feel that thanks to the perl community, I was never alone.&lt;br&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Holophrastic is proud to be the first sponsor of the 2026 Perl and Raku Conference.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>SUSE Donates USD 11,500 to The Perl and Raku Foundation</title>
      <link>https://www.perl.com/article/suse-donates-to-tprf/</link>
      <pubDate>Thu, 07 Aug 2025 09:55:26 +0000</pubDate>
      <guid>https://www.perl.com/article/suse-donates-to-tprf/</guid>
      <description>&lt;p&gt;The Perl and Raku Foundation (TPRF) is thrilled to announce a substantial
$11,500 donation from &lt;a href=&#34;https://www.suse.com/&#34;&gt;SUSE&lt;/a&gt;, one of the world&amp;rsquo;s leading
enterprise Linux and cloud-native and AI solutions providers. This generous
contribution bolsters the Perl 5 Core Maintenance Fund and demonstrates SUSE&amp;rsquo;s
commitment to the open-source ecosystem.&lt;/p&gt;
&lt;p&gt;This donation from SUSE is actually made up of two parts. $10,000 is being
donated by SUSE LLC and an additional $1,500 is being provided by &lt;a href=&#34;https://www.suse.com/sustainability/&#34;&gt;The SUSE
Open Source Network&lt;/a&gt;, to support the
development and sustainability of Perl. This aligns with the network&amp;rsquo;s mission
to empower and support open source communities.&lt;/p&gt;
&lt;h2 id=&#34;perl-is-a-fundamental-component-of-the-suse-ecosystem&#34;&gt;Perl is a Fundamental Component of the SUSE Ecosystem&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;At SUSE, Perl is a fundamental component and member of our ecosystem,&amp;rdquo; explains
Miguel Pérez Colino, Director of Operations, Linux Product Management &amp;amp;
Marketing. &amp;ldquo;We provide it as part of our Linux offerings by actively supporting
Perl packages in SUSE Linux Enterprise and openSUSE. We use it extensively in
our toolset, powering among others OpenQA and Open Build Service, this last one
is used to build not just Linux packages but also Kubernetes.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;the-perl-powered-openqa-and-open-build-service&#34;&gt;The Perl-Powered OpenQA and Open Build Service&lt;/h2&gt;
&lt;p&gt;SUSE&amp;rsquo;s &lt;a href=&#34;https://open.qa/&#34;&gt;OpenQA&lt;/a&gt; project is an automated testing framework that
ensures quality across countless hardware configurations and software
combinations. At its heart is Perl, orchestrating complex test scenarios with
the reliability that system administrators have come to expect.&lt;/p&gt;
&lt;p&gt;Similarly, &lt;a href=&#34;https://openbuildservice.org/&#34;&gt;Open Build Service&lt;/a&gt; is running on
many services written in Perl. represents the modern evolution of package
management, creating not just traditional Linux packages but also container
images and Kubernetes distributions.&lt;/p&gt;
&lt;h2 id=&#34;sustaining-the-digital-commons&#34;&gt;Sustaining the Digital Commons&lt;/h2&gt;
&lt;p&gt;SUSE&amp;rsquo;s donation is a demonstration of digital stewardship—the recognition that
the tools we rely upon require active investment to remain secure, efficient,
and relevant.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;We are proudly donating to The Perl and Raku Foundation (TPRF) to ensure Perl&amp;rsquo;s
continued development and health, which is vital to the open-source world, we
are part of, and we champion,&amp;rdquo; Colino continues.&lt;/p&gt;
&lt;p&gt;This investment addresses some critical aspects of language maintenance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security Vigilance&lt;/strong&gt;: In an era of increasing cyber threats, timely security
patches aren&amp;rsquo;t optional—they&amp;rsquo;re essential. TPRF&amp;rsquo;s maintenance fund ensures that
vulnerabilities can be addressed promptly, protecting countless systems worldwide.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance Evolution&lt;/strong&gt;: Modern computing demands continue to evolve. The fund
supports ongoing optimisation efforts that keep Perl competitive in today&amp;rsquo;s
performance-conscious environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform Diversity&lt;/strong&gt;: As computing platforms proliferate—from traditional
servers to edge devices to cloud containers—Perl must remain compatible and
efficient across this expanding landscape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Community Responsiveness&lt;/strong&gt;: Bug reports and feature requests from the global
Perl community require careful evaluation and implementation. This fund ensures
these contributions don&amp;rsquo;t languish unaddressed.&lt;/p&gt;
&lt;h2 id=&#34;a-partnership-model-for-open-source-sustainability&#34;&gt;A Partnership Model for Open Source Sustainability&lt;/h2&gt;
&lt;p&gt;SUSE&amp;rsquo;s contribution represents more than financial support—it&amp;rsquo;s a blueprint for
sustainable open-source stewardship. When organisations that build upon
open-source foundations reinvest in those foundations, they create a virtuous
cycle that benefits everyone. It&amp;rsquo;s a recognition that the digital commons we all
depend upon flourish only through collective stewardship.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MetaCPAN&#39;s Traffic Crisis: An Eventual Success Story</title>
      <link>https://www.perl.com/article/metacpan-traffic-crisis/</link>
      <pubDate>Mon, 28 Jul 2025 22:01:46 +0000</pubDate>
      <guid>https://www.perl.com/article/metacpan-traffic-crisis/</guid>
      <description>&lt;p class=&#34;attribution&#34;&gt;&#34;&lt;a rel=&#34;noopener noreferrer&#34; href=&#34;https://www.flickr.com/photos/11946169@N00/9436653177&#34;&gt;Amelia&amp;#039;s Sad Face&lt;/a&gt;&#34; by &lt;a rel=&#34;noopener noreferrer&#34; href=&#34;https://www.flickr.com/photos/11946169@N00&#34;&gt;donnierayjones&lt;/a&gt; is licensed under &lt;a rel=&#34;noopener noreferrer&#34; href=&#34;https://creativecommons.org/licenses/by/2.0/?ref=openverse&#34;&gt;CC BY 2.0 &lt;img src=&#34;https://mirrors.creativecommons.org/presskit/icons/cc.svg&#34; style=&#34;height: 1em; margin-right: 0.125em; display: inline;&#34; /&gt;&lt;img src=&#34;https://mirrors.creativecommons.org/presskit/icons/by.svg&#34; style=&#34;height: 1em; margin-right: 0.125em; display: inline;&#34; /&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://metacpan.org&#34;&gt;MetaCPAN.org&lt;/a&gt;, the essential search engine for Perl&amp;rsquo;s
CPAN repository, has faced months of severe traffic issues that brought the
service to its knees with frequent 503 errors. Here&amp;rsquo;s how the team fought back
against an army of misbehaving bots and hostile traffic.&lt;/p&gt;
&lt;h2 id=&#34;the-problem-emerges&#34;&gt;The Problem Emerges&lt;/h2&gt;
&lt;p&gt;MetaCPAN began experiencing multiple 503 service errors daily, disrupting access
for legitimate Perl developers worldwide. Traditional monitoring failed to
identify the root cause of traffic spikes overwhelming the infrastructure.&lt;/p&gt;
&lt;h2 id=&#34;initial-investigation-phase&#34;&gt;Initial Investigation Phase&lt;/h2&gt;
&lt;p&gt;The team implemented basic monitoring and took preliminary defensive measures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed uWSGI stats monitoring tools to track application performance&lt;/li&gt;
&lt;li&gt;Updated robots.txt to explicitly list bots and specify crawling restrictions&lt;/li&gt;
&lt;li&gt;Began manual IP blocking of obvious bad actors&lt;/li&gt;
&lt;li&gt;Attempted to deploy Anubis rate limiting (ultimately failed and was rolled
back)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-datadog-breakthrough&#34;&gt;The Datadog Breakthrough&lt;/h2&gt;
&lt;div style=&#34;text-align: center; margin: 20px 0; display: flex; justify-content: center; align-items: center; gap: 50px;&#34;&gt;
  &lt;img src=&#34;https://www.perl.com/images/metacpan-traffic-crisis/dd_logo_h_rgb.svg&#34; alt=&#34;Datadog logo&#34; style=&#34;width: 40%;&#34;&gt;
  &lt;img src=&#34;https://www.perl.com/images/metacpan-traffic-crisis/fastlyLogo-red-SVG.svg&#34; alt=&#34;Fastly logo&#34; style=&#34;width: 40%;&#34;&gt;
&lt;/div&gt;
&lt;p&gt;Partnership with Datadog transformed visibility into the problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Established comprehensive logging pipeline sending Fastly CDN logs for both
web and API services to Datadog&lt;/li&gt;
&lt;li&gt;Deployed Kubernetes Datadog agent to cluster&lt;/li&gt;
&lt;li&gt;Created public dashboard showing real-time traffic metrics&lt;/li&gt;
&lt;li&gt;Built private dashboard specifically to identify problematic IPs and user
agents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Finally able to see the enemy—specific IP ranges (particularly from
Alibaba.com) and user agents generating massive request volumes. However, manual
blocking proved unsustainable, requiring constant vigilance and rapid response.&lt;/p&gt;
&lt;h2 id=&#34;escalating-defences&#34;&gt;Escalating Defences&lt;/h2&gt;
&lt;p&gt;The team implemented more sophisticated blocking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed VCL snippets in Fastly to block based on user agents (later replaced
with Next Gen WAF)&lt;/li&gt;
&lt;li&gt;Blocked extensive IP ranges using Fastly&amp;rsquo;s IP Block list feature&lt;/li&gt;
&lt;li&gt;Implemented additional request rate limiting&lt;/li&gt;
&lt;li&gt;Partnered with Fastly for free enterprise services including DDoS protection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Limitation:&lt;/strong&gt; Manual processes couldn&amp;rsquo;t keep pace with evolving attack
patterns.&lt;/p&gt;
&lt;h2 id=&#34;next-generation-waf-implementation&#34;&gt;Next-Generation WAF Implementation&lt;/h2&gt;
&lt;p&gt;Deployment of Fastly&amp;rsquo;s Web Application and API Protection:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enabled next-gen WAF to automatically identify and block suspect bots&lt;/li&gt;
&lt;li&gt;Implemented categorical blocking for known bad traffic types&lt;/li&gt;
&lt;li&gt;Reduced manual intervention requirements significantly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Progress:&lt;/strong&gt; Noticeable improvement, but sophisticated attacks still
overwhelmed the service during peak periods.&lt;/p&gt;
&lt;h2 id=&#34;the-dynamic-challenge-solution&#34;&gt;The Dynamic Challenge Solution&lt;/h2&gt;
&lt;p&gt;Final defensive layer was activated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed Fastly&amp;rsquo;s Dynamic Challenge WAF feature&lt;/li&gt;
&lt;li&gt;Intelligent challenge system filtered automated bots whilst allowing
legitimate users through&lt;/li&gt;
&lt;li&gt;Dramatic reduction in successful attacks reaching MetaCPAN infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;current-state-victory-through-data&#34;&gt;Current State: Victory Through Data&lt;/h2&gt;
&lt;h2 id=&#34;bad-bots-traffic-visualization&#34;&gt;&lt;img src=&#34;https://www.perl.com/images/metacpan-traffic-crisis/badbots.png&#34; alt=&#34;Bad bots traffic visualization&#34;&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/metacpan-traffic-crisis/challenges.png&#34; alt=&#34;Traffic challenges chart&#34;&gt;&lt;/p&gt;
&lt;p&gt;Today&amp;rsquo;s
&lt;a href=&#34;https://p.datadoghq.eu/sb/c2941b7b-37bb-11f0-94e3-32bf19abf102-744ae84f98611bfd6781365a482e2155&#34;&gt;public Datadog dashboard&lt;/a&gt;
tells the success story in real-time metrics:&lt;/p&gt;
&lt;p&gt;In the last week the number of requests handled broke down as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5,190,000 bad bot requests (this includes AI scrapers) blocked&lt;/li&gt;
&lt;li&gt;3,290,000 challenges issued&lt;/li&gt;
&lt;li&gt;579,000 requests rate limited&lt;/li&gt;
&lt;li&gt;1,720,000 legitimate requests served (much of this from Fastly&amp;rsquo;s CDN cache),
with the remainder reaching the origin servers and being successfully served
to end users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So about 80% of all traffic is now blocked.&lt;/p&gt;
&lt;p&gt;The numbers demonstrate the scale of the threat MetaCPAN faced and the
effectiveness of the layered defence strategy.&lt;/p&gt;
&lt;p&gt;We have RSS feeds and a dedicated API which can be easily accessed through
&lt;a href=&#34;https://metacpan.org/pod/MetaCPAN::Client&#34;&gt;MetaCPAN::Client&lt;/a&gt; for anyone who
wants to get data from us without scraping the site. We do ask that people
&lt;a href=&#34;https://github.com/metacpan/metacpan-api/wiki/fastapi-Consumers&#34;&gt;register their user agent&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;community-heroes&#34;&gt;Community Heroes&lt;/h2&gt;
&lt;p&gt;This infrastructure battle was won through generous community support:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Break down of steps can be found on ticket&lt;/strong&gt;
&lt;a href=&#34;https://github.com/metacpan/metacpan-k8s/issues/154&#34;&gt;https://github.com/metacpan/metacpan-k8s/issues/154&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://www.fastly.com/&#34;&gt;Fastly&lt;/a&gt; and &lt;a href=&#34;https://www.datadoghq.com/&#34;&gt;Datadog&lt;/a&gt;&lt;/strong&gt;
deserve particular recognition for donating enterprise-grade services. Without
these contributions, MetaCPAN couldn&amp;rsquo;t operate at the required scale and
reliability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Additional sponsors&lt;/strong&gt; listed at &lt;a href=&#34;https://metacpan.org/about/sponsors&#34;&gt;https://metacpan.org/about/sponsors&lt;/a&gt; continue
supporting this vital community resource, though operational costs remain
significant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to help:&lt;/strong&gt; The Perl community can support MetaCPAN&amp;rsquo;s ongoing operations
through &lt;a href=&#34;https://opencollective.com/metacpan-core&#34;&gt;https://opencollective.com/metacpan-core&lt;/a&gt;, ensuring this essential
service remains available for all developers.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Analysing FIT data with Perl: interactive data analysis</title>
      <link>https://www.perl.com/article/analysing-fit-data-with-perl-interactive-data-analysis/</link>
      <pubDate>Sun, 27 Jul 2025 11:46:04 +0000</pubDate>
      <guid>https://www.perl.com/article/analysing-fit-data-with-perl-interactive-data-analysis/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/&#34;&gt;Printing statistics to the
terminal&lt;/a&gt;
or &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-producing-png-plots/&#34;&gt;plotting data extracted from FIT
files&lt;/a&gt;
is all well and good.  One problem is that the feedback loops are long.
Sometimes questions are better answered by playing with the data directly.
Enter the Perl Data Language.&lt;/p&gt;
&lt;h2 id=&#34;interactive-data-analysis&#34;&gt;Interactive data analysis&lt;/h2&gt;
&lt;p&gt;For more fine-grained analysis of our &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/&#34;&gt;FIT file
data&lt;/a&gt;, it&amp;rsquo;d
be great to be able to investigate it interactively.  Other languages such
as Ruby, Raku and Python have a built-in REPL.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; Yet Perl
doesn&amp;rsquo;t.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;  But help is at hand!  &lt;a href=&#34;https://metacpan.org/dist/PDL&#34;&gt;PDL (the Perl Data
Language)&lt;/a&gt; is designed to be used
interactively and thus has a REPL we can use to manipulate and investigate
our activity data.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;getting-set-up&#34;&gt;Getting set up&lt;/h2&gt;
&lt;p&gt;Before we can use PDL, we&amp;rsquo;ll have to install it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cpanm PDL
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After it has finished installing (this can take a while), you&amp;rsquo;ll be able to
start the &lt;code&gt;perlDL shell&lt;/code&gt; with the &lt;code&gt;pdl&lt;/code&gt; command:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;perlDL shell v1.357
 PDL comes with ABSOLUTELY NO WARRANTY. For details, see the file
 &amp;#39;COPYING&amp;#39; in the PDL distribution. This is free software and you
 are welcome to redistribute it under certain conditions, see
 the same file for details.
ReadLines, NiceSlice, MultiLines  enabled
Reading PDL/default.perldlrc...
Found docs database /home/vagrant/perl5/perlbrew/perls/perl-5.38.3/lib/site_perl/5.38.3/x86_64-linux/PDL/pdldoc.db
Type &amp;#39;help&amp;#39; for online help
Type &amp;#39;demo&amp;#39; for online demos
Loaded PDL v2.100 (supports bad values)

Note: AutoLoader not enabled (&amp;#39;use PDL::AutoLoader&amp;#39; recommended)

pdl&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To exit the &lt;code&gt;pdl&lt;/code&gt; shell, enter &lt;code&gt;Ctrl-D&lt;/code&gt; at the prompt and you&amp;rsquo;ll be returned
to your terminal.&lt;/p&gt;
&lt;h2 id=&#34;cleaning-up-to-continue&#34;&gt;Cleaning up to continue&lt;/h2&gt;
&lt;p&gt;To manipulate the data in the &lt;code&gt;pdl&lt;/code&gt; shell, we want to be able to call
individual routines from the &lt;code&gt;geo-fit-plot-data.pl&lt;/code&gt; script.  This way we can
use the arrays that some of the routines return to initialise PDL data
objects.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s easier to manipulate the data if we get ourselves a bit more organised
first.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;  In other words, we need to extract the
routines into a module, which will make calling &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-producing-png-plots/&#34;&gt;the code we created
earlier&lt;/a&gt;
from within &lt;code&gt;pdl&lt;/code&gt; much easier.&lt;/p&gt;
&lt;p&gt;Before we create a module, we need to do some refactoring.  One thing that&amp;rsquo;s
been bothering me is the way the &lt;code&gt;plot_activity_data()&lt;/code&gt; subroutine also
parses and manipulates date/time data.  This routine should be focused on
plotting data, not on massaging its requirements into the correct form.
Munging the date/time data is something that should happen in its own
routine.  This way we encapsulate the concepts and abstract away the
details.  Another way of saying this is that the plotting routine shouldn&amp;rsquo;t
&amp;ldquo;know&amp;rdquo; how to manipulate date/time information to do its job.&lt;/p&gt;
&lt;p&gt;To this end, I&amp;rsquo;ve moved the time extraction code into a routine called
&lt;code&gt;get_time_data()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_time_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# get the epoch time for the first point in the time data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $first_epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# convert timestamp data to elapsed minutes from start of activity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $elapsed_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ($epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; $first_epoch_time)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $elapsed_time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; @times;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The main change here in comparison to the &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-producing-png-plots/#power-to-the-people&#34;&gt;previous version of the
code&lt;/a&gt;
is that we pass the activity data as an argument to &lt;code&gt;get_time_data()&lt;/code&gt;,
returning the &lt;code&gt;@times&lt;/code&gt; array to the calling code.&lt;/p&gt;
&lt;p&gt;The code creating the date string used in the plot title now also resides in
its own function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_date&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# determine date from timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%d&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; $date;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where again, we&amp;rsquo;re passing the &lt;code&gt;@activity_data&lt;/code&gt; array to the function.  It
then returns the &lt;code&gt;$date&lt;/code&gt; string that we use in the plot title.&lt;/p&gt;
&lt;p&gt;Both of these routines use the &lt;code&gt;$date_parser&lt;/code&gt; object, which I&amp;rsquo;ve extracted
into a constant in the main script scope:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date_parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DateTime::Format::Strptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;making-a-mini-module&#34;&gt;Making a mini-module&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s time to make our module.  I&amp;rsquo;m not going to create the &lt;a href=&#34;https://metacpan.org/pod/Module::Starter&#34;&gt;full Perl module
infrastructure&lt;/a&gt; here, as it&amp;rsquo;s not
necessary for our current goal.  I want to import a module called
&lt;code&gt;Geo::FIT::Utils&lt;/code&gt; and then access the functions that it
provides.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;  Thus&amp;ndash;in an appropriate project
directory&amp;ndash;we need to create a file called &lt;code&gt;lib/Geo/FIT/Utils.pm&lt;/code&gt; as well as
its associated path:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir -p lib/Geo/FIT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ touch lib/Geo/FIT/Utils.pm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Opening the file in an editor and entering this &lt;a href=&#34;https://perldoc.perl.org/perlmod#Perl-Modules&#34;&gt;stub module
code&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;package&lt;/span&gt; Geo::FIT::Utils;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Exporter &lt;span style=&#34;color:#ae81ff&#34;&gt;5.57&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;import&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;our&lt;/span&gt; @EXPORT_OK &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;qw(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    extract_activity_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    show_activity_statistics
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    plot_activity_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    get_time_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    num_parts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;we now have the scaffolding of a module that (at least, theoretically)
exports the functionality we need.&lt;/p&gt;
&lt;p&gt;Line 1 specifies the name of the module.  Note that the module&amp;rsquo;s name must
match its path on the filesystem, hence why we created the file
&lt;code&gt;Geo/FIT/Utils.pm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We import the &lt;a href=&#34;https://perldoc.perl.org/Exporter&#34;&gt;&lt;code&gt;Exporter&lt;/code&gt; module&lt;/a&gt; (line
3) so that we can specify the functions to export.  This is the &lt;code&gt;@EXPORT_OK&lt;/code&gt;
array&amp;rsquo;s purpose (lines 6-12).&lt;/p&gt;
&lt;p&gt;Finally, we end the file on line 14 with the code &lt;code&gt;1;&lt;/code&gt;.  This line is
necessary so that importing the package (which in this case is also a
module) returns a true value.  The value &lt;code&gt;1&lt;/code&gt; is synonymous with Boolean true
in Perl, hence why it&amp;rsquo;s best practice to end module files with &lt;code&gt;1;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Copying all the code except the &lt;code&gt;main()&lt;/code&gt; routine from &lt;code&gt;geo-fit-plot-data.pl&lt;/code&gt;
into &lt;code&gt;Utils.pm&lt;/code&gt;, we end up with this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;package&lt;/span&gt; Geo::FIT::Utils;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; strict;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; warnings;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Exporter &lt;span style=&#34;color:#ae81ff&#34;&gt;5.57&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;import&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Geo::FIT;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Scalar::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(reftype)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; List::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(max sum)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Chart::Gnuplot;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; DateTime::Format::Strptime;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date_parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DateTime::Format::Strptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;extract_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $fit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Geo::FIT&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;file( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2025-05-08-07-58-33.fit&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;open &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; die $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;error;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $record_callback &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; ($self, $descriptor, $values) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @all_field_names &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $self&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fields_list($descriptor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; %event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $field_name (@all_field_names) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $field_value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $self&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;field_value($field_name, $descriptor, $values);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($field_value &lt;span style=&#34;color:#f92672&#34;&gt;=~&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; /[a-zA-Z]/&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                $event_data{$field_name} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $field_value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;%event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;data_message_callback_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;record&amp;#39;&lt;/span&gt;, $record_callback ) &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; die $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;error;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @header_things &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fetch_header;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $event_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fetch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $reftype &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; reftype $event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (defined $reftype &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; $reftype &lt;span style=&#34;color:#f92672&#34;&gt;eq&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HASH&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; defined %$event_data{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;}) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            push @activity_data, $event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ( $event_data );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;close;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# extract and return the numerical parts of an array of FIT data values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;num_parts&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $field_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; map { (split &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;, $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{$field_name})[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# return the average of an array of numbers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;avg&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @array &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (sum @array) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (scalar @array);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;show_activity_statistics&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Found &amp;#34;&lt;/span&gt;, scalar @activity_data, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; entries in FIT file\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $available_fields &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; join &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;, &amp;#34;&lt;/span&gt;, sort keys %{$activity_data[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Available fields: $available_fields\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $total_distance_m &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (split &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;, ${$activity_data[&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]}{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;distance&amp;#39;&lt;/span&gt;})[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $total_distance &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $total_distance_m&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Total distance: $total_distance km\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @speeds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;speed&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max @speeds;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_speed_km &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $maximum_speed&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3.6&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Maximum speed: $maximum_speed m/s = $maximum_speed_km km/h\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; avg(@speeds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_speed_km &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_speed&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3.6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $average_speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_speed);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average speed: $average_speed m/s = $average_speed_km km/h\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @powers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;power&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_power &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max @powers;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Maximum power: $maximum_power W\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_power &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; avg(@powers);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $average_power &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_power);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average power: $average_power W\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_heart_rate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max @heart_rates;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Maximum heart rate: $maximum_heart_rate bpm\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_heart_rate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; avg(@heart_rates);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $average_heart_rate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_heart_rate);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average heart rate: $average_heart_rate bpm\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;plot_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# extract data to plot from full activity data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_time_data(@activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @powers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;power&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# plot data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_date(@activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        output &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;watopia-figure-8-heart-rate-and-power.png&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        title  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Figure 8 in Watopia on $date: heart rate and power over time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        xlabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Elapsed time (min)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ylabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Heart rate (bpm)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        terminal &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;png size 1024, 768&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        xtics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            incr &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ytics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            mirror &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;off&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y2label &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Power (W)&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y2range &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1100&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y2tics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            incr &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $heart_rate_ds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@heart_rates,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $power_ds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@powers,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        axes &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x1y2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $chart&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;plot2d($power_ds, $heart_rate_ds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_time_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# get the epoch time for the first point in the time data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $first_epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# convert timestamp data to elapsed minutes from start of activity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $elapsed_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ($epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; $first_epoch_time)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $elapsed_time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; @times;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_date&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# determine date from timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%d&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; $date;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;our&lt;/span&gt; @EXPORT_OK &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;qw(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    extract_activity_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    show_activity_statistics
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    plot_activity_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    get_time_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    num_parts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; which is what we had before, but put into a nice package for easier
use.&lt;/p&gt;
&lt;p&gt;One upside to having put all this code into a module is that the
&lt;code&gt;geo-fit-plot-data.pl&lt;/code&gt; script is now much simpler:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; strict;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; warnings;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Geo::FIT::Utils &lt;span style=&#34;color:#e6db74&#34;&gt;qw(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    extract_activity_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    show_activity_statistics
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    plot_activity_data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; extract_activity_data();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    show_activity_statistics(@activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plot_activity_data(@activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;poking-and-prodding&#34;&gt;Poking and prodding&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re now ready to investigate our power and heart rate data interactively!&lt;/p&gt;
&lt;p&gt;Start &lt;code&gt;pdl&lt;/code&gt; and enter &lt;code&gt;use lib &#39;lib&#39;&lt;/code&gt; at the &lt;code&gt;pdl&amp;gt;&lt;/code&gt; prompt so that it can
find our new module:&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ pdl
perlDL shell v1.357
 PDL comes with ABSOLUTELY NO WARRANTY. For details, see the file
 &amp;#39;COPYING&amp;#39; in the PDL distribution. This is free software and you
 are welcome to redistribute it under certain conditions, see
 the same file for details.
ReadLines, NiceSlice, MultiLines  enabled
Reading PDL/default.perldlrc...
Found docs database /home/vagrant/perl5/perlbrew/perls/perl-5.38.3/lib/site_perl/5.38.3/x86_64-linux/PDL/pdldoc.db
Type &amp;#39;help&amp;#39; for online help
Type &amp;#39;demo&amp;#39; for online demos
Loaded PDL v2.100 (supports bad values)

Note: AutoLoader not enabled (&amp;#39;use PDL::AutoLoader&amp;#39; recommended)

pdl&amp;gt; use lib &amp;#39;lib&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now import the functions we wish to use:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; use Geo::FIT::Utils qw(extract_activity_data get_time_data num_parts)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we need the activity data from the FIT file to pass to the other
routines, we grab it and put it into a variable:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; @activity_data = extract_activity_data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We also need to load the time data:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; @times = get_time_data(@activity_data)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which we can then read into a PDL array:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; $time = pdl \@times
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the time data in a PDL array, we can manipulate it more easily.   For
instance, we can display elements of the array with the PDL &lt;code&gt;print&lt;/code&gt;
statement in combination with the &lt;code&gt;splice()&lt;/code&gt; method.  The following code
shows the last five elements of the &lt;code&gt;$time&lt;/code&gt; array:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $time-&amp;gt;slice(&amp;#34;-1:-5&amp;#34;)
[54.5333333333333 54.5166666666667 54.5 54.4833333333333 54.4666666666667]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Loading power output and heart rate data into PDL arrays works similarly:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; @powers = num_parts(&amp;#39;power&amp;#39;, @activity_data)

pdl&amp;gt; $power = pdl \@powers

pdl&amp;gt; @heart_rates = num_parts(&amp;#39;heart_rate&amp;#39;, @activity_data)

pdl&amp;gt; $heart_rate = pdl \@heart_rates
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-producing-png-plots/#power-to-the-people&#34;&gt;In the previous article, we wanted to know what the maximum power was for
the second
sprint.&lt;/a&gt;
Here&amp;rsquo;s the graph again for context:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/geo-fit-heart-rate-and-power.png&#34; alt=&#34;Plot of heart rate and power versus elapsed time in minutes&#34;&gt;&lt;/p&gt;
&lt;p&gt;Eyeballing the graph from above, we can see that the second sprint occurred
between approximately 47 and 48 minutes elapsed time.  We know that the
arrays of time and power data all have the same length.  Thus, if we find
out the indices of the &lt;code&gt;$time&lt;/code&gt; array between these times, we can use them to
select the corresponding power data.  To get array indices for known data
values, we use the PDL &lt;a href=&#34;https://metacpan.org/pod/PDL::Primitive#which&#34;&gt;&lt;code&gt;which&lt;/code&gt;
command&lt;/a&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; $indices = which(47 &amp;lt; $time &amp;amp; $time &amp;lt; 48)

pdl&amp;gt; print $indices
[2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835
 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850
 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865
 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can check that we&amp;rsquo;ve got the correct range of time values by passing the
&lt;code&gt;$indices&lt;/code&gt; array as a slice of the &lt;code&gt;$time&lt;/code&gt; array:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $time($indices)
[47.0166666666667 47.0333333333333 47.05 47.0666666666667 47.0833333333333
 47.1 47.1166666666667 47.1333333333333 47.15 47.1666666666667
 47.1833333333333 47.2 47.2166666666667 47.2333333333333 47.25
 47.2666666666667 47.2833333333333 47.3 47.3166666666667 47.3333333333333
 47.35 47.3666666666667 47.3833333333333 47.4 47.4166666666667
 47.4333333333333 47.45 47.4666666666667 47.4833333333333 47.5
 47.5166666666667 47.5333333333333 47.55 47.5666666666667 47.5833333333333
 47.6 47.6166666666667 47.6333333333333 47.65 47.6666666666667
 47.6833333333333 47.7 47.7166666666667 47.7333333333333 47.75
 47.7666666666667 47.7833333333333 47.8 47.8166666666667 47.8333333333333
 47.85 47.8666666666667 47.8833333333333 47.9 47.9166666666667
 47.9333333333333 47.95 47.9666666666667 47.9833333333333]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The time values lie between 47 and 48, so we can conclude that we&amp;rsquo;ve
selected the correct indices.&lt;/p&gt;
&lt;p&gt;Note that &lt;a href=&#34;https://metacpan.org/dist/PDL/view/lib/PDL/FAQ.pod#Q:-6.11-Logical-operators-and-ndarrays-&#39;%7C%7C&#39;-and-&#39;&amp;amp;&amp;amp;&#39;-don&#39;t-work!&#34;&gt;we have to use the bitwise logical AND operator here because it
operates on an element-by-element
basis&lt;/a&gt;
across the array.&lt;/p&gt;
&lt;p&gt;Selecting &lt;code&gt;$power&lt;/code&gt; array values at these indices is as simple as passing
the &lt;code&gt;$indices&lt;/code&gt; array as a slice:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $power($indices)
[229 231 232 218 210 204 255 252 286 241 231 237 260 256 287 299 318 337 305
 276 320 289 280 301 320 303 395 266 302 341 299 287 309 279 294 284 266 281
 367 497 578 512 762 932 907 809 821 847 789 740 657 649 722 715 669 657 705
 643 647]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using the &lt;code&gt;max()&lt;/code&gt; method on this output gives us the maximum power:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $power($indices)-&amp;gt;max
932
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In other words, the maximum power for the second sprint was 932 W.  Not as
good as the first sprint (which achieved 1023 W), but I was getting
tired by this stage.&lt;/p&gt;
&lt;p&gt;The same procedure allows us to find the maximum power for the first sprint
with PDL.  Again, eyeballing the graph above, we can see that the peak for
the first sprint occurred between 24 and 26 minutes.  Constructing the query
in PDL, we have&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $power(which(24 &amp;lt; $time &amp;amp; $time &amp;lt; 26))-&amp;gt;max
1023
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which gives the maximum power value we expect.&lt;/p&gt;
&lt;p&gt;We can also find out the maximum heart rate values around these times.  E.g.
for the first sprint:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $heart_rate(which(24 &amp;lt; $time &amp;amp; $time &amp;lt; 26))-&amp;gt;max
157
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;in other words, 157 bpm.  For the second sprint, we have:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pdl&amp;gt; print $heart_rate(which(47 &amp;lt; $time &amp;amp; $time &amp;lt; 49))-&amp;gt;max
165
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;i.e. 165 bpm, which matches the value that &lt;a href=&#34;https://www.perl.com/analysing-fit-data-with-perl-basic-beginnings/#calculating-a-rides-statistics&#34;&gt;we found
earlier&lt;/a&gt;.
Note that I broadened the range of times to search over heart rate data here
because its peak occurred a bit after the power peak for the second sprint.&lt;/p&gt;
&lt;h2 id=&#34;looking-forward&#34;&gt;Looking forward&lt;/h2&gt;
&lt;p&gt;Where to from here?  Well, we could extend this code to handle processing
multiple FIT files.  This would allow us to find trends over many activities
and longer periods.  Perhaps there are other data sources that one could
combine with longer trends.  For instance, if one has access to weight data
over time, then it&amp;rsquo;d be possible to work out things like power-to-weight
ratios.  Maybe looking at power and heart rate trends over a longer time can
identify things such as overtraining.  I&amp;rsquo;m not a sport scientist, so I don&amp;rsquo;t
know how to go about that, yet it&amp;rsquo;s a possibility.  Since we&amp;rsquo;ve got
fine-grained, per-ride data, if we can combine this with longer-term
analysis, there are probably many more interesting tidbits hiding in there
that we can look at and think about.&lt;/p&gt;
&lt;h2 id=&#34;open-question&#34;&gt;Open question&lt;/h2&gt;
&lt;p&gt;One thing I haven&amp;rsquo;t been able to work out is where the calorie information
is.  As far as I can tell, Zwift calculates how many calories were burned
during a given ride.  Also, if one uploads the FIT file to a service such as
Strava, then it too shows calories burned and the value is the same.  This
would imply that Strava is only displaying a value stored in the FIT file.
So where is the calorie value in the FIT data?  I&amp;rsquo;ve not been able to find
it in the data messages that &lt;code&gt;Geo::FIT&lt;/code&gt; reads, so I&amp;rsquo;ve no idea what&amp;rsquo;s going
on there.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What have we learned?  We&amp;rsquo;ve found out how to read, analyse and plot data
from Garmin FIT files all by using Perl modules.  Also, we&amp;rsquo;ve learned how to
investigate the data interactively by using the PDL shell.  Cool!&lt;/p&gt;
&lt;p&gt;One main takeaway that might not be obvious is that you don&amp;rsquo;t really need
online services such as Strava.  You should now have the tools to process,
analyse and visualise data from your own FIT files.  With &lt;code&gt;Geo::FIT&lt;/code&gt;,
&lt;code&gt;Chart::Gnuplot&lt;/code&gt; and a bit of programming, you can glue together the
components to provide much of the same (and in some cases, more)
functionality yourself.&lt;/p&gt;
&lt;p&gt;I wish you lots of fun when playing around with FIT data!&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop&#34;&gt;REPL&lt;/a&gt;
stands for read-eval-print loop and is an environment where one can
interactively enter programming language commands and manipulate data.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;It is, however, possible to (ab)use the Perl debugger and
use it as a kind of REPL.  Enter &lt;code&gt;perl -de0&lt;/code&gt; and you&amp;rsquo;re in a Perl
environment much like REPLs in other languages.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Many thanks to Harald Jörg for pointing this out to
me at the recent &lt;a href=&#34;https://act.yapc.eu/gpw2025/&#34;&gt;German Perl and Raku Workshop&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;This is an application of
&lt;a href=&#34;https://peateasea.de/assets/images/kent-beck-make-change-easy-then-easy-change.png&#34;&gt;&amp;ldquo;first make the change easy, then make the easy change&amp;rdquo;&lt;/a&gt;
(paraphrasing &lt;a href=&#34;https://x.com/KentBeck/status/250733358307500032&#34;&gt;Kent Beck&lt;/a&gt;).
An important point often overlooked in this quote is that making the change
easy can be hard.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;Not a particularly imaginative name, I know.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;The documented way to add a path to &lt;code&gt;@INC&lt;/code&gt; in &lt;code&gt;pdl&lt;/code&gt;
is via the &lt;code&gt;-Ilib&lt;/code&gt; command line option.  Unfortunately, this didn&amp;rsquo;t
work in my test environment: the local &lt;code&gt;lib/&lt;/code&gt; path wasn&amp;rsquo;t added to
&lt;code&gt;@INC&lt;/code&gt; and hence using the &lt;code&gt;Geo::FIT::Utils&lt;/code&gt; module failed with the
error that it couldn&amp;rsquo;t be located.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Proxmox Donates €10,000 to The Perl and Raku Foundation</title>
      <link>https://www.perl.com/article/proxmox-donates-to-tprf/</link>
      <pubDate>Wed, 23 Jul 2025 10:24:25 +0000</pubDate>
      <guid>https://www.perl.com/article/proxmox-donates-to-tprf/</guid>
      <description>&lt;p&gt;The Perl and Raku Foundation (TPRF) is delighted to announce a generous €10,000
donation from &lt;a href=&#34;https://www.proxmox.com/en/&#34;&gt;Proxmox Server Solutions GmbH&lt;/a&gt;, supporting the critical Perl 5
Core Maintenance Fund. Corporate partnerships play a critical role in
enabling TPRF to fulfill its mission.&lt;/p&gt;
&lt;h2 id=&#34;a-partner-in-open-source&#34;&gt;A Partner in Open Source&lt;/h2&gt;
&lt;p&gt;Proxmox Virtual Environment is a complete, open-source server management
platform for enterprise virtualization. It tightly integrates the KVM
hypervisor and Linux Containers (LXC), software-defined storage and networking
functionality, on a single platform.&lt;/p&gt;
&lt;p&gt;Proxmox is an example of an open source company that has built enterprise-grade
virtualization technology while maintaining transparency, community engagement,
and accessibility as core principles.&lt;/p&gt;
&lt;h2 id=&#34;sustaining-a-foundation-of-modern-computing&#34;&gt;Sustaining a Foundation of Modern Computing&lt;/h2&gt;
&lt;p&gt;The Perl programming language remains a cornerstone of system administration,
bioinformatics, web development, and countless other critical applications
across industries. However, the ongoing maintenance and development of Perl&amp;rsquo;s
core requires dedicated funding to ensure continued stability, security updates,
and feature enhancements.&lt;/p&gt;
&lt;p&gt;TPRF is dedicated to the advancement of the Perl and Raku programming
languages, through open discussion, collaboration, design, and code. This
mission extends beyond language development to encompass community building,
educational initiatives, and the crucial task of maintaining the robust
infrastructure that millions of applications depend upon.&lt;/p&gt;
&lt;h2 id=&#34;the-critical-nature-of-core-maintenance&#34;&gt;The Critical Nature of Core Maintenance&lt;/h2&gt;
&lt;p&gt;Without sustained funding for core maintenance work, even the most established
programming languages risk stagnation or security vulnerabilities. The Perl 5
Core Maintenance Fund specifically addresses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security Updates&lt;/strong&gt;: Ensuring timely patches for discovered vulnerabilities&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance Optimizations&lt;/strong&gt;: Maintaining competitive execution speeds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform Compatibility&lt;/strong&gt;: Supporting new operating systems and architectures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bug Resolution&lt;/strong&gt;: Addressing issues reported by the global Perl community&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation Maintenance&lt;/strong&gt;: Keeping comprehensive guides current and
accessible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Proxmox&amp;rsquo;s contribution directly enables this essential work to continue
uninterrupted, demonstrating a forward-thinking approach to technology
stewardship.&lt;/p&gt;
&lt;h2 id=&#34;a-shared-vision-for-open-source-sustainability&#34;&gt;A Shared Vision for Open Source Sustainability&lt;/h2&gt;
&lt;p&gt;This donation reflects a broader understanding within the technology industry
that sustainable open source ecosystems require active investment from
organizations that benefit from these tools. As companies increasingly seek
cost-effective alternatives to proprietary solutions, the importance of
maintaining robust open source alternatives becomes paramount.&lt;/p&gt;
&lt;h2 id=&#34;looking-forward&#34;&gt;Looking Forward&lt;/h2&gt;
&lt;p&gt;By investing in Perl&amp;rsquo;s continued development, Proxmox contributes to a
programming language ecosystem that serves developers, system administrators,
and organizations worldwide.&lt;/p&gt;
&lt;p&gt;This partnership comes at a crucial time for TPRF. Community support from
sponsors like Proxmox enables the foundation to maintain its diverse portfolio
of community-serving initiatives.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Analysing FIT data with Perl: producing PNG plots</title>
      <link>https://www.perl.com/article/analysing-fit-data-with-perl-producing-png-plots/</link>
      <pubDate>Wed, 25 Jun 2025 10:10:08 +0000</pubDate>
      <guid>https://www.perl.com/article/analysing-fit-data-with-perl-producing-png-plots/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/&#34;&gt;Last
time&lt;/a&gt;, we
worked out how to extract, collate, and print statistics about the data
contained in a FIT file.  Now we&amp;rsquo;re going to take the next logical step and
plot the time series data.&lt;/p&gt;
&lt;h2 id=&#34;start-plotting-with-gnu&#34;&gt;Start plotting with Gnu&lt;/h2&gt;
&lt;p&gt;Now that we&amp;rsquo;ve extracted data from the FIT file, what else can we do with
it?  Since this is time series data, the most natural next step is to
visualise the data values over time.  Since I know that
&lt;a href=&#34;http://www.gnuplot.info/&#34;&gt;Gnuplot&lt;/a&gt; handles time series data
well,&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; I chose to use
&lt;a href=&#34;https://metacpan.org/pod/Chart::Gnuplot&#34;&gt;&lt;code&gt;Chart::Gnuplot&lt;/code&gt;&lt;/a&gt; to plot the
data.&lt;/p&gt;
&lt;p&gt;An additional point in Gnuplot&amp;rsquo;s favour is that it can plot two datasets on
the same graph, each with its own y-axis.  Such functionality is handy when
searching for correlations between datasets of different y-axis scales and
ranges that share the same baseline data series.&lt;/p&gt;
&lt;p&gt;Clearly &lt;code&gt;Chart::Gnuplot&lt;/code&gt; relies on Gnuplot, so we need to install it first:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo apt install -y gnuplot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can install &lt;code&gt;Chart::Gnuplot&lt;/code&gt; with &lt;code&gt;cpanm&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cpanm Chart::Gnuplot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;putting-our-finger-on-the-pulse&#34;&gt;Putting our finger on the pulse&lt;/h2&gt;
&lt;p&gt;Something I like looking at is how my heart rate evolved throughout a ride;
it gives me an idea of how much effort I was putting in.  So, we&amp;rsquo;ll start
off by looking at how the heart rate data varied over time.  In other words,
we want time on the x-axis and heart rate on the y-axis.&lt;/p&gt;
&lt;p&gt;One great thing about Gnuplot is that if you give it a format string for the
time data, then plotting &amp;ldquo;just works&amp;rdquo;.  In other words, explicit conversion
to datetime data for the x-axis is unnecessary.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a script to extract the FIT data from our example data file.  It
displays some statistics about the activity and plots heart rate versus
time.  I&amp;rsquo;ve given the script the filename &lt;code&gt;geo-fit-plot-data.pl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  1&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; strict;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  2&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; warnings;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  4&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Geo::FIT;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  5&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Scalar::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(reftype)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  6&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; List::Util &lt;span style=&#34;color:#e6db74&#34;&gt;qw(max sum)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  7&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; Chart::Gnuplot;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  8&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;  9&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 10&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 11&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; extract_activity_data();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 12&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 13&lt;/span&gt;&lt;span&gt;    show_activity_statistics(@activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 14&lt;/span&gt;&lt;span&gt;    plot_activity_data(@activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 15&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 16&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 17&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;extract_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 18&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $fit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Geo::FIT&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 19&lt;/span&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;file( &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2025-05-08-07-58-33.fit&amp;#34;&lt;/span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 20&lt;/span&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;open &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; die $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;error;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 21&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 22&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $record_callback &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 23&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; ($self, $descriptor, $values) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 24&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @all_field_names &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $self&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fields_list($descriptor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 25&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 26&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; %event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 27&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $field_name (@all_field_names) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 28&lt;/span&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $field_value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $self&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;field_value($field_name, $descriptor, $values);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 29&lt;/span&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($field_value &lt;span style=&#34;color:#f92672&#34;&gt;=~&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; /[a-zA-Z]/&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 30&lt;/span&gt;&lt;span&gt;                $event_data{$field_name} &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $field_value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 31&lt;/span&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 32&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 33&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 34&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;%event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 35&lt;/span&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 36&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 37&lt;/span&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;data_message_callback_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;record&amp;#39;&lt;/span&gt;, $record_callback ) &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; die $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;error;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 38&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 39&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @header_things &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fetch_header;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 40&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 41&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 42&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 43&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 44&lt;/span&gt;&lt;span&gt;        $event_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fetch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 45&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $reftype &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; reftype $event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 46&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (defined $reftype &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; $reftype &lt;span style=&#34;color:#f92672&#34;&gt;eq&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HASH&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; defined %$event_data{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;}) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 47&lt;/span&gt;&lt;span&gt;            push @activity_data, $event_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 48&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 49&lt;/span&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ( $event_data );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 50&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 51&lt;/span&gt;&lt;span&gt;    $fit&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;close;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 52&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 53&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 54&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 55&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 56&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# extract and return the numerical parts of an array of FIT data values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 57&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;num_parts&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 58&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $field_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; shift;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 59&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 60&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 61&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; map { (split &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;, $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{$field_name})[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 62&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 63&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 64&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# return the average of an array of numbers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 65&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;avg&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 66&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @array &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 67&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 68&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (sum @array) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (scalar @array);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 69&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 70&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 71&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;show_activity_statistics&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 72&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 73&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 74&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Found &amp;#34;&lt;/span&gt;, scalar @activity_data, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; entries in FIT file\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 75&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $available_fields &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; join &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;, &amp;#34;&lt;/span&gt;, sort keys %{$activity_data[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 76&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Available fields: $available_fields\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 77&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 78&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $total_distance_m &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (split &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;, ${$activity_data[&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]}{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;distance&amp;#39;&lt;/span&gt;})[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 79&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $total_distance &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $total_distance_m&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 80&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Total distance: $total_distance km\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 81&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 82&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @speeds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;speed&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 83&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max @speeds;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 84&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_speed_km &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $maximum_speed&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3.6&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 85&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Maximum speed: $maximum_speed m/s = $maximum_speed_km km/h\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 86&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 87&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; avg(@speeds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 88&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_speed_km &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_speed&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3.6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 89&lt;/span&gt;&lt;span&gt;    $average_speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_speed);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 90&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average speed: $average_speed m/s = $average_speed_km km/h\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 91&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 92&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @powers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;power&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 93&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_power &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max @powers;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 94&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Maximum power: $maximum_power W\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 95&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 96&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_power &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; avg(@powers);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 97&lt;/span&gt;&lt;span&gt;    $average_power &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_power);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 98&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average power: $average_power W\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 99&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;100&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;101&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $maximum_heart_rate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max @heart_rates;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;102&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Maximum heart rate: $maximum_heart_rate bpm\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;103&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;104&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $average_heart_rate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; avg(@heart_rates);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;105&lt;/span&gt;&lt;span&gt;    $average_heart_rate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sprintf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%0.2f&amp;#34;&lt;/span&gt;, $average_heart_rate);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;106&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average heart rate: $average_heart_rate bpm\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;107&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;108&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;109&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;plot_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;110&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;111&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;112&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;113&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;114&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;115&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2025-05-08&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;116&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;117&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;118&lt;/span&gt;&lt;span&gt;        output &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;watopia-figure-8-heart-rate.png&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;119&lt;/span&gt;&lt;span&gt;        title  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Figure 8 in Watopia on $date: heart rate over time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;120&lt;/span&gt;&lt;span&gt;        xlabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;121&lt;/span&gt;&lt;span&gt;        ylabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Heart rate (bpm)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;122&lt;/span&gt;&lt;span&gt;        terminal &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;png size 1024, 768&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;123&lt;/span&gt;&lt;span&gt;        timeaxis &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;124&lt;/span&gt;&lt;span&gt;        xtics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;125&lt;/span&gt;&lt;span&gt;            labelfmt &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;126&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;127&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;128&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;129&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $data_set &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;130&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;131&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@heart_rates,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;132&lt;/span&gt;&lt;span&gt;        timefmt &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;133&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;134&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;135&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;136&lt;/span&gt;&lt;span&gt;    $chart&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;plot2d($data_set);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;137&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;138&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;139&lt;/span&gt;&lt;span&gt;main();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A lot has happened between this code
&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/#simple-beginnings&#34;&gt;and&lt;/a&gt;
&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/#getting-a-feel-for-the-fields&#34;&gt;the&lt;/a&gt;
&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/#event-data-a-first-impression&#34;&gt;previous&lt;/a&gt;
&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/#focusing-on-whats-relevant&#34;&gt;scripts&lt;/a&gt;.
Let&amp;rsquo;s review it to see what&amp;rsquo;s changed.&lt;/p&gt;
&lt;p&gt;The biggest changes were structural.  I&amp;rsquo;ve moved the code into separate
routines, improving encapsulation and making each more focused on one task.&lt;/p&gt;
&lt;p&gt;The FIT file data extraction code I&amp;rsquo;ve put into its own routine
(&lt;code&gt;extract_activity_data()&lt;/code&gt;; lines 17-54).  This sub returns the array of
event data that &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/&#34;&gt;we&amp;rsquo;ve been
using&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also created two utility routines &lt;code&gt;num_parts()&lt;/code&gt; (lines 57-62) and
&lt;code&gt;avg()&lt;/code&gt; (lines 65-69).  These return the numerical parts of the activity
data and average data series value, respectively.&lt;/p&gt;
&lt;p&gt;The ride statistics calculation and display code is now located in the
&lt;code&gt;show_activity_statistics()&lt;/code&gt; routine.  Now it&amp;rsquo;s out of the way, allowing us
to concentrate on other things.&lt;/p&gt;
&lt;p&gt;The plotting code is new and sits in a sub called &lt;code&gt;plot_activity_data()&lt;/code&gt;
(lines 109-137).  We&amp;rsquo;ll focus much more on that later.&lt;/p&gt;
&lt;p&gt;These routines are called from a &lt;code&gt;main()&lt;/code&gt; routine (lines 10-15) giving us a
nice bird&amp;rsquo;s eye view of what the script is trying to achieve.  Running all
the code is now as simple as calling &lt;code&gt;main()&lt;/code&gt; (line 139).&lt;/p&gt;
&lt;h2 id=&#34;particulars-of-pulse-plotting&#34;&gt;Particulars of pulse plotting&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s zoom in on the plotting code in &lt;code&gt;plot_activity_data()&lt;/code&gt;.  After having
imported &lt;code&gt;Chart::Gnuplot&lt;/code&gt; at the top of the file (line 7), we need to do a
bit of organising before we can set up the chart.  We extract the activity
data with &lt;code&gt;extract_activity_data()&lt;/code&gt; (line 11) and pass this as an argument
to &lt;code&gt;plot_activity_data()&lt;/code&gt; (line 14).  At the top of &lt;code&gt;plot_activity_data()&lt;/code&gt;
we fetch an array of the numerical heart rate data (line 112) and an array
of all the timestamps (line 113).&lt;/p&gt;
&lt;p&gt;The activity&amp;rsquo;s date (line 115) is assigned as a string variable because I
want this to appear in the chart&amp;rsquo;s title.  Although the date is present in
the activity data, I&amp;rsquo;ve chosen not to calculate its value until later.  This
way we get the plotting code up and running sooner, as there&amp;rsquo;s still a lot
to discuss.&lt;/p&gt;
&lt;p&gt;Now we&amp;rsquo;re ready to set up the chart, which takes place on lines 117-127.
We create a new &lt;code&gt;Chart::Gnuplot&lt;/code&gt; object on line 117 and configure the plot
with various keyword arguments to the object&amp;rsquo;s constructor.&lt;/p&gt;
&lt;p&gt;The parameters are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;output&lt;/code&gt; specifies the name of the output file as a string.  The name
I&amp;rsquo;ve chosen reflects the activity as well as the data being plotted.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt; is a string to use as the plot&amp;rsquo;s title.  To provide context, I
mention the name of the route (&lt;a href=&#34;https://zwiftinsider.com/route/figure-8/&#34;&gt;Figure 8&lt;/a&gt;)
within Zwift&amp;rsquo;s main virtual world (&lt;a href=&#34;https://zwiftinsider.com/watopia/&#34;&gt;Watopia&lt;/a&gt;)
as well as the date of the activity.  To highlight that we&amp;rsquo;re
plotting heart rate over time, I&amp;rsquo;ve mentioned this in the title also.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xlabel&lt;/code&gt; is a string describing the x-axis data.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ylabel&lt;/code&gt; is a string describing the y-axis data.&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;terminal&lt;/code&gt; option tells Gnuplot to use the PNG&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;
&amp;ldquo;terminal&amp;rdquo;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; and to set its dimensions to 1024x768.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeaxis&lt;/code&gt; tells Gnuplot which axis contains time-based data (in this
case the x-axis).  This enables Gnuplot to space out the data along the
axis evenly. Often, the spacing between points in time-based data isn&amp;rsquo;t
regular; for instance, data points can be missing.  Hence,
naively plotting unevenly-spaced time data can produce a distorted graph.
Telling Gnuplot that the x-axis contains time-based data allows it to
add appropriate space where necessary.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xtics&lt;/code&gt; is a hash of options to configure the behaviour of the ticks on
the x-axis.  The setting here displays hour and minute information at
each tick mark for our time data.  We omit the year, month and day
information as this is the same for all data points.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that the main chart parameters have been set, we can focus on the data
we want to plot.  In &lt;code&gt;Chart::Gnuplot&lt;/code&gt; parlance, a &lt;code&gt;Chart::Gnuplot::DataSet&lt;/code&gt;
object represents a set of data to plot.  Lines 129-134 instantiate such an
object which we later pass to the &lt;code&gt;Chart::Gnuplot&lt;/code&gt; object when plotting the
data (line 136).  One configures &lt;code&gt;Chart::Gnuplot::DataSet&lt;/code&gt; objects similarly
to how &lt;code&gt;Chart::Gnuplot&lt;/code&gt; objects are constructed: by passing various options
to its constructor.  These options include the data to plot and how this
data should be styled on the graph.&lt;/p&gt;
&lt;p&gt;The options used here have the following meanings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xdata&lt;/code&gt; is an array reference to the data to use for the x-axis.  If
this option is omitted, then Gnuplot uses the array indices of the
y-data as the x-axis values.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ydata&lt;/code&gt; is an array reference to the data to use for the y-axis.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timefmt&lt;/code&gt; specifies the format string Gnuplot should use when reading
the time data in the &lt;code&gt;xdata&lt;/code&gt; array.  Timestamps are strings and
we need to inform Gnuplot how to parse them into a form useful for
x-axis data.  Were the x-axis data a numerical data type, this option
wouldn&amp;rsquo;t be necessary.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;style&lt;/code&gt; is a string specifying the style to use for plotting the data.
In this example, we&amp;rsquo;re plotting the data points as a set of connected
lines.  Check out the
&lt;a href=&#34;https://metacpan.org/pod/Chart::Gnuplot&#34;&gt;&lt;code&gt;Chart::Gnuplot&lt;/code&gt; documentation&lt;/a&gt;
for a &lt;a href=&#34;https://metacpan.org/pod/Chart::Gnuplot#style&#34;&gt;full list of the available style options&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We finally get to plot the data on line 136.  The data set gets passed to
the &lt;code&gt;Chart::Gnuplot&lt;/code&gt; object as the argument to its &lt;code&gt;plot2d()&lt;/code&gt; method.  As
its name suggests, this plots 2D data, i.e. y versus x.  Gnuplot can also
display 3D data, in which case we&amp;rsquo;d call &lt;code&gt;plot3d()&lt;/code&gt;.  When plotting 3D data
we&amp;rsquo;d have to include a z dimension when setting up the data set.&lt;/p&gt;
&lt;p&gt;Running this code&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ perl geo-fit-plot-data.pl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;generates this plot:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/geo-fit-heart-rate-vs-timestamp.png&#34; alt=&#34;Plot of heart rate versus timestamp&#34;&gt;&lt;/p&gt;
&lt;p&gt;A couple of things are apparent when looking at this graph.  It took me a
while to get going (my pulse rose steadily over the first ~15 minutes of the
ride) and the time is weird (6 am? Me? Lol, no).  We&amp;rsquo;ll try to explain the
heart rate behaviour later.&lt;/p&gt;
&lt;p&gt;But first, what&amp;rsquo;s up with the time data?  Did I really start riding at 6
o&amp;rsquo;clock in the morning?  I&amp;rsquo;m not a morning person, so that&amp;rsquo;s not right.
Also, I&amp;rsquo;m pretty sure my neighbours wouldn&amp;rsquo;t appreciate me coughing and
wheezing at 6 am while trying to punish myself on Zwift.  So what&amp;rsquo;s going on?&lt;/p&gt;
&lt;p&gt;For those following carefully, you might have noticed the trailing &lt;code&gt;Z&lt;/code&gt; on
the timestamp data.  This means that the time zone is UTC.  Given that this
data is from May and I live in Germany, this implies that the local time
would have been 8 am.  Still rather early for me, but not too early to
disturb the neighbours too much.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;  In other
words, we need to fix the time zone to get the time data right.&lt;/p&gt;
&lt;h2 id=&#34;getting-into-the-zone&#34;&gt;Getting into the zone&lt;/h2&gt;
&lt;p&gt;How do we fix the time zone?  I&amp;rsquo;m glad you asked!  We need to parse the
timestamp into a &lt;code&gt;DateTime&lt;/code&gt; object, set the time zone, and then pass the
fixed time data to Gnuplot.  It turns out that &lt;a href=&#34;https://metacpan.org/pod/DateTime&#34;&gt;the standard &lt;code&gt;DateTime&lt;/code&gt;
library&lt;/a&gt; doesn&amp;rsquo;t parse date/time strings.
Instead, we need to use
&lt;a href=&#34;https://metacpan.org/pod/DateTime::Format::Strptime&#34;&gt;&lt;code&gt;DateTime::Format::Strptime&lt;/code&gt;&lt;/a&gt;.
This module parses date/time strings much like the &lt;a href=&#34;https://pubs.opengroup.org/onlinepubs/007904875/functions/strptime.html&#34;&gt;&lt;code&gt;strptime(3)&lt;/code&gt; POSIX
function&lt;/a&gt;
does and returns &lt;code&gt;DateTime&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;Since the module isn&amp;rsquo;t part of the core Perl distribution, we need to
install it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cpanm DateTime::Format::Strptime
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most of the code changes that follow take place only within the plotting
routine (&lt;code&gt;plot_activity_data()&lt;/code&gt;).  So, I&amp;rsquo;m going to focus on that from now
on and won&amp;rsquo;t create a new script for the new version of the code.&lt;/p&gt;
&lt;p&gt;The first thing to do is to import the &lt;code&gt;DateTime::Format::Strptime&lt;/code&gt; module:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; use Scalar::Util qw(reftype);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; use List::Util qw(max sum);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; use Chart::Gnuplot;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+use DateTime::Format::Strptime;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Extending &lt;code&gt;plot_activity_data()&lt;/code&gt; to set the correct time zone, we get this
code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;plot_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# extract data to plot from full activity data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# fix time zone in time data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date_parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DateTime::Format::Strptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10&lt;/span&gt;&lt;span&gt;        pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11&lt;/span&gt;&lt;span&gt;        time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16&lt;/span&gt;&lt;span&gt;        $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;set_time_zone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Europe/Berlin&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $time_string &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%H:%M:%S&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18&lt;/span&gt;&lt;span&gt;        $time_string;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19&lt;/span&gt;&lt;span&gt;    } @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# determine date from timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%d&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# plot data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27&lt;/span&gt;&lt;span&gt;        output &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;watopia-figure-8-heart-rate.png&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28&lt;/span&gt;&lt;span&gt;        title  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Figure 8 in Watopia on $date: heart rate over time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29&lt;/span&gt;&lt;span&gt;        xlabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30&lt;/span&gt;&lt;span&gt;        ylabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Heart rate (bpm)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31&lt;/span&gt;&lt;span&gt;        terminal &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;png size 1024, 768&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32&lt;/span&gt;&lt;span&gt;        timeaxis &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33&lt;/span&gt;&lt;span&gt;        xtics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34&lt;/span&gt;&lt;span&gt;            labelfmt &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;38&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $data_set &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;39&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;40&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@heart_rates,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;41&lt;/span&gt;&lt;span&gt;        timefmt &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%H:%M:%S&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;42&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;43&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;44&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;45&lt;/span&gt;&lt;span&gt;    $chart&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;plot2d($data_set);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;46&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The timestamp data is no longer read straight into the &lt;code&gt;@times&lt;/code&gt; array; it&amp;rsquo;s
stored in the &lt;code&gt;@timestamps&lt;/code&gt; temporary array (line 6).  This change also
makes the variable naming a bit more consistent, which is nice.&lt;/p&gt;
&lt;p&gt;To parse a timestamp string into a &lt;code&gt;DateTime&lt;/code&gt; object, we need to tell
&lt;code&gt;DateTime::Format::Strptime&lt;/code&gt; how to format the timestamp (lines 8-12).  This
is the purpose of the &lt;code&gt;pattern&lt;/code&gt; argument in the &lt;code&gt;DateTime::Format::Strptime&lt;/code&gt;
constructor (line 10).  You might have noticed that we used the same pattern
when telling Gnuplot what format the time data was in.  We also specify the
time zone (line 11) to ensure that the date/time data is parsed as UTC.&lt;/p&gt;
&lt;p&gt;Next, we fix the time zone in all elements of the &lt;code&gt;@timestamps&lt;/code&gt; array (lines
14-19).  I&amp;rsquo;ve chosen to do this within a &lt;code&gt;map&lt;/code&gt; here.  I could extract this
code into a well-named routine, but it does the job for now.  The &lt;code&gt;map&lt;/code&gt;
parses the date/time string into a &lt;code&gt;Date::Time&lt;/code&gt; object (line 15) and sets
the time zone to &lt;code&gt;Europe/Berlin&lt;/code&gt;&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; (line 16).  We only need to
plot the time data,&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; hence we format the &lt;code&gt;DateTime&lt;/code&gt;
object as a string including only hour, minute and second information (line
17).  Even though we only use hours and minutes for the x-axis tick labels
later, the time data is resolved down to the second, hence we retain the
seconds information in the &lt;code&gt;@times&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;One could write a more compact version of the time zone correction code like
this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;set_time_zone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Europe/Berlin&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%H:%M:%S&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;yet, in this case, I find giving each step a name (via a variable) helps
the code explain itself.  YMMV.&lt;/p&gt;
&lt;p&gt;The next chunk of code (lines 22-23) isn&amp;rsquo;t related to the time zone fix.  It
generalises working out the current date from the activity data.  This way I
can use a FIT file from a different activity without having to update the
&lt;code&gt;$date&lt;/code&gt; variable by hand.  The process is simple: all elements of the
&lt;code&gt;@timestamps&lt;/code&gt; array have the same date, so we choose to parse only the first
one (line 22)&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.  This gives us a &lt;code&gt;DateTime&lt;/code&gt; object
which we convert into a formatted date string (via the &lt;code&gt;strftime()&lt;/code&gt; method)
composed of the year, month and day (line 23).  We don&amp;rsquo;t need to fix the
time zone because UTC is sufficient in this case to extract the date
information.  Of course, if you&amp;rsquo;re in a time zone close to the international
date line you might need to set the time zone to get the correct date.&lt;/p&gt;
&lt;p&gt;The last thing to change is the &lt;code&gt;timefmt&lt;/code&gt; option to the
&lt;code&gt;Chart::Gnuplot::DataSet&lt;/code&gt; object on line 41.  Because we now only have hour,
minute and second information, we need to update the time format string to
reflect this.&lt;/p&gt;
&lt;p&gt;Now we&amp;rsquo;re ready to run the script again!  Doing so&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ perl geo-fit-plot-data.pl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;creates this graph&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/geo-fit-heart-rate-vs-timestamp-fixed-timezone.png&#34; alt=&#34;Plot of heart rate versus timestamp with fixed time zone&#34;&gt;&lt;/p&gt;
&lt;p&gt;where we see that the time information is correct.  Yay!  &amp;#x1f389;&lt;/p&gt;
&lt;h2 id=&#34;how-long-can-this-go-on&#34;&gt;How long can this go on?&lt;/h2&gt;
&lt;p&gt;Now that I look at the graph again, I realise something: it doesn&amp;rsquo;t matter
&lt;em&gt;when&lt;/em&gt; the data was taken (at least, not for this use case).  What matters
more is the elapsed time from the start of the activity until the end.  It
looks like we need to &lt;a href=&#34;https://datamungingwithperl.com/&#34;&gt;munge&lt;/a&gt; the time
data again.  The job now is to convert the timestamp information into
seconds elapsed since the ride began.  Since we&amp;rsquo;ve parsed the timestamp data
into &lt;code&gt;DateTime&lt;/code&gt; objects (in line 15 above), we can convert that value into
the number of seconds since the epoch (via &lt;a href=&#34;https://metacpan.org/pod/DateTime#%24dt-%3Eepoch&#34;&gt;the &lt;code&gt;epoch()&lt;/code&gt;
method&lt;/a&gt;).  As soon as we
know the epoch value for each element in the &lt;code&gt;@timestamps&lt;/code&gt; array, we can
subtract the first element&amp;rsquo;s epoch value from each element in the array.
This will give us an array containing elapsed seconds since the beginning of
the activity.  Elapsed seconds are a bit too fine-grained for an activity
extending over an hour, so we&amp;rsquo;ll also convert seconds to minutes.&lt;/p&gt;
&lt;p&gt;Making these changes to the &lt;code&gt;plot_activity_data()&lt;/code&gt; code, we get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;plot_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# extract data to plot from full activity data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# parse timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date_parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DateTime::Format::Strptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10&lt;/span&gt;&lt;span&gt;        pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11&lt;/span&gt;&lt;span&gt;        time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# get the epoch time for the first point in the time data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $first_epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# convert timestamp data to elapsed minutes from start of activity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $elapsed_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ($epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; $first_epoch_time)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22&lt;/span&gt;&lt;span&gt;        $elapsed_time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23&lt;/span&gt;&lt;span&gt;    } @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# determine date from timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%d&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# plot data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31&lt;/span&gt;&lt;span&gt;        output &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;watopia-figure-8-heart-rate.png&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32&lt;/span&gt;&lt;span&gt;        title  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Figure 8 in Watopia on $date: heart rate over time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33&lt;/span&gt;&lt;span&gt;        xlabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Elapsed time (min)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34&lt;/span&gt;&lt;span&gt;        ylabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Heart rate (bpm)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35&lt;/span&gt;&lt;span&gt;        terminal &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;png size 1024, 768&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;38&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $data_set &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;39&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;40&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@heart_rates,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;41&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;42&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;43&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;44&lt;/span&gt;&lt;span&gt;    $chart&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;plot2d($data_set);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;45&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The main changes occur in lines 14-23.  We parse the date/time information
from the first timestamp (line 15), chaining the &lt;code&gt;epoch()&lt;/code&gt; method call to
find the number of seconds since the epoch.  We store this result in a
variable for later use; it holds the epoch time at the beginning of the data
series.  After parsing the timestamps into &lt;code&gt;DateTime&lt;/code&gt; objects (line 19), we
find the epoch time for each time point (line 20).  Line 21 calculates the
elapsed time from the time stored in &lt;code&gt;$first_epoch_time&lt;/code&gt; and converts
seconds to minutes by dividing by 60.  The &lt;code&gt;map&lt;/code&gt; returns this value (line
22) and hence &lt;code&gt;@times&lt;/code&gt; now contains a series of elapsed time values in
minutes.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s important to note here that we&amp;rsquo;re no longer plotting a date/time value
on the x-axis; the elapsed time is a purely numerical value.  Thus, we
update the x-axis label string (line 33) to highlight this fact and remove
the &lt;code&gt;timeaxis&lt;/code&gt; and &lt;code&gt;xtics&lt;/code&gt;/&lt;code&gt;labelfmt&lt;/code&gt; options from the &lt;code&gt;Chart::Gnuplot&lt;/code&gt;
constructor.  The &lt;code&gt;timefmt&lt;/code&gt; option to the &lt;code&gt;Chart::Gnuplot::DataSet&lt;/code&gt;
constructor is also no longer necessary and it too has been removed.&lt;/p&gt;
&lt;p&gt;The script is now ready to go!&lt;/p&gt;
&lt;p&gt;Running it&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ perl geo-fit-plot-data.pl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;gives&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/geo-fit-heart-rate-vs-elapsed-time.png&#34; alt=&#34;Plot of heart rate versus elapsed time in minutes&#34;&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s better!&lt;/p&gt;
&lt;h2 id=&#34;reaching-for-the-sky&#34;&gt;Reaching for the sky&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-basic-beginnings/#calculating-a-rides-statistics&#34;&gt;Our statistics output from
earlier&lt;/a&gt;
told us that the maximum heart rate was 165 bpm with an average of 142 bpm.
Looking at the graph, an average of 142 bpm seems about right.  It also
looks like the maximum pulse value occurred at an elapsed time of just short
of 50 minutes.  &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-interactive-data-analysis/&#34;&gt;We can check that guess more closely
later.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s intriguing me now is what caused this pattern in the heart rate data.
What could have caused the values to go up and down like that?  Is there a
correlation with other data fields?  We know from earlier that there&amp;rsquo;s an
&lt;code&gt;altitude&lt;/code&gt; field, so we can try plotting that along with the heart rate data
and see how (or if) they&amp;rsquo;re related.&lt;/p&gt;
&lt;p&gt;Careful readers might have noticed something: how can you have a variation
in altitude when you&amp;rsquo;re sitting on an indoor trainer?  Well, Zwift simulates
going up and downhill by changing the resistance in the smart trainer.  The
resistance is then correlated to a gradient and, given time and speed data,
one can work out a virtual altitude gain or loss.  Thus, for the data set
we&amp;rsquo;re analysing here, altitude is a sensible parameter to consider.  Even if
you had no vertical motion whatsoever!&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, one of the things I like about Gnuplot is that one
can plot two data sets with different y-axes on the same plot.  Plotting
heart rate and altitude on the same graph is one such use case.&lt;/p&gt;
&lt;p&gt;To plot an extra data set on our graph, we need to set up another
&lt;code&gt;Chart::Gnuplot::DataSet&lt;/code&gt; object, this time for the altitude data.  Before
we can do that, we&amp;rsquo;ll have to extract the altitude data from the full
activity data set.  Gnuplot also needs to know which data to plot on the
primary and secondary y-axes (i.e. on the left- and right-hand sides of the
graph).  And we must remember to &lt;a href=&#34;https://xkcd.com/833/&#34;&gt;label our axes
properly&lt;/a&gt;.  That&amp;rsquo;s a fair bit of work, so I&amp;rsquo;ve done
the &lt;a href=&#34;https://en.wikipedia.org/wiki/Hard_Yakka#Etymology_of_name&#34;&gt;hard&lt;/a&gt;
&lt;a href=&#34;https://slll.cass.anu.edu.au/centres/andc/meanings-origins/y&#34;&gt;yakka&lt;/a&gt; for
ya. &amp;#x1f609;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the updated &lt;code&gt;plot_activity_data()&lt;/code&gt; code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;plot_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# extract data to plot from full activity data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @altitudes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;altitude&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# parse timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date_parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DateTime::Format::Strptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11&lt;/span&gt;&lt;span&gt;        pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12&lt;/span&gt;&lt;span&gt;        time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# get the epoch time for the first point in the time data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $first_epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# convert timestamp data to elapsed minutes from start of activity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $elapsed_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ($epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; $first_epoch_time)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23&lt;/span&gt;&lt;span&gt;        $elapsed_time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24&lt;/span&gt;&lt;span&gt;    } @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# determine date from timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%d&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# plot data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32&lt;/span&gt;&lt;span&gt;        output &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;watopia-figure-8-heart-rate-and-altitude.png&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33&lt;/span&gt;&lt;span&gt;        title  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Figure 8 in Watopia on $date: heart rate and altitude over time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34&lt;/span&gt;&lt;span&gt;        xlabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Elapsed time (min)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35&lt;/span&gt;&lt;span&gt;        ylabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Heart rate (bpm)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36&lt;/span&gt;&lt;span&gt;        terminal &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;png size 1024, 768&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37&lt;/span&gt;&lt;span&gt;        xtics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;38&lt;/span&gt;&lt;span&gt;            incr &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;39&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;40&lt;/span&gt;&lt;span&gt;        y2label &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Altitude (m)&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;41&lt;/span&gt;&lt;span&gt;        y2range &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;70&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;42&lt;/span&gt;&lt;span&gt;        y2tics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;43&lt;/span&gt;&lt;span&gt;            incr &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;44&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;45&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;46&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;47&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $heart_rate_ds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;48&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;49&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@heart_rates,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;50&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;51&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;52&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;53&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $altitude_ds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;54&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;55&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@altitudes,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;56&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;boxes&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;57&lt;/span&gt;&lt;span&gt;        axes &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x1y2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;58&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;59&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;60&lt;/span&gt;&lt;span&gt;    $chart&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;plot2d($altitude_ds, $heart_rate_ds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;61&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Line 7 extracts the altitude data from the full activity data.  This code
also strips the unit information from the altitude data so that we only have
the numerical part, which is what Gnuplot needs.  We store the altitude data
in the &lt;code&gt;@altitudes&lt;/code&gt; array.  This we use later to create a
&lt;code&gt;Chart::Gnuplot::DataSet&lt;/code&gt; object on lines 53-58.  An important line to note
here is the &lt;code&gt;axes&lt;/code&gt; setting on line 57; it tells Gnuplot to use the secondary
y-axis on the right-hand side for this data set.  I&amp;rsquo;ve chosen to use the
&lt;code&gt;boxes&lt;/code&gt; style for the altitude data (line 56) so that the output looks a bit
like the hills and valleys that it represents.&lt;/p&gt;
&lt;p&gt;To make the time data a bit easier to read and analyse, I&amp;rsquo;ve set the
increment for the ticks on the x-axis to 5 (lines 37-39).  This way it&amp;rsquo;ll be
easier to refer to specific changes in altitude and heart rate data.&lt;/p&gt;
&lt;p&gt;The settings for the secondary y-axis use the same names as for the primary
y-axis, with the exception that the string &lt;code&gt;y2&lt;/code&gt; replaces &lt;code&gt;y&lt;/code&gt;.  For instance,
to set the axis label for the secondary y-axis, we specify the &lt;code&gt;y2label&lt;/code&gt;
value, as in line 40 above.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve set the range on the secondary y-axis explicitly (line 41) because the
output looks better than what the automatic range was able to make in this
case.  Similarly, I&amp;rsquo;ve set the increment on the secondary y-axis ticks
(lines 42-44) because the automatic output wasn&amp;rsquo;t as good as what I wanted.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also renamed the variable for the heart rate data set on line 47 to be
more descriptive; the name &lt;code&gt;$data_set&lt;/code&gt; was much too generic.&lt;/p&gt;
&lt;p&gt;We specify the altitude data set first in the call to &lt;code&gt;plot2d()&lt;/code&gt; (line 60)
because we want the heart rate data plotted &amp;ldquo;on top&amp;rdquo; of the altitude data.
Had we used &lt;code&gt;$heart_rate_ds&lt;/code&gt; first in this call, the altitude data would
have obscured part of the heart rate data.&lt;/p&gt;
&lt;p&gt;Running our script in the now familiar way&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ perl geo-fit-plot-data.pl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;gives this plot&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/geo-fit-heart-rate-and-altitude.png&#34; alt=&#34;Plot of heart rate and altitude versus elapsed time in minutes&#34;&gt;&lt;/p&gt;
&lt;p&gt;Cool!  Now it&amp;rsquo;s a bit clearer why the heart rate evolved the way it did.&lt;/p&gt;
&lt;p&gt;At the beginning of the graph (in the first ~10 minutes) it looks like I was
getting warmed up and my pulse was finding a kind of base level (~130 bpm).
Then things started going uphill at about the 10-minute mark and my pulse
also kept going upwards.  This makes sense because I was working harder.
Between about 13 minutes and 19 minutes came the first hill climb on the
route and here I was riding even harder.  The effort is reflected in the
heart rate data which rose to around 160 bpm at the top of the hill.  That
explains why the heart rate went up from the beginning to roughly the
18-minute mark.&lt;/p&gt;
&lt;p&gt;Looking back over the Zwift data for that particular ride, it seems that I
took the KOM&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt; for that climb at that time, so no wonder my pulse was
high!&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;  Note that this wasn&amp;rsquo;t a special record or anything
like that; it was a short-term &lt;a href=&#34;https://support.zwift.com/en_us/segments-jerseys-and-time-limits-BkGig0kZS7&#34;&gt;live
result&lt;/a&gt;&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;
and someone else took the jersey with a faster time not long after I&amp;rsquo;d done
my best time up that climb.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/figure-8-in-watopia-kom-screenshot.jpg&#34; alt=&#34;View of cyclist riding along road in Zwift wearing a polka dot jersey&#34;&gt;&lt;/p&gt;
&lt;p&gt;It was all downhill shortly after the hill climb, which also explains why
the heart rate went down straight afterwards.  We also see similar behaviour
on the second hill climb (from about 37 minutes to 42 minutes).  Although my
pulse rose throughout the hill climb, it didn&amp;rsquo;t rise as high this
time.  This indicates that I was getting tired and wasn&amp;rsquo;t able to put as
much effort in.&lt;/p&gt;
&lt;p&gt;Just in case you&amp;rsquo;re wondering how the altitude can go
negative,&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt; part of the route goes through &amp;ldquo;underwater
tunnels&amp;rdquo;.  This highlights the flexibility of the virtual worlds within
Zwift: the designers have enormous room to let their imaginations run wild.
There are all kinds of fun things to discover along the various routes and
many that don&amp;rsquo;t exist in the Real World™.  Along with the underwater tunnels
(where it&amp;rsquo;s like riding through a giant aquarium, with sunken shipwrecks,
fish, and whales), there is a wild west style town complete with a steam
train from that era chugging past.  There are also Mayan ruins with llamas
(&lt;a href=&#34;https://zwiftinsider.com/update-1-67-0-130349/#comment-122361&#34;&gt;or maybe
alpacas?&lt;/a&gt;)
wandering around and even a section with dinosaurs grazing at the side of
the road.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what it looks like riding through an underwater tunnel:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/zwift-riding-underwater-tunnel.jpg&#34; alt=&#34;View of cyclist riding through underwater tunnel in Zwift&#34;&gt;&lt;/p&gt;
&lt;p&gt;I think that&amp;rsquo;s pretty cool.&lt;/p&gt;
&lt;p&gt;At the end of the ride (at ~53 minutes) my pulse dropped sharply.  Since
this was the warm-down phase of the ride, this also makes sense.&lt;/p&gt;
&lt;h2 id=&#34;power-to-the-people&#34;&gt;Power to the people!&lt;/h2&gt;
&lt;p&gt;There are two peaks in the heart rate data that don&amp;rsquo;t correlate with
altitude (one at ~25 minutes and another at ~48 minutes).  The altitude
change at these locations would suggest that things are fairly flat.  What&amp;rsquo;s
going on there?&lt;/p&gt;
&lt;p&gt;One other parameter that we could consider for correlations is power output.
Going uphill requires more power than riding on the flat, so we&amp;rsquo;d expect to
see higher power values (and therefore higher heart rates) when climbing.
If flat roads require less power, what&amp;rsquo;s causing the peaks in the pulse?
Maybe there&amp;rsquo;s another puzzle hiding in the data.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s combine the heart rate data with power output and see what other
relationships we can discover.  To do this we need to extract power output
data instead of altitude data.  Then we need to change the secondary y-axis
data set and configuration to produce a nice plot of power output.  Making
these changes gives this code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;plot_activity_data&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @activity_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @_;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# extract data to plot from full activity data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @heart_rates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;heart_rate&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @timestamps &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map { $_&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;} } @activity_data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @powers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; num_parts(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;power&amp;#39;&lt;/span&gt;, @activity_data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# parse timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date_parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; DateTime::Format::Strptime&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11&lt;/span&gt;&lt;span&gt;        pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12&lt;/span&gt;&lt;span&gt;        time_zone &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# get the epoch time for the first point in the time data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $first_epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# convert timestamp data to elapsed minutes from start of activity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; @times &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; map {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($_);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;epoch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22&lt;/span&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $elapsed_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ($epoch_time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; $first_epoch_time)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23&lt;/span&gt;&lt;span&gt;        $elapsed_time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24&lt;/span&gt;&lt;span&gt;    } @timestamps;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# determine date from timestamp data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $dt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $date_parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parse_datetime($timestamps[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $dt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%Y-%m-%d&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# plot data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $chart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32&lt;/span&gt;&lt;span&gt;        output &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;watopia-figure-8-heart-rate-and-power.png&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33&lt;/span&gt;&lt;span&gt;        title  &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Figure 8 in Watopia on $date: heart rate and power over time&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34&lt;/span&gt;&lt;span&gt;        xlabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Elapsed time (min)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35&lt;/span&gt;&lt;span&gt;        ylabel &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Heart rate (bpm)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36&lt;/span&gt;&lt;span&gt;        terminal &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;png size 1024, 768&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37&lt;/span&gt;&lt;span&gt;        xtics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;38&lt;/span&gt;&lt;span&gt;            incr &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;39&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;40&lt;/span&gt;&lt;span&gt;        ytics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;41&lt;/span&gt;&lt;span&gt;            mirror &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;off&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;42&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;43&lt;/span&gt;&lt;span&gt;        y2label &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Power (W)&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;44&lt;/span&gt;&lt;span&gt;        y2range &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1100&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;45&lt;/span&gt;&lt;span&gt;        y2tics &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;46&lt;/span&gt;&lt;span&gt;            incr &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;47&lt;/span&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;48&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;49&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;50&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $heart_rate_ds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;51&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;52&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@heart_rates,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;53&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;54&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;55&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;56&lt;/span&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;my&lt;/span&gt; $power_ds &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Chart::Gnuplot::DataSet&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;57&lt;/span&gt;&lt;span&gt;        xdata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@times,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;58&lt;/span&gt;&lt;span&gt;        ydata &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;\&lt;/span&gt;@powers,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;59&lt;/span&gt;&lt;span&gt;        style &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lines&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;60&lt;/span&gt;&lt;span&gt;        axes &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;x1y2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;61&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;62&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;63&lt;/span&gt;&lt;span&gt;    $chart&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;plot2d($power_ds, $heart_rate_ds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;64&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On line 7, I swapped out the altitude data extraction code with power
output.  Then, I updated the output filename (line 32) and plot title (line
33) to highlight that we&amp;rsquo;re now plotting heart rate and power data.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;mirror&lt;/code&gt; option to the &lt;code&gt;ytics&lt;/code&gt; setting (lines 40-42) isn&amp;rsquo;t an obvious
change.  Its purpose is to stop the ticks from the primary y-axis from being
mirrored to the secondary y-axis (on the right-hand side).  We want to stop
these mirrored ticks from appearing because they&amp;rsquo;ll clash with the secondary
y-axis tick marks.  The reason we didn&amp;rsquo;t need this before is that all the
y-axis ticks happened to line up and the issue wasn&amp;rsquo;t obvious until now.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve updated the secondary axis label setting to mention power (line 43).
Also, I&amp;rsquo;ve set the range to match the data we&amp;rsquo;re plotting (line 44) and to
space out the data nicely via the &lt;code&gt;incr&lt;/code&gt; option to the &lt;code&gt;y2tics&lt;/code&gt; setting
(lines 45-47).  It seemed more appropriate to use lines to plot power output
as opposed to the bars we used for the altitude data, hence the change to
the &lt;code&gt;style&lt;/code&gt; option on line 59.&lt;/p&gt;
&lt;p&gt;As we did when plotting altitude, we pass the power data set (&lt;code&gt;$power_ds&lt;/code&gt;)
to the &lt;code&gt;plot2d()&lt;/code&gt; call before &lt;code&gt;$heart_rate_ds&lt;/code&gt; (line 63).&lt;/p&gt;
&lt;p&gt;Running the script again&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ perl geo-fit-plot-data.pl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;produces this plot:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/geo-fit-heart-rate-and-power.png&#34; alt=&#34;Plot of heart rate and power versus elapsed time in minutes&#34;&gt;&lt;/p&gt;
&lt;p&gt;This plot shows the correlation between heart rate and power output that we
expected for the first hill climb.  The power output increases steadily from
the 3-minute mark up to about the 18-minute mark.  After that, it dropped
suddenly once I&amp;rsquo;d reached the top of the climb.  This makes sense: I&amp;rsquo;d just
done a personal best up that climb and needed a bit of respite!&lt;/p&gt;
&lt;p&gt;However, now we can see clearly what caused the spikes in heart rate at 25
minutes and 48 minutes: there are two large spikes in power output.  The
first spike maxes out at 1023 W;&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt; what value the other peak
has, it&amp;rsquo;s hard to tell.  &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-interactive-data-analysis/&#34;&gt;We&amp;rsquo;ll try to work out what that value is
later.&lt;/a&gt;
These spikes in power result from sprints.  In Zwift, not only can one try
to go up hills as fast as possible, but flatter sections have sprints where
one &lt;em&gt;also&lt;/em&gt; tries to go as fast as possible, albeit for shorter distances
(say 200m or 500m).&lt;/p&gt;
&lt;p&gt;Great!  We&amp;rsquo;ve worked out another puzzle in the data!&lt;/p&gt;
&lt;h2 id=&#34;a-quick-comparison&#34;&gt;A quick comparison&lt;/h2&gt;
&lt;p&gt;Zwift produces what they call &lt;em&gt;timelines&lt;/em&gt; of a given ride, which is much the
same as what we&amp;rsquo;ve been plotting here.  For instance, for the FIT file we&amp;rsquo;ve
been looking at, this is the timeline graph:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.perl.com/images/analysing-fit-data-with-perl/figure-8-in-watopia-timeline-graph-2025-05-08.png&#34; alt=&#34;Zwift timeline graph of power output, cadence, heart rate and altitude&#34;&gt;&lt;/p&gt;
&lt;p&gt;Zwift plots several datasets on this graph that have very different value
ranges.  The plot above shows power output, cadence, heart rate, and
altitude data all on one graph!  A lot is going on here and because of the
different data values and ranges, Zwift doesn&amp;rsquo;t display values on the
y-axes.  Their solution is to show all four values at a given time point
when the user hovers their mouse over the graph.  This solution only works
within a web browser and needs lots of JavaScript to work, hence this is
something I like to avoid.  That (and familiarity) is largely the reason why
I prefer PNG output for my graphs.&lt;/p&gt;
&lt;p&gt;If you take a close look at the timeline graph, you&amp;rsquo;ll notice that the
maximum power is given as 937 W and not 1023 W, which we worked out from the
FIT file data.  I don&amp;rsquo;t know what&amp;rsquo;s going on here, as the same graph in the
&lt;a href=&#34;https://support.zwift.com/en_us/using-the-zwift-companion-app-when-cycling-rJ7ayD_ES&#34;&gt;Zwift Companion
App&lt;/a&gt;
shows the 1023 W that we got.  The graph above is a screenshot from the web
application in a browser on my laptop and, at least theoretically, it&amp;rsquo;s
supposed to display the same data.  I&amp;rsquo;ve noticed a few inconsistencies
between the web browser view and that from the Zwift Companion App, so maybe
this discrepancy is one bug that still needs shaking out.&lt;/p&gt;
&lt;h2 id=&#34;a-dialogue-with-data&#34;&gt;A dialogue with data&lt;/h2&gt;
&lt;p&gt;Y&amp;rsquo;know what&amp;rsquo;d also be cool beyond plotting this data?  Playing around with
it interactively.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s also possible with Perl, but it&amp;rsquo;s &lt;a href=&#34;https://www.perl.com/article/analysing-fit-data-with-perl-interactive-data-analysis/&#34;&gt;another
story&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve been using Gnuplot since the late 90&amp;rsquo;s.  Back then,
it was the only freely available plotting software which handled
time data well.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;By default, Gnuplot will generate Postscript output.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;One can interpret the word &amp;ldquo;terminal&amp;rdquo; as a kind of
&amp;ldquo;screen&amp;rdquo; or &amp;ldquo;canvas&amp;rdquo; that the plotting library draws its output on.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve later found out that they haven&amp;rsquo;t
heard anything, so that&amp;rsquo;s good!&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;I live in Germany, so this is the relevant time zone for me.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;All dates are the same and displaying them would be
redundant, hence we omit the date information.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34;&gt;
&lt;p&gt;All elements in the array have the same date, so using
the first one does the job.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34;&gt;
&lt;p&gt;KOM stands for &amp;ldquo;king of the mountains&amp;rdquo;.&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34;&gt;
&lt;p&gt;Yes, I &lt;em&gt;am&lt;/em&gt; stoked that I managed to take that jersey!
Even if it was only for a short time.&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34;&gt;
&lt;p&gt;A live result that makes it onto a &lt;a href=&#34;https://support.zwift.com/en_us/leaderboards-S11PywdNB&#34;&gt;leaderboard&lt;/a&gt; is valid only for one hour.&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34;&gt;
&lt;p&gt;Around the 5-minute mark and again shortly before the
35-minute mark.&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34;&gt;
&lt;p&gt;One thing that this value implies is that I could power a
small bar heater for one second.  But not for very much longer!&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>
