Melodic Shorthand Notation - Quefumas/gensound GitHub Wiki
Experimental feature in Gensound 0.3. If you plan to make heavy use of it, make sure to keep up with changes in the future, as it's possible that the syntax will change. Also, it is possible that similar features with alternative or configurable behaviours be added in the future.
This feature allows the user to type an entire melody as input for an oscillator signal (Sine, Triangle, Square, Sawtooth
).
This is much easier than manually creating a new signal for each combination of pitch + duration,
and with some musical training this becomes an extremely efficient way to input melodies.
A pitch can be specified as a string:
Sine("A", 1e3) # plays middle A (A4, 440 Hz) for 1 second
Sine("Eb3", 1e3) # now E flat in 3rd octave
Sine("C#-25", 1e3) # C sharp minus 25 cents (1/100th of a semitone)
The syntax for a single pitch is:
[Pitch name][sharps/flats][octave (default 4)][+- cents]
All but the pitch name is optional.
Alternatively, you may also specify pitches simply by typing in the frequency as a string:
Sine("220.0", 1e3) # A3
By itself this is no different than passing an int or float directly, however, this enables using frequencies rather than pitch names (or any combination thereof) when using the features shown below, allowing for more flexibility for inputting melodies.
To notate a melody, separate the individual pitches with whitespace:
Triangle("C3 D E G F# E B D C# A B G# F#", duration=0.5e3) # all notes with same duration
When the octave is not specified, Gensound automatically picks the octave closest to the previous note. This is why the first B in the melody above is in the 2nd octave; that's where it's closest to the E that preceded it.
Note that Gensound makes this choice according to the step names, not acoustical frequency. That is, if the B were a B-flat and the E an E-sharp, Gensound would still pick the same octave since that's the closest B as notated on a staff.
To force Gensound to pick a higher octave than the default, use '
; for lower, use ,
:
Triangle("C3 G' C, G' C, G'", duration=0.5e3)
Since the interval C-G is a fifth, Gensound would infer G2 for the second note; the apostrophe indicates we wish to jump to the higher G (G3) instead. Then, when going back down to C, we need to add a comma, since the closest C is the one above.
Use r
to indicate a rest:
Triangle("D3 A' D r D, A' D r", duration=0.5e3) # the rests receive the same duration as the notes
To control the duration of each note/rest, append =[num. beats]
to the note, with num. beats
being in units of the duration
argument given.
When the number of beats is not explicitly indicated, Gensound uses the same value of the previous note.
Triangle("r C5=0.5 B C=1 G Ab C=0.5 B C=1 D G, C=0.5 B C=1 D F,=0.5 G Ab=2 G=0.5 F Eb=2", duration=0.5e3)
Here is a setting of a 4-part chorale, along with pauses for fermatas. Chord 3rds have been altered to somewhat resemble just intonation. Due to the rhythmic pattern of alternating half and quarter notes, almost every note required explicit indication of duration.
from gensound import Sine, Triangle, Square, Pan
sig = Triangle # Sine? Square?
beat = 0.5e3 # 120 bpm
fermata = 0.1 # make fermatas in the melody slightly longer
pause = 0.6 # and breathe for a moment before starting the next phrase
S = sig(f"r D5 D=2 C#=1 B-13=2 A=1 D E=2 F#-13={2+fermata} r={pause} F#=1 F#=2 F#=1 E=2 F#-13=1 G F#-13=2 E={2+fermata} r={pause} "
f"D+16=1 E=2 F#-13=1 E=2 D+16=1 B-13 C#=2 D+9={2+fermata} r={pause} A'=1 F#-13=2 D+16=1 E=2 G=1 F#-13 E=2 D=3", beat)
A = sig(f"r A4 B=2 A+16=1 G=2 F#-13=1 F# B-13 A A={2+fermata} r={pause} C#=1 B=2 B=1 B A A A D A A={2+fermata} r={pause} "
f"B=1 A=2 A=1 B-13 A=0.5 G F#=1 B-13 B A#-13 B={2+fermata} r={pause} A=1 A=2 B=1 A=2 A=1 A B-13 A F#-13=3", beat)
T = sig(f"r F#4-13 F#=2 F#=1 D=2 D=1 D D C#-13 D={2+fermata} r={pause} C#=1 D+16=2 D+16=1 D C#-13 D E A, D C#-13={2+fermata} r={pause} "
f"F#=1 E=2 D=1 D C#-13 D+16 D G+5 F# F#={2+fermata} r={pause} E=1 F#-13=2 F#=1 E=2 C#-13=1 A B C#-13 D=3", beat)
B = sig(f"r D3 B-16 D F# G B-13 D B-16 G A D,={2+fermata} r={pause} A#'-13=1 B=2 A=1 G#-13 A F#-13 C#-13 D F#-13 A={2+fermata} r={pause} "
f"B=1 C#-13=2 D=1 G, A B G E F# B,={2+fermata} r={pause} C#'-13=1 D C# B C#-13 B A D G, A D,=3", beat)
chorale = S*Pan(25) + B*Pan(-25) + T*Pan(80) + A*Pan(-80) # position the voices in the stereo field
chorale.play() # can you spot the parallel octaves?
Except for typing notes, the melodic syntax also accepts additional tokens, which are also delineated by whitespace:
-
|
- barline. This has no effect, but it does not throw an error, and is useful to visually organise longer melodies. -
@mute
- converts all following notes into rests of equivalent duration, until an@unmute
token appears. -
@unmute
- reverts to the usual state of interpreting pitches as notes. -
@cents_off
- ignores the 'cent' part of all following note tokens. This is useful for easy comparison of alternative tunings (for example, it can be used to easily convert the chorale example above to equal temperament). -
@cents_on
- cancels@cents_off
, going back to normal. -
@transpose:<float>
- transposes all following notes by the given amount, in semitones. -
@beat_pattern:<float>,...,<float>
- this token creates a rhythmic loop and accepts a variable-length list of floating-point numbers which cannot contain spaces. From this point onwards, all notes will receive duration values from this list via rotation. This token loses effect once a note token specifies a duration.
TODO repeat feature using
.
(why do we need it actually? due to phase inference the two signals will merge. or maybe you can use it to save specifying durations?)
TODO maybe use
-
for rests?