From 7d6ba407bd978727246b646c398bf91b64900553 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Wed, 28 Jun 2023 00:55:56 +0300 Subject: [PATCH] Added music21 and csound examples --- example.musicxml | 101 +++++++++ examples/csound/play_with_csound.py | 36 ++++ examples/music21/create_midi_file.py | 20 ++ examples/music21/create_musicxml.py | 9 + examples/music21/create_png.py | 9 + examples/music21/music21_example.py | 8 - examples/music21/music21_example_2.py | 9 - examples/music21/output/example-1.png | Bin 0 -> 10464 bytes examples/music21/output/example.musicxml | 99 +++++++++ examples/music21/output/ziffers_example.mid | Bin 0 -> 209 bytes examples/music21/output/ziffers_example.xml | 228 ++++++++++++++++++++ examples/music21/show_in_musescore.py | 9 + ziffers/classes/root.py | 14 +- ziffers/converters.py | 55 +++++ 14 files changed, 577 insertions(+), 20 deletions(-) create mode 100644 example.musicxml create mode 100644 examples/csound/play_with_csound.py create mode 100644 examples/music21/create_midi_file.py create mode 100644 examples/music21/create_musicxml.py create mode 100644 examples/music21/create_png.py delete mode 100644 examples/music21/music21_example.py delete mode 100644 examples/music21/music21_example_2.py create mode 100644 examples/music21/output/example-1.png create mode 100644 examples/music21/output/example.musicxml create mode 100644 examples/music21/output/ziffers_example.mid create mode 100644 examples/music21/output/ziffers_example.xml create mode 100644 examples/music21/show_in_musescore.py 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 0000000000000000000000000000000000000000..211c4500a30691497be295aa47d9f037e7266f02 GIT binary patch literal 10464 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b1902c5bf1c42d7db43b10666897b122e61adba1 GIT binary patch literal 209 zcmeYb$w*;fU|?flWMEQH@C_--2J%E0{s%I%FH~UoAHl-HB*Aem;lDluRBk@Qe`W@T z2Mi7j6KtZ+B{bMDFida&F&uylXAr}g;atK5yQt*`4R&B5Aj28Ra0H1sf>i? + + + 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):