{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\"FMP\"\n", "\"AudioLabs\"\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\"C3\"\n", "

Dynamic Time Warping (DTW)

\n", "
\n", "\n", "
\n", "\n", "

\n", "Following Section 3.2.1 of [Müller, FMP, Springer 2015], we explain in this notebook the basic algorithm for dynamic time warping (DTW). A general introduction can also be found in the following book chapter.\n", "\n", "

\n", "\n", "

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic Idea\n", "\n", "Given two sequences $X:=(x_1,x_2,\\ldots,x_N)$ of length $N\\in\\mathbb{N}$ and $Y:=(y_1,y_2,\\ldots,y_M)$ of length $M\\in\\mathbb{N}$, the objective of **dynamic time warping** (DTW) is to temporally align these two sequences in some optimal sense under certain constraints. The sequences may be discrete signals, feature sequences, sequences of characters, or any kind of time series. Often the indices of the sequences correspond to successive points in time that are spaced at uniform time intervals. The following figure illustrates an alignment (indicated by the red bidirectional arrows) between a sequence $X$ of length $N=9$ and a sequence $Y$ of length $M=7$.\n", "\n", "\"C0\"\n", "\n", "Each of the red bidirectional arrows encodes a correspondence between two elements $x_n$ and $y_m$ for $n\\in[1:N]$ and $m\\in[1:M]$. Such a local correspondence can be modeled by the index pair $(n,m)$. The right side of the above figure illustrates how the alignment shown on the left is encoded by a sequence of index pairs. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Warping Path\n", "\n", "To model a global alignment between the elements of the sequences $X$ and $Y$, the idea is to consider a sequence of index pairs that fulfills certain constraints. This leads to the notion of a **warping path**. By definition, an $(N,M)$-warping path of length $L\\in\\mathbb{N}$ is a sequence \n", "\n", "\\begin{equation}\n", "P=(p_1,\\ldots,p_L)\n", "\\end{equation}\n", "\n", "with $p_\\ell=(n_\\ell,m_\\ell)\\in[1:N]\\times [1:M]$ for $\\ell\\in[1:L]$ satisfying the following conditions:\n", "\n", "* Boundary condition: $p_1= (1,1)$ and $p_L=(N,M)$\n", "* Monotonicity condition: $n_1\\leq n_2\\leq\\ldots \\leq n_L$ and $m_1\\leq m_2\\leq \\ldots \\leq m_L$\n", "* Step-size condition: $p_{\\ell+1}-p_\\ell\\in \\Sigma:=\\{(1,0),(0,1),(1,1)\\}$ for $\\ell\\in[1:L]$\n", "\n", "An $(N,M)$-warping path $P=(p_1,\\ldots,p_L)$ defines an alignment between two sequences $X=(x_1,x_2,\\ldots,x_N)$ and $Y=(y_1,y_2,\\ldots,y_M)$ by assigning the element $x_{n_\\ell}$ of $X$ to the element $y_{m_\\ell}$ of $Y$. The boundary condition enforces that the first elements of $X$ and $Y$ as well as the last elements of $X$ and $Y$ are aligned to each other. The monotonicity condition reflects the requirement of faithful timing: if an element in $X$ precedes a second element in $X$, then this should also hold for the corresponding elements in $Y$, and vice versa. Finally, the step size condition with respect to the set $\\Sigma$ expresses a kind of continuity condition: no element in $X$ and $Y$ can be omitted, and there are no replications in the alignment. Note that the step size condition implies the monotonicity condition, which nevertheless has been quoted explicitly for the sake of clarity. The following figure illustrates the conditions by some examples where the conditions (boundary, monotonicity, step size) are violated.\n", "\n", "\"C0\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cost Matrix and Optimality\n", "\n", "Next, we introduce a notion that tells us something about the **quality** of a warping path. To this end, we need a way to numerically compare the elements of the feature sequences $X$ and $Y$. Let $\\mathcal{F}$ be a **feature space** and assume that $x_n,y_m\\in\\mathcal{F}$ for $n\\in[1:N]$ and $m\\in[1:M]$. To compare two different features $x,y\\in\\mathcal{F}$, one needs a **local cost measure**, which is defined to be a function\n", "\n", "\\begin{equation}\n", " c:\\mathcal{F}\\times\\mathcal{F}\\to \\mathbb{R}.\n", "\\end{equation}\n", "\n", "Typically, $c(x,y)$ is small (low cost) if $x$ and $y$ are similar to each other, and otherwise $c(x,y)$ is large (high cost). Evaluating the local cost measure for each pair of elements of the sequences $X$ and $Y$, one obtains a **cost matrix** $C\\in\\mathbb{R}^{N\\times M}$ defined by\n", "\n", "\\begin{equation}\n", "C(n,m):=c(x_n,y_m)\n", "\\end{equation}\n", "\n", "for $n\\in[1:N]$ and $m\\in[1:M]$. A tuple $(n,m)$ representing an entry of the matrix $C$ will be referred to as a **cell** of the matrix. The **total cost** $c_P(X,Y)$ of a warping path $P$ between two sequences $X$ and $Y$ with respect to the local cost measure $c$ is defined as\n", "\n", "\\begin{equation}\n", " c_P:=\\sum_{\\ell=1}^L c(x_{n_\\ell},y_{m_\\ell}) = \\sum_{\\ell=1}^L C(n_\\ell,m_\\ell).\n", "\\end{equation}\n", "\n", "The intuition of this definition is that the warping path accumulates the cost of all cells it runs through. A warping path is \"good\" if its total cost is low, and it is \"bad\" if its total cost is high. Now, we are interested in an **optimal warping path** between $X$ and $Y$, which is defined to be a warping path $P^\\ast$ that has minimal total cost among all possible warping paths. The cells of this warping path encode an overall optimal alignment between \n", "the elements of the two sequences, where the warping path conditions ensure that each element of sequence $X$ is assigned to at least one element of $Y$ and vice versa. This leads us to the definition of the **DTW distance** denoted as $\\mathrm{DTW}(X,Y)$ between the two sequences $X$ of length $N$ and $Y$ of length $M$, which is defined as the total cost of an optimal $(N,M)$-warping path $P^\\ast$:\n", "\n", "\\begin{eqnarray}\n", " \\mathrm{DTW}(X,Y) :=c_{P^\\ast}(X,Y) = \\min\\{c_P(X,Y)\\mid P \\mbox{ is an $(N,M)$-warping path} \\}\n", "\\end{eqnarray}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## DTW Algorithm using Dynamic Programming\n", "\n", "To determine an optimal warping path $P^\\ast$ for two sequences $X$ and $Y$, one could compute the total cost of all possible $(N,M)$-warping paths and then take the minimal cost. However, the number of different $(N,M)$-warping paths is exponential in $N$ and $M$. Therefore, such a naive approach is computationally infeasible for large $N$ and $M$. We now introduce an $O(NM)$ algorithm that is based on **dynamic programming**. The general idea behind dynamic programming is to break down a given problem into simpler subproblems and then to combine the solutions of the subproblems to reach an overall solution. In the case of DTW, the idea is to derive an optimal warping path for the original sequences from optimal warping paths for truncated subsequences. This idea can then be applied recursively. To formalize this idea, we define the prefix sequences \n", "$X(1\\!:\\!n) := (x_1,\\ldots x_n)$ for $n\\in[1:N]$ and $Y(1\\!:\\!m) := (y_1,\\ldots y_m)$ for $m\\in[1:M]$ and set\n", "\n", "\\begin{equation}\n", " D(n,m):=\\mathrm{DTW}(X(1\\!:\\!n),Y(1\\!:\\!m)).\n", "\\end{equation}\n", "\n", "The values $D$ define an $(N\\times M)$ matrix $D$, which is also referred to as the **accumulated cost matrix**. Each value $D(n,m)$ specifies the total (or accumulated) cost of an optimal warping path starting at cell $(1,1)$ and ending at cell $(n,m)$. Obviously, one has $D(N,M)=\\mathrm{DTW}(X,Y)$. The following table gives a description of the **DTW Algorithm** based on dynamic programming. In the first part, the accumulated cost matrix $D$ is computed iteratively using a nested loop. In the second part, the optimal warping path is computed using a backtracking procedure. For further details and a proof of the algorithm's correctness, we refer to Section 3.2.1 of [Müller, FMP, Springer 2015].\n", "\n", "\"C0\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementation\n", "\n", "We now implement the DTW algorithm as described above. As an illustrative example, we consider two sequences of real numbers and the absolute value of differences (one-dimensional Euclidean distance) as cost measure. In other words, we have the feature space $\\mathcal{F}=\\mathbb{R}$ and $c(x,y):=|x-y|$ for $x,y\\in \\mathcal{F}$.\n", "\n", "
\n", "Note: In Python indexing of lists, arrays, matrices, and so on starts with the index 0. In the following implementation, we follow the Python convention leading to a systematic index shift of minus one in relation to the algorithmic description given above. In particular, this applies to: \n", "\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:40.118091Z", "iopub.status.busy": "2024-02-15T08:50:40.117792Z", "iopub.status.idle": "2024-02-15T08:50:41.907129Z", "shell.execute_reply": "2024-02-15T08:50:41.906548Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "import scipy.spatial\n", "from numba import jit\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "X = [1, 3, 9, 2, 1]\n", "Y = [2, 0, 0, 8, 7, 2]\n", "N = len(X)\n", "M = len(Y)\n", "\n", "plt.figure(figsize=(6, 2))\n", "plt.plot(X, c='k', label='$X$')\n", "plt.plot(Y, c='b', label='$Y$')\n", "plt.legend()\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now compute the **cost matrix** $C$ using the Euclidean distance as **local cost measure** (which amounts to the absolute value in the one-dimensional case)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:41.941701Z", "iopub.status.busy": "2024-02-15T08:50:41.941406Z", "iopub.status.idle": "2024-02-15T08:50:41.946492Z", "shell.execute_reply": "2024-02-15T08:50:41.945788Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cost matrix C =\n", "[[1. 1. 1. 7. 6. 1.]\n", " [1. 3. 3. 5. 4. 1.]\n", " [7. 9. 9. 1. 2. 7.]\n", " [0. 2. 2. 6. 5. 0.]\n", " [1. 1. 1. 7. 6. 1.]]\n" ] } ], "source": [ "def compute_cost_matrix(X, Y, metric='euclidean'):\n", " \"\"\"Compute the cost matrix of two feature sequences\n", "\n", " Notebook: C3/C3S2_DTWbasic.ipynb\n", "\n", " Args:\n", " X (np.ndarray): Sequence 1\n", " Y (np.ndarray): Sequence 2\n", " metric (str): Cost metric, a valid strings for scipy.spatial.distance.cdist (Default value = 'euclidean')\n", "\n", " Returns:\n", " C (np.ndarray): Cost matrix\n", " \"\"\"\n", " X, Y = np.atleast_2d(X, Y)\n", " C = scipy.spatial.distance.cdist(X.T, Y.T, metric=metric)\n", " return C\n", "\n", "C = compute_cost_matrix(X, Y, metric='euclidean')\n", "print('Cost matrix C =', C, sep='\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, using dynamic programming, we compute the **accumulated cost matrix** $D$, which yields the **DTW distance** $\\mathrm{DTW}(X,Y)$." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:41.949716Z", "iopub.status.busy": "2024-02-15T08:50:41.949487Z", "iopub.status.idle": "2024-02-15T08:50:42.489113Z", "shell.execute_reply": "2024-02-15T08:50:42.488482Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Accumulated cost matrix D =\n", "[[ 1. 2. 3. 10. 16. 17.]\n", " [ 2. 4. 5. 8. 12. 13.]\n", " [ 9. 11. 13. 6. 8. 15.]\n", " [ 9. 11. 13. 12. 11. 8.]\n", " [10. 10. 11. 18. 17. 9.]]\n", "DTW distance DTW(X, Y) = 9.0\n" ] } ], "source": [ "@jit(nopython=True)\n", "def compute_accumulated_cost_matrix(C):\n", " \"\"\"Compute the accumulated cost matrix given the cost matrix\n", "\n", " Notebook: C3/C3S2_DTWbasic.ipynb\n", "\n", " Args:\n", " C (np.ndarray): Cost matrix\n", "\n", " Returns:\n", " D (np.ndarray): Accumulated cost matrix\n", " \"\"\"\n", " N = C.shape[0]\n", " M = C.shape[1]\n", " D = np.zeros((N, M))\n", " D[0, 0] = C[0, 0]\n", " for n in range(1, N):\n", " D[n, 0] = D[n-1, 0] + C[n, 0]\n", " for m in range(1, M):\n", " D[0, m] = D[0, m-1] + C[0, m]\n", " for n in range(1, N):\n", " for m in range(1, M):\n", " D[n, m] = C[n, m] + min(D[n-1, m], D[n, m-1], D[n-1, m-1])\n", " return D\n", "\n", "D = compute_accumulated_cost_matrix(C)\n", "print('Accumulated cost matrix D =', D, sep='\\n')\n", "print('DTW distance DTW(X, Y) =', D[-1, -1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we derive the optimal warping path $P^\\ast$ using backtracking." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:42.491712Z", "iopub.status.busy": "2024-02-15T08:50:42.491508Z", "iopub.status.idle": "2024-02-15T08:50:42.751507Z", "shell.execute_reply": "2024-02-15T08:50:42.750879Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimal warping path P = [[0, 0], [0, 1], [1, 2], [2, 3], [2, 4], [3, 5], [4, 5]]\n" ] } ], "source": [ "@jit(nopython=True)\n", "def compute_optimal_warping_path(D):\n", " \"\"\"Compute the warping path given an accumulated cost matrix\n", "\n", " Notebook: C3/C3S2_DTWbasic.ipynb\n", "\n", " Args:\n", " D (np.ndarray): Accumulated cost matrix\n", "\n", " Returns:\n", " P (np.ndarray): Optimal warping path\n", " \"\"\"\n", " N = D.shape[0]\n", " M = D.shape[1]\n", " n = N - 1\n", " m = M - 1\n", " P = [(n, m)]\n", " while n > 0 or m > 0:\n", " if n == 0:\n", " cell = (0, m - 1)\n", " elif m == 0:\n", " cell = (n - 1, 0)\n", " else:\n", " val = min(D[n-1, m-1], D[n-1, m], D[n, m-1])\n", " if val == D[n-1, m-1]:\n", " cell = (n-1, m-1)\n", " elif val == D[n-1, m]:\n", " cell = (n-1, m)\n", " else:\n", " cell = (n, m-1)\n", " P.append(cell)\n", " (n, m) = cell\n", " P.reverse()\n", " return np.array(P)\n", " \n", "P = compute_optimal_warping_path(D)\n", "print('Optimal warping path P =', P.tolist())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a sanity check, we now compute the **total cost** of the optimal warping path, which agrees with $\\mathrm{DTW}(X,Y)$." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:42.754529Z", "iopub.status.busy": "2024-02-15T08:50:42.754330Z", "iopub.status.idle": "2024-02-15T08:50:42.757675Z", "shell.execute_reply": "2024-02-15T08:50:42.757153Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total cost of optimal warping path: 9.0\n", "DTW distance DTW(X, Y) = 9.0\n" ] } ], "source": [ "c_P = sum(C[n, m] for (n, m) in P)\n", "print('Total cost of optimal warping path:', c_P)\n", "print('DTW distance DTW(X, Y) =', D[-1, -1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we visualize the cost matrix $C$ and the accumulated cost matrix $D$ along with the optimal warping path (indicated by the red dots)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:42.760239Z", "iopub.status.busy": "2024-02-15T08:50:42.760045Z", "iopub.status.idle": "2024-02-15T08:50:43.085018Z", "shell.execute_reply": "2024-02-15T08:50:43.084466Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "P = np.array(P) \n", "plt.figure(figsize=(9, 3))\n", "plt.subplot(1, 2, 1)\n", "plt.imshow(C, cmap='gray_r', origin='lower', aspect='equal')\n", "plt.plot(P[:, 1], P[:, 0], marker='o', color='r')\n", "plt.clim([0, np.max(C)])\n", "plt.colorbar()\n", "plt.title('$C$ with optimal warping path')\n", "plt.xlabel('Sequence Y')\n", "plt.ylabel('Sequence X')\n", "\n", "plt.subplot(1, 2, 2)\n", "plt.imshow(D, cmap='gray_r', origin='lower', aspect='equal')\n", "plt.plot(P[:, 1], P[:, 0], marker='o', color='r')\n", "plt.clim([0, np.max(D)])\n", "plt.colorbar()\n", "plt.title('$D$ with optimal warping path')\n", "plt.xlabel('Sequence Y')\n", "plt.ylabel('Sequence X')\n", "\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## libfmp Implemetation\n", "\n", "Some of the above functions involve nested loops, which are inefficient to compute when using Python. Using the [`jit`-compiler offered by the Python package Numba](../B/B_PythonNumba.html), one finds accelerated versions of these functions as part of the [`libfmp`-library](../B/B_libfmp.html). This library also contains functions for visualization. In the following code cell, we call some of these functions:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:43.087836Z", "iopub.status.busy": "2024-02-15T08:50:43.087594Z", "iopub.status.idle": "2024-02-15T08:50:44.757422Z", "shell.execute_reply": "2024-02-15T08:50:44.756720Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import sys\n", "sys.path.append('..')\n", "\n", "import libfmp.c3\n", "\n", "C = libfmp.c3.compute_cost_matrix(X, Y)\n", "D = libfmp.c3.compute_accumulated_cost_matrix(C)\n", "P = libfmp.c3.compute_optimal_warping_path(D)\n", "\n", "P = np.array(P)\n", "\n", "plt.figure(figsize=(9, 3))\n", "ax = plt.subplot(1, 2, 1)\n", "libfmp.c3.plot_matrix_with_points(C, P, linestyle='-', \n", " ax=[ax], aspect='equal', clim=[0, np.max(C)],\n", " title='$C$ with optimal warping path', xlabel='Sequence Y', ylabel='Sequence X');\n", "\n", "ax = plt.subplot(1, 2, 2)\n", "libfmp.c3.plot_matrix_with_points(D, P, linestyle='-', \n", " ax=[ax], aspect='equal', clim=[0, np.max(D)],\n", " title='$D$ with optimal warping path', xlabel='Sequence Y', ylabel='Sequence X');\n", "\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## LibROSA Implemetation\n", "\n", "[LibROSA](https://librosa.org/doc) also offers a DTW function that can realize different [DTW variants](../C3/C3S2_DTWvariants.html). In the following cell, we show how to call the `librosa`-function for the basic DTW algorithm introduced above. Note that the cost matrix $C$ is computed inside the function `librosa.sequence.dtw` and is not returned. For plotting purposes, we reuse the cost matrix from above. As an alternative, one can first compute the cost matrix outside `librosa.sequence.dtw` and then input this matrix to the function (instead of the sequences $X$ and $Y$)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-02-15T08:50:44.760057Z", "iopub.status.busy": "2024-02-15T08:50:44.759858Z", "iopub.status.idle": "2024-02-15T08:50:45.094266Z", "shell.execute_reply": "2024-02-15T08:50:45.093658Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import librosa\n", "\n", "D, P = librosa.sequence.dtw(X, Y, metric='euclidean', \n", " step_sizes_sigma=np.array([[1, 1], [0, 1], [1, 0]]),\n", " weights_add=np.array([0, 0, 0]), weights_mul=np.array([1, 1, 1]))\n", "\n", "plt.figure(figsize=(9, 3))\n", "ax = plt.subplot(1, 2, 1)\n", "libfmp.c3.plot_matrix_with_points(C, P, linestyle='-', \n", " ax=[ax], aspect='equal', clim=[0, np.max(C)],\n", " title='$C$ with optimal warping path', xlabel='Sequence Y', ylabel='Sequence X');\n", "\n", "ax = plt.subplot(1, 2, 2)\n", "libfmp.c3.plot_matrix_with_points(D, P, linestyle='-', \n", " ax=[ax], aspect='equal', clim=[0, np.max(D)],\n", " title='$D$ with optimal warping path', xlabel='Sequence Y', ylabel='Sequence X');\n", "\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further Notes\n", "\n", "* In the [FMP notebook an DTW variants](../C3/C3S2_DTWvariants.html) we discuss various modifications to speed up DTW computations as well as to better control the overall course of the warping paths.\n", "* We represent in the [FMP notebook on music synchronization](../C3/C3_MusicSynchronization.html) an example application, where we apply DTW for automatically aligning to different recordings of the same piece of music.\n", "* In the FMP notebooks, we discuss many more alignment algorithms related to DTW. All these algorithms are based on dynamics programming (DP). For example: \n", " * In the context of [chord recognition](../C5/C5.html), we study the [Viterbi Algorithm](../C5/C5S3_Viterbi.html) for finding an optimal chord sequence. \n", " * In the context of [audio retrieval](../C7/C7.html), we introduce [Subsequence DTW](../C7/C7S2_SubsequenceDTW.html), which is a local variant of DTW.\n", " * In the context of [music structure analysis](../C4/C4.html), introduce a DP-based algorithm for computing an [audio thumbnail](../C4/C4S3_AudioThumbnailing.html)—the most representative and descriptive section of a music recording. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Acknowledgment: This notebook was created by Meinard Müller and Frank Zalkow.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n", "
\"C0\"\"C1\"\"C2\"\"C3\"\"C4\"\"C5\"\"C6\"\"C7\"\"C8\"
" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.16" } }, "nbformat": 4, "nbformat_minor": 1 }