Random seeds

As an algorithmic random music generator, SoundHelix heavily relies on random generators and therefore random seeds.

A song can be generated either by providing a random seed or by providing a song name. If SoundHelix was not started with a song name, SoundHelix will use a randomly-chosen random seed based on the system clock (or an explicit random seed) to seed the SongNameEngine specified in the XML file, then generate a song name with it and hash the song name to get the random seed that is used for the rest of the song. Therefore, the SongNameEngine should be able to generate as many different song names as possible so that the resulting random seed space after hashing is as big as possible. If SoundHelix is started with a song name, the SongNameEngine will not be instantiated and used; instead, the given song name will be hashed to get the random seed for the rest of the song. Note that it is allowed to provide song names which cannot be generated by the configured SongNameEngine.

The random seed that is derived from the song name (no matter if generated by a SongNameEngine or if entered manually) is called the main random seed. The main random seed is the basis for seeding the main SoundHelix components (HarmonyEngine, ArrangementEngine and Player) as well as for the "structure" tag (for the latter, this is only relevant if special random XML tags are used). The main random seed is not passed to these components directly, but by deriving a random seed that is augmented by the hash of the implementation class used and the position where it is used (the main components have a fixed position each). The class name and the position is used as a "random salt" to make sure that each subcomponent is seeded with a different random seed that is consistently derived from the main random seed.

Each time a component uses a subcomponent (for example, if a SequenceEngine is used within an ArrangementEngine), the subcomponent is initialized by using the calling component's random seed, augmented by the hash of the implementation class used and the position (relative order) where it is used. This is a recursive process, because subcomponents can create subsubcomponents and so on. You can imagine the components within the XML file forming a tree where the main random seed is provided as the root, and the seed sifts down from the root to the leaves while adapting it by applying the random seed augmentation scheme at every node.

Starting with SoundHelix 0.2, you can provide an optional "seed" attribute or an optional "salt" attribute whenever a component is instantiated. The "seed" attribute must contain a 64-bit signed constant (in decimal notation), the "salt" attribute must contain a 32-bit signed constant (in decimal notation). With the former, you can directly specify the random seed to use for this component. With the latter, you can change the "salt" that is used to augment the random seed to the given number. Note that if a constant random seed is used for a component, all its descendants will use a fixed random seed based on the given random seed (unless a constant is provided as well), by using the standard random seed derivation technique. Where can salting or seeding be used? Suppose you have configured a SequenceEngine which creates a bass line and you want to use a certain bass line for all your songs generated by that XML file. Simply give the SequenceEngine constant random seeds until you have a bass line that you like, then keep this constant for the SequenceEngine. The rest of the song will then still be random, but your SequenceEngine will always generate the same output (keep in mind, however, that the SequenceEngine output also depends on the chords provided by the HarmonyEngine, this will of course influence the result; components which solely rely on the random seed, like the PatternEngines, will really always give the same result with the same random seed). The salt can be used to have the same random seed for two or more components while still making the random seed dependent on the parent random seed. For example, suppose you want two RandomPatternEngines which create almost identical results (they have the same configuration except that the list of offsets of the second one is transposed up by one octave, resulting in an unison). If they have the same parent component and you give both the same salt number (e.g. the attribute salt="12345"), they are guaranteed to produce the same result except for the transposition, while the result still depends on the parent random seed. If you want the two RandomPatternEngines to produce the same identical result across all songs, you can use a fixed seed (e.g., seed="4711") for both instead. If you use a custom salt, make sure that you use a number which would not normally be used, otherwise an additional component might use the same salt, which might be unexpected. In general, the components will by default be instantiated using a salt that equals their position in the XML file within a component (starting from 0). For custom salts, use numbers which are very likely not used by the standard scheme, i.e., use salts that start with (for example) 10000. Starting with SoundHelix 0.8, you can define a global salt using the "globalSalt" attribute. Unlike using the "salt" attribute, which uses the salt on the parent component's random seed, the "globalSalt" attribute uses the salt on the global random seed. Using the same "globalSalt" value, you can have two or more components that are located anywhere in the component hierarchy which are random-seeded identically.

Why do we use a random seed for components instead of passing the current random generator (including its state) to subcomponents? The reason is that subcomponents should not influence the parent component, i.e., the random decisions made in subcomponents should not change later random decisions of the calling component as this would make the random-based behaviour of the parent component virtually unpredictable. The inverse is not necessarily true; random decisions made in the parent component can influence the random seeds passed on to the child components, but only until the child component has been instantiated. By using individual random seeds and therefore individual random generators for the components, the random generators between the components are decoupled from each other. Note, however, that the random seeds used have a strong coupling.

SoundHelix uses random seeds that are 64 bits long. However, the standard Java Random class, which is used by SoundHelix, only uses the lower 48 bits of the 64 bit seed value. This could change in the future to use a different implementation which uses the full 64 bits of the seed. It is pretty unlikely, however, that more than 64 bits will ever be used by SoundHelix.

Add new comment