komoog

About

Convert your komoot hiking/cycling trips to audio signals.

from komoog.komoot import download_all_komoot_tours, choose_downloaded_komoot_tour
from komoog.audio import convert_tour_to_audio, play_audio

download_all_komoot_tours()
tour = choose_downloaded_komoot_tour()
audio, sampling_rate = convert_tour_to_audio(tour,
                                             approximate_length_in_seconds=4,
                                             set_tune_to_follow_tour_profile=True,
                                            )
play_audio(audio, sampling_rate)

After hiking I noticed that komoot comes with elevation profiles of tour hiking trips:

Tour profile

This reminded me of wave tables I know from sound synthesis. Because I'm always looking for sounds to use when making music, I decided to write code that generates sounds from hiking profiles that can be used in sound synthesis.

Note that I adapted code from js-on/medium_komoot to access trips on komoot.

Install

pip install komoog

komoog was developed and tested for

  • Python 3.6

  • Python 3.7

  • Python 3.8

So far, the package's functionality was tested on macOS only.

Prerequisites

Save your komoot credentials in ~/.komoog/komoot.json as

{
    "email" : "your@email.com",
    "password" : "yourpassword",
    "clientid" : "yourclientid"
}

You can find your client id in the komoot url when you log in. Click on your username, then on "Planned Tours" or "Completed Tours". The URL will change to something like

https://www.komoot.com/user/1851102841208/tours?type=planned

Here, 1851102841208 is your clientid.

Dependencies

komoog directly depends on the following packages which will be installed by pip during the installation process

  • numpy>=1.17

  • scipy>=1.5.0

  • gpxpy>=1.4.2

  • simplejson>=3.17.2

  • simpleaudio=>=1.0.4

  • matplotlib>=3.0.0

Documentation

The full documentation is available at komoog.readthedocs.io.

Changelog

Changes are logged in a separate file.

License

This project is licensed under the MIT License. Note that this excludes any images/pictures/figures shown here or in the documentation.

Contributing

If you want to contribute to this project, please make sure to read the code of conduct and the contributing guidelines. In case you're wondering about what to contribute, we're always collecting ideas of what we want to implement next in the outlook notes.

Contributor Covenant

Dev notes

Fork this repository, clone it, and install it in dev mode.

git clone git@github.com:YOURUSERNAME/komoog.git
make

If you want to upload to PyPI, first convert the new README.md to README.rst

make readme

It will give you warnings about bad .rst-syntax. Fix those errors in README.rst. Then wrap the whole thing

make pypi

It will probably give you more warnings about .rst-syntax. Fix those until the warnings disappear. Then do

make upload

Tutorial

import komoog.komoot as komoot
import komoog.audio as audio
import matplotlib.pyplot as pl

Download komoot tours

tours = komoot.download_all_komoot_tours()
for i, tour in enumerate(tours):
    print(i, tour['name'])
0 Colorado Provencale in Rustrel – La Doa Loop from Rustrel
1 Lookout – L´Aiguebrun Loop from Buoux
2 Gorges de Régalon – Vue de la Gorge Loop from Quartier Gardet
3 Forêt des Cèdres - Vue au sud – Belvédère Loop from Lacoste
4 Crête du Grand luberon – Le Mourre Nègre (1125m) Loop from Rue de l'Église
5 Beautiful Cliffs – Gorges d'Oppedette Loop from D 201
6 Beautiful Cliffs – Gorges d'Oppedette Loop from D 201
7 Chateau des Eveques Loop from Fontaine-de-Vaucluse
8 Valescure vaucluse Loop from Fontaine-de-Vaucluse
9 Colorado Provencale in Rustrel – Aussicht auf die Sahara Loop from Rustrel
10 Chateau des Eveques – Belle vue Loop from Fontaine-de-Vaucluse
11 Porte de Saignon – Ortskern Saignon Loop from Apt
12 Ortskern Saignon – Porte de Saignon Loop from Apt
13 Möllensee und Kiessee

Load komoot tours from harddrive

Tours are saved in ~/.komoog/.

import komoog.io as io
tours = io.read_tours()

Plot elevation profile

import komoog.gpx as gpx

gpx_tracks = gpx.convert_tour_to_gpx_tracks(tours[2])
distance, elevation = gpx.convert_gpx_tracks_to_arrays(gpx_tracks)

pl.plot(distance, elevation)
pl.xlabel('distance [m]')
pl.ylabel('elevation [m]')
_images/output_8_1.png

Convert elevation profile to signal

This will normalize the profile to the correct ranges and remove duplicates

x, y = audio.convert_distance_and_elevation_to_signal(distance, elevation)

pl.plot(x,y )
_images/output_10_1.png

By default, signals will be maximized. If you don’t want them maximized, define a maximum elevation difference that will correspond to the range [-1,1] instead, e.g. 2000 meters.

x, y = audio.convert_distance_and_elevation_to_signal(distance,
                                                      elevation,
                                                      max_elevation_difference=2000)

pl.plot(x,y)
pl.ylim(-1,1)
_images/output_12_1.png

Convert signal to audio

# approximate_length_in_seconds = 0 will give a single loop of the signal
audio_data, sampling_rate = audio.convert_signal_to_audio(x,
                                                     y,
                                                     approximate_length_in_seconds=0
                                                    )
pl.plot(audio_data)
_images/output_14_1.png

Loop wave

audio_data, sampling_rate = audio.convert_signal_to_audio(
                                                     x,
                                                     y,
                                                     approximate_length_in_seconds=1/100
                                                    )
pl.plot(audio_data)
_images/output_16_1.png

Generate longer audio data and play it

audio_data, sampling_rate = audio.convert_signal_to_audio(x,y,)
audio.play_audio(audio_data, sampling_rate)

You should’ve heard a sound now. Note that by default, a sound length of 1s is produced.

Tune to different notes

audio_data, sampling_rate = audio.convert_signal_to_audio(
                                                     x,
                                                     y,
                                                     tune='A'
                                                    )
audio.play_audio(audio_data, sampling_rate)

These are the tunes that work:

audio._NOTES
{'C': -9,
 'C#': -8,
 'Db': -8,
 'D': -7,
 'D#': -6,
 'Eb': -6,
 'E': -5,
 'F': -4,
 'F#': -3,
 'Gb': -3,
 'G': -2,
 'G#': -1,
 'Ab': -1,
 'A': 0,
 'A#': 1,
 'Bb': 1,
 'B': 2}

You can also tune to a specific frequency, e.g. 200 Hz.

audio_data, sampling_rate = audio.convert_signal_to_audio(
                                                     x,
                                                     y,
                                                     tune=220,
                                                    )
audio.play_audio(audio_data, sampling_rate)

Change sampling rate

audio_data, sampling_rate = audio.convert_signal_to_audio(
                                                     x,
                                                     y,
                                                     sampling_rate=48000,
                                                    )
audio.play_audio(audio_data, sampling_rate)

Write .wav file

io.write_wav('./example.wav',audio_data,sampling_rate)

Converting tours to sound without going through all the hassle

audio_data, sampling_rate = audio.convert_tour_to_audio(
                                     tours[2],
                                     max_elevation_difference=1000,
                                     sampling_rate=48000,
                                     tune='A',
                                     approximate_length_in_seconds=3,
                                    )
audio.play_audio(audio_data, sampling_rate)

Plot a tour

import komoog.plot as plot

plot.plot_tour(tours[2])
_images/out_signal.png

Make the frequency of the sound follow the elevation profile

audio_data, sampling_rate = audio.convert_tour_to_audio(
                                     tours[2],
                                     max_elevation_difference=1000,
                                     sampling_rate=48000,
                                     tune='A#',
                                     approximate_length_in_seconds=3,
                                     set_tune_to_follow_tour_profile=True,
                                    )
audio.play_audio(audio_data, sampling_rate)

Interactive conversion without downloading all tours previously

tour = komoot.choose_komoot_tour_live()

audio_data, sampling_rate = audio.convert_tour_to_audio(tour)
audio.play_audio(audio_data, sampling_rate)

Play some melodies

notes = ['C','D','E','F','G','F','E','D','C']
durations = [0.25]* 8 + [0.5] #seconds
import numpy as np

audios = []
for dur, note in zip(durations, notes):
    audio_data, sampling_rate = audio.convert_tour_to_audio(
                                         tours[2],
                                         max_elevation_difference=1000,
                                         tune=note,
                                         approximate_length_in_seconds=dur,
                                        )
    audios.append(audio_data)
audio.play_audio(np.concatenate(audios).astype(np.int16), sampling_rate)
# with pauses
durations = ([0.25]*6+[0.5])*2 #seconds
notes = ['C','C','G','G','A','A','G','F','F','E','E','D','D','C']
pause = np.zeros((1000,))

audios = []
for dur, note in zip(durations, notes):
    audio_data, sampling_rate = audio.convert_tour_to_audio(
                                         tours[2],
                                         max_elevation_difference=1000,
                                         tune=note,
                                         approximate_length_in_seconds=dur,
                                        )
    audios.extend([audio_data,pause])
audio.play_audio(np.concatenate(audios).astype(np.int16), sampling_rate)

Komoot

Obtaining tours and tour data from komoot. Adapted from https://github.com/js-on/medium_komoot.

komoog.komoot.choose_downloaded_komoot_tour()[source]

Choose a previously downloaded tour. Tour can be passed to komoog.gpx.convert_tour_to_gpx_tracks() afterwards.

komoog.komoot.choose_komoot_tour_live()[source]

Login with user credentials, download tour information, choose a tour, and download it. Can be passed to komoog.gpx.convert_tour_to_gpx_tracks() afterwards.

komoog.komoot.download_all_komoot_tours()[source]

Login with user credentials, download tour information and all tours. Tours will be saved in a custom directory. Tours can be passed to komoog.gpx.convert_tour_to_gpx_tracks() afterwards.

komoog.komoot.get_tour(tours, tour_id, session)[source]

Returns a tour including coordinates given a tour_id (position of the tour in tours).

komoog.komoot.get_tours_and_session()[source]

Returns a list of the user's tours on komoot and a requests.Session object.

Audio

Audio handling and conversion.

komoog.audio.convert_distance_and_elevation_to_audio(distance, elevation, max_elevation_difference=0, tune='C', sampling_rate=44100, approximate_length_in_seconds=1)[source]

Convert a distance/elevation profile to an audio signal.

Parameters
  • distance (numpy.ndarray) -- Contains the covered 2D distance in meters.

  • elevation (numpy.ndarray) -- Contains the corresponding elevation profile in meters

  • max_elevation_difference (float, default = 0) -- Used to control the level of the audio signal. If this value is <= 0, the audio level will always be maximized. If given a positive value, this value will represent the maximum scale of the audio signal. If the elevation profile's elevation difference is larger than this value, the signal will simply be maximized. A good value is max_elevation_difference = 2000.

  • tune (str or float) --

    Desired frequency of the sound. Can be any of

    [ 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#',
      'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B' ]
    

    where 'A' corresponds to 440Hz.

    Can also be a frequency in Hz.

  • sampling_rate (int, default = 44100) -- Sampling rate in Hz

  • approximate_length_in_seconds (float, default = 1.) -- The desired length of the audio signal in seconds If equal to zero, will return a single loop.

Returns

  • audio (numpy.ndarray of numpy.int16) -- The transformed audio signal

  • sampling_rate (int) -- The sampling rate of the audio signal.

komoog.audio.convert_distance_and_elevation_to_profile_audio(distance, elevation, max_elevation_difference=0, tune='C', sampling_rate=44100, approximate_length_in_seconds=1)[source]

Convert a distance/elevation profile to an audio signal that mimicks the elevation profile.

Parameters
  • distance (numpy.ndarray) -- Contains the covered 2D distance in meters.

  • elevation (numpy.ndarray) -- Contains the corresponding elevation profile in meters

  • max_elevation_difference (float, default = 0) -- Used to control the level of the audio signal. If this value is <= 0, the audio level will always be maximized. If given a positive value, this value will represent the maximum scale of the audio signal. If the elevation profile's elevation difference is larger than this value, the signal will simply be maximized. A good value is max_elevation_difference = 2000.

  • tune (str or float) --

    Desired frequency of the sound. Can be any of

    [ 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#',
      'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B' ]
    

    where 'A' corresponds to 440Hz.

    Can also be a frequency in Hz.

  • sampling_rate (int, default = 44100) -- Sampling rate in Hz

  • approximate_length_in_seconds (float, default = 1.) -- The desired length of the audio signal in seconds If equal to zero, will return a single loop.

Returns

  • audio (numpy.ndarray of numpy.int16) -- The transformed audio signal

  • sampling_rate (int) -- The sampling rate of the audio signal.

komoog.audio.convert_distance_and_elevation_to_signal(distance, elevation, max_elevation_difference=0)[source]

Convert a distance/elevation profile to a signal, i.e. normalize distance to range [0,1] and y to range [-1,1].

Parameters
  • distance (numpy.ndarray) -- Contains the covered 2D distance in meters.

  • elevation (numpy.ndarray) -- Contains the corresponding elevation profile in meters

  • max_elevation_difference (float, default = 0) -- Used to control the level of the audio signal. If this value is <= 0, the audio level will always be maximized. If given a positive value, this value will represent the maximum scale of the audio signal. If the elevation profile's elevation difference is larger than this value, the signal will simply be maximized. A good value is max_elevation_difference = 2000.

Returns

  • x (numpy.ndarray) -- covered distance in range [0,1]

  • y (numpy.ndarray) -- signal in range [-1,1]

komoog.audio.convert_signal_to_audio(x, y, tune='C', sampling_rate=44100, approximate_length_in_seconds=1)[source]

Convert a normalized distance/elevation signal to an audio signal.

Parameters
  • distance (numpy.ndarray) -- Contains the covered 2D distance in meters.

  • elevation (numpy.ndarray) -- Contains the corresponding elevation profile in meters

  • tune (str or float) --

    Desired frequency of the sound. Can be any of

    [ 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#',
      'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B' ]
    

    where 'A' corresponds to 440Hz.

    Can also be a frequency in Hz.

  • sampling_rate (int, default = 44100) -- Sampling rate in Hz

  • approximate_length_in_seconds (float, default = 1.) -- The desired length of the audio signal in seconds If equal to zero, will return a single loop.

Returns

  • audio (numpy.ndarray of numpy.int16) -- The transformed audio signal

  • sampling_rate (int) -- The sampling rate of the audio signal.

komoog.audio.convert_tour_to_audio(tour, max_elevation_difference=0, tune='C', sampling_rate=44100, approximate_length_in_seconds=1, set_tune_to_follow_tour_profile=False)[source]

Convert a hiking tour to audio.

Parameters
  • tour (dict) -- A komoot tour item as provided by e.g. komoog.io.read_tours().

  • max_elevation_difference (float, default = 0) -- Used to control the level of the audio signal. If this value is <= 0, the audio level will always be maximized. If given a positive value, this value will represent the maximum scale of the audio signal. If the elevation profile's elevation difference is larger than this value, the signal will simply be maximized. A good value is max_elevation_difference = 2000.

  • tune (str or float) --

    Desired frequency of the sound. Can be any of

    [ 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#',
      'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B' ]
    

    where 'A' corresponds to 440Hz.

    Can also be a frequency in Hz.

  • sampling_rate (int, default = 44100) -- Sampling rate in Hz

  • approximate_length_in_seconds (float, default = 1.) -- The desired length of the audio signal in seconds If equal to zero, will return a single loop.

  • set_tune_to_follow_tour_profile (bool, defaukt = False) -- If set to True the tune of the returned audio signal will follow the tour profile.

Returns

  • audio (numpy.ndarray of numpy.int16) -- The transformed audio signal

  • sampling_rate (int) -- The sampling rate of the audio signal.

komoog.audio.get_tune(tune)[source]

Convert a tune value to a frequency.

komoog.audio.play_audio(audio_data, sampling_rate)[source]

Play audio data.

GPX

GPX file handling.

komoog.gpx.convert_gpx_tracks_to_arrays(gpx_tracks)[source]

Take a list of gpx Track objects and convert them to two arrays, one containing the two-dimensional distance covered on the globe, the second containing the elevation.

Parameters

gpx_tracks (list of gpx.Track) -- list of tracks to convert

Returns

  • distance (numpy.ndarray) -- Contains the covered 2D distance in meters.

  • elevation (numpy.ndarray) -- Contains the corresponding elevation profile in meters

komoog.gpx.convert_tour_to_gpx_tracks(tour)[source]

I/O

File I/O.

komoog.io.read_gpx(fn)[source]

Read a gpx file. Returns a gpxpy.GPX object. Pass to komoog.gpx.convert_gpx_tracks_to_arrays() as

gpx = read_gpx('Tour.gpx')
convert_gpx_tracks_to_arrays(gpx.tracks)

to retrieve distance and elevation profile.

komoog.io.read_tours()[source]

Read downloaded tours from ~/.komoog/tours.json

komoog.io.write_tours(tours)[source]

Write downloaded tours to ~/.komoog/tours.json

komoog.io.write_wav(fn, audio_data, sampling_rate)[source]

Write audio data to a wav file.

Plot

Plotting signals.

komoog.plot.plot_signal(x, y, gradientphases=5, alpha=0.2, figsize=(4, 1))[source]

Plot a signal

Parameters
  • x (numpy.ndarray) -- signal x-values in range [0,1]

  • y (numpy.ndarray) -- signal y-values in range [-1,1]

  • gradientphases (int, default = 5) -- How many shades the signal shadow will show

  • alpha (float, default = 0.1) -- Transparency of shade

  • figsize (tuple of float, default = (4,1)) -- figure size

Returns

ax -- The axis on which the signal was plotted

Return type

matplotlib.Axes

komoog.plot.plot_tour(tour, gradientphases=5, alpha=0.2, figsize=(4, 1), max_elevation_difference=0)[source]

Plot a tour as a signal

Parameters
  • tour (dict) -- A downloaded tour

  • gradientphases (int, default = 5) -- How many shades the signal shadow will show

  • alpha (float, default = 0.1) -- Transparency of shade

  • figsize (tuple of float, default = (4,1)) -- figure size

  • max_elevation_difference (float, default = 0) -- for signal normlization

Returns

ax -- The axis on which the signal was plotted

Return type

matplotlib.Axes

Paths

Path handling

komoog.paths.get_credentials()[source]

Returns credentials for komoot login in structure

{
    "email" : "",
    "password" : "",
    "clientid" : ""
}

from the file ~/.komoog/komoot.json