You use audio sample files to produce music.
You hunger for new, unusual samples for inspiration.
The internet has samples.
SampleMgr
describes, collects and organizes your sample files. Use it to
fill up your local drive with the samples you want rather than a random pile
that come with music apps.
SampleMgr manages audio sample files and communicates their location to Hz.Samplo or other sample file consumer. SampleMgr supports asynchronous (background) downloading, caching and serving of network-based samples.
Samples are organized on concepts of repository
, library
, bank
,
and voice
. We can think of a repository as equivalent to
a github repository (repo) that obeys a standard directory
structure. We currently support two file organizing schemes below
a repository:
MIDIJS/FatBoy/accordion
DrumKits/AkaiXR10
Concept | Description |
---|---|
repo |
name+internet URL containing sample libraries or banks. |
bank |
collection of voices or sounds |
voice |
collection of sound files organized either by MIDI key. Voices can be either tonal or percussive. |
sound |
collection of 1 or more sound files organized sound name (eg bd, hh). These are usually percussive. |
Since the internet is a big place, we introduce the repo file.
This file is a list of your favorite repositories (their internet location and contents).
The repo file is used to convert a simple name to one or more internet URLs
and these are converted to a directory name in your workspace. Hz
ships
with a default repo file, shown below. You can create your own
repo file and TOC-files to suit your own tastes.
All this machinery is presented in order to make literally thousands of internet-hosted sample files easily accessible automatically and portably. Of course you also have your own personal/private collection of sample files and these can be either be explicitly organized into your repo file or presented to SampleMgr directly in your songs.
If you are a Strudel user and have your own favorite online repositories you can access their files directly through SampleMgr methods. This approach bypasses the repo file just-described but requires these steps:
strudel.json
Note that this approach is the same as used by standalone Strudel differing
only insofar as the sample files are downloaded and managed by your
internet browser and secreted in an obscure location on your computer's
filesystem. We've streamlined this process via Synchronize()
method described below.
Here's a quick overview of the SampleMgr API. For most cases these one or two methods are all that's required to use internet-based sample files in your projects. All the remaining details are presented for more advanced use-cases.
For the common use-case where you know a bank
and a voice
, you only
need a single method to ensure sample files are available on your computer
as shown here:
let asamps = await SampleMgr.PreloadSamples("MIDIJS/FatBoy", "accordion");
let presetStr = asamps.AsPreset();
Once complete, you'll find 88 sample files below your current workspace:
${WS}/_cache/github/paulrosen/midi-js-soundfonts/FatBoy/accordion-mp3/
strudel.json
from the
repository via eg: let repo = "github:tidalcycles/dirt-samples";
let sampleMap = await SampleMgr.FetchSampleMap(repo);
let [presetStr, sndTOC] = await SampleMgr.PreloadRemoteSamples(sampleMap,
sounds, asPreset);
For your convenience we've combined these two steps into a single method,
async Synchronize(requestlist)
. This method supports multiple
repo requests defined as the combination of a repo reference and
the collection of sounds you require from it. Note that the
elements of requestlist
may be interpretted as MIDI channel indices.
Moreover, when a repo supports tonal sounds (like a soundfont), only
a single sound should be present on a channel.
let {result, preset} = await SampleMgr.Synchronize([
{
url: 'github:eddyflux/crate',
sounds: ['bd', 'rim', 'sd', 'rd', 'hh'],
},
{
url: 'hz:MIDIJS/FatBoy',
sounds: ['electric_piano_1'], // one tonal sound per channel
aliases: {
'gm_epiano1': 'electric_piano_1',
'epiano': 'electric_piano_1',
}
},
{
url: 'hz:MIDIJS/FatBoy',
sounds: ['acoustic_bass'],
aliases: {
'gm_acoustic_bass': 'acoustic_bass',
'abass': 'acoustic_bass'
}
},
]);
After preloading ther requested sample files, you can either inspect the result for individual local file path names or make these files known to Hz.Samplo like so:
await samplo.LoadPreset(presetStr, "MIDIJS/FatBoy_accordion");
You can find complete examples here.
SampleMgr method | Description |
---|---|
async Init(repolist?) |
Optional, returns a promise that resolves to an initialized SMgrRepolist . If filename isn't provided, the default is returned. |
GetRepolists() |
Returns an array of names of repolists. |
GetRepolist(name) |
Returns the named SMgrRepoList . |
async PreloadSamples(bank, voice, repolist=null) |
Ensures that the bank+voice sample files are available in the local cache. Returns a promise that resolves to a SMgrVoice instance. This can be used to produce a Hz.Plugin-style preset value. |
AsPresetFromLocalVoice(cwd, name, voicedata) |
Returns a Hz.Samplo preset that refers to a locally defined voice. voicedata is either an array or a dictionary. |
async FetchSampleMap(reporef) |
Returns a sample map associated with an internet sample file source. |
async PreloadRemoteSamples(sampleMap, sounds, asPreset) |
Ensures that sample files associated with the list of sounds(voices) are available in the local cache. Returns a promise that resolves to a pair: [value, sndMap] where the type of value depends upon asPreset . sndMap is used to map sound references (like "bd:0") to integer offsets within the sample set. |
{result, preset} = async Synchronize(reqlist) |
A convenience method that combines FetchSampleMap and PreloadRemoteSamples . |
SMgrRepolist method | Description |
---|---|
GetFile() |
Returns the filename associated with this repolist. |
async Initialize() |
Ensures that all TOC files supporting the repo are loaded. |
GetBankNames() |
Returns a list of bank names known to the repo. |
GetBank(name) |
Returns a SMgrBank for the named repo bank. |
SMgrBank method | Description |
---|---|
GetName() |
returns the bank name. |
GetVoiceNames() |
returns the list of voices present in the bank. |
GetVoice(name) |
returns the SMgrVoice for the voicename in the bank. |
SMgrVoice method | Description |
---|---|
AsPreset() |
returns a Hz.Samplo preset that refers to a locally cached voice files. |
GetName() |
returns the voice's name |
GetBank() |
returns the voice's bank name |
IsPercussive() |
returns true if the voice is percussive |
IsTonal() |
returns true if the voice is tonal |
GetCategory() |
returns either "tonal" or "percussive" |
GetURLs() |
returns an array of local sample file references |
GetNumPercSamps() |
returns 0 or the size of the samples array |
GetNumTonalSamps() |
returns 0 or the number of entries in the samples object |
GetURLForNote(note) |
returns [fileref, transpose] |
GetURLForIndex(i) |
returns fileref at i |
You can find complete examples here.
The repo file is a JSON formatted dictionary that maps a repo name
to a repo TOC (table of contents) file. When you request
samples for VCSL/handchimes
, we first locate the repo named VCSL in the
repolist, then find handchimes in its TOC. There we'll find an enumeration
of the voice sample files.
{
"Dirt": {
"URL": "https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master",
"TOC": "tocs/dirt-samples.json",
"CacheDir": "github/tidalcycles/Dirt-Samples"
},
"Strudel": {
"URL": "https://raw.githubusercontent.com/tidalcycles/strudel/main/website/public",
"TOC": "tocs/strudel.json",
"CacheDir": "github/tidalcycles/strudel"
},
"DrumKits": {
"URL": "https://raw.githubusercontent.com/ritchse/tidal-drum-machines/main/machines",
"TOC": "tocs/tidal-drum-machines.json",
"CacheDir": "github/ritchse/tidal-drum-machines",
"Multibank": 1
},
"VCSL": {
"URL": "https://raw.githubusercontent.com/sgossner/VCSL/master",
"TOC": "tocs/vcsl.json",
"CacheDir": "github/sgossner/VCSL"
},
"midi-js-soundfonts": {
"URL": "https://raw.githubusercontent.com/paulrosen/midi-js-soundfonts/master",
"TOC": "tocs/midi-js-soundfonts.json",
"CacheDir": "github/paulrosen/midi-js-soundfonts",
"Multibank": 1,
"LICENSE": "MIT, Copyright (C) 2012 Benjamin Gleitzman (gleitz@mit.edu)"
},
"loophole": {
"URL": "https://loophole-letters.vercel.app/samples",
"TOC": "tocs/loophole.json",
"CacheDir": "loophole-letters.vercel.app/samples"
}
}
The Repo TOC file is a JSON formatted dictionary that maps voice names to
their collection of sample files. Here's a portion of the VCSL
TOC file.
We've selected this subset because it shows the difference between
tonal and percussive voices. The first is a dictionary whose keys
are note-names and percussion voice are an array indexed by a voice
variation/index. In both cases the value of each entry is a relative
pathname to a sample file.
{
"handchimes": {
"A#3": "Idiophones/Struck Idiophones/Hand Chimes/sus_A#3_r01_main.wav",
"A#5": "Idiophones/Struck Idiophones/Hand Chimes/sus_A#5_r01_main.wav",
"A4": "Idiophones/Struck Idiophones/Hand Chimes/sus_A4_r01_main.wav",
"C3": "Idiophones/Struck Idiophones/Hand Chimes/sus_C3_r01_main.wav",
"C4": "Idiophones/Struck Idiophones/Hand Chimes/sus_C4_r01_main.wav",
"C5": "Idiophones/Struck Idiophones/Hand Chimes/sus_C5_r01_main.wav",
"C6": "Idiophones/Struck Idiophones/Hand Chimes/sus_C6_r01_main.wav",
"D3": "Idiophones/Struck Idiophones/Hand Chimes/sus_D3_r01_main.wav",
"D4": "Idiophones/Struck Idiophones/Hand Chimes/sus_D4_r01_main.wav",
"D5": "Idiophones/Struck Idiophones/Hand Chimes/sus_D5_r01_main.wav",
"E3": "Idiophones/Struck Idiophones/Hand Chimes/sus_E3_r01_main.wav",
"E4": "Idiophones/Struck Idiophones/Hand Chimes/sus_E4_r01_main.wav",
"E5": "Idiophones/Struck Idiophones/Hand Chimes/sus_E5_r01_main.wav",
"F#3": "Idiophones/Struck Idiophones/Hand Chimes/sus_F#3_r01_main.wav",
"F#4": "Idiophones/Struck Idiophones/Hand Chimes/sus_F#4_r01_main.wav",
"F#5": "Idiophones/Struck Idiophones/Hand Chimes/sus_F#5_r01_main.wav",
"G#3": "Idiophones/Struck Idiophones/Hand Chimes/sus_G#3_r01_main.wav",
"G#4": "Idiophones/Struck Idiophones/Hand Chimes/sus_G#4_r01_main.wav",
"G#5": "Idiophones/Struck Idiophones/Hand Chimes/sus_G#5_r01_main.wav"
},
"hihat": [
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_Close_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_Close_rr2_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v1_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v1_rr2_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v2_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v2_rr2_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v3_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v3_rr2_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v4_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitC_v4_rr2_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitLoose_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitLoose_rr2_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitOC_rr5_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitO_rr1_Mid.wav",
"Idiophones/Struck Idiophones/Hi-Hat Cymbal/HiHat_HitO_rr2_Mid.wav"
],
}