Source code for motif.contour_extractors.salamon
# -*- coding: utf-8 -*-
"""Salamon's method for extracting contours
"""
import csv
import numpy as np
import os
import subprocess
from subprocess import CalledProcessError
from motif.core import ContourExtractor
from motif.core import Contours
OUTPUT_FILE_STRING = "vamp_melodia-contours_melodia-contours_contoursall"
VAMP_PLUGIN = b"vamp:melodia-contours:melodia-contours:contoursall"
def _check_binary():
'''Check if the vamp plugin is available and can be called.
Returns
-------
True if callable, False otherwise
'''
sonic_annotator_exists = True
try:
subprocess.check_output(['which', 'sonic-annotator'])
except CalledProcessError:
sonic_annotator_exists = False
if sonic_annotator_exists:
avail_plugins = subprocess.check_output(["sonic-annotator", "-l"])
if VAMP_PLUGIN in avail_plugins:
return True
else:
return False
else:
return False
BINARY_AVAILABLE = _check_binary()
[docs]class Salamon(ContourExtractor):
'''Salamon's method for extracting contours
'''
@property
def audio_samplerate(self):
"""Sample rate of preprocessed audio.
Returns
-------
audio_samplerate : float
Number of samples per second.
"""
return 44100.0
@property
def sample_rate(self):
"""Sample rate of output contours
Returns
-------
sample_rate : float
Number of samples per second.
"""
return 44100.0 / 128.0
@property
def min_contour_len(self):
"""Minimum allowed contour length.
Returns
-------
min_contour_len : float
Minimum allowed contour length in seconds.
"""
return 0.0
@classmethod
[docs] def get_id(cls):
"""Identifier of this extractor.
Returns
-------
id : str
Identifier of this extractor.
"""
return "salamon"
[docs] def compute_contours(self, audio_filepath):
"""Compute contours as in Justin Salamon's melodia.
This calls a vamp plugin in the background, which creates a csv file.
The csv file is loaded into memory and the file is deleted.
Parameters
----------
audio_filepath : str
Path to audio file.
Returns
-------
Instance of Contours object
"""
if not BINARY_AVAILABLE:
raise EnvironmentError(
"Either the vamp plugin {} needed to compute these contours or "
"sonic-annotator is not available.".format(VAMP_PLUGIN)
)
if not os.path.exists(audio_filepath):
raise IOError(
"The audio file {} does not exist".format(audio_filepath)
)
tmp_audio = self._preprocess_audio(
audio_filepath, normalize_format=True, normalize_volume=True
)
input_file_name = os.path.basename(tmp_audio)
output_file_name = "{}_{}.csv".format(
input_file_name.split('.')[0], OUTPUT_FILE_STRING
)
output_dir = os.path.dirname(tmp_audio)
output_path = os.path.join(output_dir, output_file_name)
args = [
"sonic-annotator", "-d", VAMP_PLUGIN,
"{}".format(tmp_audio), "-w", "csv", "--csv-force"
]
os.system(' '.join(args))
if not os.path.exists(output_path):
raise IOError(
"Unable to find vamp output file {}".format(output_path)
)
c_numbers, c_times, c_freqs, c_sal = _load_contours(output_path)
os.remove(output_path)
os.remove(tmp_audio)
return Contours(
c_numbers, c_times, c_freqs, c_sal, self.sample_rate, audio_filepath
)
def _load_contours(fpath):
""" Load contour data from vamp output csv file.
Parameters
----------
fpath : str
Path to vamp output csv file.
Returns
-------
index : np.array
Array of contour numbers
times : np.array
Array of contour times
freqs : np.array
Array of contour frequencies
contour_sal : np.array
Array of contour saliences
"""
index = []
times = []
freqs = []
contour_sal = []
with open(fpath, 'r') as fhandle:
reader = csv.reader(fhandle, delimiter=',')
contour_num = 0
for row in reader:
index.extend([contour_num]*len(row[14::3]))
times.extend(row[14::3])
freqs.extend(row[15::3])
contour_sal.extend(row[16::3])
contour_num += 1
n_rows = np.min([
len(index), len(times),
len(freqs), len(contour_sal)
])
index = np.array(index[:n_rows], dtype=int)
times = np.array(times[:n_rows], dtype=float)
freqs = np.array(freqs[:n_rows], dtype=float)
contour_sal = np.array(contour_sal[:n_rows], dtype=float)
non_nan_rows = ~(np.logical_or(
np.logical_or(np.isnan(times), np.isnan(freqs)),
np.isnan(contour_sal)))
index = index[non_nan_rows]
times = times[non_nan_rows]
freqs = freqs[non_nan_rows]
contour_sal = contour_sal[non_nan_rows]
return index, times, freqs, contour_sal