CreativePython 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- creativepython-0.1.1/MANIFEST.in +4 -0
- creativepython-0.1.1/PKG-INFO +146 -0
- creativepython-0.1.1/README.md +97 -0
- {creativepython-0.1.0 → creativepython-0.1.1}/pyproject.toml +11 -7
- creativepython-0.1.1/src/CreativePython/_soundfont.py +66 -0
- creativepython-0.1.1/src/CreativePython/examples/ArvoPart.CantusInMemoriam.py +76 -0
- creativepython-0.1.1/src/CreativePython/examples/ConcretPH_Xenakis.py +41 -0
- creativepython-0.1.1/src/CreativePython/examples/DeepPurple.SmokeOnTheWater.py +67 -0
- creativepython-0.1.1/src/CreativePython/examples/JS_Bach.Canon.TriasHarmonica.BWV1072.py +77 -0
- creativepython-0.1.1/src/CreativePython/examples/JS_Bach.Canon_1.GoldbergGround.BWV1087.py +39 -0
- creativepython-0.1.1/src/CreativePython/examples/Mozart.MusikalischesWurfelspiel.py +101 -0
- creativepython-0.1.1/src/CreativePython/examples/PierreCage.StructuresPourDeuxChances.py +47 -0
- creativepython-0.1.1/src/CreativePython/examples/RGB_Display.py +73 -0
- creativepython-0.1.1/src/CreativePython/examples/TerryRiley.InC.py +34 -0
- creativepython-0.1.1/src/CreativePython/examples/arpeggiator1.py +30 -0
- creativepython-0.1.1/src/CreativePython/examples/arpeggiator2.py +32 -0
- creativepython-0.1.1/src/CreativePython/examples/autumnLeaves.py +77 -0
- creativepython-0.1.1/src/CreativePython/examples/axelF.py +26 -0
- creativepython-0.1.1/src/CreativePython/examples/biosignals.txt +3555 -0
- creativepython-0.1.1/src/CreativePython/examples/boids.py +267 -0
- creativepython-0.1.1/src/CreativePython/examples/brownianMelody.py +47 -0
- creativepython-0.1.1/src/CreativePython/examples/changesByTupac.py +23 -0
- creativepython-0.1.1/src/CreativePython/examples/clementine.py +140 -0
- creativepython-0.1.1/src/CreativePython/examples/continuousPitchInstrumentAudio.py +54 -0
- creativepython-0.1.1/src/CreativePython/examples/drumExample.py +22 -0
- creativepython-0.1.1/src/CreativePython/examples/drumMachinePattern1.py +50 -0
- creativepython-0.1.1/src/CreativePython/examples/drumsComeAlive.py +87 -0
- creativepython-0.1.1/src/CreativePython/examples/fibonacci.py +19 -0
- creativepython-0.1.1/src/CreativePython/examples/findPitchOctave.py +14 -0
- creativepython-0.1.1/src/CreativePython/examples/furElise.py +27 -0
- creativepython-0.1.1/src/CreativePython/examples/generativeMusic.py +47 -0
- creativepython-0.1.1/src/CreativePython/examples/goldenTree.py +55 -0
- creativepython-0.1.1/src/CreativePython/examples/guidoWordMusic.py +70 -0
- creativepython-0.1.1/src/CreativePython/examples/harmonicesMundi.py +67 -0
- creativepython-0.1.1/src/CreativePython/examples/harmonicesMundiRevisisted.py +65 -0
- creativepython-0.1.1/src/CreativePython/examples/harmonographLateral.py +47 -0
- creativepython-0.1.1/src/CreativePython/examples/harmonographRotary.py +70 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoBlackDown.png +0 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoOctave.png +0 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoParallel.py +73 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoSimple.py +86 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoWhiteCenterDown.png +0 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoWhiteLeftDown.png +0 -0
- creativepython-0.1.1/src/CreativePython/examples/iPianoWhiteRightDown.png +0 -0
- creativepython-0.1.1/src/CreativePython/examples/midiIn1.py +14 -0
- creativepython-0.1.1/src/CreativePython/examples/midiIn2.py +13 -0
- creativepython-0.1.1/src/CreativePython/examples/midiIn3.py +25 -0
- creativepython-0.1.1/src/CreativePython/examples/midiOut.a.py +12 -0
- creativepython-0.1.1/src/CreativePython/examples/midiOut.b.py +14 -0
- creativepython-0.1.1/src/CreativePython/examples/midiSynthesizer.py +34 -0
- creativepython-0.1.1/src/CreativePython/examples/midiSynthesizer2.py +55 -0
- creativepython-0.1.1/src/CreativePython/examples/moondog-bird_slament.wav +0 -0
- creativepython-0.1.1/src/CreativePython/examples/musicalSphere.py +178 -0
- creativepython-0.1.1/src/CreativePython/examples/note.py +55 -0
- creativepython-0.1.1/src/CreativePython/examples/octoplus.py +56 -0
- creativepython-0.1.1/src/CreativePython/examples/oscIn1.py +13 -0
- creativepython-0.1.1/src/CreativePython/examples/oscIn2.py +19 -0
- creativepython-0.1.1/src/CreativePython/examples/pentatonicMelody.py +33 -0
- creativepython-0.1.1/src/CreativePython/examples/pianoPhase.py +28 -0
- creativepython-0.1.1/src/CreativePython/examples/pianoRollGenerator.py +43 -0
- creativepython-0.1.1/src/CreativePython/examples/playNote.py +7 -0
- creativepython-0.1.1/src/CreativePython/examples/proteinMusic.py +59 -0
- creativepython-0.1.1/src/CreativePython/examples/randomCircles.py +36 -0
- creativepython-0.1.1/src/CreativePython/examples/randomCirclesThroughMidiInput.py +52 -0
- creativepython-0.1.1/src/CreativePython/examples/randomCirclesTimed.py +67 -0
- creativepython-0.1.1/src/CreativePython/examples/retrograde.a.py +22 -0
- creativepython-0.1.1/src/CreativePython/examples/retrograde.b.py +36 -0
- creativepython-0.1.1/src/CreativePython/examples/retrograde.c.py +41 -0
- creativepython-0.1.1/src/CreativePython/examples/rowYourBoat.py +61 -0
- creativepython-0.1.1/src/CreativePython/examples/scaleTutor.py +27 -0
- creativepython-0.1.1/src/CreativePython/examples/sierpinskiTriangle.py +52 -0
- creativepython-0.1.1/src/CreativePython/examples/simpleButtonInstrument.py +34 -0
- creativepython-0.1.1/src/CreativePython/examples/simpleCircleInstrument.py +70 -0
- creativepython-0.1.1/src/CreativePython/examples/sineMelody.py +23 -0
- creativepython-0.1.1/src/CreativePython/examples/sineMelodyPlus.py +28 -0
- creativepython-0.1.1/src/CreativePython/examples/sliderControl.py +66 -0
- creativepython-0.1.1/src/CreativePython/examples/sonifyBiosignals.py +79 -0
- creativepython-0.1.1/src/CreativePython/examples/sonifyImage.py +109 -0
- creativepython-0.1.1/src/CreativePython/examples/soundscapeLoutrakiSunset.jpg +0 -0
- creativepython-0.1.1/src/CreativePython/examples/stringQuartet.py +36 -0
- creativepython-0.1.1/src/CreativePython/examples/textMusic.py +62 -0
- creativepython-0.1.1/src/CreativePython/examples/theWayItIs.py +33 -0
- creativepython-0.1.1/src/CreativePython/examples/themeAndVariations.py +64 -0
- creativepython-0.1.1/src/CreativePython/examples/throwingDice.py +37 -0
- creativepython-0.1.1/src/CreativePython/examples/windChimes.py +56 -0
- creativepython-0.1.1/src/CreativePython/examples/zipfMetrics.py +81 -0
- creativepython-0.1.1/src/CreativePython/resources/.DS_Store +0 -0
- creativepython-0.1.1/src/CreativePython/resources/550973__luizguilherme_a__clean-guitarr-riff.mp3 +0 -0
- creativepython-0.1.1/src/CreativePython/resources/chopper.jpg +0 -0
- creativepython-0.1.1/src/CreativePython/resources/de-brazzas-monkey.jpg +0 -0
- creativepython-0.1.1/src/CreativePython/scripts/creativePythonSetup.py +115 -0
- creativepython-0.1.1/src/CreativePython.egg-info/PKG-INFO +146 -0
- creativepython-0.1.1/src/CreativePython.egg-info/SOURCES.txt +104 -0
- {creativepython-0.1.0 → creativepython-0.1.1}/src/CreativePython.egg-info/requires.txt +0 -3
- creativepython-0.1.1/src/CreativePython.egg-info/top_level.txt +8 -0
- {creativepython-0.1.0 → creativepython-0.1.1}/src/gui.py +35 -31
- {creativepython-0.1.0 → creativepython-0.1.1}/src/image.py +18 -2
- {creativepython-0.1.0 → creativepython-0.1.1}/src/midi.py +179 -130
- {creativepython-0.1.0 → creativepython-0.1.1}/src/music.py +120 -83
- {creativepython-0.1.0 → creativepython-0.1.1}/src/osc.py +18 -6
- {creativepython-0.1.0 → creativepython-0.1.1}/src/timer.py +5 -5
- creativepython-0.1.0/MANIFEST.in +0 -1
- creativepython-0.1.0/PKG-INFO +0 -189
- creativepython-0.1.0/README.md +0 -137
- creativepython-0.1.0/src/CreativePython.egg-info/PKG-INFO +0 -189
- creativepython-0.1.0/src/CreativePython.egg-info/SOURCES.txt +0 -22
- creativepython-0.1.0/src/CreativePython.egg-info/entry_points.txt +0 -3
- creativepython-0.1.0/src/CreativePython.egg-info/top_level.txt +0 -13
- creativepython-0.1.0/src/creativepython_setup.py +0 -85
- creativepython-0.1.0/src/iannix.py +0 -383
- creativepython-0.1.0/src/markov.py +0 -263
- creativepython-0.1.0/src/zipf.py +0 -232
- {creativepython-0.1.0 → creativepython-0.1.1}/LICENSE +0 -0
- {creativepython-0.1.0 → creativepython-0.1.1}/setup.cfg +0 -0
- {creativepython-0.1.0/src → creativepython-0.1.1/src/CreativePython}/__init__.py +0 -0
- {creativepython-0.1.0 → creativepython-0.1.1}/src/CreativePython.egg-info/dependency_links.txt +0 -0
- {creativepython-0.1.0 → creativepython-0.1.1}/src/_RealtimeAudioPlayer.py +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: CreativePython
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A Python-based software environment for developing algorithmic art projects.
|
|
5
|
+
Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Dr. Bill Manaris
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://jythonmusic.me
|
|
29
|
+
Keywords: music,audio,midi,learning,algorithmic art,algoart
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Requires-Python: >=3.9
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: tinysoundfont>=0.3.6
|
|
37
|
+
Requires-Dist: osc4py3>=1.0.8
|
|
38
|
+
Requires-Dist: mido>=1.3.3
|
|
39
|
+
Requires-Dist: PySide6>=6.9.1
|
|
40
|
+
Requires-Dist: sounddevice>=0.5.2
|
|
41
|
+
Requires-Dist: soundfile>=0.13.1
|
|
42
|
+
Requires-Dist: tqdm>=4.67.1
|
|
43
|
+
Requires-Dist: pooch>=1.8
|
|
44
|
+
Requires-Dist: pypianoroll>=1.0
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: build; extra == "dev"
|
|
47
|
+
Requires-Dist: twine; extra == "dev"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# CreativePython
|
|
51
|
+
|
|
52
|
+
CreativePython is a Python-based software environment for learning and developing algorithmic art projects. It mirrors the [JythonMusic API](https://jythonmusic.me/api-reference/), and is powered by [PySide6](https://wiki.qt.io/Qt_for_Python) and [portaudio](http://portaudio.com/).
|
|
53
|
+
|
|
54
|
+
CreativePython is distributed under the MIT License.
|
|
55
|
+
|
|
56
|
+
[Homepage](https://jythonmusic.me/)
|
|
57
|
+
|
|
58
|
+
This package is still under development.
|
|
59
|
+
|
|
60
|
+
# Beginner Installation (IDLE)
|
|
61
|
+
|
|
62
|
+
Download and install the latest version of [Python](https://www.python.org/downloads/).
|
|
63
|
+
|
|
64
|
+
Download the [CreativePython IDLE Install Script](https://www.dropbox.com/scl/fi/utlo75xun8wuukv7b477i/idleInstaller.py?rlkey=1rumkr7y3263ifd4tflj1o9lo&dl=1).
|
|
65
|
+
|
|
66
|
+
Open the `.py` script with IDLE, Python's Integrated Development Learning Environment. From the toolbar, select `Run`, then `Run Module`.
|
|
67
|
+
|
|
68
|
+
CreativePython will ask permission to download a high-quality soundfont (FluidR3 G2-2.sf2) for you. **IMPORTANT**: type `yes` in the IDLE Shell and hit enter. Without a soundfont, CreativePython can't produce MIDI sounds.
|
|
69
|
+
|
|
70
|
+
# Custom Installation
|
|
71
|
+
|
|
72
|
+
## Windows
|
|
73
|
+
|
|
74
|
+
Install CreativePython using `pip`:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
python -m pip install CreativePython
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## MacOS
|
|
81
|
+
|
|
82
|
+
Use [Homebrew](https://brew.sh/) to install the prerequisite [portaudio](http://portaudio.com/) library, then install CreativePython using `pip`:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
brew install portaudio
|
|
86
|
+
pip install CreativePython
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Linux
|
|
90
|
+
|
|
91
|
+
Use apt, or your preferred package manager, to install the prerequisite [portaudio](http://portaudio.com/) library, then install CreativePython using `pip`:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
sudo apt-get portaudio
|
|
95
|
+
pip install CreativePython
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
# Using CreativePython
|
|
99
|
+
|
|
100
|
+
## import
|
|
101
|
+
|
|
102
|
+
CreativePython's core modules are the `music`, `gui`, `image`, `timer`, `osc`, and `midi` libraries. You can import these libraries into your python code using:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
import music
|
|
106
|
+
from music import *
|
|
107
|
+
from music import Note, Play, C4, HN
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or a similar statement. CreativePython includes a number of useful constants, so we recommend using wildcard imports like `from music import *`.
|
|
111
|
+
|
|
112
|
+
**NOTE**: When you import `music`, CreativePython will ask permission to download a high-quality soundfont (FluidR3 G2-2.sf2) for you. You should only have to do this once.
|
|
113
|
+
|
|
114
|
+
## running CreativePython programs
|
|
115
|
+
|
|
116
|
+
CreativePython is designed for use in Python's Interactive Mode. To use Interactive Mode, enter a command like:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
python -i <filename>.py
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Example
|
|
123
|
+
|
|
124
|
+
Download [playNote.py](https://www.dropbox.com/scl/fi/z6rkjy4xnofmg0t899se3/playNote.py?rlkey=o3t8c91ne6agj2lqf2aupl8m5&dl=1):
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
# playNote.py
|
|
128
|
+
# Demonstrates how to play a single note.
|
|
129
|
+
|
|
130
|
+
from music import * # import music library
|
|
131
|
+
|
|
132
|
+
note = Note(C4, HN) # create a middle C half note
|
|
133
|
+
Play.midi(note) # and play it!
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
In a terminal, run the code using:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
python -i playNote.py
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If this is the first time you've used CreativePython, it will ask to download a soundfont.
|
|
143
|
+
|
|
144
|
+
After that, you should hear a single C4 note.
|
|
145
|
+
|
|
146
|
+
[[Download All Examples](https://www.dropbox.com/scl/fo/rvc8m8pt4m0281qn0t4oi/AO2Y0W2qOrOcurlQmLa7M54?rlkey=0sf80bmov135tc85dk9k7ats6&dl=1)]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# CreativePython
|
|
2
|
+
|
|
3
|
+
CreativePython is a Python-based software environment for learning and developing algorithmic art projects. It mirrors the [JythonMusic API](https://jythonmusic.me/api-reference/), and is powered by [PySide6](https://wiki.qt.io/Qt_for_Python) and [portaudio](http://portaudio.com/).
|
|
4
|
+
|
|
5
|
+
CreativePython is distributed under the MIT License.
|
|
6
|
+
|
|
7
|
+
[Homepage](https://jythonmusic.me/)
|
|
8
|
+
|
|
9
|
+
This package is still under development.
|
|
10
|
+
|
|
11
|
+
# Beginner Installation (IDLE)
|
|
12
|
+
|
|
13
|
+
Download and install the latest version of [Python](https://www.python.org/downloads/).
|
|
14
|
+
|
|
15
|
+
Download the [CreativePython IDLE Install Script](https://www.dropbox.com/scl/fi/utlo75xun8wuukv7b477i/idleInstaller.py?rlkey=1rumkr7y3263ifd4tflj1o9lo&dl=1).
|
|
16
|
+
|
|
17
|
+
Open the `.py` script with IDLE, Python's Integrated Development Learning Environment. From the toolbar, select `Run`, then `Run Module`.
|
|
18
|
+
|
|
19
|
+
CreativePython will ask permission to download a high-quality soundfont (FluidR3 G2-2.sf2) for you. **IMPORTANT**: type `yes` in the IDLE Shell and hit enter. Without a soundfont, CreativePython can't produce MIDI sounds.
|
|
20
|
+
|
|
21
|
+
# Custom Installation
|
|
22
|
+
|
|
23
|
+
## Windows
|
|
24
|
+
|
|
25
|
+
Install CreativePython using `pip`:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
python -m pip install CreativePython
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## MacOS
|
|
32
|
+
|
|
33
|
+
Use [Homebrew](https://brew.sh/) to install the prerequisite [portaudio](http://portaudio.com/) library, then install CreativePython using `pip`:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
brew install portaudio
|
|
37
|
+
pip install CreativePython
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Linux
|
|
41
|
+
|
|
42
|
+
Use apt, or your preferred package manager, to install the prerequisite [portaudio](http://portaudio.com/) library, then install CreativePython using `pip`:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
sudo apt-get portaudio
|
|
46
|
+
pip install CreativePython
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
# Using CreativePython
|
|
50
|
+
|
|
51
|
+
## import
|
|
52
|
+
|
|
53
|
+
CreativePython's core modules are the `music`, `gui`, `image`, `timer`, `osc`, and `midi` libraries. You can import these libraries into your python code using:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
import music
|
|
57
|
+
from music import *
|
|
58
|
+
from music import Note, Play, C4, HN
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or a similar statement. CreativePython includes a number of useful constants, so we recommend using wildcard imports like `from music import *`.
|
|
62
|
+
|
|
63
|
+
**NOTE**: When you import `music`, CreativePython will ask permission to download a high-quality soundfont (FluidR3 G2-2.sf2) for you. You should only have to do this once.
|
|
64
|
+
|
|
65
|
+
## running CreativePython programs
|
|
66
|
+
|
|
67
|
+
CreativePython is designed for use in Python's Interactive Mode. To use Interactive Mode, enter a command like:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
python -i <filename>.py
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Example
|
|
74
|
+
|
|
75
|
+
Download [playNote.py](https://www.dropbox.com/scl/fi/z6rkjy4xnofmg0t899se3/playNote.py?rlkey=o3t8c91ne6agj2lqf2aupl8m5&dl=1):
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
# playNote.py
|
|
79
|
+
# Demonstrates how to play a single note.
|
|
80
|
+
|
|
81
|
+
from music import * # import music library
|
|
82
|
+
|
|
83
|
+
note = Note(C4, HN) # create a middle C half note
|
|
84
|
+
Play.midi(note) # and play it!
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
In a terminal, run the code using:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
python -i playNote.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If this is the first time you've used CreativePython, it will ask to download a soundfont.
|
|
94
|
+
|
|
95
|
+
After that, you should hear a single C4 note.
|
|
96
|
+
|
|
97
|
+
[[Download All Examples](https://www.dropbox.com/scl/fo/rvc8m8pt4m0281qn0t4oi/AO2Y0W2qOrOcurlQmLa7M54?rlkey=0sf80bmov135tc85dk9k7ats6&dl=1)]
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "CreativePython"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "A Python-based software environment for developing algorithmic art projects."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -24,23 +24,27 @@ dependencies = [
|
|
|
24
24
|
"tinysoundfont>=0.3.6",
|
|
25
25
|
"osc4py3>=1.0.8",
|
|
26
26
|
"mido>=1.3.3",
|
|
27
|
-
"numpy>=2.3.2",
|
|
28
|
-
"pyaudio>=0.2.14",
|
|
29
27
|
"PySide6>=6.9.1",
|
|
30
28
|
"sounddevice>=0.5.2",
|
|
31
29
|
"soundfile>=0.13.1",
|
|
32
30
|
"tqdm>=4.67.1",
|
|
33
31
|
"pooch>=1.8",
|
|
34
|
-
"platformdirs>=4.2",
|
|
35
32
|
"pypianoroll>=1.0"
|
|
36
33
|
]
|
|
37
34
|
|
|
38
35
|
[project.urls]
|
|
39
36
|
Homepage = "https://jythonmusic.me"
|
|
40
37
|
|
|
41
|
-
[
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
[tool.setuptools.package-data]
|
|
39
|
+
CreativePython = [
|
|
40
|
+
"examples/*",
|
|
41
|
+
"scripts/*",
|
|
42
|
+
"resources/*",
|
|
43
|
+
# "bin/windows/*",
|
|
44
|
+
# "bin/macos/*",
|
|
45
|
+
# "licenses/*",
|
|
46
|
+
# "wheels/*.whl"
|
|
47
|
+
]
|
|
44
48
|
|
|
45
49
|
[project.optional-dependencies]
|
|
46
50
|
dev = ["build", "twine"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from platformdirs import user_data_dir
|
|
3
|
+
from pooch import retrieve
|
|
4
|
+
import shutil, sys, os
|
|
5
|
+
|
|
6
|
+
APP = "CreativePython"
|
|
7
|
+
ORG = "CofC"
|
|
8
|
+
|
|
9
|
+
SOUNDFONT_NAME = "default.sf2"
|
|
10
|
+
CACHE_DIR = Path(user_data_dir(APP, ORG)) / "SoundFonts"
|
|
11
|
+
SOUNDFONT_PATH = CACHE_DIR / SOUNDFONT_NAME
|
|
12
|
+
|
|
13
|
+
SF2_URL = "https://www.dropbox.com/s/xixtvox70lna6m2/FluidR3%20GM2-2.SF2?dl=1"
|
|
14
|
+
SF2_SHA256 = "2ae766ab5c5deb6f7fffacd6316ec9f3699998cce821df3163e7b10a78a64066"
|
|
15
|
+
|
|
16
|
+
##### SOUNDFONTS #####
|
|
17
|
+
|
|
18
|
+
def _soundfontPath():
|
|
19
|
+
"""Expose the intended soundfont path as a method."""
|
|
20
|
+
return SOUNDFONT_PATH
|
|
21
|
+
|
|
22
|
+
def _findSoundfont(candidate=None):
|
|
23
|
+
"""
|
|
24
|
+
Finds a soundfont 'default.sf2' and returns its location.
|
|
25
|
+
'candidate' can be another path containing the soundfont.
|
|
26
|
+
"""
|
|
27
|
+
candidates = []
|
|
28
|
+
soundfontPath = None
|
|
29
|
+
|
|
30
|
+
# path as argument?
|
|
31
|
+
if candidate:
|
|
32
|
+
candidates.append(Path(candidate))
|
|
33
|
+
|
|
34
|
+
# path as environment variable?
|
|
35
|
+
env = os.getenv("CREATIVEPYTHON_SOUNDFONT")
|
|
36
|
+
if env:
|
|
37
|
+
candidates.append(Path(env))
|
|
38
|
+
|
|
39
|
+
# default soundfont location
|
|
40
|
+
candidates+= [SOUNDFONT_PATH, Path.home() / "SoundFonts" / SOUNDFONT_NAME]
|
|
41
|
+
|
|
42
|
+
# find first valid candidate
|
|
43
|
+
i = 0
|
|
44
|
+
while soundfontPath is None and i < len(candidates):
|
|
45
|
+
c = candidates[i]
|
|
46
|
+
if c and c.exists():
|
|
47
|
+
soundfontPath = str(c)
|
|
48
|
+
|
|
49
|
+
return soundfontPath
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _downloadSoundfont(destination=SOUNDFONT_PATH):
|
|
53
|
+
"""
|
|
54
|
+
Downloads FluidR3 GM2-2 to given destination as 'default.sf2'
|
|
55
|
+
"""
|
|
56
|
+
# create parent directory
|
|
57
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
# download soundfont
|
|
59
|
+
path = retrieve(
|
|
60
|
+
url=SF2_URL,
|
|
61
|
+
known_hash=f"sha256:{SF2_SHA256}",
|
|
62
|
+
progressbar=True,
|
|
63
|
+
fname=destination.name,
|
|
64
|
+
path=str(destination.parent)
|
|
65
|
+
)
|
|
66
|
+
return path
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ArvoPart.CantusInMemoriam.py
|
|
2
|
+
#
|
|
3
|
+
# Recreates a variation of Arvo Part's "Cantus in Memoriam Benjamin
|
|
4
|
+
# Britten" (1977) for string orchestra and bell, using Mod functions.
|
|
5
|
+
|
|
6
|
+
from music import *
|
|
7
|
+
|
|
8
|
+
# musical parameters
|
|
9
|
+
repetitions = 2 # length of piece
|
|
10
|
+
tempo = 112 # tempo of piece
|
|
11
|
+
bar = WN+HN # length of a measure
|
|
12
|
+
|
|
13
|
+
# create musical data structure
|
|
14
|
+
cantusScore = Score("Cantus in Memoriam Benjamin Britten", tempo)
|
|
15
|
+
|
|
16
|
+
bellPart = Part(TUBULAR_BELLS, 0)
|
|
17
|
+
violinPart = Part(VIOLIN, 1)
|
|
18
|
+
|
|
19
|
+
# bell
|
|
20
|
+
bellPitches = [REST, A4, REST, REST, A4, REST, REST, A4]
|
|
21
|
+
bellDurations = [bar/2, bar/2, bar, bar/2, bar/2, bar, bar/2, bar/2]
|
|
22
|
+
|
|
23
|
+
bellPhrase = Phrase(0.0)
|
|
24
|
+
bellPhrase.addNoteList(bellPitches, bellDurations)
|
|
25
|
+
bellPart.addPhrase(bellPhrase)
|
|
26
|
+
|
|
27
|
+
# violin - define descending aeolian scale and rhythms
|
|
28
|
+
pitches = [A5, G5, F5, E5, D5, C5, B4, A4]
|
|
29
|
+
durations = [HN, QN, HN, QN, HN, QN, HN, QN]
|
|
30
|
+
|
|
31
|
+
# violin 1
|
|
32
|
+
violin1Phrase = Phrase(bar * 6.5) # start after 6 and 1/2 measures
|
|
33
|
+
violin1Phrase.addNoteList(pitches, durations)
|
|
34
|
+
|
|
35
|
+
# violin 2
|
|
36
|
+
violin2Phrase = violin1Phrase.copy()
|
|
37
|
+
violin2Phrase.setStartTime(bar * 7.0) # start after 7 measures
|
|
38
|
+
Mod.elongate(violin2Phrase, 2.0) # double durations
|
|
39
|
+
Mod.transpose(violin2Phrase, -12) # an octave lower
|
|
40
|
+
|
|
41
|
+
# violin 3
|
|
42
|
+
violin3Phrase = violin2Phrase.copy()
|
|
43
|
+
violin3Phrase.setStartTime(bar * 8.0) # start after 8 measures
|
|
44
|
+
Mod.elongate(violin3Phrase, 2.0) # double durations
|
|
45
|
+
Mod.transpose(violin3Phrase, -12) # an octave lower
|
|
46
|
+
|
|
47
|
+
# violin 4
|
|
48
|
+
violin4Phrase = violin3Phrase.copy()
|
|
49
|
+
violin4Phrase.setStartTime(bar * 10.0) # start after 10 measures
|
|
50
|
+
Mod.elongate(violin4Phrase, 2.0) # double durations
|
|
51
|
+
Mod.transpose(violin4Phrase, -12) # an octave lower
|
|
52
|
+
|
|
53
|
+
# repeat phrases enough times
|
|
54
|
+
Mod.repeat(violin1Phrase, 8 * repetitions)
|
|
55
|
+
Mod.repeat(violin2Phrase, 4 * repetitions)
|
|
56
|
+
Mod.repeat(violin3Phrase, 2 * repetitions)
|
|
57
|
+
Mod.repeat(violin4Phrase, repetitions)
|
|
58
|
+
|
|
59
|
+
# violin part
|
|
60
|
+
violinPart.addPhrase(violin1Phrase)
|
|
61
|
+
violinPart.addPhrase(violin2Phrase)
|
|
62
|
+
violinPart.addPhrase(violin3Phrase)
|
|
63
|
+
violinPart.addPhrase(violin4Phrase)
|
|
64
|
+
|
|
65
|
+
# score
|
|
66
|
+
cantusScore.addPart(bellPart)
|
|
67
|
+
cantusScore.addPart(violinPart)
|
|
68
|
+
|
|
69
|
+
# fade in, and fade out
|
|
70
|
+
Mod.fadeIn(cantusScore, WN)
|
|
71
|
+
Mod.fadeOut(cantusScore, WN * 12)
|
|
72
|
+
|
|
73
|
+
# view, play, and write
|
|
74
|
+
View.sketch(cantusScore)
|
|
75
|
+
Play.midi(cantusScore)
|
|
76
|
+
Write.midi (cantusScore, "ArvoPart.CantusInMemoriam.mid")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ConcretPH_Xenakis.py
|
|
2
|
+
#
|
|
3
|
+
# A short example which generates a random cloud texture
|
|
4
|
+
# inspired by Iannis Xenakis's 'Concret PH' composition
|
|
5
|
+
#
|
|
6
|
+
# see http://en.wikipedia.org/wiki/Concret_PH
|
|
7
|
+
|
|
8
|
+
from music import *
|
|
9
|
+
from random import *
|
|
10
|
+
|
|
11
|
+
# constants for controlling musical parameters
|
|
12
|
+
cloudWidth = 64 # length of piece (in quarter notes)
|
|
13
|
+
cloudDensity = 23.44 # how dense the cloud may be
|
|
14
|
+
particleDuration = 0.2 # how long each sound particle may be
|
|
15
|
+
numParticles = int(cloudDensity * cloudWidth) # how many particles
|
|
16
|
+
|
|
17
|
+
part = Part(BREATHNOISE)
|
|
18
|
+
|
|
19
|
+
# make particles (notes) and add them to cloud (part)
|
|
20
|
+
for i in range(numParticles):
|
|
21
|
+
|
|
22
|
+
# create note with random attributes
|
|
23
|
+
pitch = randint(0, 127) # pick from 0 to 127
|
|
24
|
+
duration = random() * particleDuration # 0 to particleDuration
|
|
25
|
+
dynamic = randint(0, 127) # pick from silent to loud
|
|
26
|
+
panning = random() # pick from left to right
|
|
27
|
+
note = Note(pitch, duration, dynamic, panning) # create note
|
|
28
|
+
|
|
29
|
+
# now, place it somewhere in the cloud (time continuum)
|
|
30
|
+
startTime = random() * cloudWidth # pick from 0 to end of piece
|
|
31
|
+
phrase = Phrase(startTime) # create phrase with this start time
|
|
32
|
+
phrase.addNote(note) # add the above note
|
|
33
|
+
part.addPhrase(phrase) # and add both to the part
|
|
34
|
+
# now, all notes have been created
|
|
35
|
+
|
|
36
|
+
# add some elegance to the end
|
|
37
|
+
Mod.fadeOut(part, 20)
|
|
38
|
+
|
|
39
|
+
View.show(part)
|
|
40
|
+
Play.midi(part)
|
|
41
|
+
Write.midi(part, "ConcretPh.mid")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# DeepPurple.SmokeOnTheWater.py
|
|
2
|
+
#
|
|
3
|
+
# Demonstrates how to combine melodic lines, chords, and
|
|
4
|
+
# percussion. This is based on the intro of "Smoke On the Water"
|
|
5
|
+
# by Deep Purple.
|
|
6
|
+
|
|
7
|
+
from music import *
|
|
8
|
+
|
|
9
|
+
##### define the data structure
|
|
10
|
+
score = Score("Deep Purple, Smoke On The Water", 110) # 110 bpm
|
|
11
|
+
|
|
12
|
+
guitarPart = Part(OVERDRIVE_GUITAR, 0)
|
|
13
|
+
bassPart = Part(ELECTRIC_BASS, 1)
|
|
14
|
+
drumPart = Part(0, 9) # using MIDI channel 9 (percussion)
|
|
15
|
+
|
|
16
|
+
guitarPhrase1 = Phrase(0.0) # guitar opening melody
|
|
17
|
+
guitarPhrase2 = Phrase(32.0) # guitar opening melody an octave lower
|
|
18
|
+
bassPhrase = Phrase(64.0) # bass melody
|
|
19
|
+
drumPhrase = Phrase(96.0) # drum pattern
|
|
20
|
+
|
|
21
|
+
##### create musical data
|
|
22
|
+
# guitar opening melody (16QN = 4 measures)
|
|
23
|
+
guitarPitches = [G2, AS2, C3, G2, AS2, CS3, C3, G2, AS2, C3, AS2,
|
|
24
|
+
G2]
|
|
25
|
+
guitarDurations = [QN, QN, DQN, QN, QN, EN, HN, QN, QN, DQN, QN,
|
|
26
|
+
DHN+EN]
|
|
27
|
+
guitarPhrase1.addNoteList(guitarPitches, guitarDurations)
|
|
28
|
+
|
|
29
|
+
# create a power-chord sound by repeating the melody an octave lower
|
|
30
|
+
guitarPhrase2.addNoteList(guitarPitches, guitarDurations)
|
|
31
|
+
Mod.transpose(guitarPhrase2, -12)
|
|
32
|
+
|
|
33
|
+
# bass melody (32EN = 4 measures)
|
|
34
|
+
bassPitches1 = [G2, G2, G2, G2, G2, G2, G2, G2, G2, G2,
|
|
35
|
+
G2, G2, G2, G2, G2, G2, G2, G2]
|
|
36
|
+
bassDurations1 = [EN, EN, EN, EN, EN, EN, EN, EN, EN, EN,
|
|
37
|
+
EN, EN, EN, EN, EN, EN, EN, EN]
|
|
38
|
+
bassPitches2 = [AS2, AS2, C3, C3, C3, AS2, AS2, G2, G2,
|
|
39
|
+
G2, G2, G2, G2, G2]
|
|
40
|
+
bassDurations2 = [EN, EN, EN, EN, EN, EN, EN, EN, EN,
|
|
41
|
+
EN, EN, EN, EN, EN]
|
|
42
|
+
bassPhrase.addNoteList(bassPitches1, bassDurations1)
|
|
43
|
+
bassPhrase.addNoteList(bassPitches2, bassDurations2)
|
|
44
|
+
|
|
45
|
+
# snare drum pattern (2QN x 8 = 4 measures)
|
|
46
|
+
drumPitches = [REST, SNR] * 8
|
|
47
|
+
drumDurations = [QN, QN] * 8
|
|
48
|
+
drumPhrase.addNoteList(drumPitches, drumDurations)
|
|
49
|
+
|
|
50
|
+
##### repeat material as needed (based on how it's arranged in time)
|
|
51
|
+
Mod.repeat(guitarPhrase1, 8)
|
|
52
|
+
Mod.repeat(guitarPhrase2, 6)
|
|
53
|
+
Mod.repeat(bassPhrase, 4)
|
|
54
|
+
Mod.repeat(drumPhrase, 2)
|
|
55
|
+
|
|
56
|
+
##### combine musical material
|
|
57
|
+
guitarPart.addPhrase(guitarPhrase1)
|
|
58
|
+
guitarPart.addPhrase(guitarPhrase2)
|
|
59
|
+
bassPart.addPhrase(bassPhrase)
|
|
60
|
+
drumPart.addPhrase(drumPhrase)
|
|
61
|
+
score.addPart(guitarPart)
|
|
62
|
+
score.addPart(bassPart)
|
|
63
|
+
score.addPart(drumPart)
|
|
64
|
+
|
|
65
|
+
##### play and write score to a MIDI file
|
|
66
|
+
Play.midi(score)
|
|
67
|
+
Write.midi(score, "DeepPurple.SmokeOnTheWater.mid")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# JS_Bach.Canon.TriasHarmonica.BWV1072.py
|
|
2
|
+
#
|
|
3
|
+
# This program creates J.S. Bach's 'Trias Harmonica' BWV 1072 canon.
|
|
4
|
+
#
|
|
5
|
+
# This canon is constructed using two parts (choirs), each consisting
|
|
6
|
+
# of four voices. The first part's voices use the original theme,
|
|
7
|
+
# each separated by a half-note delay. The second part's voices use
|
|
8
|
+
# the inverted theme delayed by a quarter note, each separated by
|
|
9
|
+
# a half-note delay.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
from music import *
|
|
13
|
+
|
|
14
|
+
# define the theme (for choir 1)
|
|
15
|
+
pitches1 = [C4, D4, E4, F4, G4, F4, E4, D4]
|
|
16
|
+
durations1 = [DQN, EN, DQN, EN, DQN, EN, DQN, EN]
|
|
17
|
+
|
|
18
|
+
# define the inverted theme (for choir 2)
|
|
19
|
+
pitches2 = [G4, F4, E4, D4, C4, D4, E4, F4]
|
|
20
|
+
durations2 = [DQN, EN, DQN, EN, DQN, EN, DQN, EN]
|
|
21
|
+
|
|
22
|
+
# how many times to repeat the theme
|
|
23
|
+
times = 8
|
|
24
|
+
|
|
25
|
+
# choir 1 - 4 voices separated by half note
|
|
26
|
+
choir1 = Part()
|
|
27
|
+
|
|
28
|
+
voice1 = Phrase(0.0)
|
|
29
|
+
voice1.addNoteList(pitches1, durations1)
|
|
30
|
+
Mod.repeat(voice1, times) # repeat a number of times
|
|
31
|
+
voice1.addNote(C4, DQN) # add final note
|
|
32
|
+
|
|
33
|
+
voice2 = voice1.copy() # voice 2 is a copy of voice 1
|
|
34
|
+
voice2.setStartTime(HN) # separated by half note
|
|
35
|
+
|
|
36
|
+
voice3 = voice1.copy() # voice 3 is a copy of voice 1
|
|
37
|
+
voice3.setStartTime(HN*2) # separated by two half notes
|
|
38
|
+
|
|
39
|
+
voice4 = voice1.copy() # voice 4 is a copy of voice 1
|
|
40
|
+
voice4.setStartTime(HN*3) # separated by three half notes
|
|
41
|
+
|
|
42
|
+
choir1.addPhrase(voice1)
|
|
43
|
+
choir1.addPhrase(voice2)
|
|
44
|
+
choir1.addPhrase(voice3)
|
|
45
|
+
choir1.addPhrase(voice4)
|
|
46
|
+
|
|
47
|
+
# choir 2 - 4 voices inverted, delayed by quarter note,
|
|
48
|
+
# separated by a half note.
|
|
49
|
+
choir2 = Part()
|
|
50
|
+
|
|
51
|
+
voice5 = Phrase(QN) # delayed by quarter note
|
|
52
|
+
voice5.addNoteList(pitches2, durations2)
|
|
53
|
+
Mod.repeat(voice5, times) # repeat a number of times
|
|
54
|
+
voice5.addNote(G4, DQN) # add final note
|
|
55
|
+
|
|
56
|
+
voice6 = voice5.copy() # voice 6 is a copy of voice 5
|
|
57
|
+
voice6.setStartTime(QN + HN) # separated by half note
|
|
58
|
+
|
|
59
|
+
voice7 = voice5.copy() # voice 7 is a copy of voice 5
|
|
60
|
+
voice7.setStartTime(QN + HN*2) # separated by two half notes
|
|
61
|
+
|
|
62
|
+
voice8 = voice5.copy() # voice 8 is a copy of voice 5
|
|
63
|
+
voice8.setStartTime(QN + HN*3) # separated by three half note
|
|
64
|
+
|
|
65
|
+
choir2.addPhrase(voice5)
|
|
66
|
+
choir2.addPhrase(voice6)
|
|
67
|
+
choir2.addPhrase(voice7)
|
|
68
|
+
choir2.addPhrase(voice8)
|
|
69
|
+
|
|
70
|
+
# score
|
|
71
|
+
canon = Score("J.S. Bach, Trias Harmonica (BWV 1072)", 100)
|
|
72
|
+
canon.addPart(choir1)
|
|
73
|
+
canon.addPart(choir2)
|
|
74
|
+
|
|
75
|
+
# play score, and write it to a MIDI file
|
|
76
|
+
Play.midi(canon)
|
|
77
|
+
Write.midi(canon, "JS_Bach.Canon.TriasHarmonica.BWV1072.mid")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# JS_Bach.Canon_1.GoldbergGround.BWV1087.py
|
|
2
|
+
#
|
|
3
|
+
# This program (re)creates J.S. Bach's Canon No. 1 of the Fourteen on
|
|
4
|
+
# the Goldberg Ground.
|
|
5
|
+
#
|
|
6
|
+
# This canon is constructed using the Goldberg ground as the subject
|
|
7
|
+
# (soggetto) combined with the retrograde of itself.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
from music import *
|
|
11
|
+
|
|
12
|
+
# how many times to repeat the theme
|
|
13
|
+
times = 6
|
|
14
|
+
|
|
15
|
+
# define the data structure
|
|
16
|
+
score = Score("J.S. Bach, Canon 1, Goldberg Ground (BWV1087)", 100)
|
|
17
|
+
part = Part()
|
|
18
|
+
voice1 = Phrase(0.0)
|
|
19
|
+
|
|
20
|
+
# create musical material (soggetto)
|
|
21
|
+
pitches = [G3, F3, E3, D3, B2, C3, D3, G2]
|
|
22
|
+
rhythms = [QN, QN, QN, QN, QN, QN, QN, QN]
|
|
23
|
+
voice1.addNoteList(pitches, rhythms)
|
|
24
|
+
|
|
25
|
+
# create 2nd voice
|
|
26
|
+
voice2 = voice1.copy()
|
|
27
|
+
Mod.retrograde(voice2) # follower is retrograde of leader
|
|
28
|
+
|
|
29
|
+
# combine musical material
|
|
30
|
+
part.addPhrase(voice1)
|
|
31
|
+
part.addPhrase(voice2)
|
|
32
|
+
score.addPart(part)
|
|
33
|
+
|
|
34
|
+
# repeat canon as desired
|
|
35
|
+
Mod.repeat(score, times)
|
|
36
|
+
|
|
37
|
+
# play score and write it to a MIDI file
|
|
38
|
+
Play.midi(score)
|
|
39
|
+
Write.midi(score, "JS_Bach.Canon_1.GoldbergGround.BWV1087.mid")
|