More examples and some minor fixes
This commit is contained in:
@ -8,6 +8,8 @@ Ziffers python supports following live coding and computer-aided composition env
|
|||||||
|
|
||||||
* [Sardine](https://github.com/Bubobubobubobubo/sardine)
|
* [Sardine](https://github.com/Bubobubobubobubo/sardine)
|
||||||
* [Music21](https://github.com/cuthbertLab/music21)
|
* [Music21](https://github.com/cuthbertLab/music21)
|
||||||
|
* [Csound](http://www.csounds.com/manual/html/ScoreTop.html)
|
||||||
|
* [Sonicsynth](https://github.com/Frikallo/SonicSynth)
|
||||||
|
|
||||||
# Status:
|
# Status:
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
"""Example of using ziffers with Csound score."""
|
||||||
try:
|
try:
|
||||||
import ctcsound
|
import ctcsound
|
||||||
except (ImportError,TypeError):
|
except (ImportError,TypeError):
|
||||||
@ -5,30 +6,35 @@ except (ImportError,TypeError):
|
|||||||
|
|
||||||
from ziffers import *
|
from ziffers import *
|
||||||
|
|
||||||
|
# Csound numeric score is very versatile so it is hard to do generic tranformer
|
||||||
|
# See http://www.csounds.com/manual/html/ScoreTop.html and http://www.csounds.com/manual/html/ScoreStatements.html
|
||||||
|
|
||||||
|
# There is a simple converter implemented that uses format:
|
||||||
|
# i {instrument} {start time} {duration} {amplitude} {frequency}
|
||||||
|
# See ziffers_to_csound_score in ziffers/converters.py
|
||||||
|
|
||||||
if(csound_imported):
|
if(csound_imported):
|
||||||
|
|
||||||
# Parse ziffers notation
|
# Parse ziffers notation, scale in SCALA format
|
||||||
parsed = zparse("w 0 1 q 0 1 2 3 r e 5 3 9 2 q r 0")
|
parsed = zparse("w 0 024 q 0 1 2 346 r e (5 3 9 2 -605)+(0 -3 6) q 0h24e67s9^s1^e3^5^7", key="D4", scale="100. 200. 250. 400. 560.")
|
||||||
|
|
||||||
# Convert to csound score
|
score = ziffers_to_csound_score(parsed, 180, 1500, "FooBar") # 180 bpm, 1500 amplitude, FooBar instrument
|
||||||
score = to_csound_score(parsed, 180, 1500, "Meep")
|
|
||||||
|
print("Generated score:")
|
||||||
# Outputs: i {instrument} {start time} {duration} {amplitude} {pitch}
|
|
||||||
print(score)
|
print(score)
|
||||||
|
|
||||||
|
# Define FooBar Csound instrument
|
||||||
orc = """
|
orc = """
|
||||||
instr Meep
|
instr FooBar
|
||||||
out(linen(oscili(p4,p5),0.1,p3,0.1))
|
out(linen(oscili(p4,p5),0.1,p3,0.1))
|
||||||
endin
|
endin
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Run score with Csound
|
||||||
c = ctcsound.Csound()
|
c = ctcsound.Csound()
|
||||||
c.setOption("-odac") # Using SetOption() to configure Csound
|
c.setOption("-odac")
|
||||||
|
|
||||||
c.compileOrc(orc)
|
c.compileOrc(orc)
|
||||||
|
|
||||||
c.readScore(score)
|
c.readScore(score)
|
||||||
|
|
||||||
c.start()
|
c.start()
|
||||||
c.perform()
|
c.perform()
|
||||||
c.stop()
|
c.stop()
|
||||||
|
|||||||
82
examples/jupyter/test_jupylet.ipynb
Normal file
82
examples/jupyter/test_jupylet.ipynb
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Playing note: 60\n",
|
||||||
|
"Playing note: 67\n",
|
||||||
|
"Playing note: 64\n",
|
||||||
|
"Playing note: 69\n",
|
||||||
|
"Playing note: 65\n",
|
||||||
|
"Playing note: 76\n",
|
||||||
|
"Playing note: 67\n",
|
||||||
|
"Playing note: 64\n",
|
||||||
|
"Playing note: 69\n",
|
||||||
|
"Playing note: 65\n",
|
||||||
|
"Playing note: 62\n",
|
||||||
|
"Playing note: 71\n",
|
||||||
|
"Playing note: 74\n",
|
||||||
|
"Playing note: 65\n",
|
||||||
|
"Playing note: 67\n",
|
||||||
|
"Playing note: 69\n",
|
||||||
|
"Playing note: 64\n",
|
||||||
|
"Playing note: 62\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Stream callback called with status: <sounddevice.CallbackFlags: output underflow>.\n",
|
||||||
|
"Stream callback called with status: <sounddevice.CallbackFlags: output underflow>.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"from jupylet.audio.bundle import *\n",
|
||||||
|
"from clockblocks import *\n",
|
||||||
|
"from ziffers import *\n",
|
||||||
|
"\n",
|
||||||
|
"melody = zparse(\"q 0 4 2 5 e 3 9 4 2 s 5 3 1 6 8 3 4 5 2 1\")\n",
|
||||||
|
"bpm = 180\n",
|
||||||
|
"\n",
|
||||||
|
"clock = Clock(initial_tempo=bpm)\n",
|
||||||
|
"\n",
|
||||||
|
"for n in melody.evaluated_values:\n",
|
||||||
|
" if(isinstance(n, Pitch)):\n",
|
||||||
|
" print(\"Playing note: \" + str(n.get_note()))\n",
|
||||||
|
" tb303.play(n.get_note(), n.get_beat())\n",
|
||||||
|
" clock.wait(n.get_beat())\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.11.0b4"
|
||||||
|
},
|
||||||
|
"orig_nbformat": 4
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
||||||
130
examples/jupyter/test_music21.ipynb
Normal file
130
examples/jupyter/test_music21.ipynb
Normal file
File diff suppressed because one or more lines are too long
@ -1,20 +1,26 @@
|
|||||||
from music21 import *
|
from music21 import *
|
||||||
from ziffers import *
|
from ziffers import *
|
||||||
|
|
||||||
|
|
||||||
# Create melody string
|
# Create melody string
|
||||||
melody = "q 0 2 4 r e 1 4 3 2 s 0 1 2 6 e 2 8 2 1 h 0"
|
melody = "q 0 2 4 r e 1 4 3 2 s 0 1 2 6 e 2 8 2 1 h 0"
|
||||||
#bass_line = "(q 0 2 4 6 e 1 4 3 2 s 0 1 2 6 e 2 8 2 1 h 0)-7"
|
bass_line = "(q 0 2 4 6 e 1 4 3 2 s 0 1 2 6 e 2 8 2 1 h 0)-(7)"
|
||||||
|
|
||||||
# Parse ziffers notation to melody from string
|
# Parse ziffers notation to melody from string
|
||||||
parsed = zparse(melody)
|
parsed = zparse(melody)
|
||||||
#parsed_bass = zparse(bass_line)
|
parsed_bass = zparse(bass_line)
|
||||||
|
|
||||||
# Convert to music21 object
|
# Convert to music21 objects
|
||||||
s2 = to_music21(parsed, time="4/4")
|
part1 = to_music21(parsed, time="4/4")
|
||||||
|
part2 = to_music21(parsed_bass, time="4/4")
|
||||||
|
|
||||||
# Merge melody and bass line
|
# Add instruments
|
||||||
#s2.append(to_music21(parsed_bass, time="4/4"))
|
part1.insert(instrument.Piano())
|
||||||
|
part2.insert(instrument.Soprano())
|
||||||
|
|
||||||
|
# Create score
|
||||||
|
song = stream.Score()
|
||||||
|
song.insert(0,part1)
|
||||||
|
song.insert(0,part2)
|
||||||
|
|
||||||
# Write to midi file under examples/music21/midi folder
|
# Write to midi file under examples/music21/midi folder
|
||||||
s2.write('midi', fp='examples/music21/output/ziffers_example.mid')
|
song.write('midi', fp='examples/music21/output/ziffers_example.mid')
|
||||||
Binary file not shown.
@ -2,7 +2,7 @@ from music21 import *
|
|||||||
from ziffers import *
|
from ziffers import *
|
||||||
|
|
||||||
# Parse Ziffers string to music21 object
|
# Parse Ziffers string to music21 object
|
||||||
s = to_music21('(i v vi vii^dim)@(q0 e 2 1 q 012)', scale="Lydian", time="4/4")
|
s = to_music21('(i v vi vii^dim)@(q0 e 2 1 q 012)', key="d3", scale="Minor", time="4/4", bpm=190)
|
||||||
|
|
||||||
# See https://web.mit.edu/music21/doc/installing/installAdditional.html
|
# See https://web.mit.edu/music21/doc/installing/installAdditional.html
|
||||||
# Attempt to open / show the midi in MuseScore
|
# Attempt to open / show the midi in MuseScore
|
||||||
|
|||||||
17
examples/sonicsynth/play_with_sonicsynth.py
Normal file
17
examples/sonicsynth/play_with_sonicsynth.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'''Simple example of using SonicSynth to play Ziffers melody.'''
|
||||||
|
from sonicsynth import *
|
||||||
|
from ziffers import *
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
melody = zparse("(q 0 4 2 5 e 3 9 4 2 s 5 3 1 6 8 3 4 5 2 1 q 0)+(0 3 -2 4 2)", key="E3", scale="Aerathitonic")
|
||||||
|
|
||||||
|
def build_waveform(melody, bpm):
|
||||||
|
for item in melody.evaluated_values:
|
||||||
|
if isinstance(item, Pitch):
|
||||||
|
time_in_seconds = item.duration * 4 * 60 / bpm
|
||||||
|
yield generate_square_wave(frequency=item.freq, amplitude=0.25, duration=time_in_seconds)
|
||||||
|
|
||||||
|
waveform = np.concatenate(list(build_waveform(melody,130)))
|
||||||
|
|
||||||
|
player = Playback(44100)
|
||||||
|
player.play(waveform)
|
||||||
23
examples/sonicsynth/test_arpeggio_effect.py
Normal file
23
examples/sonicsynth/test_arpeggio_effect.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'''Testing arpeggio effect with sonicsynth and ziffers'''
|
||||||
|
from sonicsynth import *
|
||||||
|
from ziffers import *
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
melody = zparse("(q 0 024 2 246 e 4 2 6 2 q 5 0)+(0 3 1 2)", key="D4", scale="Minor")
|
||||||
|
|
||||||
|
def build_waveform(melody, bpm):
|
||||||
|
for item in melody.evaluated_values:
|
||||||
|
if isinstance(item, Pitch):
|
||||||
|
time_in_seconds = item.duration * 4 * 60 / bpm
|
||||||
|
yield generate_sine_wave(frequency=item.freq, amplitude=0.5, duration=time_in_seconds)
|
||||||
|
elif isinstance(item, Chord):
|
||||||
|
time_in_seconds = item.durations[0] * 4 * 60 / bpm
|
||||||
|
for pitch in item.pitch_classes:
|
||||||
|
# Create "NES arpeggio effect"
|
||||||
|
for i in range(1,len(item.durations)):
|
||||||
|
yield generate_sine_wave(frequency=pitch.freq, amplitude=0.5, duration=time_in_seconds/(len(item.durations)*3))
|
||||||
|
|
||||||
|
waveform = np.concatenate(list(build_waveform(melody,180)))
|
||||||
|
|
||||||
|
player = Playback(44100)
|
||||||
|
player.play(waveform)
|
||||||
@ -188,6 +188,16 @@ def test_romans(pattern: str, expected: list):
|
|||||||
def test_romans_pcs(pattern: str, expected: list):
|
def test_romans_pcs(pattern: str, expected: list):
|
||||||
assert get_items(zparse(pattern),len(expected)*2,"pitches") == expected*2
|
assert get_items(zparse(pattern),len(expected)*2,"pitches") == expected*2
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern,expected",
|
||||||
|
[
|
||||||
|
("(0 1 2 3 r)+(1)", [1,2,3,4,None]),
|
||||||
|
("(0 1 2 3 r)-(0 1 4)", [0, -1, -2, -3, None, 1, 0, -1, -2, None, 4, 3, 2, 1, None])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_operations(pattern: str, expected: list):
|
||||||
|
assert get_items(zparse(pattern),len(expected)*2,"pitch_class") == expected*2
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"pattern,expected",
|
"pattern,expected",
|
||||||
[
|
[
|
||||||
|
|||||||
@ -224,6 +224,10 @@ class Pitch(Event):
|
|||||||
def get_octave(self):
|
def get_octave(self):
|
||||||
"""Getter for octave"""
|
"""Getter for octave"""
|
||||||
return self.octave
|
return self.octave
|
||||||
|
|
||||||
|
def get_beat(self):
|
||||||
|
"""Getter for beat"""
|
||||||
|
return self.beat
|
||||||
|
|
||||||
def get_pitch_class(self):
|
def get_pitch_class(self):
|
||||||
"""Getter for pitche"""
|
"""Getter for pitche"""
|
||||||
|
|||||||
@ -555,6 +555,8 @@ class ListOperation(Sequence):
|
|||||||
outcome = __chord_operation(first, second, False, options)
|
outcome = __chord_operation(first, second, False, options)
|
||||||
elif isinstance(second, Chord):
|
elif isinstance(second, Chord):
|
||||||
outcome = __chord_operation(second, first, True, options)
|
outcome = __chord_operation(second, first, True, options)
|
||||||
|
elif isinstance(first, Rest) or isinstance(second, Rest):
|
||||||
|
outcome = Rest(duration=first.get_duration())
|
||||||
else:
|
else:
|
||||||
outcome = Pitch(
|
outcome = Pitch(
|
||||||
pitch_class=operation(
|
pitch_class=operation(
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
"""Collection of converters"""
|
"""Collection of converters"""
|
||||||
from ziffers import zparse, Ziffers, Pitch, Rest, Chord
|
from ziffers import zparse, Ziffers, Pitch, Rest, Chord, accidentals_from_note_name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from music21 import converter, note, stream, meter, chord, environment
|
from music21 import converter, note, stream, meter, chord, environment, tempo, key
|
||||||
music21_imported: bool = True
|
music21_imported: bool = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
music21_imported: bool = False
|
music21_imported: bool = False
|
||||||
@ -13,52 +13,26 @@ try:
|
|||||||
except (ImportError, TypeError) as Error:
|
except (ImportError, TypeError) as Error:
|
||||||
csound_imported: bool = False
|
csound_imported: bool = False
|
||||||
|
|
||||||
def freq_to_pch(freq: float) -> str:
|
def ziffers_to_csound_score(ziffers: Ziffers, bpm: int=80, amp: float=1500, instr: (int|str)=1) -> str:
|
||||||
"Format frequency to Csound PCH format"
|
""" Transform Ziffers object to Csound score in format:
|
||||||
return f"{freq:.2f}"
|
i {instrument} {start time} {duration} {amplitude} {frequency} """
|
||||||
|
|
||||||
|
if not csound_imported:
|
||||||
|
raise ImportError("Install Csound")
|
||||||
|
|
||||||
# Csound score example: i1 0 1 0.5 8.00
|
|
||||||
# 1. The first number is the instrument number. In this case it is instrument 1.
|
|
||||||
# 2. The second number is the start time in seconds. In this case it is 0 seconds.
|
|
||||||
# 3. The third number is the duration in seconds. In this case it is 1 second.
|
|
||||||
# 4. The fourth number is the amplitude. In this case it is 0.5.
|
|
||||||
# 5. The fifth number is the frequency. In this case it is 8 Hz.
|
|
||||||
|
|
||||||
def freqs_and_durations_to_csound_score(pairs: list[list[float|list[float], float|list[float]]], bpm: int=80, amp: float=0.5, instr: (int|str)=1) -> str:
|
|
||||||
"""Tranforms list of lists containing frequencies and note lengths to csound score format.
|
|
||||||
Note lengths are transformed in seconds with the bpm.
|
|
||||||
Start time in seconds is calculated and summed from the note lengths.
|
|
||||||
If frequency is None, then it is a rest.
|
|
||||||
If frequency is a list, then it is a chord.
|
|
||||||
|
|
||||||
Example input: [[261.6255653005986, 0.5], [None, 0.25], [440.0, 0.125], [[261.6255653005986, 329.62755691286986, 391.9954359817492], [0.25, 0.125, 0.25]]]"""
|
|
||||||
score = ""
|
score = ""
|
||||||
instr = f'"{instr}"' if isinstance(instr, str) else instr
|
instr = f'"{instr}"' if isinstance(instr, str) else instr
|
||||||
start_time = 0
|
start_time = 0
|
||||||
for pair in pairs:
|
for item in ziffers.evaluated_values:
|
||||||
if isinstance(pair[0], list):
|
if isinstance(item, Chord):
|
||||||
for freq, dur in zip(pair[0], pair[1]):
|
for freq, dur in zip(item.get_freq(), item.get_duration()):
|
||||||
score += f"i {instr} {start_time} {dur * 4 * 60 / bpm} {amp} {freq_to_pch(freq)}\n"
|
score += f"i {instr} {start_time} {dur * 4 * 60 / bpm} {amp} {freq:.2f}\n"
|
||||||
start_time += max(pair[1]) * 4 * 60 / bpm
|
start_time += max(item.get_duration()) * 4 * 60 / bpm
|
||||||
else:
|
elif isinstance(item, Rest):
|
||||||
if pair[0] is None:
|
score += f"i {instr} {start_time} {item.get_duration() * 4 * 60 / bpm} {amp} 0\n"
|
||||||
score += f"i {instr} {start_time} {pair[1] * 4 * 60 / bpm} {amp} 0\n"
|
elif isinstance(item, Pitch):
|
||||||
else:
|
score += f"i {instr} {start_time} {item.get_duration() * 4 * 60 / bpm} {amp} {item.get_freq():.2f}\n"
|
||||||
score += f"i {instr} {start_time} {pair[1] * 4 * 60 / bpm} {amp} {freq_to_pch(pair[0])}\n"
|
start_time += item.get_duration() * 4 * 60 / bpm
|
||||||
start_time += pair[1] * 4 * 60 / bpm
|
|
||||||
return score
|
|
||||||
|
|
||||||
def to_csound_score(expression: str | Ziffers, bpm: int=80, amp: float=0.5, instr: (int|str)=1) -> str:
|
|
||||||
""" Transform Ziffers object to Csound score """
|
|
||||||
if not csound_imported:
|
|
||||||
raise ImportError("Install Csound library")
|
|
||||||
|
|
||||||
if isinstance(expression, Ziffers):
|
|
||||||
score = freqs_and_durations_to_csound_score(expression.freq_pairs(),bpm,amp,instr)
|
|
||||||
else:
|
|
||||||
parsed = zparse(expression)
|
|
||||||
score = freqs_and_durations_to_csound_score(parsed.freq_pairs(),bpm,amp,instr)
|
|
||||||
|
|
||||||
return score
|
return score
|
||||||
|
|
||||||
def to_music21(expression: str | Ziffers, **options):
|
def to_music21(expression: str | Ziffers, **options):
|
||||||
@ -68,7 +42,7 @@ def to_music21(expression: str | Ziffers, **options):
|
|||||||
raise ImportError("Install Music21 library")
|
raise ImportError("Install Music21 library")
|
||||||
|
|
||||||
# Register the ZiffersMusic21 converter
|
# Register the ZiffersMusic21 converter
|
||||||
converter.registerSubconverter(ZiffersMusic21)
|
converter.registerSubConverter(ZiffersMusic21)
|
||||||
|
|
||||||
if isinstance(expression, Ziffers):
|
if isinstance(expression, Ziffers):
|
||||||
if options:
|
if options:
|
||||||
@ -118,12 +92,23 @@ if music21_imported:
|
|||||||
parsed = zparse(dataString)
|
parsed = zparse(dataString)
|
||||||
|
|
||||||
note_stream = stream.Part()
|
note_stream = stream.Part()
|
||||||
|
|
||||||
if "time" in options:
|
if "time" in options:
|
||||||
m_item = meter.TimeSignature(options["time"]) # Common time
|
m_item = meter.TimeSignature(options["time"]) # Common time
|
||||||
else:
|
else:
|
||||||
m_item = meter.TimeSignature("c") # Common time
|
m_item = meter.TimeSignature("c") # Common time
|
||||||
|
|
||||||
note_stream.insert(0, m_item)
|
note_stream.insert(0, m_item)
|
||||||
|
|
||||||
|
if "key" in options:
|
||||||
|
accidentals = accidentals_from_note_name(options["key"])
|
||||||
|
if "scale" in options and options["scale"].upper() == "MINOR":
|
||||||
|
accidentals-=3 # If minor, subtract 3 from accidentals
|
||||||
|
note_stream.append(key.KeySignature(accidentals))
|
||||||
|
|
||||||
|
if "bpm" in options:
|
||||||
|
note_stream.append(tempo.MetronomeMark(number=options["bpm"]))
|
||||||
|
|
||||||
for item in parsed:
|
for item in parsed:
|
||||||
if isinstance(item, Pitch):
|
if isinstance(item, Pitch):
|
||||||
m_item = note.Note(item.note)
|
m_item = note.Note(item.note)
|
||||||
|
|||||||
@ -231,7 +231,10 @@ def accidentals_from_note_name(name: str) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
int: Integer representing number of flats or sharps: -7 flat to 7 sharp.
|
int: Integer representing number of flats or sharps: -7 flat to 7 sharp.
|
||||||
"""
|
"""
|
||||||
idx = CIRCLE_OF_FIFTHS.index(name.upper())
|
if name not in CIRCLE_OF_FIFTHS:
|
||||||
|
name = midi_to_note_name(note_name_to_midi(name))
|
||||||
|
|
||||||
|
idx = CIRCLE_OF_FIFTHS.index(name)
|
||||||
return idx - 6
|
return idx - 6
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user