Skip to content

Commit

Permalink
Add support for audio commons filters, store recent queries/filters
Browse files Browse the repository at this point in the history
  • Loading branch information
ffont committed Jan 21, 2021
1 parent 312c2be commit 235fdb7
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 34 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -6,4 +6,5 @@ __pycache__
VST2_SDK
api_key.h
elk_platform/static
freesound_api_key.py
freesound_api_key.py
recent_queries_and_filters.json
67 changes: 49 additions & 18 deletions elk_platform/freesound_interface.py
Expand Up @@ -8,11 +8,37 @@
fields_param = "id,previews,license,name,username,analysis"
descriptor_names = "rhythm.onset_times"

ac_descriptors_default_filter_ranges = {
'low': [0, 33],
'mid': [33, 66],
'high': [66, 100],
}

def find_sounds_by_query(query, n_sounds=15, min_length=0, max_length=300, page_size=50):
ac_descriptors_filter_ranges = {
'brightness': ac_descriptors_default_filter_ranges,
# Here we can add custom ranges for the different descriptors, for now all us default
}


def prepare_ac_descriptors_filter(ac_descriptors_filters_dict):
filter_texts = []
for descriptor_name, text_value in ac_descriptors_filters_dict.items():
range_min, range_max = ac_descriptors_filter_ranges.get(descriptor_name, ac_descriptors_default_filter_ranges)[text_value]
filter_text = "ac_{}:[{}+TO+{}]".format(descriptor_name, range_min, range_max)
filter_texts.append(filter_text)
return ' '.join(filter_texts)


def find_sounds_by_query(query, n_sounds=15, min_length=0, max_length=300, page_size=50, ac_descriptors_filters={}):
if n_sounds > 128:
n_sounds = 128
url = 'https://freesound.org/apiv2/search/text/?query="{0}"&filter=duration:[{1}+TO+{2}]&fields={3}&page_size={4}&descriptors={5}&group_by_pack=1&token={6}'.format(query, min_length, max_length, fields_param, page_size, descriptor_names, FREESOUND_API_KEY)

query_filter = "duration:[{}+TO+{}]".format(min_length, max_length)
ac_descriptors_filter = prepare_ac_descriptors_filter(ac_descriptors_filters)
if ac_descriptors_filter:
query_filter += " {}".format(ac_descriptors_filter)

url = 'https://freesound.org/apiv2/search/text/?query="{}"&filter={}&fields={}&page_size={}&descriptors={}&group_by_pack=1&token={}'.format(query, query_filter, fields_param, page_size, descriptor_names, FREESOUND_API_KEY)
print(url)
r = requests.get(url, timeout=30)
response = r.json()
Expand All @@ -21,30 +47,24 @@ def find_sounds_by_query(query, n_sounds=15, min_length=0, max_length=300, page_
return random.sample(response['results'], min(n_sounds, n_results))


def find_sound_by_query(query, min_length=0, max_length=300, page_size=50):
sound = find_sounds_by_query(query=query, n_sounds=1, min_length=min_length, max_length=max_length, page_size=page_size)
def find_sound_by_query(query, min_length=0, max_length=300, page_size=50, ac_descriptors_filters={}):
sound = find_sounds_by_query(query=query, n_sounds=1, min_length=min_length, max_length=max_length, page_size=page_size, ac_descriptors_filters=ac_descriptors_filters)
if not sound:
return None # Make consistent interface with find_sound_by_similarity which only returns 1 element
else:
return sound[0]


def find_sound_by_similarity(sound_id):
url = 'https://freesound.org/apiv2/sounds/{0}/similar?&fields={1}&descriptors={2}&token={3}'.format(sound_id, fields_param, descriptor_names, FREESOUND_API_KEY)
print(url)
r = requests.get(url, timeout=30)
response = r.json()
if 'results' in response and len(response['results']) > 0:
return random.choice(response['results'])
else:
return None


def find_random_sounds(n_sounds=15, min_length=0, max_length=300, report_callback=None):
def find_random_sounds(n_sounds=15, min_length=0, max_length=300, report_callback=None, ac_descriptors_filters={}):
new_sounds = []

query_filter = "duration:[{}+TO+{}]".format(min_length, max_length)
ac_descriptors_filter = prepare_ac_descriptors_filter(ac_descriptors_filters)
if ac_descriptors_filter:
query_filter += " {}".format(ac_descriptors_filter)

# First make query to know how many sounds there are in Freesound with min_length, max_length filter
url = 'https://freesound.org/apiv2/search/text/?filter=duration:[{}+TO+{}]&fields=&page_size=1&group_by_pack=0&token={}'.format(min_length, max_length, FREESOUND_API_KEY)
url = 'https://freesound.org/apiv2/search/text/?filter={}&fields=&page_size=1&group_by_pack=0&token={}'.format(query_filter, FREESOUND_API_KEY)
print(url)
r = requests.get(url, timeout=30)
response = r.json()
Expand All @@ -54,7 +74,7 @@ def find_random_sounds(n_sounds=15, min_length=0, max_length=300, report_callbac
selected_sounds = random.sample(range(0, num_sounds_found), n_sounds)
for count, sound_position in enumerate(selected_sounds):
page = sound_position + 1
url = 'https://freesound.org/apiv2/search/text/?filter=duration:[{}+TO+{}]&fields={}&page_size=1&descriptors={}&page={}&group_by_pack=0&token={}'.format(min_length, max_length, fields_param, descriptor_names, page, FREESOUND_API_KEY)
url = 'https://freesound.org/apiv2/search/text/?filter={}&fields={}&page_size=1&descriptors={}&page={}&group_by_pack=0&token={}'.format(query_filter, fields_param, descriptor_names, page, FREESOUND_API_KEY)
print(url)
r = requests.get(url, timeout=30)
response = r.json()
Expand All @@ -65,3 +85,14 @@ def find_random_sounds(n_sounds=15, min_length=0, max_length=300, report_callbac
report_callback(count, len(selected_sounds))

return new_sounds


def find_sound_by_similarity(sound_id):
url = 'https://freesound.org/apiv2/sounds/{0}/similar?&fields={1}&descriptors={2}&token={3}'.format(sound_id, fields_param, descriptor_names, FREESOUND_API_KEY)
print(url)
r = requests.get(url, timeout=30)
response = r.json()
if 'results' in response and len(response['results']) > 0:
return random.choice(response['results'])
else:
return None
84 changes: 69 additions & 15 deletions elk_platform/source_states.py
@@ -1,3 +1,4 @@
import json
import math
import numpy
import os
Expand All @@ -15,6 +16,29 @@
N_LEDS = 9


n_recent_items_to_store = 10
recent_queries_and_filters_path = 'recent_queries_and_filters.json'
if os.path.exists(recent_queries_and_filters_path):
recent_queries_and_filters = json.load(open(recent_queries_and_filters_path, 'r'))
else:
recent_queries_and_filters = {
'queries': [],
'query_filters': [],
}

def add_recent_query(query):
recent_queries_and_filters['queries'].append(query)
if len(recent_queries_and_filters['queries']) > n_recent_items_to_store:
recent_queries_and_filters['queries'] = recent_queries_and_filters['queries'][len(recent_queries_and_filters['queries']) - n_recent_items_to_store:]
json.dump(recent_queries_and_filters, open(recent_queries_and_filters_path, 'w'))

def add_recent_query_filter(query_filter):
recent_queries_and_filters['query_filters'].append(query_filter)
if len(recent_queries_and_filters['query_filters']) > n_recent_items_to_store:
recent_queries_and_filters['query_filters'] = recent_queries_and_filters['query_filters'][len(recent_queries_and_filters['query_filters']) - n_recent_items_to_store:]
json.dump(recent_queries_and_filters, open(recent_queries_and_filters_path, 'w'))


# send_func (norm to val), get_func (val to val), parameter_label, value_label_template, set osc address
sound_parameters_info_dict = {
"gain": (lambda x: 12.0 * 2.0 * (x - 0.5) if x >= 0.5 else 36.0 * 2.0 * (x - 0.5), lambda x: float(x), "Gain", "{0:.2f} dB", "/set_sound_parameter"),
Expand Down Expand Up @@ -123,19 +147,20 @@
note_layout_types = ['Contiguous', 'Interleaved']
num_sounds_options = [1, 2, 3, 4, 5, 6, 8, 12, 16, 24, 32, 64]
ac_descriptors_options = ['off', 'low', 'mid', 'high']
ac_descriptors_names = ['brightness', 'hardness', 'depth', 'roughness','boominess', 'warmth', 'sharpness']

query_settings_info_dict = {
'num_sounds': (lambda x:num_sounds_options[int(x * (len(num_sounds_options) - 1))], 'Num sounds', '{}', 16),
'num_sounds': (lambda x:num_sounds_options[int(round(x * (len(num_sounds_options) - 1)))], 'Num sounds', '{}', 16),
'min_length': (lambda x:float(pow(x, 4) * 300), 'Min length', '{0:.1f}s', 0.0),
'max_length': (lambda x:float(pow(x, 4) * 300), 'Max length', '{0:.1f}s', 0.5),
'layout': (lambda x:note_layout_types[int(x + 0.5 * (len(note_layout_types) - 1))], 'Layout', '{}', note_layout_types[1]),
'brightness': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Brightness', '{}', ac_descriptors_options[0]),
'hardness': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Hardness', '{}', ac_descriptors_options[0]),
'depth': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Depth', '{}', ac_descriptors_options[0]),
'roughness': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Roughness', '{}', ac_descriptors_options[0]),
'boominess': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Boominess', '{}', ac_descriptors_options[0]),
'warmth': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Warmth', '{}', ac_descriptors_options[0]),
'sharpness': (lambda x:ac_descriptors_options[int(x * (len(ac_descriptors_options) - 1))], 'Sharpness', '{}', ac_descriptors_options[0]),
'layout': (lambda x:note_layout_types[int(round(x * (len(note_layout_types) - 1)))], 'Layout', '{}', note_layout_types[1]),
'brightness': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Brightness', '{}', ac_descriptors_options[0]),
'hardness': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Hardness', '{}', ac_descriptors_options[0]),
'depth': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Depth', '{}', ac_descriptors_options[0]),
'roughness': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Roughness', '{}', ac_descriptors_options[0]),
'boominess': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Boominess', '{}', ac_descriptors_options[0]),
'warmth': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Warmth', '{}', ac_descriptors_options[0]),
'sharpness': (lambda x:ac_descriptors_options[int(round(x * (len(ac_descriptors_options) - 1)))], 'Sharpness', '{}', ac_descriptors_options[0]),
}

query_settings_pages = [
Expand Down Expand Up @@ -428,12 +453,16 @@ def load_query_results(self, new_sounds, note_mapping_type=1, assigned_notes_per
midi_notes = assigned_notes_per_sound_list[sound_idx]

self.send_add_or_replace_sound_to_plugin(-1, sound, assinged_notes=midi_notes, trigger_download="none" if sound_idx < n_sounds -1 else "all")


def consolidate_ac_descriptors_from_kwargs(self, kwargs_dict):
return {key: value for key, value in kwargs_dict.items() if key in ac_descriptors_names and value != 'off'}


def add_or_replace_sound_by_query(self, sound_idx=-1, query='', min_length=0, max_length=300, page_size=50, **kwargs):
sm.show_global_message("Searching {}...".format(query), duration=3600)
sm.block_ui_input = True
try:
selected_sound = find_sound_by_query(query=query, min_length=min_length, max_length=max_length, page_size=page_size)
selected_sound = find_sound_by_query(query=query, min_length=min_length, max_length=max_length, page_size=page_size, ac_descriptors_filters=self.consolidate_ac_descriptors_from_kwargs(kwargs))
if selected_sound is not None:
sm.show_global_message("Loading sound...")
self.send_add_or_replace_sound_to_plugin(sound_idx, selected_sound)
Expand Down Expand Up @@ -465,7 +494,7 @@ def new_preset_by_query(self, query='', num_sounds=16, min_length=0, max_length=
sm.show_global_message("Searching\n{}...".format(query), duration=3600)
sm.block_ui_input = True
try:
new_sounds = find_sounds_by_query(query=query, n_sounds=num_sounds, min_length=min_length, max_length=max_length, page_size=page_size)
new_sounds = find_sounds_by_query(query=query, n_sounds=num_sounds, min_length=min_length, max_length=max_length, page_size=page_size, ac_descriptors_filters=self.consolidate_ac_descriptors_from_kwargs(kwargs))
if not new_sounds:
sm.show_global_message("No results found!")
else:
Expand All @@ -490,7 +519,7 @@ def show_progress_message(count, total):
sm.show_global_message("Entering the\nunknown...", duration=3600)
sm.block_ui_input = True
try:
new_sounds = find_random_sounds(n_sounds=num_sounds, min_length=min_length, max_length=max_length, report_callback=show_progress_message)
new_sounds = find_random_sounds(n_sounds=num_sounds, min_length=min_length, max_length=max_length, report_callback=show_progress_message, ac_descriptors_filters=self.consolidate_ac_descriptors_from_kwargs(kwargs))
if not new_sounds:
sm.show_global_message("No results found!")
else:
Expand Down Expand Up @@ -1070,6 +1099,7 @@ class EnterQuerySettingsState(GoBackOnEncoderLongPressedStateMixin, PaginatedSta
allow_change_num_sounds = True
allow_change_layout = True
title = ""
last_recent_loaded = 0

query_settings_values = {}
pages = query_settings_pages
Expand Down Expand Up @@ -1116,6 +1146,7 @@ def on_encoder_pressed(self, shift=False):
if self.callback is not None:
query_settings = self.query_settings_values.copy()
query_settings.update({'layout': note_layout_types.index(query_settings['layout'])}) # Change the format of layout parameter
add_recent_query_filter(query_settings) # Store the filter settings
callback_data = {'query_settings': query_settings}
if self.extra_data_for_callback is not None:
callback_data.update(self.extra_data_for_callback)
Expand All @@ -1127,6 +1158,16 @@ def on_fader_moved(self, fader_idx, value, shift=False):
send_func, _, _, _ = query_settings_info_dict[parameter_name]
self.query_settings_values[parameter_name] = send_func(value)

def on_button_pressed(self, button_idx, shift=False):
if button_idx == 1:
# Load one of the recent stored query filters (if any)
if recent_queries_and_filters['query_filters']:
recent_filter = recent_queries_and_filters['query_filters'][self.last_recent_loaded % len(recent_queries_and_filters['query_filters'])].copy()
recent_filter.update({'layout': note_layout_types[recent_filter['layout']]})
sm.show_global_message('Loaded recent\n qeury filters ({})'.format(self.last_recent_loaded % len(recent_queries_and_filters['query_filters']) + 1), duration=0.5)
self.query_settings_values = recent_filter
self.last_recent_loaded += 1


class NewPresetOptionsMenuState(GoBackOnEncoderLongPressedStateMixin, MenuState):

Expand Down Expand Up @@ -1229,6 +1270,7 @@ def move_to_choose_preset_name_state(self, preset_idx_name):
web_form_id="enterName",
callback=self.save_current_preset_to,
current_text=current_preset_name,
store_recent=False,
extra_data_for_callback={
'preset_idx': preset_idx,
},
Expand Down Expand Up @@ -1614,10 +1656,13 @@ class EnterTextViaHWOrWebInterfaceState(EnterDataViaWebInterfaceState):
available_chars = [char for char in " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]
char_position = 1
data_received_key = 'query'
store_recent = True
frame_count = 0
last_recent_loaded = 0

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.store_recent = kwargs.get('store_recent', True)
self.current_text = [char for char in kwargs.get('current_text', ' ')]
self.current_text = self.current_text + [' '] * (self.max_length - len(self.current_text))

Expand Down Expand Up @@ -1669,7 +1714,10 @@ def on_encoder_rotated(self, direction, shift=False):
self.current_text[self.cursor_position] = self.available_chars[self.char_position % len(self.available_chars)]

def on_encoder_pressed(self, shift=False):
self.on_data_received({self.data_received_key: ''.join(self.current_text).strip()})
entered_text = ''.join(self.current_text).strip()
if self.store_recent:
add_recent_query(entered_text)
self.on_data_received({self.data_received_key: entered_text})

def on_button_pressed(self, button_idx, shift=False):
if button_idx == 1:
Expand All @@ -1692,7 +1740,13 @@ def on_button_pressed(self, button_idx, shift=False):
# Load a random query from the prefined list
random_query = random.choice(predefined_queries)
self.current_text = [char for char in random_query] + [' '] * (self.max_length - len(random_query))

elif button_idx == 6:
# Load one of the recent stored queries (if any)
if recent_queries_and_filters['queries']:
recent_query = recent_queries_and_filters['queries'][self.last_recent_loaded % len(recent_queries_and_filters['queries'])]
self.current_text = [char for char in recent_query] + [' '] * (self.max_length - len(recent_query))
self.last_recent_loaded += 1


class SoundPrecisionEditorState(GoBackOnEncoderLongPressedStateMixin, State):

Expand Down

0 comments on commit 235fdb7

Please sign in to comment.