Basics

The following lists the terminology used in the SoundHelix documentation, the SoundHelix API and the SoundHelix sourcecode.

Note that this documentation is mainly intended for end-users. Developers should additionally look at the Javadocs available for SoundHelix.

Beat

A beat is a beat is a beat. Look here for a better definition.

Bar

An integer number of beats forms a bar. Song lengths are specified as numbers of bars, but other than that, bars are currently not used by SoundHelix.

Tick

The basic time unit in SoundHelix is a tick. A tick divides a beat into an integer number of parts. Songs usually use 4 ticks per beat, but other numbers (3, 8, 12, 16 or 24 ticks per beat) are also common. For the MidiPlayer the number of ticks per beat must be a divider of 24 (but only if MIDI time synchronization is used, so that an integer number of MIDI timing ticks is sent per tick, because MIDI timing ticks need to be sent 24 times per beat), so 16 would not be a valid number in this case, but the others would. If you want to be able to use triplets, make sure that the number of ticks per beat is a multiple of 3 (e.g., 12 allows you to use triplets and quarter notes within the same song).

Pitch

In SoundHelix a pitch represents a relative note frequency, counted in halftones and provided as a signed integer. Pitches are normalized to "c". Therefore, pitch 0 equals "c", pitch 1 equals "c#", pitch 2 equals "d", pitch -1 equals "B", etc. SoundHelix does not support fractions of halftones for pitches. Many aspects in SoundHelix implicitly assume that a heptatonic western scale with 12 halftones per octave is used, meaning for instance that increasing a pitch by 12 doubles the note's frequency.

Chord

SoundHelix supports arbitrary 3-pitch chords (triads), provided that the 3 pitches are pairwise different and that their lowest and highest pitch are not farther apart than 11 halftones. Standard chord types (major, minor, diminished and augmented) with an inversion indicator can be specified using an easy syntax (e.g., "C" for C major without inversion, "Em4" for E minor with first inversion, "Am6" for A minor with second inversion, "Gdim4" for G diminished in first inversion or "Eaug" for E augmented without inversion). For arbitrary chords, you can list the 3 pitches directly, separated by colons, to specify the chord (e.g., "-7:-3:4" for the non-standard chord F, A, e).

Starting with SoundHelix 0.5, "fake" seventh chords that consist of 3 instead of 4 pitches are supported using an easy syntax (e.g., "C7" for C major seventh, "Am7" for A minor seventh). From the usual 4 pitches (e.g., C, E, G, A#), the third one is omitted. This still pretty much sounds like a seventh chord; after all, it contains the root pitch, the third that distinguishes major and minor and the seventh, whose existence distinguishes normal major/minor chords from seventh major/minor chords. Inversions of these chords are also available (e.g., "C76", "C74", "Am76", "Am74").

Starting with SoundHelix 0.9, additional chords are supported: sus2 and sus4.

Chords are used in many places throughout SoundHelix, especially in HarmonyEngines and SequenceEngines.

Before version 0.2, SoundHelix only supported major and minor chords, each in one of 3 flavors (without inversion, with first inversion or with second inversion), but the inversion for each chord could not explicitly be chosen in the PatternHarmonyEngine of that version.

The following table lists the supported chords:

Type Mnemonic Pitches relative to
root note
Notes with
root note C
Chord examples
Major - 0, 4, 7 C E G C, C4, C6
Major 7th 7 0, 4, 10 C E A# C7, C74, C76
Minor m 0, 3, 7 C D# G Cm, Cm4, Cm6
Minor 7th m7 0, 3, 10 C D# A# Cm7, Cm74, Cm76
Augmented aug 0, 4, 8 C E G# Caug, Caug4, Caug6
Diminished dim 0, 3, 6 C D# F# Cdim, Cdim4, Cdim6
Sustained 2 sus2 0, 2, 7 C D G Csus2, Csus24, Csus26
Sustained 4 sus4 0, 5, 7 C F G Csus4, Csus44, Csus46
Generic - pitch1, pitch2, pitch3 - 0:3:7, 0:4:7, -5:-3:4

Chord offset

A chord offset is a signed integer that selects a specific pitch from a chord. Offset 0 references the low pitch, offset 1 the middle and offset 2 the high pitch, offset 3 the low pitch transposed up by one octave, -1 the high pitch transposed down by one octave, etc. Note that the low pitch of a chord does not have to be the root pitch; this is only the case for normalized chords. For chords with inversion (which are by definition not normalized), the low pitch is never the root pitch.

In many components in SoundHelix (especially patterns), pitches are not used directly, but are provided as chord offsets, which are then converted to pitches by using the respective current chord when the song is generated.

Chord rotation

A chord can be rotated up or down, which cycles the chord through the 3 possible chord inversion types. Rotating a chord up means transposing the low pitch of a chord up by one octave, rotating a chord down means transposing the high pitch down by one octave. Rotating the chord "Am" up and down results in the following pattern (..., Am6 <-> Am <-> Am4 <-> Am6 <-> Am, ...), where a step to the left means "rotate down", a step to the right means "rotate up". Note that rotating a chord up 3 times effectively transposes the chord up by one octave, rotating 3 times down transposes the chord down by one octave.

Chord rotation is possible for all chords, i.e., even for chords that have no standard notation.

Chord normalization

Normalizing a chord means rotating the chord up or down or leaving it unchanged, so that the chord's low pitch equals the chord's root pitch. For example, this converts both an "Am4" and an "Am6" chord to its normalized counterpart "Am", whereas "Am" stays "Am", because it is already normalized. The rule is simple: "6" chords are rotated up once to normalize them, "4" chords are rotated down once.

Normalization is only possible for chords that have a uniquely identifiable root pitch, which is currently possible for all major, minor, major seventh, minor seventh and diminished chords. It is not possible to determine a unique root pitch for augmented chords (e.g., the chords "Caug", "Eaug6" and "G#aug4" are not distinguishable if you look at their pitches, because augmented chords are the only chords that have a completely symmetric pitch distribution of 0-4-8, which is independent of the inversion used). For all other chords no root pitch is defined, and so they also cannot be normalized, which means that the chords always remain unchanged if they are normalized.

Chords with more than three pitches

SoundHelix could support chords with more than three pitches in the future (e.g., real rather than fake sevenths chords) , but this currently would be incompatible with the usage of chord offsets. It would be unclear, for example, how to interpret chord offsets if 3-pitch and 4-pitch chords were used within the same song (3 would mean the low pitch transposed one octave up for 3-pitch chords, but would mean the pitch of the 4th pitch for 4-pitch chords).

Harmony

A harmony is a sequence of chords that is used as a repeating template throughout the song. Each chord in the template has a length that is usually counted in beats. A chord template usually forms a chord section. Within a chord section the active instruments generally don't change. However, it is also possible to subdivide a chord template into more than one chord section. For example, you might have a chord section for the song's stanzas and another one for the chord's refrain (chorus). The chord sections don't need to have the same length.

Harmonies are created using HarmonyEngines.

Velocity

All notes have an associated velocity, which (like in the MIDI specification) determines the speed or strength of pushing a keyboard key down, which can be used to influence the sound of the note. For example, hitting a piano key harder makes a louder sound, but also changes the specifics of the sound itself. Usually the velocity is used to control the volume of a note, but it is up to the player how to interpret the velocity (e.g. a sampler might use different sets of samples for different velocity ranges). In SoundHelix, velocities are specified in the range of 0 (off) to a configurable maximum (full velocity). Using a velocity of 0 is the same as using a pause. Configuring the maximum velocity of a song is possible since version 0.7, allowing to choose any convenient maximum velocity value. Prior to version 0.7, the maximum velocity was always the not so convenient value of 32767. If you want to use each value of the full MIDI velocity range from 0 to 127, the maximum song velocity should at least be 127, otherwise there will be some granularity in the possible MIDI velocity values.

Velocity exponent

Some components which deal with velocity generation support the usage of a velocity exponent. For example, the CrescendoPatternEngine generates a pattern with increasing (or decreasing) velocity. You can define a decimal exponent that specifies if the increase/decrease is linear, quadratic, cubic, etc. For example, instead of using 32767 * x (with x between 0 and 1) to interpolate linearly between minimum and maximum velocity, SoundHelix will use 32767 * xa, where a is the velocity exponent. An exponent of 2 starts off slower than linear and ends faster than linear, 3 is even slower in the beginning and even faster in the end, etc. An exponent of 0.5 (resembling a square root) will start off faster than linear and ends slower than linear, 0.3333 (resembling a cube root) is even faster in the beginning and slower in the end. Negative exponents are handled a bit differently: Instead of using "xa", SoundHelix will use "1 - x-a", which converts a velocity increase into a velocity decrease of the same speed.

As diagrams say more than a thousands words, here are 4 diagrams that show different velocity exponent examples (the x axis shows time, the y axis shows the output velocity, with 0 meaning minimum velocity and 1 meaning maximum velocity). Notice the symmetry between the diagrams:

Exponents 1, 1/2, 1/5 and 1/8 (from bottom to top) Exponents -1, -2, -5 and -8 (from left to right)
   
Exponents -1, -1/2, -1/5 and -1/8 (from top to bottom) Exponents 1, 2, 5 and 8 (from left to right)

Sequence

A sequence is an ordered list of notes and pauses for one voice. A note has a pitch, a length in ticks, a velocity between 0 and the maximum velocity as well as a legato flag. A pause just has a length in ticks. If the legato flag is set, the player will make sure that the note-on for the immediately following note will be sent before the note-off for the current note, unless the following note has the same pitch as the current note (in this case the legato flag is ignored). This makes it possible to change the pitch of a note without restarting the ADSR (attack, decay, sustain, release) cycle. Together with portamento (if available), this makes nice slides possible. Note however, that legato must be supported by the playback device if it is to be used. Also note that the legato flag is ignored for a note if that note is followed by a pause, because then the legato flag would have no effect. All generated sequences span the whole song.

Sequences are created by SequenceEngines. Most SequenceEngines can create more than one sequence, so in general, they create tracks.

Pattern

Like sequences, patterns are sequences of notes and pauses (for one voice or several voices) and may serve, for example, as inputs for SequenceEngines. In contrast to sequences, patterns are often used repetitively to create a sequence, however, certain SequenceEngines (e.g., the conditional patterns in the DrumSequenceEngine) use a given pattern only once. While applying a pattern to create a sequence, often the pattern's notes are transposed according to the current chord. Therefore, patterns usually don't use pitches directly, but chord offsets, which are then converted to pitches when a sequence is created, based on the current chord. Additionally (and unlike sequences), patterns may contain wildcard characters, which may have a special meaning that is interpreted by the SequenceEngine. For example, the PatternSequenceEngine supports a wildcard character for choosing a pitch that can be used to make a transition from one chord to the next.

Patterns can be created programmatically, but can also be entered manually, for example in the StringPatternEngine. Here, patterns are comma-separated strings of either an offset (syntax: "offset", e.g., "5"), which is a note of 1 tick using the maximum velocity, or a combination of offset and length (syntax: "offset/length", e.g., "5/2"), which is a note of the given number of ticks using the maximum velocity, or a combination of offset, length and velocity (syntax: "offset/length:velocity", e.g., "5/2:22000"), which is a note of the given number of ticks and the given velocity. For all 3 constructs, an optional "~" character can be used after the offset (syntax: "offset~/length:velocity", e.g., "5~/2:22000"), which enables legato for the note: for the following note the NOTE_ON will be sent before the NOTE_OFF of the previous note, which can be used for slides, etc., if supported by the playing device. For the first two constructs, instead of an offset the character "-" can be used, which equals a pause (syntax: "-" and "-/length").

Some SequenceEngines allow to specify a pattern restart mode for its patterns. Patterns are usually repeated (i.e. restarted) after they have been used in full. In addition, patterns may be restarted for each new chord or chord section.

Patterns are created by PatternEngines.

Pattern expansion

Pattern entries can be multiplied by using the syntax "(patternfragment)*count", which will be expanded to the pattern fragment concatenated count times, with a "," in between. These constructs can be nested. For example, the string "((0/2)*3,1/2)*2" will be expanded to "0/2,0/2,0/2,1/2,0/2,0/2,0/2,1/2" before parsing this as a pattern string. Such pattern expansion is available since version 0.5.

In addition, patterns can be transposed using the syntax "(patternfragment)+delta" and "(patternfragment)-delta". For example, the pattern "(1/2,4/2)+2" is transformed to "3/2,6/2". Such pattern transformation is available since version 0.9.

Pattern functions

Pattern entries can contain functions, which are replaced by the function evaluation result. Pattern functions are available since version 0.7.

The supported functions are shown below:

Function Parameters Example Description
E pulses, steps, on-fragment, off-fragment E(5,16,0/2,-/2) Expands to the Euclidean rhythm (based on the Bjorklund algorithm due to E. Bjørklund) generated by the given number of pulses spread across the given number of steps. Each "on" in this pattern is replaced by the on-fragment, each "off" is replaced by the off-fragment.

Pattern scaling

Starting with SoundHelix 0.8, for every pattern you can optionally use a separate ticks-per-beat value. At runtime, the pattern is scaled length-wise (meaning the lengths of all notes and pauses are scaled) to match the ticks-per-beat value of a song. For example, let's assume your song's ticks-per-beat value is 12 and you have a melody that consists only of quarter, half and full notes. You can then provide the melody pattern by configuring it to have a ticks-per-beat value of 4. In the pattern, a tick value of 1 then would be a quarter note, 2 would be a half note and 4 would be a full note (rather than 3, 6 and 12, respectively). At runtime, the pattern's note/pause lengths will then be scaled by a factor of 3 automatically. This approach also eases the migration of patterns if you want to change your song's ticks-per-beat value later (e.g. from 4 to 12).

Track

One or more sequences form a track. A whole track is assigned to an instrument. A track can be of melodic or of rhythmic type. Melodic tracks are subject to note transposition by the player whereas rhythmic tracks are not subject to transposition. Rhythmic tracks can be used in cases where the pitch defines the sound to play rather than the frequency to use, e.g., for drum machines or for the special drum channel (channel number 10) in the General MIDI specification.

Currently, all SequenceEngines create melodic tracks, except for the DrumSequenceEngine, which creates rhythmic tracks.

Tracks are generated by SequenceEngines.

Arrangement

An arrangement is a set of tracks, which represents the entire song. Each track in an arrangement is assigned a unique name, which can be referenced by a Player.

Arrangements are created by ArrangementEngines and are played back by Players.

ActivityVector

An ActivityVector is a vector of bits that defines for each tick of the song whether an instrument should be active or not, and therefore spans the whole song. SequenceEngines take one or more ActivityVectors as input and together with the provided patterns or other logic produce one or more sequences that are active or inactive according to the ActivityVectors.

A SequenceEngine need not strictly adhere to the ActivityVectors it receives. For example, the DrumSequenceEngine supports conditional patterns, which allow creating notes whenever ActivityVector activity changes. This allows for example to create a snare crescendo before the base drum starts. This creation of notes is allowed even if the ActivityVector is inactive at that time.

Activity segment

A consecutive subsection of active bits (surrounded by an inactive bit on the left and the right or the song start or end, respectively) of an ActivityVector is called an activity segment (or simply segment). An ActivityVector can have any number of activity segments (even 0, if the ActivityVector is never active).

Pause segment

A consecutive subsection of inactive bits (surrounded by an active bit on the left and the right) of an ActivityVector is called a pause segment. Note that by using this definition an initial or final subsection of inactive bits is not considered to be a pause segment, as these cannot be surrounded by an active bit on the left and the right. An ActivityVector can have any number of pause segments (even 0, if the ActivityVector is never active or if it only has one activity segment).

Player

A player is a component that is used to play an arrangement in realtime. Certain aspects of the player (for example, the number of beats per minute) can be remote-controlled at runtime.

LFOs

An LFO (low frequency oscillator) is a slowly changing periodic source of values, usually based on a mathematical function (e.g. sine or triangle wave). For SoundHelix, LFOs generate a stream of values between 0 and 1, whose speed and phase can be synchronized to various sources. Such values can be scaled by some factor and cropped to a certain range, before they can be assigned to e.g. a MIDI controller. By using LFOs, you can remote-control any MIDI controller, e.g. the volume or filter values of a MIDI channel.

The time granularity of an LFO is always a tick. This means that an LFO value cannot be changed quicker than once per tick. This is usually fine, if not, you can increase the number of ticks per beat in your song to have a finer granularity.

Non-conditional LFOs

The MidiPlayer allows the definition of non-conditional LFOs, whose values are mapped directly to a MIDI controller. Such LFOs constantly generate values based on a mathematical function throughout the song.

Conditional LFOs

In addition to any number of sequences, a track can also hold any number of LFOs. Therefore, SequenceEngines can generate LFOs as well. Currently, such LFOs can be created as part of the conditional LFO feature introduced with version 0.8 in the DrumSequenceEngine. Such track-based LFOs can be mapped to a MIDI controller in the MidiPlayer. The nice thing about this is that you can trigger the activity of an LFO via some condition (e.g. that the base drum was inactive and will become active). During the song, such an LFO has a default value, unless the condition is met, then it generates values according to the underlying mathematical function during a limited time range, falling back to the default again afterwards.

Component

In various locations in SoundHelix XML files, a Java class can be chosen for providing a certain functionality. At runtime, an instance of the class will be created and will be configured using the configuration provided by the XML block in the XML file where the class is referenced. Such a configured class instance is called a component. Components can be nested. For example, a SequenceEngine component might include a PatternEngine component. In this case, the SequenceEngine is the parent component and the PatternEngine is the child component. A component can have any number of children, but it can only have one parent. Cross-referencing components in the XML file is not supported (for example, it is not possible to create one PatternEngine and use it within two SequenceEngines).

Add new comment