Tidal icon indicating copy to clipboard operation
Tidal copied to clipboard

Support for sample banks

Open jarmitage opened this issue 8 years ago • 32 comments

I’ve been discussing with @kindohm for a while how to manage an expanded library of samples. Today the default way of achieving this is to use the samples folder like a namespace, e.g.:

../samples/tracknamebd/_X.wav ../samples/tracknamesn/_X.wav

There are several disadvantages to this:

  • Long folder names
  • Many folders
  • No means of programmatically switching tracks
  • Long load time and memory footprint in SuperDirt, when usually only a small % of the library gets used in one session

If we take the single level directory of samples to be the bottleneck, then an alternative would be to introduce something like bank. In Tidal, to select a sound from a bank:

d1 $ bank "track1" # sound "bd"

Use multiple sound banks in a single pattern:

d1 $ bank "track1 track2" # sound "bd sn"

Index individual samples as usual:

d1 $ bank "track1 track2" # sound "bd:1 sn:2"

A set of global samples will play when no bank is specified:

d1 $ sound "bd"

Some may want to access banks inside a sound pattern:

d1 $ sound "bd|track1 sn|track2"

Banks would be loaded only when called in tidal (this may require delaying the initial playback by X cycles whilst the samples load). However in SuperDirt, offer an option to load all banks by default:

~dirt.loadSampleBanks

Example directory structure:

../samples/global/bd/_X.wav ../samples/global/sn/_X.wav ../samples/track1/bd/_X.wav ../samples/track1/sn/_X.wav ../samples/track2/bd/_X.wav ../samples/track2/sn/_X.wav

I'm not sure what implementation considerations there would need to be for this, but from a user perspective I feel this would greatly improve my compositional and performance process.

Maybe this proposal should also integrate managing SynthDefs as well?

jarmitage avatar Sep 08 '17 15:09 jarmitage

@jarmitage: There have been discussions in the past about some sort of sample/synth bank format. Perhaps this will be the kick we need to get this implemented?

sfradkin avatar Sep 08 '17 15:09 sfradkin

All samples are kept in RAM, in order to be able to efficiently play back in different speeds. You can always load only very few samples in SuperDirt and then add more as you go. The only problem is that loading from disk is asynchronous, so you can't reliably integrate it into a tidal pattern (unless you accept the possible omissions while loading).

Having several banks of sounds (samples or synths) is implemented in SuperDirt by the class DirtSoundLibrary. You can keep an array/dictionary of them and switch them if you want to re-map your instrument names. But normally, this is rather more costly than less, because several libraries will share certain samples. So far we have no hierarchies of sound libraries where you could swap out parts without touching the parent. It wouldn't be hard, but adds complexity.

Just a few questions:

Long folder names

I don't understand: most folder names are pretty short.

Many folders

how would banks reduce the number of folders?

No means of programmatically switching tracks

Are you sure you want to switch tracks by rewiring the sample names? Wouldn't that be better done in tidal with switching between actual patterns?

Long load time and memory footprint in SuperDirt, when usually only a small % of the library gets used in one session

The default sample library is deliberately large. It's really easy to make a different startup script that loads only what you need. You can always load more on the fly.

Sorry, I don't want to shine too much of a critical light on what you are proposing, I just try to understand better what the main point is.

telephon avatar Sep 08 '17 17:09 telephon

"long folder names"

As an author of custom samples, I encounter this often. I compose "tracks" and require my sample folders to be named/grouped by track:

trackname1bd
trackname2bd

Typing out "trackname" is a bit much for every sample in a pattern:

d1 $ sound "trackname1bd trackname1sd [trackname1bd trackname1sd] trackname1ch*4"

It would be much easier to type short samples and specify a "bank":

d1 $ sound "bd sd [bd sd] ch*4" # bank "trackname1"

kindohm avatar Sep 08 '17 17:09 kindohm

Thanks @telephon.

All samples are kept in RAM, in order to be able to efficiently play back in different speeds. You can always load only very few samples in SuperDirt and then add more as you go.

To "add more as you go" right now, you would call ~dirt.loadSoundFiles("...") right? I am proposing that you should be able to do this from Tidal, whilst you compose/perform, without having to think specifically about "loading the samples". Just do d1 $ bank "track1" # sound "bd" and ~dirt.loadSoundFiles("../samples/track1/*") would be called (or equivalent function would be handled by SuperDirt).

The only problem is that loading from disk is asynchronous, so you can't reliably integrate it into a tidal pattern (unless you accept the possible omissions while loading).

I agree this is an issue. I mentioned in my proposal "this may require delaying the initial playback by X cycles whilst the samples load" but I guess this would be potentially complex to implement?

Having several banks of sounds (samples or synths) is implemented in SuperDirt by the class DirtSoundLibrary...

Ok. I'm not sure I fully understand how this works. Can you provide some example code? Do people currently use this? Is it accessible from Tidal?

Long folder names

I don't understand: most folder names are pretty short.

They are usually short, but here we are considering the case where you want to have a lot of samples and easily remember how to call them in Tidal. As noted at the start of this proposal, the naive approach to this is name your folders tracknamebd tracknamesn etc. This is where the folder names get longer.

Many folders

how would banks reduce the number of folders?

Following the previous point, if you could organise your samples hierarchically in folders (see the end of the proposal for an example directory structure) instead of by folder name, this would reduce the total number of folders in the top level directory. Having a syntax in Tidal which handles this (the bank idea) would enable declaring patterns somewhat separately from the sound collection used, which would keep pattern strings short.

No means of programmatically switching tracks

Are you sure you want to switch tracks by rewiring the sample names? Wouldn't that be better done in tidal with switching between actual patterns?

Having consistent sample names across tracks would be very useful; this is similar to how bd sn etc are standardised across drum machine banks. Also, the idea of switching across tracks seems creatively intriguing.

Long load time and memory footprint in SuperDirt, when usually only a small % of the library gets used in one session

The default sample library is deliberately large. It's really easy to make a different startup script that loads only what you need. You can always load more on the fly.

I think we're kind of saying the same thing here. I want to be able to "load more on the fly", but from Tidal, and I want to do it whilst thinking "I want to make this sound" not "I want to load this sound and then I want to make this sound". To manage the associated files, I want to organise my sample directory with an extra level so I can store banks of sounds that may have the same name i.e. multiple bd in different banks.

jarmitage avatar Sep 08 '17 17:09 jarmitage

This is how it looks in superdirt (untested!):

(
~dirt = SuperDirt.new;
~banks = (); 
~banks[\first] = DirtSoundLibrary.new;
~banks[\second] = DirtSoundLibrary.new;
~banks[\first].loadSoundFileFolder("<your first path here>");
~banks[\second].loadSoundFileFolder("<your second path here>");
)

~dirt.soundLibrary = ~banks[\second];
~dirt.soundLibrary = ~banks[\first];

there are several ways you could call something like ~dirt.soundLibrary = ~banks[\second]; from tidal, this is more a question of convention. The easiest is probably to just define an instrument that mean "switch library" and use its note to say which. But that might be too crude for what you are after.

Btw. there is already a scrip that lets you load samples on demand, you could test it to see if you can reduce your memory problems:

https://github.com/musikinformatik/SuperDirt/blob/master/scripts/auto-load-samples.scd

telephon avatar Sep 08 '17 17:09 telephon

The easiest is probably to just define an instrument that mean "switch library" and use its note to say which.

Just so I definitely understand you, can you pseudo-code this in Tidal?

jarmitage avatar Sep 08 '17 17:09 jarmitage

Not saying the "bank" idea isn't interesting, but FYI at least for the "long folder prefix" problem you can currently do something in Tidal like

d1 $ s (fmap ("track1"++) "bd sn" )

bgold-cosmos avatar Sep 08 '17 19:09 bgold-cosmos

Actually, for that matter, you can use the <$> <*> notation to have a bank pattern and a sample pattern: (the flip is there to force the structure to come from the sample pattern)

d1 $ s (flip (++) <$> "bd sn*2" <*> "track1 track2")

As a nicer looking function sprefix p = with s_p (liftA2 (++) (p::Pattern String))

d1 $ sprefix "track1 track2" $ s "bd sn*2"

It doesn't quite have the nice sound p1 # bank p2 structure (I can't see a way to get that), but it's close.

bgold-cosmos avatar Sep 08 '17 19:09 bgold-cosmos

Current workaround that almost ticks all boxes

Tidal:

let bank p = with s_p (liftA2 (++) (p::Pattern String))
    b = bank

d1 $ b "a b" $ s "bd sn"  -- plays "abd bsn"

Folder hierarchy:

/path/to/a/abd/n.wav
/path/to/a/asn/n.wav
/path/to/b/bbd/n.wav
/path/to/b/bsn/n.wav

SuperDirt:

~dirt.loadSoundFiles("/path/to/a/*")
~dirt.loadSoundFiles("/path/to/b/*")

jarmitage avatar Sep 18 '17 23:09 jarmitage

Btw. the method loadSoundFiles has an argument namingFunction. It takes the path as an argument and you can supply it to generate synth names.

telephon avatar Sep 19 '17 10:09 telephon

It works to put ~dirt.loadSoundFiles("/path/to/a/*") in the SuperCollider startup file like this:

SuperDirt.start

~dirt.loadSoundFiles("/path/to/a/*")

And press Ctrl-Enter. However, when it starts up, it produces an error:

ERROR: syntax error, unexpected '~', expecting $end
  in file 'selected text'
  line 3 char 1:

  ~dirt.loadSoundFiles("/path/to/a/*") 
  ^

Is there a way to load sound files in the startup file?

mtift avatar May 30 '18 12:05 mtift

@mtift have a look at superdirt_startup.scd for pointers https://github.com/musikinformatik/superdirt (I think this would have been better as a new issue, I'll tidy away these two comments at some point)

yaxu avatar May 30 '18 20:05 yaxu

maybe you have the wrong tilde, there are two different ones. This should be the correct one: ~.

telephon avatar May 30 '18 20:05 telephon

Sorry for commenting an old issue. I thought it seemed relevant.

(And since I'm off topic, I'll mention that I've been enjoying The Oxford Handbook of Algorithmic Music and that I found @telephon's article about the philosophy of time to be especially helpful.)

mtift avatar May 30 '18 21:05 mtift

@mtift this is encouraging to hear, given that it is rather dense. Why did you find it useful?

telephon avatar May 31 '18 16:05 telephon

@telephon This is probably the strangest Github comment I've left in a while, but I've recently been fascinated by critical theories concerning the "event" of literature and the arts (probably stemming from recent articles in Critical Inquiry), and I enjoyed your comments about object/event, presence/existence, etc. The way you presented the concept of time/events with (live) coding made me realize how my own views of coding as a "preparational activity" was at odds with my views of music as an activity (what Christopher Small calls "musicking"), and it helped me combine previously disparate ideas. So the article was useful in the personal sense of exploring my nascent fascination with live coding, but also useful in practical ways, such as your bibliography full of books and articles that I'm planning to read. I could really go on and on, and I have a lot of questions, but maybe another forum would be better.

mtift avatar May 31 '18 17:05 mtift

@mtift for me, too, rethinking programming has been very much motivated by my need to superimpose disparate interests. And at the same time this helps to abandon some strong preconceptions, too. Good that this works for you as well.

I don't think that programming and theory should be separate, so I'm enjoying the apparent digression from github convention here! On the other hand, it is all not about the current issue, so yes, let's continue elsewhere if you like, for example on the livecode mailing list or you can also just drop me an email.

telephon avatar Jun 07 '18 08:06 telephon

@jarmitage This issue is getting a bit old now. Is this still something you're after?

yaxu avatar Nov 26 '19 16:11 yaxu

@yaxu it’s not immediately relevant to me so may as well leave it for now, can always reopen later

jarmitage avatar Nov 26 '19 16:11 jarmitage

Hi! I'm looking for a way to easily switch between sample packs. The workaround provided by @jarmitage no longer works. Do you have any suggestions on how to proceed? Combined with the lazy loading it would be nice to keep all my samples ready at one place.

gzavo avatar Jun 21 '21 13:06 gzavo

Here's an updated workaround:

let withS :: String -> (String -> String) -> ControlPattern -> ControlPattern 
    withS name f pat = withValue (\cm -> Map.adjust (applyFIS id id f) name cm) pat
    _bank name = withS "s" (name ++)
    bank namepat pat = innerJoin $ (\name -> _bank name pat) <$> namepat
    b = bank

yaxu avatar Nov 11 '21 23:11 yaxu

@yaxu I see the issue was closed, but the support has not been added to Tidal, so it's quite frustrating to see this issue dismissed. To me, it seems like pretty basic functionality to be able to write patterns like [bd hh cp hh] and be able to swap out live which kit to use for these sounds, so I was quite surprised to learn that Tidal is missing this. Strudel has it, for example.

Is it really the intent to require users who have written a pattern using 808 samples, and want to use a different instrument, to (during a live performance) manually replace all instances of "808bd", etc. to other strings? I feel that would make for a very boring performance :)

Please consider adding this to the next version of BootTidal.hs, because the below is an unpleasant experience for brand new users of tidalcycles. Some of the below would be good issues for the SuperDirt project, but without proper bank support, there's not much point to creating a pull request, as Tidal users won't be able to make use of the better organization by default.


For users who are here because they are stuck trying to get the above workaround for the lack of bank support to work, a few tips:

  1. set up a BootTidal.hs file
  • you'll need to copy the original file from the tidalcycles repository at the appropriate version and modify it, e.g. here is the file for tidalcycles version 1.9.5
  • you'll then need to tell your editor or editor extension to read it, e.g. using vscode with the "TidalCycles for VSCode" extension, open settings and paste the path to your modified BootTidal.hs into the setting called tidalcycles.bootTidalPath
  1. edit BootTidal.hs as follows:
  • note that you'll probably get syntax errors, because the :set syntax is not recognized as valid syntax for a .hs file
  • add the line import qualified Data.Map as Map after the other imports
  • paste the following code at the bottom of the file, after the other :{ ... } blocks:
:{
let withS :: String -> (String -> String) -> ControlPattern -> ControlPattern 
    withS name f pat = withValue (\cm -> Map.adjust (applyFIS id id f) name cm) pat
    _bank name = withS "s" (name ++)
    bank namepat pat = innerJoin $ (\name -> _bank name pat) <$> namepat
    b = bank
:}
  1. use the bank in your music code as follows, e.g. to play "808bd 808sn", use d1 $ b "808" $ s "bd sn"
  2. note that some samples are not correctly organized by default, as in they don't match their un-banked counterparts. e.g. for the 808 samples, I had to move some around to get them to match the default counterparts:
sample default name move from move to new name
cowbell 808:0 808/CB.WAV 808cb/CB.WAV 808cb (to match default cb)
hi-hat 808:1 808/CH.WAV 808hh/HH.WAV 808hh (to match default hh)
claves 808:2 808/CL.WAV 808cl/CL.WAV 808cl
clap 808:3 808/CP.WAV 808cp/CP.WAV 808cp (to match default cp)
shaker 808:4 808/MA.WAV 808ma/MA.WAV 808ma
rimshot 808:5 808/RS.WAV 808rs/RS.WAV 808rs (to match default rs)
open hi-hat 808oh 808oh/ 808ho/ 808ho (to match default ho)
  1. note that there are other samples not organized as banks, but as sample numbers within a bank, and you apparently just have to figure out what drum each index corresponds to, with no string label. e.g. the db kit is organized as follows:
sample default name
closed hi-hat db:0
crash db:1
clap db:2
mid tom db:3
low tom db:4
kick db:5
low kick db:6
open hi-hat db:7
muted triangle db:8
triangle db:9
ride db:10
snare db:11
snare 2 db:12

qguv avatar Mar 16 '25 12:03 qguv

Hi @qguv, we are all users here, and contributors to the software are volunteers. Please be aware that you are not making a feature request as part of a transactional arrangement. We are all working together.

Thanks for sharing these detailed instructions for the workaround.

Happy to reopen the issue, it was originally closed by the person who opened it, who was happy at the time with the workaround. With no-one volunteering to work on it, that's how it was left.

Since then, strudel has appeared, and gained a bank feature. It would be great for a bank feature to mirror strudel's, so that sound "bd sd" # bank "tr909" would translate to "tr909_bd tr909_sd" (with _ delimiter). I think this would be best done as a simple change to SuperDirt. Tidal would just need the extra bank control added if it isn't there already. We could then make quick use of the sample banks arranged this way for strudel.

Dirt-Samples is indeed unfortunately a mess of samples from a wide range of sources of mostly unknown provenance / license. They are well overdue for replacing.

yaxu avatar Mar 26 '25 13:03 yaxu

So does bank refer to all samples or just a subset? I would say that the best way to specify banks would be by dictionaries that inherit from others. So you can make sub-banks, where a part of the samples is replaces. This way, you don't have to support all samples from the basic sound library.

What do you think? How is it done in strudel?

telephon avatar Mar 27 '25 09:03 telephon

So does bank refer to all samples or just a subset? I would say that the best way to specify banks would be by dictionaries that inherit from others. So you can make sub-banks, where a part of the samples is replaces. This way, you don't have to support all samples from the basic sound library.

What do you think?

Hmm I don't quite understand 'dictionaries that inherit from others' - which others?

How is it done in strudel?

A message with a 'bank' value is prepended to the sampleset name, separated by _. So s "bd sd" # bank "tr909" would be the same as s "tr909_bd tr909_sd".

Maybe it would be better if the bank was separate metadata but it works quite well.

A small related detail is that there is a metadata json file that has a mapping from names to locations: https://github.com/felixroos/dough-samples/blob/main/tidal-drum-machines.json

So if you load samples from such a file, the standard can be implemented in the mapping rather than the organisation of the files themselves.

yaxu avatar Mar 27 '25 15:03 yaxu

Hmm I don't quite understand 'dictionaries that inherit from others' - which others?

well when you write s "bd sd" # bank "tr909" and your sample bank has a sample called "tr909_bd" but no "tr909_sd", what do you do? Do you have to copy the files from your default sample bank into the other folder of tr909? This wouldn't be good, right?

telephon avatar Mar 27 '25 18:03 telephon

Hmm I don't quite understand 'dictionaries that inherit from others' - which others?

well when you write s "bd sd" # bank "tr909" and your sample bank has a sample called "tr909_bd" but no "tr909_sd", what do you do? Do you have to copy the files from your default sample bank into the other folder of tr909? This wouldn't be good, right?

Ah I see what you mean. Yes currently in strudel there's no fallback, you generally choose one bank per pattern and silence for missing samples. It's true being able to give a list of banks would be a nice feature, like s "bd sd rd" # bank "mykicks:tr909:gretsch".

yaxu avatar Mar 27 '25 19:03 yaxu

Ok, a list would be a simple way to go, true. s "bd sd rd" # bank "tr909::" could then mean that if not found, you'd fall back on the default bank.

telephon avatar Mar 30 '25 10:03 telephon

Since then, strudel has appeared, and gained a bank feature. It would be great for a bank feature to mirror strudel's, so that sound "bd sd" # bank "tr909" would translate to "tr909_bd tr909_sd" (with _ delimiter). I think this would be best done as a simple change to SuperDirt.

This is already working. It's been implemented for a longer time.

Here is a helper function for loading "into" a bank:

~loadBank = { |bank, paths| ~dirt.soundLibrary.loadSoundFiles(paths, namingFunction: { |path| bank ++ path.basename }) };
~loadBank.("foufou");

Then you can run:

s "bd sd rd" # bank "foufou"

telephon avatar Mar 30 '25 20:03 telephon

Actually, there is just one naming difference. This we have to resolve: if strudel uses an underscore, we can add that to superdirt as well ...

telephon avatar Mar 30 '25 20:03 telephon