<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ulysseszh.github.io/feed/tags/python.xml" rel="self" type="application/atom+xml" /><link href="https://ulysseszh.github.io/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-04-30T17:49:58-07:00</updated><id>https://ulysseszh.github.io/feed/tags/python.xml</id><title type="html"><![CDATA[Ulysses’ trip]]></title><subtitle>Here we are at the awesome (awful) blog written by UlyssesZhan!</subtitle><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><entry><title type="html"><![CDATA[The buildup kickroll in <cite>Time to beat the odds</cite>]]></title><link href="https://ulysseszh.github.io/music/2025/06/22/beat-the-odds-kickroll.html" rel="alternate" type="text/html" title="The buildup kickroll in Time to beat the odds" /><published>2025-06-22T00:31:43-07:00</published><updated>2025-06-22T00:31:43-07:00</updated><id>https://ulysseszh.github.io/music/2025/06/22/beat-the-odds-kickroll</id><content type="html" xml:base="https://ulysseszh.github.io/music/2025/06/22/beat-the-odds-kickroll.html"><![CDATA[<details>
<summary>
Python preamble
</summary>
<p>Packages to install from PyPI: <code>numpy</code>, <code>scipy</code>, <code>matplotlib</code>, <code>soundfile</code>.</p>
<table class="rouge-table"><tbody><tr><td class="highlight language-python"><pre><code><span class="line line-1"><span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>
</span><span class="line line-2"><span class="kn">from</span> <span class="n">scipy.signal</span> <span class="kn">import</span> <span class="n">ShortTimeFFT</span><span class="p">,</span> <span class="n">find_peaks</span>
</span><span class="line line-3"><span class="kn">from</span> <span class="n">scipy.signal.windows</span> <span class="kn">import</span> <span class="n">hann</span>
</span><span class="line line-4"><span class="kn">from</span> <span class="n">scipy.optimize</span> <span class="kn">import</span> <span class="n">fsolve</span>
</span><span class="line line-5"><span class="kn">from</span> <span class="n">scipy.stats</span> <span class="kn">import</span> <span class="n">linregress</span>
</span><span class="line line-6"><span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
</span><span class="line line-7"><span class="kn">from</span> <span class="n">matplotlib.colors</span> <span class="kn">import</span> <span class="n">LogNorm</span>
</span><span class="line line-8"><span class="kn">import</span> <span class="n">soundfile</span>
</span></code></pre></td></tr></tbody></table>
</details>
<p>There is a nice song by 影虎。called <cite>Time to beat the odds</cite>:</p>
<div class="embed-container">
<iframe src="https://www.youtube.com/embed/C-XxL_LUB18" frameborder="0" allowfullscreen="">
</iframe>
</div>
<p>At 1:18, there is an interesting buildup kickroll. This effect is created by mixing a steady 32nd note kickroll with a gradually-accelerating buildup kickroll. The gradually-accelerating part is not quantized to dyadic note values, so it is pretty hard to analyze by ear.</p>
<p>One can obtain a clearer sample of this kickroll by consulting the file <code>Dr_KICKroll00.ogg</code> from the <a href="https://bmssearch.net/bmses/Lay7qGChaJUgrs" target="_blank" rel="external">BMS</a>. The waveform of the sample looks like this:</p>
<details>
<summary>
Python code
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-python"><pre><code><span class="line line-1"><span class="n">samples</span><span class="p">,</span> <span class="n">fs</span> <span class="o">=</span> <span class="n">soundfile</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="sh">"</span><span class="s">Kagetora_Time-to-beat-the-odds_bofet/Dr_KICKroll00.ogg</span><span class="sh">"</span><span class="p">)</span>
</span><span class="line line-2"><span class="n">fs</span> <span class="o">/=</span> <span class="mi">1000</span> <span class="c1"># Use ms and kHz</span>
</span><span class="line line-3"><span class="k">if</span> <span class="n">samples</span><span class="p">.</span><span class="n">ndim</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
</span><span class="line line-4">	<span class="n">samples</span> <span class="o">=</span> <span class="n">samples</span><span class="p">.</span><span class="nf">mean</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span><span class="line line-5"><span class="n">N</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">samples</span><span class="p">)</span>
</span><span class="line line-6">
</span><span class="line line-7"><span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>
</span><span class="line line-8"><span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="n">N</span><span class="p">)</span> <span class="o">/</span> <span class="n">fs</span><span class="p">,</span> <span class="n">samples</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
</span><span class="line line-9"><span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Time (ms)</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-10"><span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Amplitude</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-11"><span class="n">plt</span><span class="p">.</span><span class="nf">xlim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">N</span><span class="o">/</span><span class="n">fs</span><span class="p">)</span>
</span><span class="line line-12"><span class="n">plt</span><span class="p">.</span><span class="nf">ylim</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></code></pre></td></tr></tbody></table>
</details>
<figure>
<img src="/assets/images/figures/2025-06-22-beat-the-odds-kickroll/samples.svg" class="dark-adaptive" alt="Waveform plot."/>

</figure>
<p>There is clearly a repeating pattern that is occurring at an gradually-increasing frequency. Each occurrence of the pattern is a kick. The task is to find the regularity of the kicks.</p>
<p>We can see that, at the start of each kick, the waveform is oscillating more rapidly than in later times of the kick. Therefore, a natural idea is to capture this high-frequency oscillation feature using the short-time Fourier transform (STFT), and we would expect to see some peaks at the high-frequency region of the spectrogram of the waveform at each kick occurrence. SciPy provides a convenient function for STFT, and then we can plot the spectrogram of the waveform:</p>
<details>
<summary>
Python code
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-python"><pre><code><span class="line line-1"><span class="n">hop</span> <span class="o">=</span> <span class="mi">32</span>
</span><span class="line line-2"><span class="n">SFT</span> <span class="o">=</span> <span class="nc">ShortTimeFFT</span><span class="p">(</span><span class="nf">hann</span><span class="p">(</span><span class="n">hop</span><span class="p">),</span> <span class="n">hop</span><span class="p">,</span> <span class="n">fs</span><span class="p">)</span>
</span><span class="line line-3"><span class="n">spectrogram</span> <span class="o">=</span> <span class="n">SFT</span><span class="p">.</span><span class="nf">spectrogram</span><span class="p">(</span><span class="n">samples</span><span class="p">)</span>
</span><span class="line line-4">
</span><span class="line line-5"><span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>
</span><span class="line line-6"><span class="n">im</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span>
</span><span class="line line-7">	<span class="n">spectrogram</span><span class="p">,</span>
</span><span class="line line-8">	<span class="n">aspect</span><span class="o">=</span><span class="sh">'</span><span class="s">auto</span><span class="sh">'</span><span class="p">,</span>
</span><span class="line line-9">	<span class="n">origin</span><span class="o">=</span><span class="sh">'</span><span class="s">lower</span><span class="sh">'</span><span class="p">,</span>
</span><span class="line line-10">	<span class="n">extent</span><span class="o">=</span><span class="n">SFT</span><span class="p">.</span><span class="nf">extent</span><span class="p">(</span><span class="n">N</span><span class="p">),</span>
</span><span class="line line-11">	<span class="n">norm</span><span class="o">=</span><span class="nc">LogNorm</span><span class="p">(</span><span class="n">vmin</span><span class="o">=</span><span class="mf">0.001</span><span class="p">,</span> <span class="n">vmax</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="nf">max</span><span class="p">(</span><span class="n">spectrogram</span><span class="p">)),</span>
</span><span class="line line-12"><span class="p">)</span>
</span><span class="line line-13"><span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Time (ms)</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-14"><span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Frequency (kHz)</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-15"><span class="n">plt</span><span class="p">.</span><span class="nf">colorbar</span><span class="p">(</span><span class="n">im</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sh">'</span><span class="s">Magnitude</span><span class="sh">'</span><span class="p">)</span>
</span></code></pre></td></tr></tbody></table>
</details>
<figure>
<img src="/assets/images/figures/2025-06-22-beat-the-odds-kickroll/spectrogram.svg" class="dark-adaptive" alt="Spectrogram of the kick roll sample."/>

</figure>
<p>We can see that there are clearly some peaks at the high-frequency region of the spectrogram. Pick out the peaks:</p>
<details>
<summary>
Python code
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-python"><pre><code><span class="line line-1"><span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>
</span><span class="line line-2"><span class="n">data</span> <span class="o">=</span> <span class="n">spectrogram</span><span class="p">[</span><span class="mi">14</span><span class="p">:,</span> <span class="p">:].</span><span class="nf">mean</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span><span class="line line-3"><span class="n">peaks</span><span class="p">,</span> <span class="n">peak_properties</span> <span class="o">=</span> <span class="nf">find_peaks</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mf">0.004</span><span class="p">,</span> <span class="n">distance</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span>
</span><span class="line line-4"><span class="n">peaks</span> <span class="o">=</span> <span class="n">peaks</span> <span class="o">*</span> <span class="n">hop</span> <span class="o">/</span> <span class="n">fs</span>
</span><span class="line line-5"><span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Time (ms)</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-6"><span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Average magnitude for high frequencies</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-7"><span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">SFT</span><span class="p">.</span><span class="nf">t</span><span class="p">(</span><span class="n">N</span><span class="p">),</span> <span class="n">data</span><span class="p">)</span>
</span><span class="line line-8"><span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">peaks</span><span class="p">,</span> <span class="n">peak_properties</span><span class="p">[</span><span class="sh">'</span><span class="s">peak_heights</span><span class="sh">'</span><span class="p">],</span> <span class="sh">"</span><span class="s">o</span><span class="sh">"</span><span class="p">)</span>
</span></code></pre></td></tr></tbody></table>
</details>
<figure>
<img src="/assets/images/figures/2025-06-22-beat-the-odds-kickroll/peaks.svg" class="dark-adaptive" alt="Each peak is approximately at the start of a kick."/>

</figure>
<p>To check that those peaks make sense, we can mark the positions of the peaks on the waveform to see if they are indeed approximately at the start of each kick:</p>
<details>
<summary>
Python code
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-python"><pre><code><span class="line line-1"><span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>
</span><span class="line line-2"><span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="n">N</span><span class="p">)</span> <span class="o">/</span> <span class="n">fs</span><span class="p">,</span> <span class="n">samples</span><span class="p">)</span>
</span><span class="line line-3"><span class="k">for</span> <span class="n">peak</span> <span class="ow">in</span> <span class="n">peaks</span><span class="p">:</span>
</span><span class="line line-4">	<span class="n">plt</span><span class="p">.</span><span class="nf">axvline</span><span class="p">(</span><span class="n">peak</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-5"><span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Time (ms)</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-6"><span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Amplitude</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-7"><span class="n">plt</span><span class="p">.</span><span class="nf">xlim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span>
</span><span class="line line-8"><span class="n">plt</span><span class="p">.</span><span class="nf">ylim</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></code></pre></td></tr></tbody></table>
</details>
<figure>
<img src="/assets/images/figures/2025-06-22-beat-the-odds-kickroll/peaks_on_samples.svg" class="dark-adaptive" alt="Waveform with high-frequency spectrogram peaks marked."/>

</figure>
<p>We can see that although they are not perfectly at the start of each kick, the peaks are indeed approximately at the start of each kick. Now, plot the time intervals between each pair of consecutive peaks. If the plot is in log-scale, one can see that they approximately form a straight line, which means that the time intervals decay geometrically. We can use a simple linear regression to fit the data.</p>
<details>
<summary>
Python code
</summary>
<table class="rouge-table"><tbody><tr><td class="highlight language-python"><pre><code><span class="line line-1"><span class="n">fig</span><span class="p">,</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>
</span><span class="line line-2"><span class="n">ax</span><span class="p">.</span><span class="nf">set_yscale</span><span class="p">(</span><span class="sh">'</span><span class="s">log</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-3"><span class="n">intervals</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">diff</span><span class="p">(</span><span class="n">peaks</span><span class="p">)</span>
</span><span class="line line-4"><span class="n">indices</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">intervals</span><span class="p">))</span>
</span><span class="line line-5"><span class="n">lin_res</span> <span class="o">=</span> <span class="nf">linregress</span><span class="p">(</span><span class="n">indices</span><span class="p">,</span> <span class="n">np</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="n">intervals</span><span class="p">))</span>
</span><span class="line line-6"><span class="n">ax</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">indices</span><span class="p">,</span> <span class="n">intervals</span><span class="p">,</span> <span class="sh">'</span><span class="s">o</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-7"><span class="n">ax</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">indices</span><span class="p">,</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">lin_res</span><span class="p">.</span><span class="n">intercept</span> <span class="o">+</span> <span class="n">lin_res</span><span class="p">.</span><span class="n">slope</span> <span class="o">*</span> <span class="n">indices</span><span class="p">))</span>
</span><span class="line line-8"><span class="n">ax</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Kick</span><span class="sh">'</span><span class="p">)</span>
</span><span class="line line-9"><span class="n">ax</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Interval (ms)</span><span class="sh">'</span><span class="p">)</span>
</span></code></pre></td></tr></tbody></table>
</details>
<figure>
<img src="/assets/images/figures/2025-06-22-beat-the-odds-kickroll/intervals.svg" class="dark-adaptive" alt="Intervals between consecutive peaks."/>

</figure>
<p>With the linear regression, we can conclude that the decay ratio is <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.9762</mn></mrow><annotation encoding="application/x-tex">0.9762</annotation></semantics></math></span></span>.</p>
<p>If we had guessed that the time intervals decay geometrically and that the first kick has the length of a 32nd note, we could calculate the decay ratio <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi></mrow><annotation encoding="application/x-tex">a</annotation></semantics></math></span></span> by solving the equation <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><mn>8</mn></mfrac><mrow><mo fence="true">(</mo><mn>1</mn><mo>−</mo><msup><mi>a</mi><mn>59</mn></msup><mo fence="true">)</mo></mrow><mo>=</mo><mn>4</mn><mrow><mo fence="true">(</mo><mn>1</mn><mo>−</mo><mi>a</mi><mo fence="true">)</mo></mrow><mo separator="true">,</mo></mrow><annotation encoding="application/x-tex">\fr18\p{1-a^{59}}=4\p{1-a},</annotation></semantics></math></span></span></span> where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mn>8</mn></mrow><annotation encoding="application/x-tex">1/8</annotation></semantics></math></span></span> is the note value of a 32nd note, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>4</mn></mrow><annotation encoding="application/x-tex">4</annotation></semantics></math></span></span> is the total note value of this kickroll (a measure with <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>4</mn></mrow><annotation encoding="application/x-tex">4</annotation></semantics></math></span></span> quarter notes), and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>59</mn></mrow><annotation encoding="application/x-tex">59</annotation></semantics></math></span></span> is the total number of kicks (obtained by counting the patterns in the waveform). Solving this equation numerically gives us <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>=</mo><mn>0.9764</mn></mrow><annotation encoding="application/x-tex">a=0.9764</annotation></semantics></math></span></span>, which is pretty close to the value we got before.</p>]]></content><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><category term="music" /><category term="fourier transform" /><category term="python" /><summary type="html"><![CDATA[There is a gradually-accelerating buildup kickroll at 1:18 in the song <cite>Time to beat the odds</cite> by 影虎。. In order to know the details of this rhythm, I have to analyze the sample using Python. The main method is to do a short-time Fourier transform and to find the peaks in the high-frequency region of the spectrogram. A linear regression shows that the time intervals decay geometrically.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ulysseszh.github.io/assets/images/covers/2025-06-22-beat-the-odds-kickroll.png" /><media:content medium="image" url="https://ulysseszh.github.io/assets/images/covers/2025-06-22-beat-the-odds-kickroll.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>