diff --git a/example.musicxml b/example.musicxml
new file mode 100644
index 0000000..5dafd65
--- /dev/null
+++ b/example.musicxml
@@ -0,0 +1,101 @@
+
+
+
+ Music21 Fragment
+
+ Music21
+
+ 2023-06-27
+ music21 v.9.1.0
+
+
+
+
+ 7
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 10080
+
+
+ G
+ 2
+
+
+
+
+ D
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+ E
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+ 10080
+ quarter
+
+
+
+ D
+ 0
+ 4
+
+ 5040
+ eighth
+
+
+
+
+ E
+ 0
+ 4
+
+ 5040
+ eighth
+ natural
+
+
+
+
+ G
+ 0
+ 4
+
+ 5040
+ eighth
+
+
+
+ 5040
+ eighth
+
+
+ light-heavy
+
+
+
+
\ No newline at end of file
diff --git a/examples/csound/play_with_csound.py b/examples/csound/play_with_csound.py
new file mode 100644
index 0000000..95ebb66
--- /dev/null
+++ b/examples/csound/play_with_csound.py
@@ -0,0 +1,36 @@
+try:
+ import ctcsound
+except (ImportError,TypeError):
+ csound_imported = false
+
+from ziffers import *
+
+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")
+
+ # Convert to csound score
+ score = to_csound_score(parsed, 180, 1500, "Meep")
+
+ # Outputs: i {instrument} {start time} {duration} {amplitude} {pitch}
+ print(score)
+
+ orc = """
+ instr Meep
+ out(linen(oscili(p4,p5),0.1,p3,0.1))
+ endin
+ """
+
+ c = ctcsound.Csound()
+ c.setOption("-odac") # Using SetOption() to configure Csound
+
+ c.compileOrc(orc)
+
+ c.readScore(score)
+
+ c.start()
+ c.perform()
+ c.stop()
+else:
+ print("Csound not found! First download from https://csound.com/ and add to PATH or PYENV (Windows path: C:\Program Files\Csound6_x64\bin). Then install ctcsound with 'pip install ctcsound'.")
\ No newline at end of file
diff --git a/examples/music21/create_midi_file.py b/examples/music21/create_midi_file.py
new file mode 100644
index 0000000..9423d5c
--- /dev/null
+++ b/examples/music21/create_midi_file.py
@@ -0,0 +1,20 @@
+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"
+
+# Parse ziffers notation to melody from string
+parsed = zparse(melody)
+#parsed_bass = zparse(bass_line)
+
+# Convert to music21 object
+s2 = to_music21(parsed, time="4/4")
+
+# Merge melody and bass line
+#s2.append(to_music21(parsed_bass, time="4/4"))
+
+# Write to midi file under examples/music21/midi folder
+s2.write('midi', fp='examples/music21/output/ziffers_example.mid')
\ No newline at end of file
diff --git a/examples/music21/create_musicxml.py b/examples/music21/create_musicxml.py
new file mode 100644
index 0000000..6a34e28
--- /dev/null
+++ b/examples/music21/create_musicxml.py
@@ -0,0 +1,9 @@
+from music21 import *
+from ziffers import *
+
+# Parse ziiffers object to music21 object
+parsed = zparse("q 1 024 5 235 h 02345678{12} 0", key='C', scale='Zyditonic')
+s2 = to_music21(parsed,time="4/4")
+
+# Save to MusicXML file
+s2.write('musicxml', fp='examples/music21/output/ziffers_example.xml')
\ No newline at end of file
diff --git a/examples/music21/create_png.py b/examples/music21/create_png.py
new file mode 100644
index 0000000..1f6893f
--- /dev/null
+++ b/examples/music21/create_png.py
@@ -0,0 +1,9 @@
+from music21 import *
+from ziffers import *
+
+# Parse ziiffers object to music21 object
+parsed = zparse('1 2 qr e 124')
+s2 = to_music21(parsed,time="4/4")
+
+# Save object as png file
+s2.write("musicxml.png", fp="examples/music21/output/example.png")
\ No newline at end of file
diff --git a/examples/music21/music21_example.py b/examples/music21/music21_example.py
deleted file mode 100644
index c53f80d..0000000
--- a/examples/music21/music21_example.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from music21 import *
-from sardine import *
-from ziffers import *
-
-
-s = to_music21('(i v vi vii^dim)@(q0 e2 s1 012)',time="4/4")
-
-s.show('midi')
\ No newline at end of file
diff --git a/examples/music21/music21_example_2.py b/examples/music21/music21_example_2.py
deleted file mode 100644
index 6b8321b..0000000
--- a/examples/music21/music21_example_2.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from music21 import *
-from sardine import *
-from ziffers import *
-
-parsed = zparse('1 2 qr e 124')
-s2 = to_music21(parsed,time="4/4")
-
-s2.show()
-s2.show('midi')
\ No newline at end of file
diff --git a/examples/music21/output/example-1.png b/examples/music21/output/example-1.png
new file mode 100644
index 0000000..211c450
Binary files /dev/null and b/examples/music21/output/example-1.png differ
diff --git a/examples/music21/output/example.musicxml b/examples/music21/output/example.musicxml
new file mode 100644
index 0000000..bda8da7
--- /dev/null
+++ b/examples/music21/output/example.musicxml
@@ -0,0 +1,99 @@
+
+
+
+
+
+ 2023-06-27
+ music21 v.9.1.0
+
+
+
+
+ 7
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 10080
+
+
+ G
+ 2
+
+
+
+
+ D
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+ E
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+ 10080
+ quarter
+
+
+
+ D
+ 0
+ 4
+
+ 5040
+ eighth
+
+
+
+
+ E
+ 0
+ 4
+
+ 5040
+ eighth
+ natural
+
+
+
+
+ G
+ 0
+ 4
+
+ 5040
+ eighth
+
+
+
+ 5040
+ eighth
+
+
+ light-heavy
+
+
+
+
\ No newline at end of file
diff --git a/examples/music21/output/ziffers_example.mid b/examples/music21/output/ziffers_example.mid
new file mode 100644
index 0000000..b1902c5
Binary files /dev/null and b/examples/music21/output/ziffers_example.mid differ
diff --git a/examples/music21/output/ziffers_example.xml b/examples/music21/output/ziffers_example.xml
new file mode 100644
index 0000000..11e60ed
--- /dev/null
+++ b/examples/music21/output/ziffers_example.xml
@@ -0,0 +1,228 @@
+
+
+
+ Music21 Fragment
+
+ Music21
+
+ 2023-06-27
+ music21 v.9.1.0
+
+
+
+
+ 7
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 10080
+
+
+ G
+ 2
+
+
+
+
+ C
+ 1
+ 4
+
+ 10080
+ quarter
+ sharp
+
+
+
+ C
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+
+
+
+ F
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+
+ A
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+ C
+ 0
+ 5
+
+ 10080
+ quarter
+
+
+
+ F
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+
+
+
+
+ C
+ 0
+ 5
+
+ 10080
+ quarter
+ natural
+
+
+
+
+
+
+ C
+ 0
+ 4
+
+ 20160
+ half
+ natural
+
+
+
+
+ F
+ 0
+ 4
+
+ 20160
+ half
+ natural
+
+
+
+
+ G
+ 0
+ 4
+
+ 20160
+ half
+ natural
+
+
+
+
+ A
+ 0
+ 4
+
+ 20160
+ half
+ natural
+
+
+
+
+ C
+ 0
+ 5
+
+ 20160
+ half
+ natural
+
+
+
+
+ D
+ -1
+ 5
+
+ 20160
+ half
+ flat
+
+
+
+
+ F
+ 0
+ 5
+
+ 20160
+ half
+ natural
+
+
+
+
+ G
+ 0
+ 5
+
+ 20160
+ half
+ natural
+
+
+
+
+ F
+ 0
+ 6
+
+ 20160
+ half
+ natural
+
+
+
+ 20160
+ half
+
+
+ light-heavy
+
+
+
+
\ No newline at end of file
diff --git a/examples/music21/show_in_musescore.py b/examples/music21/show_in_musescore.py
new file mode 100644
index 0000000..910eaac
--- /dev/null
+++ b/examples/music21/show_in_musescore.py
@@ -0,0 +1,9 @@
+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")
+
+# See https://web.mit.edu/music21/doc/installing/installAdditional.html
+# Attempt to open / show the midi in MuseScore
+s.show()
\ No newline at end of file
diff --git a/ziffers/classes/root.py b/ziffers/classes/root.py
index 165914a..939a174 100644
--- a/ziffers/classes/root.py
+++ b/ziffers/classes/root.py
@@ -153,9 +153,17 @@ class Ziffers(Sequence):
def pairs(self) -> list[tuple]:
"""Return list of pitches and durations"""
return [
- (val.get_pitch_class(), val.get_duration())
+ [val.get_pitch_class(), val.get_duration()]
for val in self.evaluated_values
- if isinstance(val, Pitch)
+ if isinstance(val, Pitch) or isinstance(val, Chord) or isinstance(val, Rest)
+ ]
+
+ def freq_pairs(self) -> list[tuple]:
+ """Return list of pitches in freq and durations"""
+ return [
+ [val.get_freq(), val.get_duration()]
+ for val in self.evaluated_values
+ if isinstance(val, Pitch) or isinstance(val, Chord) or isinstance(val, Rest)
]
def octaves(self) -> list[int]:
@@ -194,4 +202,4 @@ class Ziffers(Sequence):
return all_items
if len(all_items) == 1:
return all_items[0]
- return None
+ return None
\ No newline at end of file
diff --git a/ziffers/converters.py b/ziffers/converters.py
index 36b124a..1c0418f 100644
--- a/ziffers/converters.py
+++ b/ziffers/converters.py
@@ -7,12 +7,67 @@ try:
except ImportError:
music21_imported: bool = False
+try:
+ import ctcsound
+ csound_imported: bool = True
+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}"
+
+# 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)
+
+ return score
+
def to_music21(expression: str | Ziffers, **options):
"""Helper for passing options to the parser"""
if not music21_imported:
raise ImportError("Install Music21 library")
+ # Register the ZiffersMusic21 converter
converter.registerSubconverter(ZiffersMusic21)
if isinstance(expression, Ziffers):