Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ttm/music
Browse files Browse the repository at this point in the history
  • Loading branch information
ttm committed Jun 14, 2024
2 parents 43684c8 + 2ec62cc commit 188b367
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 85 deletions.
38 changes: 7 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ The precision of Music makes it the perfect choice for many scientific uses. At

* **Sample-based synth**, meaning that the state is updated at each sample. For example, when we have a note with a vibrato, each sample is associated to a different frequency. By doing this the synthesized sound is the closest it can be to the mathematical model that describes it.
* **Musical structures** with emphasis in symmetry and discourse.
* **Speech and singing interfaces** available to synthesize voices.

Music can be used alone or with other packages, and it's ideal for audiovisualization of data. For example, it can be used with [Percolation](https://github.com/ttm/percolation) and [Participation](https://github.com/ttm/participation) for harnessing open linked social data, or with [audiovisual analytics vocabulary and ontology (AAVO)](https://github.com/ttm/aavo).

Expand All @@ -38,36 +37,17 @@ This install method is especially useful when reloading the modified module in s

Every dependency is installed by default by `pip`, but you can take a look at [requirements.txt](https://github.com/ttm/music/blob/master/requirements.txt).

To use the singing interface you'll also nead eSpeak, SoX, and abcMIDI, while MIDI.pm and FFT.pm are needed by eCantorix to synthesize singing sequences.

#### Linux

On Ubuntu everything can be installed with:

```console
sudo apt install espeak sox abcmidi
sudo cpan install MIDI
sudo cpan install Math::FFT
```

<!-- TODO: add instructions for other distros -->

#### macOS

On macOS you can first install [Homebrew](https://brew.sh/), and then:

```console
brew install espeak sox abcmidi perl
cpan install MIDI
cpan install Math::FFT
```

<!-- TODO: add instructions for Windows -->

## Examples

Inside [the examples folder](https://github.com/ttm/music/tree/master/examples) you can find some scripts that use the main features of Music.

* [chromatic_scale](https://github.com/ttm/music/tree/master/examples/chromatic_scale.py): writes twelve notes into a WAV file from a sequence of frequencies.
* [penta_effects](https://github.com/ttm/music/tree/master/examples/chromatic_scale.py): writes a pentatonic scale repeated once clean, once with pitch, one with vibrato, one with Doppler, and one with FM, into a WAV stereo file.
* [noisy](https://github.com/ttm/music/tree/master/examples/noisy.py): writes into a WAV file a sequence of different noises.
* [thirty_notes](https://github.com/ttm/music/tree/master/examples/thirty_notes.py) and [thirty_numpy_notes](https://github.com/ttm/music/tree/master/examples/thirty_numpy_notes.py) generate a sequence of sounds by using a synth class (in this case the class [`Being`](https://github.com/ttm/music/tree/master/music/legacy/classes.py)).
* [campanology](https://github.com/ttm/music/tree/master/examples/campanology.py) and [geometric_music](https://github.com/ttm/music/tree/master/examples/geometric_music.py) both use `Being` as their synth, but this time with permutations.
* [isynth](https://github.com/ttm/music/tree/master/examples/isynth.py) also uses a synth class, but of a different kind, [`IteratorSynth`](https://github.com/ttm/music/tree/master/music/legacy/classes.py), that iterates through arbitrary lists of variables.

## Package structure

The modules are:
Expand All @@ -78,10 +58,6 @@ The modules are:
* **io** for reading and writing WAV files, both mono and stereo.
* **functions** for normalization.
* **structures** for higher level musical structures such as permutations (and related to algebraic groups and change ringing peals), scales, chords, counterpoint, tunings, etc.
* **singing** for singing with eCantorix. While it's not properly documented, and it might need some tweaks, it's working. Speech is currently achieved through espeak in the most obvious way, using os.system as in:
* [Penalva](https://github.com/ttm/penalva/blob/master/penalva.py)
* [Lunhani](https://github.com/ttm/lunhani/blob/master/lunhani.py)
* [Soares](https://github.com/ttm/soares/blob/master/soares.py)
* **legacy** for musical pieces that are rendered with the Music package and might be used as material to make more music.
* **tables** for the generation of lookup tables for some basic waveform.
* **utils** for various functions regarding conversions, mix, etc.
Expand Down
30 changes: 15 additions & 15 deletions examples/campanology.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import music

##############
# Notice that you might relate a peal or any set of permutations
# to a sonic characteristic (frequency, duration, vibrato depth, vibrato frequency,
# attack duration, etc) through at least 3 methods:
# 1) initiate a Being(), set its permutations to the permutation sequence,
# its domain to the values to be permuted, and its curseq to
# the name of the Being sequence to be yielded by the permutation of the domain.
#
# 2) Achieve the sequence of values through peal.act() or just using permutation(domain)
# for all the permutations at hand.
# Then render the notes directly (e.g. using M.core.V_) or passing the sequence of values
# to a synth, such as Being()
"""
Notice that you might relate a peal or any set of permutations to a sonic
characteristic (frequency, duration, vibrato depth, vibrato frequency, attack
duration, etc.) through at least 3 methods:
1) initiate a Being(), set its permutations to the permutation sequence, its
domain to the values to be permuted, and its curseq to the name of theBeing
sequence to be yielded by the permutation of the domain.
2) Achieve the sequence of values through peal.act() or just using permutation
(domain) for all the permutations at hand. Then render the note directly
(e.g. using M.core.V_) or passing the sequence of values to a synth, such
as Being().
"""

pe3 = music.structures.peals.PlainChanges.PlainChanges(3)
pe3 = music.structures.peals.PlainChanges(3)
music.structures.symmetry.print_peal(pe3.act(), [0])
freqs = sum(pe3.act([220,440,330]), [])
freqs = sum(pe3.act([220, 440, 330]), [])

nnotes = len(freqs)

being = music.legacy.Being()
being.f_ = freqs
being.render(nnotes, 'campanology_1.wav')

### OR
# OR
being = music.legacy.Being()
being.domain = [220, 440, 330]
being.perms = pe3.peal_direct
Expand Down
30 changes: 30 additions & 0 deletions examples/chromatic_scale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
""" Simple script that writes a chromatic scale on a WAV file. """

import music

scale = [
261.63, # C4
277.18, # C#4
293.66, # D4
311.13, # D#4
329.63, # E4
349.23, # F4
369.99, # F#4
392.00, # G4
415.30, # G#4
440.00, # A4
466.16, # A#4
493.88 # B4
]

sonic_vector = []

for note in scale:
sound = music.core.synths.note(freq=note,
duration=0.4)
sonic_vector.append(sound)

stack = music.utils.horizontal_stack(*sonic_vector)

music.core.io.write_wav_mono(sonic_vector=stack,
filename='chromatic_scale.wav')
25 changes: 12 additions & 13 deletions examples/geometric_music.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,28 @@

# 2) set its parameters using sequences to be iterated through
being.d_ = [1/2, 1/4, 1/4] # durations in seconds
being.fv_ = [0, 1,5,15,150,1500,15000] # vibrato frequency
being.fv_ = [0, 1, 5, 15, 150, 1500, 15000] # vibrato frequency
being.nu_ = [5] # vibrato depth in semitones (maximum deviation of pitch)
being.f_ = [220, 330] # frequencies for the notes

s1 = being.render(30)
being.f_ += [440]
being.fv_ = [1,2,3,4,5]
being.fv_ = [1, 2, 3, 4, 5]
s2 = being.render(30)
s3 = music.utils.horizontal_stack(s1, s2, s1 + s2, (s1, s2),
s1*s2[::-1],
s1[::7] + s2[::7])
s3 = music.utils.horizontal_stack(s1, s2, s1 + s2, (s1, s2), s1*s2[::-1],
s1[::7] + s2[::7])

# X) Tweak with special sets of permutations derived from change ringing (campanology)
# or from finite group theory (algebra):
# X) Tweak with special sets of permutations derived from change ringing
# (campanology) or from finite group theory (algebra):
nel = 4
pe4 = music.structures.peals.PlainChanges.PlainChanges(nel)
pe4 = music.structures.PlainChanges(nel)
being.perms = pe4.peal_direct
being.domain = [220*2**(i/12) for i in (0,3,6,9)]
being.domain = [220 * 2 ** (i / 12) for i in (0, 3, 6, 9)]
being.curseq = 'f_'
being.f_ = []
nnotes = len(being.perms)*nel # len(being.perms) == factorial(nel)
being.stay(nnotes)
being.nu_= [0]
being.nu_ = [0]
being.d_ += [1/2]
s4 = being.render(nnotes)

Expand All @@ -39,9 +38,9 @@
b2.f_ = []
nnotes = len(being.perms)*nel # len(being.perms) == factorial(nel)
b2.stay(nnotes)
b2.nu_= [2,5,10,30,37]
b2.fv_ = [1,3,6,15,100,1000,10000]
b2.d_ = [1,1/6,1/6,1/6]
b2.nu_ = [2, 5, 10, 30, 37]
b2.fv_ = [1, 3, 6, 15, 100, 1000, 10000]
b2.d_ = [1, 1/6, 1/6, 1/6]
s42 = b2.render(nnotes)

i4 = music.structures.permutations.InterestingPermutations(4)
Expand Down
11 changes: 6 additions & 5 deletions examples/isynth.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import music

tables = music.legacy.tables.Basic()
pe3 = music.structures.peals.PlainChanges.PlainChanges(3)
tables = music.tables.PrimaryTables()
pe3 = music.structures.PlainChanges(3)
music.structures.symmetry.print_peal(pe3.act(), [0])
freqs = sum(pe3.act([220,440,330]), [])
freqs = sum(pe3.act([220, 440, 330]), [])

isynth = music.legacy.IteratorSynth.IteratorSynth()
isynth = music.legacy.IteratorSynth()
isynth.fundamental_frequency_sequence = freqs
isynth.tab_sequence = [tables.sine, tables.triangle, tables.square, tables.saw]

pcm_samples = music.utils.horizontal_stack(*[isynth.renderIterate() for i in range(len(freqs))])
pcm_samples = music.utils.horizontal_stack(*[isynth.renderIterate()
for i in range(len(freqs))])

music.core.io.write_wav_mono(pcm_samples, 'isynth.wav')
22 changes: 22 additions & 0 deletions examples/noisy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
""" Simple script that writes a pentatonic scale on a WAV file
with different effects.
"""

import music

noises = ['brown', 'pink', 'white', 'blue', 'violet', 'black']
sonic_vector = []
silence = music.core.synths.silence(duration=0.4)
beep = music.core.synths.note(duration=0.1)

for noise in noises:
sonic_vector.append(music.core.synths.noises.noise(noise_type=noise))
sonic_vector.append(silence)
sonic_vector.append(beep)
sonic_vector.append(silence)

sonic_vector.append(music.core.synths.noises.gaussian_noise())

stack = music.utils.horizontal_stack(*sonic_vector)
music.core.io.write_wav_stereo(sonic_vector=stack,
filename='noisy.wav')
52 changes: 52 additions & 0 deletions examples/penta_effects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
""" Simple script that writes a pentatonic scale on a WAV file
with different effects.
"""

import music

scale = [
261.63, # C4
293.66, # D4
329.63, # E4
392.00, # G4
440.00 # A4
]

sonic_vector = []
for note in scale:
sound = music.core.synths.note(freq=note,
duration=0.4)
sonic_vector.append(sound)

sonic_vector.append(music.core.synths.silence())

for note in scale:
sound = music.core.synths.note_with_glissando(start_freq=note,
end_freq=note+30,
duration=0.4)
sonic_vector.append(sound)

sonic_vector.append(music.core.synths.silence())

for note in scale:
sound = music.core.synths.note_with_vibrato(freq=note,
duration=0.4)
sonic_vector.append(sound)

sonic_vector.append(music.core.synths.silence())

for note in scale:
sound = music.core.synths.note_with_doppler(freq=note,
duration=0.4)
sonic_vector.append(sound)

sonic_vector.append(music.core.synths.silence())

for note in scale:
sound = music.core.synths.note_with_fm(freq=note,
duration=0.4)
sonic_vector.append(sound)

stack = music.utils.horizontal_stack(*sonic_vector)
music.core.io.write_wav_stereo(sonic_vector=stack,
filename='penta_effects.wav')
12 changes: 6 additions & 6 deletions examples/thirty_notes.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import music
from music.legacy import Being

# 1) start a ѕynth
being = music.legacy.Being()
# 1) start a synth
being = Being()

# 2) set its parameters using sequences to be iterated through
being.d_ = [1/2, 1/4, 1/4] # durations in seconds
being.fv_ = [0, 1,5,15,150,1500,15000] # vibrato frequency
being.fv_ = [0, 1, 5, 15, 150, 1500, 15000] # vibrato frequency
being.nu_ = [5] # vibrato depth in semitones (maximum deviation of pitch)
being.f_ = [220, 330] # frequencies for the notes

# 3) render the wavfile
being.render(30, 'thirty_notes.wav') # render 30 notes iterating though the lists above
# 3) render the wavfile with 30 notes iterating though the lists above
being.render(30, 'thirty_notes.wav')
26 changes: 11 additions & 15 deletions examples/thirty_numpy_notes.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import music
from music.legacy import Being
from music.utils import horizontal_stack
from music.core.io import write_wav_stereo

# 1) start a ѕynth
being = music.legacy.Being()

# 2) set its parameters using sequences to be iterated through
being.d_ = [1/2, 1/4, 1/4] # durations in seconds
being.fv_ = [0, 1,5,15,150,1500,15000] # vibrato frequency
being.nu_ = [5] # vibrato depth in semitones (maximum deviation of pitch)
being.f_ = [220, 330] # frequencies for the notes
being = Being()

# 3) Use numpy arrays directly and use them to concatenate and/or mix sounds:
s1 = being.render(30)
being.f_ += [440]
being.fv_ = [1,2,3,4,5]
being.fv_ = [1, 2, 3, 4, 5]
s2 = being.render(30)

# s1 then s2 then s1 and s2 at the same time, then at the same time but one in each LR channel,
# then s1 times s2 reversed, then s1+s2 but jumping 6 samples before using one:
s3 = music.utils.horizontal_stack(s1, s2, s1 + s2, (s1, s2),
s1*s2[::-1],
s1[::7] + s2[::7])
music.core.io.write_wav_stereo(s3, 'thirty_numpy_notes.wav')
# s1 then s2 then s1 and s2 at the same time, then at the same time but one in
# each LR channel, then s1 times s2 reversed, then s1+s2 but jumping 6 samples
# before using one:
s3 = horizontal_stack(s1, s2, s1 + s2, (s1, s2), s1*s2[::-1], s1[::7] +
s2[::7])
write_wav_stereo(s3, 'thirty_numpy_notes.wav')
1 change: 1 addition & 0 deletions music/structures/peals/plain_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, nelements=4, nhunts=None, hunts=None):
sympy.combinatorics.Permutation(i, i + 1, size=nelements)
for i in range(nelements - 1)]
self.domains = []
self.perform_peal(nelements, dict(hunts))
self.hunts = hunts
self.nelements = nelements

Expand Down

0 comments on commit 188b367

Please sign in to comment.