Set-up
This week, I have expanded spotify.R and moved all of the functions, including new functions, there.
library(tidyverse)
library(spotifyr)
source('spotify.R')
New functions
Last week, we used several custom functions to work with the Spotify API:
get_tidy_audio_analysis to load audio analyses from Spotify, one track at a time
compmus_normalise to normalise audio features using common techniques, including:
manhattan
euclidean
chebyshev
compmus_long_distance to compare to series of audio features against each other using common distance metrics, including:
manhattan
aitchison
euclidean
cosine
angular
This week, we have a few new custom functions.
compmus_align aligns two levels of structure with each other, e.g., Spotify segments with beats or bars.
compmus_summarise helps to summarise features within higher levels of structure, including:
mean
acentre [Aitchison centre]
rms [root mean square]
max
Common combinations
| Non-negative (e.g., chroma) |
Manhattan |
Manhattan |
mean |
|
|
Aitchison |
Aitchison centre |
|
Euclidean |
cosine |
root mean square |
|
|
angular |
root mean square |
|
Chebyshev |
[none] |
max |
| Full-range (e.g., timbre) |
[none] |
Euclidean |
mean |
|
Euclidean |
cosine |
root mean square |
|
|
angular |
root mean square |
Bloed, Zweet en Tranen
The following examples from Andre Hazes’s âBloed, Zweet en Tranenâ highlight how to use these functions. Can you choose better combinations of normalisations, distances, and summary statistics?
bzt <-
get_tidy_audio_analysis('54RPlXW7LlMNJMrkIJuXGI') %>%
compmus_align(bars, segments) %>%
select(bars) %>% unnest(bars) %>%
mutate(
pitches =
map(segments,
compmus_summarise, pitches,
method = 'rms', norm = 'euclidean')) %>%
mutate(
timbre =
map(segments,
compmus_summarise, timbre,
method = 'mean'))
Cepstrogram
We can use compmus_gather_timbre much like compmus_gather_chroma last week to yield a cepstrogram. Try different levels of structure – Spotify’s estimates of beats, bars, or sections – to see which level is the most meaningful.
bzt %>%
compmus_gather_timbre %>%
ggplot(
aes(
x = start + duration / 2,
width = duration,
y = basis,
fill = value)) +
geom_tile() +
labs(x = 'Time (s)', y = NULL, fill = 'Magnitude') +
scale_fill_viridis_c(option = 'E') +
theme_classic()
Self-Similarity Matrices
The function compmus_self_similarity is a wrapper around compmus_long_distance from last week, for the case where the distances are computed form the same track. Try different distance functions to see what is most useful for this track – and compare your results with a self-similarity matrix based on chroma.
bzt %>%
compmus_self_similarity(timbre, 'cosine') %>%
ggplot(
aes(
x = xstart + xduration / 2,
width = xduration,
y = ystart + yduration / 2,
height = yduration,
fill = d)) +
geom_tile() +
coord_fixed() +
scale_fill_viridis_c(option = 'E', guide = 'none') +
theme_classic() +
labs(x = '', y = '')
LS0tDQp0aXRsZTogIldlZWsgOSDCtyBTdHJ1Y3R1cmUgQW5hbHlzaXMgwrcgU2VsZi1TaW1pbGFyaXR5IE1hdHJpY2VzIg0KYXV0aG9yOiAiSm9obiBBc2hsZXkgQnVyZ295bmUiDQpkYXRlOiAiMjcgRmVicnVhcnkgMjAxOSINCm91dHB1dDoNCiAgICBodG1sX25vdGVib29rOg0KICAgICAgICB0aGVtZTogZmxhdGx5DQotLS0NCg0KIyMgU2V0LXVwDQoNClRoaXMgd2VlaywgSSBoYXZlIGV4cGFuZGVkIGBzcG90aWZ5LlJgIGFuZCBtb3ZlZCBhbGwgb2YgdGhlIGZ1bmN0aW9ucywgaW5jbHVkaW5nIG5ldyBmdW5jdGlvbnMsIHRoZXJlLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzcG90aWZ5cikNCnNvdXJjZSgnc3BvdGlmeS5SJykNCmBgYA0KDQojIyBOZXcgZnVuY3Rpb25zDQoNCkxhc3Qgd2Vlaywgd2UgdXNlZCBzZXZlcmFsIGN1c3RvbSBmdW5jdGlvbnMgdG8gd29yayB3aXRoIHRoZSBTcG90aWZ5IEFQSToNCg0KICAtIGBnZXRfdGlkeV9hdWRpb19hbmFseXNpc2AgdG8gbG9hZCBhdWRpbyBhbmFseXNlcyBmcm9tIFNwb3RpZnksIG9uZSB0cmFjayBhdCBhIHRpbWUNCiAgLSBgY29tcG11c19ub3JtYWxpc2VgIHRvIG5vcm1hbGlzZSBhdWRpbyBmZWF0dXJlcyB1c2luZyBjb21tb24gdGVjaG5pcXVlcywgaW5jbHVkaW5nOg0KICAgICAgLSBgbWFuaGF0dGFuYA0KICAgICAgLSBgZXVjbGlkZWFuYA0KICAgICAgLSBgY2hlYnlzaGV2YA0KICAtIGBjb21wbXVzX2xvbmdfZGlzdGFuY2VgIHRvIGNvbXBhcmUgdG8gc2VyaWVzIG9mIGF1ZGlvIGZlYXR1cmVzIGFnYWluc3QgZWFjaCBvdGhlciB1c2luZyBjb21tb24gZGlzdGFuY2UgbWV0cmljcywgaW5jbHVkaW5nOg0KICAgICAgLSBgbWFuaGF0dGFuYA0KICAgICAgLSBgYWl0Y2hpc29uYA0KICAgICAgLSBgZXVjbGlkZWFuYA0KICAgICAgLSBgY29zaW5lYA0KICAgICAgLSBgYW5ndWxhcmANCiAgDQpUaGlzIHdlZWssIHdlIGhhdmUgYSBmZXcgbmV3IGN1c3RvbSBmdW5jdGlvbnMuIA0KDQogIC0gYGNvbXBtdXNfYWxpZ25gIGFsaWducyB0d28gbGV2ZWxzIG9mIHN0cnVjdHVyZSB3aXRoIGVhY2ggb3RoZXIsIGUuZy4sIFNwb3RpZnkgc2VnbWVudHMgd2l0aCBiZWF0cyBvciBiYXJzLg0KICAtIGBjb21wbXVzX3N1bW1hcmlzZWAgaGVscHMgdG8gc3VtbWFyaXNlIGZlYXR1cmVzIHdpdGhpbiBoaWdoZXIgbGV2ZWxzIG9mIHN0cnVjdHVyZSwgaW5jbHVkaW5nOg0KICAgICAgLSBgbWVhbmANCiAgICAgIC0gYGFjZW50cmVgIFtBaXRjaGlzb24gY2VudHJlXQ0KICAgICAgLSBgcm1zYCBbcm9vdCBtZWFuIHNxdWFyZV0NCiAgICAgIC0gYG1heGANCg0KIyMgQ29tbW9uIGNvbWJpbmF0aW9ucw0KDQp8IERvbWFpbiAgICAgICAgICAgICAgICAgICAgICB8IE5vcm1hbGlzYXRpb24gfCBEaXN0YW5jZSAgfCBTdW1tYXJ5IFN0YXRpc3RpYyB8DQp8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS18DQp8IE5vbi1uZWdhdGl2ZSAoZS5nLiwgY2hyb21hKSB8IE1hbmhhdHRhbiAgICAgfCBNYW5oYXR0YW4gfCBtZWFuICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBBaXRjaGlzb24gfCBBaXRjaGlzb24gY2VudHJlICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEV1Y2xpZGVhbiAgICAgfCBjb3NpbmUgICAgfCByb290IG1lYW4gc3F1YXJlICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBhbmd1bGFyICAgfCByb290IG1lYW4gc3F1YXJlICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IENoZWJ5c2hldiAgICAgfCBbbm9uZV0gICAgfCBtYXggICAgICAgICAgICAgICB8DQp8IEZ1bGwtcmFuZ2UgKGUuZy4sIHRpbWJyZSkgICB8IFtub25lXSAgICAgICAgfCBFdWNsaWRlYW4gfCBtZWFuICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEV1Y2xpZGVhbiAgICAgfCBjb3NpbmUgICAgfCByb290IG1lYW4gc3F1YXJlICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBhbmd1bGFyICAgfCByb290IG1lYW4gc3F1YXJlICB8DQoNCg0KIyMgQmxvZWQsIFp3ZWV0IGVuIFRyYW5lbg0KDQpUaGUgZm9sbG93aW5nIGV4YW1wbGVzIGZyb20gQW5kcmUgSGF6ZXMncyDigJhCbG9lZCwgWndlZXQgZW4gVHJhbmVu4oCZIGhpZ2hsaWdodCBob3cgdG8gdXNlIHRoZXNlIGZ1bmN0aW9ucy4gQ2FuIHlvdSBjaG9vc2UgYmV0dGVyIGNvbWJpbmF0aW9ucyBvZiBub3JtYWxpc2F0aW9ucywgZGlzdGFuY2VzLCBhbmQgc3VtbWFyeSBzdGF0aXN0aWNzPw0KDQpgYGB7cn0NCmJ6dCA8LSANCiAgICBnZXRfdGlkeV9hdWRpb19hbmFseXNpcygnNTRSUGxYVzdMbE1OSk1ya0lKdVhHSScpICU+JSANCiAgICBjb21wbXVzX2FsaWduKGJhcnMsIHNlZ21lbnRzKSAlPiUgDQogICAgc2VsZWN0KGJhcnMpICU+JSB1bm5lc3QoYmFycykgJT4lIA0KICAgIG11dGF0ZSgNCiAgICAgICAgcGl0Y2hlcyA9IA0KICAgICAgICAgICAgbWFwKHNlZ21lbnRzLCANCiAgICAgICAgICAgICAgICBjb21wbXVzX3N1bW1hcmlzZSwgcGl0Y2hlcywgDQogICAgICAgICAgICAgICAgbWV0aG9kID0gJ3JtcycsIG5vcm0gPSAnZXVjbGlkZWFuJykpICU+JSANCiAgICBtdXRhdGUoDQogICAgICAgIHRpbWJyZSA9IA0KICAgICAgICAgICAgbWFwKHNlZ21lbnRzLCANCiAgICAgICAgICAgICAgICBjb21wbXVzX3N1bW1hcmlzZSwgdGltYnJlLCANCiAgICAgICAgICAgICAgICBtZXRob2QgPSAnbWVhbicpKQ0KYGBgDQoNCiMjIyBDZXBzdHJvZ3JhbQ0KDQpXZSBjYW4gdXNlIGBjb21wbXVzX2dhdGhlcl90aW1icmVgIG11Y2ggbGlrZSBgY29tcG11c19nYXRoZXJfY2hyb21hYCBsYXN0IHdlZWsgdG8geWllbGQgYSBjZXBzdHJvZ3JhbS4gVHJ5IGRpZmZlcmVudCBsZXZlbHMgb2Ygc3RydWN0dXJlIC0tIFNwb3RpZnkncyBlc3RpbWF0ZXMgb2YgYmVhdHMsIGJhcnMsIG9yIHNlY3Rpb25zIC0tIHRvIHNlZSB3aGljaCBsZXZlbCBpcyB0aGUgbW9zdCBtZWFuaW5nZnVsLg0KDQpgYGB7cn0NCmJ6dCAlPiUgDQogICAgY29tcG11c19nYXRoZXJfdGltYnJlICU+JSANCiAgICBnZ3Bsb3QoDQogICAgICAgIGFlcygNCiAgICAgICAgICAgIHggPSBzdGFydCArIGR1cmF0aW9uIC8gMiwgDQogICAgICAgICAgICB3aWR0aCA9IGR1cmF0aW9uLCANCiAgICAgICAgICAgIHkgPSBiYXNpcywgDQogICAgICAgICAgICBmaWxsID0gdmFsdWUpKSArIA0KICAgIGdlb21fdGlsZSgpICsNCiAgICBsYWJzKHggPSAnVGltZSAocyknLCB5ID0gTlVMTCwgZmlsbCA9ICdNYWduaXR1ZGUnKSArDQogICAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gJ0UnKSArDQogICAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMjIFNlbGYtU2ltaWxhcml0eSBNYXRyaWNlcw0KDQpUaGUgZnVuY3Rpb24gYGNvbXBtdXNfc2VsZl9zaW1pbGFyaXR5YCBpcyBhIHdyYXBwZXIgYXJvdW5kIGBjb21wbXVzX2xvbmdfZGlzdGFuY2VgIGZyb20gbGFzdCB3ZWVrLCBmb3IgdGhlIGNhc2Ugd2hlcmUgdGhlIGRpc3RhbmNlcyBhcmUgY29tcHV0ZWQgZm9ybSB0aGUgc2FtZSB0cmFjay4gVHJ5IGRpZmZlcmVudCBkaXN0YW5jZSBmdW5jdGlvbnMgdG8gc2VlIHdoYXQgaXMgbW9zdCB1c2VmdWwgZm9yIHRoaXMgdHJhY2sgLS0gYW5kIGNvbXBhcmUgeW91ciByZXN1bHRzIHdpdGggYSBzZWxmLXNpbWlsYXJpdHkgbWF0cml4IGJhc2VkIG9uIGNocm9tYS4NCg0KYGBge3J9DQpienQgJT4lIA0KICAgIGNvbXBtdXNfc2VsZl9zaW1pbGFyaXR5KHRpbWJyZSwgJ2Nvc2luZScpICU+JSANCiAgICBnZ3Bsb3QoDQogICAgICAgIGFlcygNCiAgICAgICAgICAgIHggPSB4c3RhcnQgKyB4ZHVyYXRpb24gLyAyLCANCiAgICAgICAgICAgIHdpZHRoID0geGR1cmF0aW9uLA0KICAgICAgICAgICAgeSA9IHlzdGFydCArIHlkdXJhdGlvbiAvIDIsDQogICAgICAgICAgICBoZWlnaHQgPSB5ZHVyYXRpb24sDQogICAgICAgICAgICBmaWxsID0gZCkpICsgDQogICAgZ2VvbV90aWxlKCkgKw0KICAgIGNvb3JkX2ZpeGVkKCkgKw0KICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICdFJywgZ3VpZGUgPSAnbm9uZScpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkgKw0KICAgIGxhYnMoeCA9ICcnLCB5ID0gJycpDQpgYGANCg0K