See you on The Dark Side of the Moon

Author

Vít Gabrhel

Introduction or Any Madness You Like

On March 1st 1973, exactly 50 years ago, The Dark Side of the Moon by Pink Floyd was released. It is hard to overstate the impact this album had on music and art in general.

For me, it is one of the key pieces of my adolescent soundscape. I remember On The Run blasting from speakers in a classmate’s car, accompanied by his verbalized doubts whether I have gone insane. The melancholic sentiment associated with the human perception of the passage of Time whenever the cacophony of the clocks starts. The unnerving confrontation with death and cesation of consciousness in The Great Gig in The Sky.

(a)

(b)

Figure 1: “Pink Floyd, The Dark Side of the Moon, devilcore cyberpunk vaporwave pixel diffusion, unholy geometry, radiating –stylize 1250

So, I decided to prepare a small tribute upon realizing the anniversary. And being the data nerd that I am, I used this opportunity to explore the data behind the album. To be more specific, the data available at Spotify. Also, I employed DALL-E to create several visual pieces based on the original artwork since I am fond of generative art. So, you may find them throughout the text along with the command prompts used in the process. Finally, you may also find trivia like breadcrumbs scattered here and there.

Enjoy!

Packaging Unlike Hipgnosis

The name of this section refers to the fact that the original artwork for the album was created by Hipgnosis, a design studio founded by Storm Thorgerson and Aubrey Powell. At the same time, it is a wordplay on the fact that the text is written using Python packages like plotly or spotipy.

At any rate, the album’s artwork depicts light refracting from a triangular dispersive prism. A spicy detail is that this physical phenomenon has found itself lost in the culture wars.

Load Python packages
import plotly.graph_objs as go
import plotly.express as px
import numpy as np
import pandas as pd

# Credentials import 
from credentials import client_id, client_secret

# Spotify connection
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from spotipy.oauth2 import SpotifyOAuth

(a)

(b)

Figure 2: “Create an abstract, geometric image inspired by the Suprematism movement and Kazimir Malevich’s work from 1913. The image should incorporate elements that are reminiscent of Pink Floyd’s album ‘The Dark Side of the Moon’, such as a rainbow spectrum or a prism. The image should be predominantly dark in color, with shades of black, blue, and purple. The image should also include a prominent circular

Spotify or Everything under the sun is in tune

The first step is to create a developer account at Spotify and obtain the credentials. Then, one can connect to the Spotify API using the spotipy package. There are several articles covering the process of connecting to the API, so check (e.g.) Building a CLI Spotify playlist generator using Python & Spotipy out if you want to know more.

After connecting to the API, I extracted information about Pink Floyd as artists (like 'genres'), but also about their albums (like 'release_date'). Finally, I downloaded information about individual songs (like 'popularity').

This approach allows me to focus on the album in detail, but also in comparison to other albums by Pink Floyd. But let us start with Pink Floyd as artist.

Connect to spotipy and return data on the album
# Replace the values below with your own credentials
client_id = client_id
client_secret = client_secret

# Authenticate with the Spotify API using your credentials
client_credentials_manager = SpotifyClientCredentials(client_id, client_secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

# Search for the "Dark Side of the Moon" album by Pink Floyd
results = sp.search(q='artist:Pink Floyd album:"Dark Side of the Moon"', type='album')

# Get the album's ID
album_id = results['albums']['items'][0]['id']

# Get the audio features for each track on the album
tracks = sp.album_tracks(album_id)['items']

features = [sp.audio_features(track['id'])[0] for track in tracks]

album_tracks = sp.album_tracks(album_id)['items']
album_track_ids = [track['id'] for track in album_tracks]

# Get the artist's ID
results = sp.search(q='artist:Pink Floyd', type='artist')
artist_id = results['artists']['items'][0]['id']

Album features or All that you taste

There are several properties or audio features of the songs on the album that can be used to describe the album as a whole. For example, there is:

  • danceability - how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity.
  • acousticness - a confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 represents high confidence the track is acoustic.
  • instrumentalness - predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as instrumental in this context. Rap or spoken word tracks are clearly “vocal”. The closer the instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the value approaches 1.0.

Let’s calculate the average of danceability, accousticness, and instrumentalness for each album and see how each of them compares to the other selected albums! But there is a disclaimer - I decided to plot only 6 selected ones out of the total of 20. The reason is that including all would - in my view - defeat the purpose. Anyway, let’s see how The Dark Side of The Moon fares!

Extract the audio features of the Pink Floyd albums’ tracks and plot them
# Get Pink Floyd's album names and IDs
albums = []
results = sp.artist_albums(artist_id, album_type='album', limit=50)
albums.extend(results['items'])
while results['next']:
    results = sp.next(results)
    albums.extend(results['items'])

# Append the album names 
album_names = []
for album in albums:
    album_names.append(album['name'])

# Return only distinct album names
album_names = list(set(album_names))

# Return only studio albums
studio_albums = []
for album in album_names:
    if 'Live' not in album and 'Remix' not in album:
        studio_albums.append(album)

album_ids = []
for album in studio_albums:
    results = sp.search(q='artist:Pink Floyd album:' + album, type='album')
    items = results['albums']['items']
    if items:
        album_ids.append(items[0]['id'])

# Create a list to hold the album data
album_data = []

# Iterate over the album IDs
for album_id in album_ids:
    # Get the album name
    album_name = sp.album(album_id)['name']
    
    # Get the track IDs for the album
    tracks = sp.album_tracks(album_id)
    track_ids = [track['id'] for track in tracks['items']]
    
    # Get the audio features for the tracks
    audio_features = sp.audio_features(track_ids)
    
    # Calculate the average danceability, instrumentalness, and acousticness for the album
    danceability_sum = 0
    instrumentalness_sum = 0
    acousticness_sum = 0
    num_tracks = 0
    for track in audio_features:
        if track['danceability'] is not None and track['instrumentalness'] is not None and track['acousticness'] is not None:
            danceability_sum += track['danceability']
            instrumentalness_sum += track['instrumentalness']
            acousticness_sum += track['acousticness']
            num_tracks += 1
    
    if num_tracks == 0:
        avg_danceability = 0
        avg_instrumentalness = 0
        avg_acousticness = 0
    else:
        avg_danceability = danceability_sum / num_tracks
        avg_instrumentalness = instrumentalness_sum / num_tracks
        avg_acousticness = acousticness_sum / num_tracks
    
    # Add the album data to the list
    album_data.append({'Album': album_name, 'Danceability': avg_danceability, 'Instrumentalness': avg_instrumentalness, 'Acousticness': avg_acousticness})

# Create a dataframe from the album data
df = pd.DataFrame(album_data, columns=['Album', 'Danceability', 'Instrumentalness', 'Acousticness'])

# Define the regex pattern to match substrings inside parentheses or square brackets
pattern = r"[\(\[].*?[\)\]]"

# Use the str.replace() method to remove the substrings and trim whitespaces
df['Album'] = df['Album'].str.replace(pattern, "", regex=True).str.strip()

# Create a dataframe with only the albums of interest
df_line_plot = df[df['Album'].isin(['The Dark Side Of The Moon', 'The Wall', 'Wish You Were Here', 'Animals', 'The Final Cut', 'The Division Bell'])]

# Plot df using plotly express - line plot
fig = px.line(df_line_plot, x=['Danceability', 'Instrumentalness', 'Acousticness'], y='Album',
              labels={'Album': 'Album', 'value': 'Value', 'variable': 'Property'},
              title='Album features or All that you taste',
              template="simple_white")

fig.show()

So, it seems that the Dark Side of the Moon is the most instrumental album of the selected ones. It is also perhaps a least danceable album within the selection. So, perhaps the best way how to enjoy is to warm our bones beside the fire?

But what about the popularity of the songs on the albums? And what is it in the first place? Spotify defines it as follows:

  • popularity - the popularity of the track. The value will be between 0 and 100, with 100 being the most popular. The popularity is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how recent those plays are.

Now, let’s see!

Extract the popularity scores of the Pink Floyd albums’ tracks and plot them
# Create an empty list to store the popularity data
popularity_data_list = []

# Loop through the album IDs
for album_id in album_ids:
    # Get the album name
    album_name = sp.album(album_id)['name']

    # Get the track IDs for the album
    tracks = sp.album_tracks(album_id)
    track_ids = [track['id'] for track in tracks['items']]

    # Get the popularity values for each track
    popularity_values = sp.tracks(track_ids)['tracks']

    # Append the popularity data to the list
    popularity_data_list.append(pd.DataFrame([{'Album': album_name, 'Song': track['name'], 'Popularity': track['popularity']} for track in popularity_values]))

# Concatenate all DataFrames in the list
popularity_data = pd.concat(popularity_data_list, ignore_index=True)

# Define the regex pattern to match substrings inside parentheses or square brackets
pattern = r"[\(\[].*?[\)\]]"

# Use the str.replace() method to remove the substrings and trim whitespaces
popularity_data['Album'] = popularity_data['Album'].str.replace(pattern, "", regex=True).str.strip()

# Create a sample of 6 albums with all their songs - The Dark Side of the Moon, The Wall, Wish You Were Here, Animals, The Final Cut and The Division Bell
df_box_plot = popularity_data[popularity_data['Album'].isin(['The Dark Side Of The Moon', 'The Wall', 'Wish You Were Here', 'Animals', 'The Final Cut', 'The Division Bell'])]

# Keep only album and popularity columns
df_box_plot = df_box_plot[['Album', 'Popularity', 'Song']]

colors = ['blue', 'red', 'green', 'orange', 'purple', 'yellow']

# Plotly boxplot on popularity_data - grouped by Album
fig = px.box(df_box_plot, x='Album', y='Popularity', title='Popularity of songs by Pink Floyd',
             template="simple_white", 
             color='Song', 
             color_discrete_sequence=colors,
             points="all")

# Hide legend
fig.update_layout(showlegend=False)
                  
fig.show()

Over all, The Dark Side of the Moon is the most popular album of the selected ones. In addition, the popularity scores of the individual songs are quite similar. This means that the album is relatively homogenous popularity-wise. Or, in other words, there seem to be no quite unpopular songs on the album.

Tribute Playlist or Different tunes

Now, Spotify API has the capacity to recommend songs based on a given song (or a list of songs). So, let’s try it out on The Dark Side of the Moon!

Create a playlist of songs that are similar to the songs on the album The Dark Side of the Moon
# Return album_id of The Dark Side of the Moon
album_id = sp.search(q='The Dark Side of the Moon', type='album')['albums']['items'][0]['id']

# Get the track IDs for the album
tracks = sp.album_tracks(album_id)
track_ids = [track['id'] for track in tracks['items']]

# Get song recommendations based on track_ids of The Dark Side of the Moon
recommendations = sp.recommendations(seed_tracks=[track_ids[1], track_ids[3], track_ids[5], track_ids[6], track_ids[8]], limit=50)

# Create a list of dictionaries, each containing track name, artist name, album name, release date, and track uri
tracks = []
for track in recommendations['tracks']:
    tracks.append({'Track Name': track['name'], 'Artist Name': track['artists'][0]['name'], 'Album Name': track['album']['name'], 'Release Date': track['album']['release_date'], 'Track URI': track['uri']})

# Create a dataframe from the list of track dictionaries
df = pd.DataFrame(tracks)

# Parse year from release date and create a new column "Release Year"
df['Release Year'] = df['Release Date'].str[:4]

# Create a dataframe with columns "Release Date" and "Count" based on the number of occurences of release dates
df_count = df.groupby('Release Year').size().reset_index(name='Count')

# Create a Plotly table object
table = go.Table(
    header=dict(values=list(df[['Artist Name', 'Track Name']].columns),
                fill_color='#18bc9c',
                align='left'),
    cells=dict(values=[df[col] for col in df[['Artist Name', 'Track Name']].columns],
               fill_color='whitesmoke',
               align='left'))

# Define the layout for the table - 100% width and height
layout = go.Layout(
    width=800,
    height=1000,
    autosize=True
)

# Combine the table and layout objects into a Figure object
fig = go.Figure(data=[table], layout=layout)

# Display the table in the notebook
fig.show()

Tribute Playlist

Inspired or inspiring? Or both?

The generated list of songs is quite interesting. It contains songs across decades and genres. There are songs from the albums preceeding as well as following The Dark Side of the Moon.

Some might consider thinking about this as an “inspirational lineage”. So, let’s take a look at the number of songs from the list released per year.

What was the year of release of the recommended albums?
# Create plotly bar chart of the number of songs released per year. Add color one bar.
fig = px.bar(df_count, x='Release Year', y='Count', title='Number of songs released per year',
             template="simple_white", color_discrete_sequence=['blue', 'red', 'green', 'orange', 'purple', 'yellow'],
             color='Release Year')

# Update the layout
fig.update_layout(
    title_text='Number of songs released per year',
    xaxis_title="Release Year",
    yaxis_title="Number of Songs",
    xaxis_tickangle=-45,
    xaxis_tickfont_size=14,
    yaxis=dict(
        tickmode='linear',
        tick0=0,
        dtick=5
    ),
    bargap=0.15,
    bargroupgap=0.1,
    template="simple_white"
)

# Hide legend
fig.update_layout(showlegend=False)

# Display the bar chart in the notebook
fig.show()

All right, Spotify recommends quite evenly across decades, but the distribution of values is denser towards the 70s.

Another neat capacity of the API is the option to create a playlist programmatically. Let’s try it out! A small tip - in case you want to run your own “app”, don’t forget to set up the redirect_uri in the Spotify Developer Dashboard.

Programaticaly create a playlist named See you on The Dark Side of the Moon
# Create a Spotify playlist based on the recommendations

# Get the client ID and client secret from the environment variables
sp_oauth = SpotifyOAuth(
    client_id=client_id,
    client_secret=client_secret,
    redirect_uri="http://localhost:8000/callback",
    scope="playlist-modify-public"
)

# Token generation: # access_token = sp_oauth.get_access_token()
access_token = sp_oauth.get_cached_token()

sp = spotipy.Spotify(auth=access_token["access_token"])

# Get the user's Spotify username
username = sp.current_user()['id']

# Create a new playlist
playlist = sp.user_playlist_create(username, 'See you on The Dark Side of the Moon')

# Get the playlist ID
playlist_id = playlist['id']

# Get the track URIs
track_uris = df['Track URI'].tolist()

# Add the tracks to the playlist
sp.user_playlist_add_tracks(username, playlist_id, track_uris)

# Add the description of the playlist
sp.user_playlist_change_details(username, playlist_id, description='A playlist of songs that sound like "Time" by Pink Floyd')

So, the playlist should be ready. Let’s check it out here.

Honorary mentions or All you create

Before I wrap up this post, I would like to turn your attention to some of The Dark Side of the Moon-related projects.

First of all, Hans Zimmer used Eclipse in the trailer for Dune. It is a nice touch on the megalomaniac project by Alejandro Jodorowsky as Pink Floyd was supposedly on his short-list for the movie soundtrack. Full circle.

Secondly, if you like rock music, don’t miss out on The Doom Side of The Moon as it is a rewarding take on the album from a more metal-ish perspective.

Finaly, Darkside or Tom Stoppard’s excellent mashup of the album and topics such as the trolley problem, the tragedy of the commons, and the prisoner’s dilemma. Remember Iwan Rheon as Ramsay Bolton in Game of Thrones? He is one of the characters and let’s just say that the wheels are not turning well for him.

Closing words or There is no dark side in the moon, really

Great, that’s it. If you have made it this far, I bow to you and thank you for reading! I hope you enjoyed the post. If you have any questions or comments, please feel free to reach out to me via Contact.

(a)

(b)

Figure 3: “Pink Floyd, The Dark Side of the Moon, abstract, full hd render + 3d octane render +4k UHD + immense detail + dramatic lighting + well lit + black, purple, blue, pink, cerulean, teal, metallic colours, + fine details + octane render + 8k

   

Return to Blog.