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)
|
||||
* [Music21](https://github.com/cuthbertLab/music21)
|
||||
* [Csound](http://www.csounds.com/manual/html/ScoreTop.html)
|
||||
* [Sonicsynth](https://github.com/Frikallo/SonicSynth)
|
||||
|
||||
# Status:
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
"""Example of using ziffers with Csound score."""
|
||||
try:
|
||||
import ctcsound
|
||||
except (ImportError,TypeError):
|
||||
@ -5,30 +6,35 @@ except (ImportError,TypeError):
|
||||
|
||||
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):
|
||||
|
||||
# Parse ziffers notation
|
||||
parsed = zparse("w 0 1 q 0 1 2 3 r e 5 3 9 2 q r 0")
|
||||
# Parse ziffers notation, scale in SCALA format
|
||||
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 = to_csound_score(parsed, 180, 1500, "Meep")
|
||||
|
||||
# Outputs: i {instrument} {start time} {duration} {amplitude} {pitch}
|
||||
score = ziffers_to_csound_score(parsed, 180, 1500, "FooBar") # 180 bpm, 1500 amplitude, FooBar instrument
|
||||
|
||||
print("Generated score:")
|
||||
print(score)
|
||||
|
||||
# Define FooBar Csound instrument
|
||||
orc = """
|
||||
instr Meep
|
||||
instr FooBar
|
||||
out(linen(oscili(p4,p5),0.1,p3,0.1))
|
||||
endin
|
||||
"""
|
||||
|
||||
# Run score with Csound
|
||||
c = ctcsound.Csound()
|
||||
c.setOption("-odac") # Using SetOption() to configure Csound
|
||||
|
||||
c.setOption("-odac")
|
||||
c.compileOrc(orc)
|
||||
|
||||
c.readScore(score)
|
||||
|
||||
c.start()
|
||||
c.perform()
|
||||
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 ziffers import *
|
||||
|
||||
|
||||
# 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"
|
||||
#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
|
||||
parsed = zparse(melody)
|
||||
#parsed_bass = zparse(bass_line)
|
||||
parsed_bass = zparse(bass_line)
|
||||
|
||||
# Convert to music21 object
|
||||
s2 = to_music21(parsed, time="4/4")
|
||||
# Convert to music21 objects
|
||||
part1 = to_music21(parsed, time="4/4")
|
||||
part2 = to_music21(parsed_bass, time="4/4")
|
||||
|
||||
# Merge melody and bass line
|
||||
#s2.append(to_music21(parsed_bass, time="4/4"))
|
||||
# Add instruments
|
||||
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
|
||||
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 *
|
||||
|
||||
# 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
|
||||
# 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):
|
||||
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(
|
||||
"pattern,expected",
|
||||
[
|
||||
|
||||
@ -224,6 +224,10 @@ class Pitch(Event):
|
||||
def get_octave(self):
|
||||
"""Getter for octave"""
|
||||
return self.octave
|
||||
|
||||
def get_beat(self):
|
||||
"""Getter for beat"""
|
||||
return self.beat
|
||||
|
||||
def get_pitch_class(self):
|
||||
"""Getter for pitche"""
|
||||
|
||||
@ -555,6 +555,8 @@ class ListOperation(Sequence):
|
||||
outcome = __chord_operation(first, second, False, options)
|
||||
elif isinstance(second, Chord):
|
||||
outcome = __chord_operation(second, first, True, options)
|
||||
elif isinstance(first, Rest) or isinstance(second, Rest):
|
||||
outcome = Rest(duration=first.get_duration())
|
||||
else:
|
||||
outcome = Pitch(
|
||||
pitch_class=operation(
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
"""Collection of converters"""
|
||||
from ziffers import zparse, Ziffers, Pitch, Rest, Chord
|
||||
from ziffers import zparse, Ziffers, Pitch, Rest, Chord, accidentals_from_note_name
|
||||
|
||||
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
|
||||
except ImportError:
|
||||
music21_imported: bool = False
|
||||
@ -13,52 +13,26 @@ try:
|
||||
except (ImportError, TypeError) as Error:
|
||||
csound_imported: bool = False
|
||||
|
||||
def freq_to_pch(freq: float) -> str:
|
||||
"Format frequency to Csound PCH format"
|
||||
return f"{freq:.2f}"
|
||||
def ziffers_to_csound_score(ziffers: Ziffers, bpm: int=80, amp: float=1500, instr: (int|str)=1) -> str:
|
||||
""" Transform Ziffers object to Csound score in format:
|
||||
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 = ""
|
||||
instr = f'"{instr}"' if isinstance(instr, str) else instr
|
||||
start_time = 0
|
||||
for pair in pairs:
|
||||
if isinstance(pair[0], list):
|
||||
for freq, dur in zip(pair[0], pair[1]):
|
||||
score += f"i {instr} {start_time} {dur * 4 * 60 / bpm} {amp} {freq_to_pch(freq)}\n"
|
||||
start_time += max(pair[1]) * 4 * 60 / bpm
|
||||
else:
|
||||
if pair[0] is None:
|
||||
score += f"i {instr} {start_time} {pair[1] * 4 * 60 / bpm} {amp} 0\n"
|
||||
else:
|
||||
score += f"i {instr} {start_time} {pair[1] * 4 * 60 / bpm} {amp} {freq_to_pch(pair[0])}\n"
|
||||
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)
|
||||
|
||||
for item in ziffers.evaluated_values:
|
||||
if isinstance(item, Chord):
|
||||
for freq, dur in zip(item.get_freq(), item.get_duration()):
|
||||
score += f"i {instr} {start_time} {dur * 4 * 60 / bpm} {amp} {freq:.2f}\n"
|
||||
start_time += max(item.get_duration()) * 4 * 60 / bpm
|
||||
elif isinstance(item, Rest):
|
||||
score += f"i {instr} {start_time} {item.get_duration() * 4 * 60 / bpm} {amp} 0\n"
|
||||
elif isinstance(item, Pitch):
|
||||
score += f"i {instr} {start_time} {item.get_duration() * 4 * 60 / bpm} {amp} {item.get_freq():.2f}\n"
|
||||
start_time += item.get_duration() * 4 * 60 / bpm
|
||||
return score
|
||||
|
||||
def to_music21(expression: str | Ziffers, **options):
|
||||
@ -68,7 +42,7 @@ def to_music21(expression: str | Ziffers, **options):
|
||||
raise ImportError("Install Music21 library")
|
||||
|
||||
# Register the ZiffersMusic21 converter
|
||||
converter.registerSubconverter(ZiffersMusic21)
|
||||
converter.registerSubConverter(ZiffersMusic21)
|
||||
|
||||
if isinstance(expression, Ziffers):
|
||||
if options:
|
||||
@ -118,12 +92,23 @@ if music21_imported:
|
||||
parsed = zparse(dataString)
|
||||
|
||||
note_stream = stream.Part()
|
||||
|
||||
if "time" in options:
|
||||
m_item = meter.TimeSignature(options["time"]) # Common time
|
||||
else:
|
||||
m_item = meter.TimeSignature("c") # Common time
|
||||
|
||||
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:
|
||||
if isinstance(item, Pitch):
|
||||
m_item = note.Note(item.note)
|
||||
|
||||
@ -231,7 +231,10 @@ def accidentals_from_note_name(name: str) -> int:
|
||||
Returns:
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user