Skip to content

How To

This section provides you with the tools that will help you take full advantage of this library

All the examples presented in this page can be copied and tested in a file (or in a Jupyter Notebook) containing the following basic instructions:

Code
from freesound import *

API_KEY = "<your-api-key>"
USER_ID = "<your-user-id>"

c = FreeSoundClient(USER_ID,API_KEY)

INFO you can find the source code of the code showed in these guides on GitHub

Filter by Audio Feature

The filters parameter of the FreeSoundClient.search() method allows you to request information about sound files that only holds certain values for specific fields, i.e. it allows you to filter out those files which do not comply with specific attributes.

The Freesound API includes filters based on the [Audio Commons Initiative][http://www.audiocommons.org/] project, which aimed at promoting the use of open audio content and at developing technologies with which to support an envisioned ecosystem of content repositories, production tools and users.

A list of all the Audio Common filters can be found in the Appendix Lists of this Documentation These filters are all prefixed with the label ac_ and can be used as regular filters, i.e. by specifying them in the search's filter attribute either with a string or with the FreeSoundFilter utility.

Ranges and Conditions in Filters

Filters can be a double-edge sword when you want to narrow down your searches. Let's consider a simple scenario:

Code
filters = FreeSoundFilters(duration=1).aslist
result = c.search("piano",fields=Field.duration,filter=filters)

This query will filter out those sound whose duration is 1 second, or rather EXACTLY 1 second, no more, no less.

Still, by the time of writing this article, this query will produce 51 results.

Because of this the Freesound API allows filtering by range, a feature that this library implements with the Filter class and its 3 classmethods:

  • Filter.RANGE(minimum,maximum) to add filters in a range between 2 values ()
  • Filter.AT_LEAST(minimum) to specify only a minimum limit and can be interpreted as greater than
  • Filter.UP_TO(maximum) to specify only a maximum limit and can be interpreted as lower than

Substituting a range to the previous example means:

Code
filters = FreeSoundFilters(duration=Filter.RANGE(0.8, 1.2)).aslist
result = c.search("piano",fields=Field.duration,filter=filters)

Your search will include those files which are approximately 1 second long, giving you a wider choice of sounds.

For filters such as created it might be especially useful the use of ranges of time frames, and the best way to format a time range is the Python built in's library datetime. The class Filter will automatically understand that you are using a datetime object, and it will automatically format the date in isoformat for you.

If you are not familiar with the datetime library, this article might help.

Code
filters = FreeSoundFilters(created=Filter.RANGE((datetime.now()-timedelta(weeks=20)),datetime.now())).aslist
result = c.search("music",fields=Field.duration,filter=filters)

This query will filter out only those files which are created in the last 20 weeks.

The Filter class also provides 2 conditionals statements:

  • Filter.OR
  • Filter.AND

This feature allows you to build queries such as:

Code
filters = FreeSoundFilters(type=Filter.OR('wav','aiff'), duration=Filter.UP_TO(1),created=Filter.RANGE((datetime.now()-timedelta(weeks=20)),datetime.now())).aslist

which will exclude mp3 files, ogg files etc. from the search results.

Sorting (FreeSoundSort)

Another utility that can help you formulate your queries is the FreeSoundSort class. The sort attribute of the FreeSoundClient.search() method aspects 9 possible values:

  • score
  • duration_desc
  • duration_asc
  • created_desc
  • created_asc
  • downloads_desc
  • downloads_asc
  • rating_desc
  • rating_asc

if you do forget these values you can always start typing FreeSoundSort. and your IDE will suggest you possible valid values that you can use in your queries:

filters hints
click to enlarge

This example shows how to use it in context:

Code
c.search(query="piano", sort_by=FreeSoundSort.created_asc)

Download a lot of files at once

Another important parameter of the FreesuondClient.search() method is page_size which by default is set to 15 and states the max number of items that will be downloaded in one page of the response. When you need to gather a lot of data from the Freesound Database you can hence reduce the number of requests by using a higher page_size value (up to 150).

This parameter works well in coordination with FreeSoundClient.download_results() a method that you might have already seen in the Tutorial. This method allows you to download sound files from a query response and deals with pagination automatically.

It accepts 2 parameters:

  • output_folder_path, which defines the folder in which the files will be downloaded
  • files_count the number of files that you want to download
Code
c.download_results(output_folder_path="sound_lib", files_count=100)

So if you want to download 100 files from the Freesound Database the best approach would be to set both search(...,page_size=100) and download_results(files_count=100):

Code
filters = FreeSoundFilters(type="wav", samplerate=48000).aslist
c.search(query="piano", fields=Field.download, filter=filters, page_size=100)
c.download_results(output_folder="tutorials/sound_lib",files_count=100)

With this approach you will limit the requests to the minimum because the search results are not paginated and download_results will find all the download links in one page

NOTE: Remember that if you want to download files from the Freesound Database, you MUST include the field download in the search request

There are different scenarios that we would like to summarize:

  • download_results will never overwrite an existing sound file. If the sound file already exists in the destination download folder the client will just skip it, i.e. it will not make a request to the server
  • if page_size is smaller than files_count the client will take care of pagination for you
  • if files_count is bigger than the actual length of resulting the sound list, i.e. fewer files than files_count are found in the Database with your query, this value will be set to the length of the response list.

Access the search results data

The search method actually returns the results list, which means that you can assign the response from the Database to a variable and manipulate the resulting dictionary as you need. The following is a simple demonstration of this feature:

Code
result = c.search("piano",page_size=3)
print(result['results'][0])
Output
{
"id": 148471,
"name": "PIANO_LOUD_AB6.wav"
}

This response is a dictionary containing the following keys:

  • count: the total count of the files found
  • previous: the url of the previous page (for pagination)
  • next: the url of the next page (for pagination)
  • results: a list of sounds and their features matching the search request

You can use the variable result as you would do with a regular dict, for example result['results'][0] will return the data about the first sound instance in the list. Another example could be:

Code
# store the id of only those sounds that have a `pitch_centroid` lower than 0.4
result = c.search(query="piano detuned", fields=Field.analysis, descriptors=Descriptor.sfx_pitch_centroid, page_size=100)
results_list = result['results']

ids:list[int] = []

for i,snd in enumerate(results_list):
    centroid = snd['analysis']['sfx']['pitch_centroid']
    mean = centroid['mean']
    if mean <= 0.4:
        ids.append(snd['id'])

print(ids)

Handle pagination manually

There are no limitation about the number of search calls that you can perform in your program, and even though it is always better to optimize your searches via the fields and filter attributes, there might be scenarios in which you need to perform multiple searches.

Let's consider this possible scenario: you are looking for as many uncompressed recordings as possible of a single notes played by a prepared piano sampled at 44100, files that you might eventually want to download. You then want to retrieve the loudness of each sound sample and store all this data in a json file.

You could formulate your requests as follows:

Code
fields = FreeSoundFields([Field.download, Field.analysis]).aslist
filters = FreeSoundFilters(type="wav", samplerate=44100, tag=["prepared"], ac_single_event=True).aslist
c.search(query="piano", fields=fields, filter=filters, descriptors=Descriptor.lowlevel_average_loudness, page_size=15)

By the time of writing this tutorial this search produces 151 results.

In the FreeSound API Tutorial we describe how to save the analysis data in a json file using the FreeSoundClient.write_results_list method.

Behind the scenes this method save sound data from the client's attribute results_list, which keeps track each search performed by the client. However, this attribute does not deal with pagination, i.e. if your page size is set to 15, results_list will store only the first 15 results.

So the first thing would be to increment the page_size to the highest possible value which is 150. How do you handle the 151th file? You need to access the next page.

results_list does not store information about pagination, but the return value of search does. You can update your script as follows:

Code
fields = FreeSoundFields([Field.download, Field.analysis]).aslist
filters = FreeSoundFilters(type="wav", samplerate=44100, tag=["prepared"], ac_single_event=True).aslist
result = c.search(query="piano", fields=fields, filter=filters, descriptors=Descriptor.lowlevel_average_loudness, page_size=150)

From result you can access the next keyword and get the url of the second page of your query. print(result['next']) will output:

Output
https://freesound.org/apiv2/search/text/?&query=piano&filter=type:wav%20samplerate:44100%20tag:prepared%20ac_single_event:True&weights=&page=2&page_size=150&fields=id,name,download,analysis&descriptors=lowlevel.average_loudness

Once you have this value, you can call the FreeSoundClient.get_next_page() method passing the url to it, which will update the client results_list with the results from the second page.

At this point you can store all your data in a json file at once calling write_results_list.

Code
c = FreeSoundClient(USER_ID,API_KEY)

fields = FreeSoundFields([Field.download, Field.analysis]).aslist
filters = FreeSoundFilters(type="wav", samplerate=44100, tag=["prepared"], ac_single_event=True).aslist
result = c.search(query="piano", fields=fields, filter=filters, descriptors=Descriptor.lowlevel_average_loudness, page_size=150)
print(c.results_list['count']) # 150

c.get_next_page(result['next'])
print(c.results_list['count']) # 151

NOTICE: If you just only wanted to download the 151 files you could have call c.download_list() directly as this method handles pagination automatically

Load a result list

If you want to recover your data from a previous session or import it in another script, you can use the FreeSoundClient.load_results_list() method that load a json file saved by write_results_list. It usage is pretty straight forward:

Code
c = FreeSoundClient(USER_ID,API_KEY)
c.load_results_list("tutorials/records/240301T2042_results_list.json")
c.dump_results()

this method checks that the file is properly formatted, i.e. it can find the results, the timestamp and the count keywords. It is not advice to write such file by hand.

Working with specific sound instances

Another useful public method of the FreeSoundClient is get_track_info which is allows you to retrieve information about a sound given its id.

This function implements this request of the Freesound API.

Code
result = c.get_track_info(382353)
print(result)
Output
<freesound.freesound_track.FreeSoundTrack Cymbale-longue-02.wav>

The result of this call is a FreeSoundSoundInstance another utility class which basically represent a Sound Instance as described here. You can access all its fields as attributes, i.e. result.download will return its download url (or None), or you can create a dictionary out of it with the method as_dict()

Code
print(result.as_dict())
Output
{
"id": 382353,
"name": "Cymbale-longue-02.wav",
"url": "https://freesound.org/people/lomographicmusic/sounds/382353/",
"tags": [
    "prepared",
    "detuned",
    "piano"
],
"description": "A cymbal flippantly thrown into the piano, recorded with Tascam DP008. \r\n\r\nUne cymbale jet\u00e9e n\u00e9gligemment dans le piano, frapp\u00e9e par les marteaux et capt\u00e9e par le brave Tascam DP008 !",
"created": "2017-02-28T20:16:07",
"license": "https://creativecommons.org/licenses/by/4.0/",
"type": "wav",
"channels": 2,
"filesize": 493772,
"bitrate": 0,
"bitdepth": 16,
"duration": 2.79891,
"samplerate": 44100.0,
"username": "lomographicmusic",
"pack": "https://freesound.org/apiv2/packs/21508/",
"download": "https://freesound.org/apiv2/sounds/382353/download/",
"bookmark": "https://freesound.org/apiv2/sounds/382353/bookmark/",
"previews": {
    "preview-hq-mp3": "https://cdn.freesound.org/previews/382/382353_7119516-hq.mp3",
    "preview-hq-ogg": "https://cdn.freesound.org/previews/382/382353_7119516-hq.ogg",
    "preview-lq-mp3": "https://cdn.freesound.org/previews/382/382353_7119516-lq.mp3",
    "preview-lq-ogg": "https://cdn.freesound.org/previews/382/382353_7119516-lq.ogg"
},
"images": {
    "waveform_m": "https://cdn.freesound.org/displays/382/382353_7119516_wave_M.png",
    "waveform_l": "https://cdn.freesound.org/displays/382/382353_7119516_wave_L.png",
    "spectral_m": "https://cdn.freesound.org/displays/382/382353_7119516_spec_M.jpg",
    "spectral_l": "https://cdn.freesound.org/displays/382/382353_7119516_spec_L.jpg",
    "waveform_bw_m": "https://cdn.freesound.org/displays/382/382353_7119516_wave_bw_M.png",
    "waveform_bw_l": "https://cdn.freesound.org/displays/382/382353_7119516_wave_bw_L.png",
    "spectral_bw_m": "https://cdn.freesound.org/displays/382/382353_7119516_spec_bw_M.jpg",
    "spectral_bw_l": "https://cdn.freesound.org/displays/382/382353_7119516_spec_bw_L.jpg"
},
"num_downloads": 31,
"avg_rating": 3.0,
"num_ratings": 1,
"rate": "https://freesound.org/apiv2/sounds/382353/rate/",
"comments": "https://freesound.org/apiv2/sounds/382353/comments/",
"num_comments": 0,
"comment": "https://freesound.org/apiv2/sounds/382353/comment/",
"similar_sounds": "https://freesound.org/apiv2/sounds/382353/similar/",
"analysis": "No descriptors specified. You should indicate which descriptors you want with the 'descriptors' request parameter.",
"analysis_stats": "https://freesound.org/apiv2/sounds/382353/analysis/",
"analysis_frames": "https://freesound.org/data/analysis/382/382353-fs-essentia-extractor_legacy_frames.json",
"is_explicit": false
}

You can create a dict from a FreeSoundSoundInstance, and you can also create a FreeSoundSoundInstance from a dict. This MUST contain at least the fields id and name. If it contains non-valid fields, the program will raise a DataError exception.

Code
# notice 'tegs' instead of 'tags'
t = FreeSoundSoundInstance({'id': 524545, 'name': 'Piano12 B Flat', 'tegs': ['note', 'synthesizer', 'Piano'], 'type': 'mp3', 'download': 'https://freesound.org/apiv2/sounds/524545/download/'})
print(t.name)
Output
freesound.freesound_errors.DataError: Could not create a FreeSoundTrack 'tegs' is not a valid field

One important thing to notice is that the name attribute in a FreeSoundSoundInstance is manipulated. Spaces will be replaced by - and the extension will be added to it. The original name of the last examples is 'Piano12 B Flat', but t.name will become

Output
Piano12-B-Flat.mp3

Download one sound

There are simple scenarios in which you might already have a FreeSoundSoundInstance or a valid download link. In these cases you might find the FreeSoundClient.download_track() method more suitable (or just simpler) than download_list. Given a download url and a file name, this function will just download that file locally. For example:

Code
result = c.get_track_info(382353)
c.download_track(result.download, result.name)

You can specify an output folder using the outfolder parameter, but if you don't the program will prompt you to specify one. If the file already exists you will be prompted to choose how to handle the situation. Setting the skip attribute to True defaults to not downloading the file if it already exists.

Code
result = c.get_track_info(382353)
c.download_track(result.download, result.name,outfolder='tutorials/sound_lib', skip=True)