├── README.md
├── USAGE_EXAMPLE.md
├── abc2midi.ipynb
├── chords2midi.ipynb
├── chords2midi.py
└── mido2midi.ipynb
/README.md:
--------------------------------------------------------------------------------
1 | # Writing music with ChatGPT
2 |
3 | Tips and tools for experimenting with writing music with the aid of [ChatGPT](https://ai.com), and getting its notations as MIDI files.
4 |
5 | See video of example usage [here](https://github.com/olaviinha/MusicWithChatGPT/blob/main/USAGE_EXAMPLE.md).
6 |
7 | If you have tips, [please share](https://github.com/olaviinha/MusicWithChatGPT/discussions)!
8 |
9 | ## Additions to Message ("prompt")
10 |
11 | These tips work as addition to your freely formed message to ChatGPT, where you ask for any musical notation. These tips usually result in notation only, meaning the converted MIDI file will be piano-only. Asking ChatGPT to produce notation with different instruments (such as drums) that would translate to MIDI seems hard and prone to failure so far.
12 |
13 | Example message: `Can you write an emotional sci-fi theme `...
14 |
15 | - `in ABC notation?`
16 | Produces a copyable ABC notation block that can be copy-pasted to [abc2midi notebook](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/abc2midi.ipynb?force_theme=dark), which will convert it to a MIDI file and provide an instant download. So far this is the best method.
17 |
18 | - `chord progression? Please do not add further explanations about the progressions.`
19 | Produces textual chord progression that can be copy-pasted to [chords2midi notebook](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/chords2midi.ipynb?force_theme=dark), which will convert it to a MIDI file and provide an instant download. Asking ChatGPT not to add further explanations will prevent having to manually edit the copy-pasted text when using the notebook, as ChatGPT will often mention chords in the explanations, and those chords will also end up in the MIDI files (messing up the intended progression).
20 | You may also quickly preview and edit provided chord progressions by copy-pasting them to [Chords Guru Turbo 100a Deluxe](https://ki.gy/cv) instead (midi export currently offline).
21 |
22 | - `to a MIDI file using Python Mido?`
23 | Produces a copyable code block that can be copy-pasted directly to [mido2midi notebook](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/mido2midi.ipynb?force_theme=dark) and executed, saving a MIDI file (providing ChatGPT did it right).
24 | **Note:** you don't have to understand any of the code, all you need to do is copy-paste it.
25 | **Fair warning:** this method is prone to failures (sour code from ChatGPT) and generally sounds like more random notation.
26 |
27 | You may also want to make your request more specific by adding further instructions to your prompt, such as ...`It should be in 110 BPM tempo and A major key. It should have a verse, a bridge and a chorus.`
28 |
29 | ## Tools
30 |
31 | - [abc2midi](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/abc2midi.ipynb?force_theme=dark) – saves ABC notation as MIDI file.
32 | - [chords2midi](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/chords2midi.ipynb?force_theme=dark) – saves textual chord progression as MIDI file.
33 | - [mido2midi](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/mido2midi.ipynb?force_theme=dark) – executes Mido code block, thus saving a MIDI file.
34 | - [Chords Guru Turbo 100a Deluxe](https://ki.gy/cv) – quickly preview/edit textual chord progressions.
35 |
--------------------------------------------------------------------------------
/USAGE_EXAMPLE.md:
--------------------------------------------------------------------------------
1 | [<- Go back to main page](https://github.com/olaviinha/MusicWithChatGPT)
2 |
3 | ---
4 |
5 | ## Usage example with [chords2midi](https://colab.research.google.com/github/olaviinha/MusicWithChatGPT/blob/main/chords2midi.ipynb?force_theme=dark) notebook:
6 |
7 | https://user-images.githubusercontent.com/50331907/233775523-2c1ba015-0c15-4bbf-b060-7c4c87647b21.mp4
8 |
9 | Once you have the notebook open and you have executed the Setup cell, you may naturally copy/paste and download new chord progressions as many times as you like:
10 |
11 | https://user-images.githubusercontent.com/50331907/233775526-c6f1c886-c289-4c19-99db-ca05a25e4c18.mp4
12 |
13 | In this latter example
14 | - we untick `auto_download` -> Download buttons will appear instead of automatic downloads
15 | - we tick `midi_file_per_line` -> a separate midi file will be generated for each line of text we pasted in the right side box (Verse, Chorus and Bridge will be in their own midi files)
16 |
17 |
--------------------------------------------------------------------------------
/abc2midi.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "mount_file_id": "1UYVdwqGUR0AGaDoWB6eNIJuIrrwhyV5L",
8 | "authorship_tag": "ABX9TyMf1oYjfAq3R6wQok9uwBxC",
9 | "include_colab_link": true
10 | },
11 | "kernelspec": {
12 | "name": "python3",
13 | "display_name": "Python 3"
14 | },
15 | "language_info": {
16 | "name": "python"
17 | }
18 | },
19 | "cells": [
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {
23 | "id": "view-in-github",
24 | "colab_type": "text"
25 | },
26 | "source": [
27 | "
"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "source": [
33 | "#abc2mid | Music with ChatGPT | Github\n",
34 | "\n",
35 | "Converts ABC notations to MIDI.\n",
36 | "\n",
37 | "---\n",
38 | "\n",
39 | "### If you are using ChatGPT at the same time:\n",
40 | "\n",
41 | "- Run the _Setup_ cell by pressing the play button right under the _Setup_ title.\n",
42 | "- A box will open on the right side of the screen. You may copy-paste ABC notation block directly from ChatGPT to that box and use the _Download the ABC notation entered in the right side box as .mid_ cell to immediately download whatever you currently have in the box, as a MIDI file. You can paste new ABC notation in the box and run the cell again, as often and many times as you like.\n",
43 | "\n",
44 | "- Don't be alarmed by the MIDI-related warnings in the cell: most warnings can be ignored and MIDI file will work fine.\n",
45 | "
\n",
46 | "\n",
47 | "### If you have saved `.abc` files and want to convert them to `.mid`:\n",
48 | "\n",
49 | "You can copy-paste ABC notation block from ChatGPT to a text editor and save it as `.abc` to your Google Drive (to convert it later). If you have already done that:\n",
50 | "\n",
51 | "- Run the _Setup_ cell by pressing the play button right under the Setup title.\n",
52 | "\n",
53 | "- Use the _Convert .abc files to .mid files_ cell.\n",
54 | "\n",
55 | "- All file or directory paths entered in this cell should be relative to your Google Drive root (My Drive).
E.g. `input` value should be `Music/ABC-files/rasputin.abc` if you have a directory called _Music_ in your drive, containing a subdirectory called _ABC-files_, containing a file _rasputin.abc_. If you enter a directory path, all `.abc` files found in the directory will be converted to `.mid`.\n",
56 | "\n",
57 | "- If you opt not to mount Drive, Files browser should open on the left side of the screen. Use it to upload/download files, and enter paths relative to that directory.
E.g. `input` value should be `rasputin.abc` if you uploaded `rasputin.abc` to the box on the left.\n",
58 | "\n"
59 | ],
60 | "metadata": {
61 | "id": "GBgr33OisX3y"
62 | }
63 | },
64 | {
65 | "cell_type": "code",
66 | "source": [
67 | "#@title #Setup\n",
68 | "#@markdown This cell needs to be run only once. It will setup prerequisites.
\n",
69 | "\n",
70 | "force_setup = False\n",
71 | "repositories = ['https://github.com/sshlien/abcmidi']\n",
72 | "pip_packages = ''\n",
73 | "apt_packages = ''\n",
74 | "mount_drive = False #@ param {type:\"boolean\"}\n",
75 | "skip_setup = False #@ param {type:\"boolean\"}\n",
76 | "drive_mounted = False\n",
77 | "\n",
78 | "# Download the repo from Github\n",
79 | "import os\n",
80 | "from google.colab import output, files\n",
81 | "import warnings\n",
82 | "warnings.filterwarnings('ignore')\n",
83 | "%cd /content/\n",
84 | "\n",
85 | "# inhagcutils\n",
86 | "if not os.path.isfile('/content/inhagcutils.ipynb') and force_setup == False:\n",
87 | " !pip -q install import-ipynb {pip_packages}\n",
88 | " if apt_packages != '':\n",
89 | " !apt-get update && apt-get install {apt_packages}\n",
90 | " !curl -s -O https://raw.githubusercontent.com/olaviinha/inhagcutils/master/inhagcutils.ipynb\n",
91 | "import import_ipynb\n",
92 | "from inhagcutils import *\n",
93 | "\n",
94 | "drive_root = '/content/faux_drive/'\n",
95 | "if not os.path.isdir(drive_root):\n",
96 | " os.mkdir(drive_root)\n",
97 | "\n",
98 | "if len(repositories) > 0 and skip_setup == False:\n",
99 | " for repo in repositories:\n",
100 | " %cd /content/\n",
101 | " install_dir = fix_path('/content/'+path_leaf(repo).replace('.git', ''))\n",
102 | " repo = repo if '.git' in repo else repo+'.git'\n",
103 | " !git clone {repo}\n",
104 | " if os.path.isfile(install_dir+'setup.py') or os.path.isfile(install_dir+'setup.cfg'):\n",
105 | " !pip install -e ./{install_dir}\n",
106 | " if os.path.isfile(install_dir+'requirements.txt'):\n",
107 | " !pip install -r {install_dir}/requirements.txt\n",
108 | "\n",
109 | "if len(repositories) == 1:\n",
110 | " %cd {install_dir}\n",
111 | "\n",
112 | "dir_tmp = '/content/tmp/'\n",
113 | "create_dirs([dir_tmp])\n",
114 | "\n",
115 | "import time, sys, re\n",
116 | "from datetime import timedelta\n",
117 | "import math\n",
118 | "\n",
119 | "# Build abc2midi\n",
120 | "%cd /content/abcmidi\n",
121 | "!make\n",
122 | "!make install\n",
123 | "%cd /content\n",
124 | "\n",
125 | "notation_file = \"/content/Paste ABC notation here\"\n",
126 | "\n",
127 | "# Create abc-notation.abc\n",
128 | "!echo '' > \"{notation_file}\"\n",
129 | "\n",
130 | "output.clear()\n",
131 | "\n",
132 | "# Open right box\n",
133 | "files.view(notation_file)\n",
134 | "files.view(notation_file)\n",
135 | "\n",
136 | "# !nvidia-smi\n",
137 | "op(c.ok, 'Setup finished.', time=True)"
138 | ],
139 | "metadata": {
140 | "id": "Zl44n6FXsbnY",
141 | "cellView": "form"
142 | },
143 | "execution_count": null,
144 | "outputs": []
145 | },
146 | {
147 | "cell_type": "code",
148 | "source": [
149 | "#@title # Download the ABC notation entered in the right side box as `.mid`\n",
150 | "#@markdown This cell will convert the current contents of the right side box to MIDI and provide a download. You may paste new content in the box and re-run this cell as many times as you like.\n",
151 | "\n",
152 | "# #@markdown If you enter a directory path in output_dir, MIDI files will be automatically saved there. Otherwise you'll get a file download when running this cell.\n",
153 | "# output_dir = \"temp/abc/midi\" #@param {type:\"string\"}\n",
154 | "\n",
155 | "uniq_id = gen_id()\n",
156 | "\n",
157 | "with open(notation_file, 'r') as f:\n",
158 | " abc_text = f.read()\n",
159 | "\n",
160 | "title_match = re.search('^T:(.*)$', abc_text, re.MULTILINE)\n",
161 | "if title_match:\n",
162 | " song_title = title_match.group(1).strip()\n",
163 | " file_out = dir_tmp+slug(song_title)+'_'+uniq_id+'.mid'\n",
164 | "else:\n",
165 | " song_title = uniq_id\n",
166 | " file_out = dir_tmp+uniq_id+'.mid'\n",
167 | " op(c.warn, 'No song title found, using:', uniq_id, time=True)\n",
168 | "\n",
169 | "\n",
170 | "!abc2midi \"{notation_file}\" -o \"{file_out}\"\n",
171 | "\n",
172 | "print()\n",
173 | "if os.path.isfile(file_out):\n",
174 | " op(c.ok, 'MIDI saved as', path_leaf(file_out), time=True)\n",
175 | " op(c.title, 'Downloading...', time=True)\n",
176 | " files.download(file_out)\n",
177 | "else:\n",
178 | " op(c.fail, 'Error saving MIDI.', time=True)"
179 | ],
180 | "metadata": {
181 | "id": "vMwNIN3j3Rhr",
182 | "cellView": "form"
183 | },
184 | "execution_count": null,
185 | "outputs": []
186 | },
187 | {
188 | "cell_type": "code",
189 | "source": [
190 | "#@title # Convert `.abc` files to`.mid` files\n",
191 | "\n",
192 | "#@markdown If you have saved ABC notations as `.abc` files using a text editor, use this cell to conver them to MIDI files. If a directory path is entered as `input`, all `.abc` files in the directory will be converted.\n",
193 | "\n",
194 | "mount_drive = True #@param {type:\"boolean\"}\n",
195 | "input = \"\" #@param {type:\"string\"}\n",
196 | "output_dir = \"\" #@param {type:\"string\"}\n",
197 | "end_session_when_done = False #@ param {type: \"boolean\"}\n",
198 | "\n",
199 | "\n",
200 | "# Mount Drive\n",
201 | "if mount_drive == True and drive_mounted == False:\n",
202 | " from google.colab import drive\n",
203 | " drive.mount('/content/drive')\n",
204 | " drive_root = '/content/drive/My Drive'\n",
205 | " os.symlink('/content/drive/My Drive', '/content/mydrive')\n",
206 | " drive_root = '/content/mydrive/'\n",
207 | " drive_mounted = True\n",
208 | "else:\n",
209 | " if mount_drive == False:\n",
210 | " files.view('/content/faux_drive/')\n",
211 | " files.view('/content/faux_drive/')\n",
212 | " op(c.title, 'Since you did not mount drive, please upload your files to the directory that opened on the left side of the screen. You can ignore input and output_dir and just run this cell after you have uploaded your files.')\n",
213 | "\n",
214 | "\n",
215 | "uniq_id = gen_id()\n",
216 | "\n",
217 | "if drive_mounted == True:\n",
218 | " if os.path.isfile(drive_root+input):\n",
219 | " inputs = [drive_root+input]\n",
220 | " dir_in = path_dir(drive_root+input)\n",
221 | " elif input != '' and os.path.isdir(drive_root+input):\n",
222 | " dir_in = drive_root+fix_path(input)\n",
223 | " # What to do if input is directory path\n",
224 | " inputs = glob(dir_in+'*.abc') + glob(dir_in+'*.ABC')\n",
225 | " elif os.path.isdir(drive_root+input) and '*' in input:\n",
226 | " dir_in = path_dir(drive_root+input)\n",
227 | " inputs = glob(drive_root+input)\n",
228 | " else:\n",
229 | " op(c.fail, 'FAIL!', 'Input should be a path to a file or a directory.')\n",
230 | " sys.exit('Input not understood.')\n",
231 | "else:\n",
232 | " if os.path.isdir(drive_root+input):\n",
233 | " dir_in = drive_root+fix_path(input)\n",
234 | " inputs = glob(dir_in+'*.abc') + glob(dir_in+'*.ABC')\n",
235 | " elif os.path.isfile(drive_root+input):\n",
236 | " dir_in = path_dir(drive_root+input)\n",
237 | " inputs = [input]\n",
238 | "\n",
239 | "# Output\n",
240 | "if output_dir == '':\n",
241 | " if mount_drive is True:\n",
242 | " dir_out = dir_in\n",
243 | " else:\n",
244 | " dir_out = drive_root\n",
245 | "else:\n",
246 | " if not os.path.isdir(drive_root+output_dir):\n",
247 | " os.mkdir(drive_root+output_dir)\n",
248 | " dir_out = drive_root+fix_path(output_dir)\n",
249 | "\n",
250 | "timer_start = time.time()\n",
251 | "total = len(inputs)\n",
252 | "\n",
253 | "# -- DO THINGS --\n",
254 | "for i, input in enumerate(inputs, 1):\n",
255 | " ndx_info = str(i)+'/'+str(total)+' '\n",
256 | " op(c.title, ndx_info+'Processing', input, time=True)\n",
257 | " file_out = dir_out+basename(input)+'__'+uniq_id+str(i).zfill(2)+'.mid'\n",
258 | " !abc2midi \"{input}\" -o \"{file_out}\"\n",
259 | " if os.path.isfile(file_out):\n",
260 | " op(c.ok, 'File saved as', file_out.replace(drive_root, ''), time=True)\n",
261 | " else:\n",
262 | " op(c.fail, 'ERROR saving file', file_out.replace(drive_root, ''), time=True)\n",
263 | "# -- END THINGS --\n",
264 | "\n",
265 | "timer_end = time.time()\n",
266 | "\n",
267 | "print()\n",
268 | "op(c.okb, 'Elapsed', timedelta(seconds=timer_end-timer_start), time=True)\n",
269 | "op(c.ok, 'FIN.')\n",
270 | "\n",
271 | "if end_session_when_done is True: end_session()"
272 | ],
273 | "metadata": {
274 | "id": "Znu4P-Gtsdrr",
275 | "cellView": "form"
276 | },
277 | "execution_count": null,
278 | "outputs": []
279 | }
280 | ]
281 | }
--------------------------------------------------------------------------------
/chords2midi.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "mount_file_id": "1UYVdwqGUR0AGaDoWB6eNIJuIrrwhyV5L",
8 | "authorship_tag": "ABX9TyNqDw1i+5nMdd7unM06D5VW",
9 | "include_colab_link": true
10 | },
11 | "kernelspec": {
12 | "name": "python3",
13 | "display_name": "Python 3"
14 | },
15 | "language_info": {
16 | "name": "python"
17 | }
18 | },
19 | "cells": [
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {
23 | "id": "view-in-github",
24 | "colab_type": "text"
25 | },
26 | "source": [
27 | "
"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "source": [
33 | "#chords2mid | Music with ChatGPT | Github\n",
34 | "\n",
35 | "Saves chord progressions as midi.\n",
36 | "\n",
37 | "---\n",
38 | "\n",
39 | "- Run the _Setup_ cell by pressing the play button right under the _Setup_ title.\n",
40 | "- A box will open on the right side of the screen. You may copy-paste a chord progression directly from ChatGPT to that box and run the _Download chord progression as .mid_ cell to immediately download whatever you currently have in the box, as a MIDI file. You can paste new chord progression in the box and run the cell again, as often and many times as you like.\n",
41 | "- All valid chords will be parsed from the text entered in the right side box. Any lines of descriptive text that mention chord(s) should be removed, as otherwise those chords will also end up in the MIDI file.\n",
42 | "- `humanize` will add light randomized variation to note velocities (volume) and start times.\n",
43 | "- `midi_file_per_line` will generate an individual MIDI file for each line pasted in the box. Otherwise all chords are saved as a single MIDI file.\n",
44 | "- `auto_download` will send all generated MIDI files to your browser for download as soon as they are generated. Note: if you used together with `midi_file_per_line`, your browser may be flooded with downloads."
45 | ],
46 | "metadata": {
47 | "id": "GBgr33OisX3y"
48 | }
49 | },
50 | {
51 | "cell_type": "code",
52 | "source": [
53 | "#@title #Setup\n",
54 | "#@markdown This cell needs to be run only once. It will setup prerequisites.
\n",
55 | "# #@markdown Mounting Drive will enable this notebook to save outputs directly to your Drive. Otherwise you will need to copy/download them manually from this notebook.\n",
56 | "\n",
57 | "force_setup = False\n",
58 | "repositories = []\n",
59 | "pip_packages = 'pychord mido'\n",
60 | "apt_packages = ''\n",
61 | "mount_drive = False #@ param {type:\"boolean\"}\n",
62 | "skip_setup = False #@ param {type:\"boolean\"}\n",
63 | "\n",
64 | "# Download the repo from Github\n",
65 | "import os\n",
66 | "from google.colab import output, files\n",
67 | "import warnings\n",
68 | "warnings.filterwarnings('ignore')\n",
69 | "%cd /content/\n",
70 | "\n",
71 | "# inhagcutils\n",
72 | "if not os.path.isfile('/content/inhagcutils.ipynb') and force_setup == False:\n",
73 | " !pip -q install import-ipynb {pip_packages}\n",
74 | " if apt_packages != '':\n",
75 | " !apt-get update && apt-get install {apt_packages}\n",
76 | " !curl -s -O https://raw.githubusercontent.com/olaviinha/inhagcutils/master/inhagcutils.ipynb\n",
77 | "import import_ipynb\n",
78 | "from inhagcutils import *\n",
79 | "\n",
80 | "# Mount Drive\n",
81 | "if mount_drive == True:\n",
82 | " if not os.path.isdir('/content/drive'):\n",
83 | " from google.colab import drive\n",
84 | " drive.mount('/content/drive')\n",
85 | " drive_root = '/content/drive/My Drive'\n",
86 | " if not os.path.isdir('/content/mydrive'):\n",
87 | " os.symlink('/content/drive/My Drive', '/content/mydrive')\n",
88 | " drive_root = '/content/mydrive/'\n",
89 | " drive_root_set = True\n",
90 | "else:\n",
91 | " create_dirs(['/content/faux_drive'])\n",
92 | " drive_root = '/content/faux_drive/'\n",
93 | "\n",
94 | "if len(repositories) > 0 and skip_setup == False:\n",
95 | " for repo in repositories:\n",
96 | " %cd /content/\n",
97 | " install_dir = fix_path('/content/'+path_leaf(repo).replace('.git', ''))\n",
98 | " repo = repo if '.git' in repo else repo+'.git'\n",
99 | " !git clone {repo}\n",
100 | " if os.path.isfile(install_dir+'setup.py') or os.path.isfile(install_dir+'setup.cfg'):\n",
101 | " !pip install -e ./{install_dir}\n",
102 | " if os.path.isfile(install_dir+'requirements.txt'):\n",
103 | " !pip install -r {install_dir}/requirements.txt\n",
104 | "\n",
105 | "if len(repositories) == 1:\n",
106 | " %cd {install_dir}\n",
107 | "\n",
108 | "dir_tmp = '/content/tmp/'\n",
109 | "create_dirs([dir_tmp])\n",
110 | "\n",
111 | "import time, sys\n",
112 | "from datetime import timedelta\n",
113 | "import math\n",
114 | "from pychord import Chord\n",
115 | "import librosa\n",
116 | "from mido import Message, MidiFile, MidiTrack, MetaMessage, bpm2tempo\n",
117 | "import itertools\n",
118 | "\n",
119 | "chords_file = \"/content/Paste chord progression here\"\n",
120 | "!echo '' > \"{chords_file}\"\n",
121 | "\n",
122 | "output.clear()\n",
123 | "# !nvidia-smi\n",
124 | "op(c.ok, 'Setup finished.', time=True)\n",
125 | "\n",
126 | "\n",
127 | "files.view(chords_file)\n",
128 | "files.view(chords_file)"
129 | ],
130 | "metadata": {
131 | "id": "Zl44n6FXsbnY",
132 | "cellView": "form"
133 | },
134 | "execution_count": null,
135 | "outputs": []
136 | },
137 | {
138 | "cell_type": "code",
139 | "source": [
140 | "#@title ## Download chord progression as `.mid`\n",
141 | "octave = 3 #@param {type:\"integer\"}\n",
142 | "bpm = 120 #@param {type:\"integer\"}\n",
143 | "humanize = False #@param {type:\"boolean\"}\n",
144 | "midi_file_per_line = False #@param {type:\"boolean\"}\n",
145 | "auto_download = True #@param {type:\"boolean\"}\n",
146 | "repeat_progression = 1 #@ param {type:\"integer\"}\n",
147 | "\n",
148 | "\n",
149 | "chords_per_bar = 1\n",
150 | "uniq_id = gen_id()\n",
151 | "timer_start = time.time()\n",
152 | "\n",
153 | "with open(chords_file, 'r') as f:\n",
154 | " chords_string = f.read()\n",
155 | "\n",
156 | "lines = [x.replace('\\t', ' ') for x in chords_string.split('\\n')]\n",
157 | "\n",
158 | "chord_series = []\n",
159 | "for line in lines:\n",
160 | " words = line.split(' ')\n",
161 | " chord_series.append(' '.join([x.replace(',', '').replace('.', '') for x in words]).split(' '))\n",
162 | "\n",
163 | "if midi_file_per_line == False:\n",
164 | " chord_series = [list(itertools.chain.from_iterable(chord_series))]\n",
165 | "\n",
166 | "for i, words in enumerate(chord_series, 1):\n",
167 | "\n",
168 | " chords = []\n",
169 | " for word in words:\n",
170 | " try:\n",
171 | " Chord(word)\n",
172 | " chords.append(word)\n",
173 | " except:\n",
174 | " pass\n",
175 | "\n",
176 | " if len(chords) > 0:\n",
177 | " chords_found = len(chords)\n",
178 | " midi_chords = []\n",
179 | " for chord in chords:\n",
180 | " chord = Chord(chord)\n",
181 | " notes = chord.components_with_pitch(root_pitch=octave)\n",
182 | " midi_chords.append([librosa.note_to_midi(note) for note in notes])\n",
183 | "\n",
184 | " op(c.title, 'Generating MIDI from '+str(chords_found)+' chords:')\n",
185 | " print(' '.join(chords))\n",
186 | "\n",
187 | " octave_shift = 0\n",
188 | " velocity_min = 64\n",
189 | " velocity_max = 84\n",
190 | "\n",
191 | " tpb = 128\n",
192 | " bar = tpb\n",
193 | " humanization_amount = 9\n",
194 | "\n",
195 | " mid = MidiFile(ticks_per_beat=tpb)\n",
196 | " track = MidiTrack()\n",
197 | " mid.tracks.append(track)\n",
198 | " track.append(MetaMessage('time_signature', numerator=4, denominator=4, clocks_per_click=24, notated_32nd_notes_per_beat=8, time=0))\n",
199 | " track.append(MetaMessage('set_tempo', tempo=bpm2tempo(bpm), time=0))\n",
200 | "\n",
201 | " for repeat in range(repeat_progression):\n",
202 | " for i, chord in enumerate(midi_chords, 1):\n",
203 | " velocity = random.randrange(velocity_min, velocity_max) if velocity_min != velocity_max else velocity_max\n",
204 | " start = 0 if humanize == False else random.randint(0, humanization_amount)\n",
205 | " end = bar\n",
206 | " for note in chord:\n",
207 | " track.append(Message('note_on', note=int(note), velocity=velocity, time=start))\n",
208 | " for note in chord:\n",
209 | " track.append(Message('note_off', note=int(note), velocity=tpb-1, time=end))\n",
210 | "\n",
211 | " fn_chords = '_'.join([slug(c).capitalize() for c in chords[:4]])\n",
212 | " file_out = '/content/'+uniq_id+'_'+str(i).zfill(2)+'_'+fn_chords+'.mid'\n",
213 | " mid.save(file_out)\n",
214 | "\n",
215 | " print()\n",
216 | " if os.path.isfile(file_out):\n",
217 | " if auto_download:\n",
218 | " files.download(file_out)\n",
219 | " else:\n",
220 | " dl_btn(file_out)\n",
221 | " # dl_btn(file_out)\n",
222 | " print()\n",
223 | " else:\n",
224 | " op(c.fail, 'Error occurred.')\n",
225 | "\n",
226 | "\n",
227 | "timer_end = time.time()\n",
228 | "op(c.ok, 'Done.')\n",
229 | "print()\n",
230 | "\n",
231 | "# op(c.okb, 'Elapsed', timedelta(seconds=timer_end-timer_start), time=True)\n",
232 | "# op(c.ok, 'FIN.')\n",
233 | "\n",
234 | "# if end_session_when_done is True: end_session()"
235 | ],
236 | "metadata": {
237 | "id": "X1aoj6UchILO",
238 | "cellView": "form"
239 | },
240 | "execution_count": null,
241 | "outputs": []
242 | }
243 | ]
244 | }
--------------------------------------------------------------------------------
/chords2midi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Prerequisites:
4 | # pip install pychord mido
5 |
6 | import os
7 | import sys
8 | import argparse
9 | import random
10 | import mido
11 | from pychord import Chord
12 | from mido import Message, MidiFile, MidiTrack, MetaMessage
13 |
14 | def main(arguments):
15 |
16 | parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
17 | parser.add_argument('--chords', help="Chords as midi notes in format \"60,62|70,73|80,82,84\"", type=str)
18 | parser.add_argument('--cpb', help="Play per chord", default=1, type=int)
19 | parser.add_argument('--bpm', help="BPM", default=120, type=int)
20 | parser.add_argument('--humanize', help="Humanize", default=None)
21 | parser.add_argument('--octave_shift', '--octave_shift', help="Octave shift", default=0, type=int)
22 | parser.add_argument('--outfile', help="Output file", type=str)
23 | args, leftovers = parser.parse_known_args()
24 |
25 | velocity_min = 64
26 | velocity_max = 84
27 | tpb = 128
28 | humanization_amount = 9
29 | if args.humanize != None: velocity_min = 74
30 | octave_shift = args.octave_shift or 0
31 |
32 | bar = tpb
33 |
34 | mid = MidiFile(ticks_per_beat=tpb)
35 | track = MidiTrack()
36 | mid.tracks.append(track)
37 | track.append(MetaMessage('time_signature', numerator=4, denominator=4, clocks_per_click=24, notated_32nd_notes_per_beat=8, time=0))
38 | track.append(MetaMessage('set_tempo', tempo=mido.bpm2tempo(args.bpm), time=0))
39 |
40 | for i, chord in enumerate(args.chords.split('|')):
41 | for repeat in range(args.cpb):
42 | notes = chord.split(',')
43 | if args.octave_shift != 0:
44 | notes = [int(note)+int(args.octave_shift) for note in notes]
45 | velocity = random.randrange(velocity_min, velocity_max) if velocity_min != velocity_max else velocity_max
46 | start = 0 if args.humanize == None else random.randint(0, humanization_amount)
47 | end = bar
48 | for note in notes:
49 | track.append(Message('note_on', note=int(note), velocity=velocity, time=start))
50 | for note in notes:
51 | track.append(Message('note_off', note=int(note), velocity=tpb-1, time=end))
52 |
53 | mid.save(args.outfile)
54 | print(args.outfile)
55 |
56 | if __name__ == '__main__':
57 | sys.exit(main(sys.argv[1:]))
58 |
--------------------------------------------------------------------------------
/mido2midi.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "mount_file_id": "1UYVdwqGUR0AGaDoWB6eNIJuIrrwhyV5L",
8 | "authorship_tag": "ABX9TyMMtI9IRW2hDFpZCE/2/J/x",
9 | "include_colab_link": true
10 | },
11 | "kernelspec": {
12 | "name": "python3",
13 | "display_name": "Python 3"
14 | },
15 | "language_info": {
16 | "name": "python"
17 | }
18 | },
19 | "cells": [
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {
23 | "id": "view-in-github",
24 | "colab_type": "text"
25 | },
26 | "source": [
27 | "
"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "source": [
33 | "#mido2midi | Music with ChatGPT | Github\n",
34 | "\n",
35 | "Utility notebook to quickly execute ChatGPT-generated mido code blocks and download the resulting MIDI file.\n",
36 | "\n",
37 | "### Usage\n",
38 | "- When you run the Setup cell, a box will open on the left side of the screen. Generated MIDI files will appear in this box. You can download them by right-click.\n",
39 | "\n",
40 | "- If the ChatGPT-generated code block produces errors, you may ask ChatGPT to fix it:
_This code produces error \\. Can you fix it?_"
41 | ],
42 | "metadata": {
43 | "id": "GBgr33OisX3y"
44 | }
45 | },
46 | {
47 | "cell_type": "code",
48 | "source": [
49 | "#@title #Setup\n",
50 | "#@markdown This cell needs to be run only once. It will setup prerequisites.
\n",
51 | "\n",
52 | "force_setup = False\n",
53 | "repositories = []\n",
54 | "pip_packages = 'mido'\n",
55 | "apt_packages = ''\n",
56 | "mount_drive = False #@ param {type:\"boolean\"}\n",
57 | "skip_setup = False #@ param {type:\"boolean\"}\n",
58 | "\n",
59 | "# Download the repo from Github\n",
60 | "import os\n",
61 | "from google.colab import output, files\n",
62 | "import warnings\n",
63 | "warnings.filterwarnings('ignore')\n",
64 | "%cd /content/\n",
65 | "\n",
66 | "# inhagcutils\n",
67 | "if not os.path.isfile('/content/inhagcutils.ipynb') and force_setup == False:\n",
68 | " !pip -q install import-ipynb {pip_packages}\n",
69 | " if apt_packages != '':\n",
70 | " !apt-get update && apt-get install {apt_packages}\n",
71 | " !curl -s -O https://raw.githubusercontent.com/olaviinha/inhagcutils/master/inhagcutils.ipynb\n",
72 | "import import_ipynb\n",
73 | "from inhagcutils import *\n",
74 | "\n",
75 | "if len(repositories) > 0 and skip_setup == False:\n",
76 | " for repo in repositories:\n",
77 | " %cd /content/\n",
78 | " install_dir = fix_path('/content/'+path_leaf(repo).replace('.git', ''))\n",
79 | " repo = repo if '.git' in repo else repo+'.git'\n",
80 | " !git clone {repo}\n",
81 | " if os.path.isfile(install_dir+'setup.py') or os.path.isfile(install_dir+'setup.cfg'):\n",
82 | " !pip install -e ./{install_dir}\n",
83 | " if os.path.isfile(install_dir+'requirements.txt'):\n",
84 | " !pip install -r {install_dir}/requirements.txt\n",
85 | "\n",
86 | "if len(repositories) == 1:\n",
87 | " %cd {install_dir}\n",
88 | "\n",
89 | "dir_tmp = '/content/tmp/'\n",
90 | "create_dirs([dir_tmp])\n",
91 | "\n",
92 | "import time, sys\n",
93 | "from datetime import timedelta\n",
94 | "import math\n",
95 | "import shutil\n",
96 | "\n",
97 | "# DO stuff\n",
98 | "\n",
99 | "downloaded_files = []\n",
100 | "new_files = []\n",
101 | "\n",
102 | "%cd {dir_tmp}\n",
103 | "\n",
104 | "output.clear()\n",
105 | "\n",
106 | "files.view(dir_tmp)\n",
107 | "files.view(dir_tmp)\n",
108 | "\n",
109 | "# !nvidia-smi\n",
110 | "op(c.ok, 'Setup finished.', time=True)"
111 | ],
112 | "metadata": {
113 | "id": "Zl44n6FXsbnY",
114 | "cellView": "form",
115 | "outputId": "8bd9152c-507a-415a-bc5a-c0d28dce7535",
116 | "colab": {
117 | "base_uri": "https://localhost:8080/",
118 | "height": 34
119 | }
120 | },
121 | "execution_count": 2,
122 | "outputs": [
123 | {
124 | "output_type": "display_data",
125 | "data": {
126 | "text/plain": [
127 | ""
128 | ],
129 | "application/javascript": [
130 | "\n",
131 | " ((filepath) => {{\n",
132 | " if (!google.colab.kernel.accessAllowed) {{\n",
133 | " return;\n",
134 | " }}\n",
135 | " google.colab.files.view(filepath);\n",
136 | " }})(\"/content/tmp\")"
137 | ]
138 | },
139 | "metadata": {}
140 | },
141 | {
142 | "output_type": "display_data",
143 | "data": {
144 | "text/plain": [
145 | ""
146 | ],
147 | "application/javascript": [
148 | "\n",
149 | " ((filepath) => {{\n",
150 | " if (!google.colab.kernel.accessAllowed) {{\n",
151 | " return;\n",
152 | " }}\n",
153 | " google.colab.files.view(filepath);\n",
154 | " }})(\"/content/tmp\")"
155 | ]
156 | },
157 | "metadata": {}
158 | },
159 | {
160 | "output_type": "stream",
161 | "name": "stdout",
162 | "text": [
163 | "\u001b[90m2023-02-22 16:28:47 \u001b[92mSetup finished.\u001b[0m\n"
164 | ]
165 | }
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "source": [
171 | "\n",
172 | "\n",
173 | "\n",
174 | "#\n",
175 | "# Paste ChatGPT-generated mido code block here and run this cell.\n",
176 | "#\n",
177 | "\n",
178 | "\n"
179 | ],
180 | "metadata": {
181 | "id": "dMv2C4uPY2_j"
182 | },
183 | "execution_count": null,
184 | "outputs": []
185 | },
186 | {
187 | "cell_type": "code",
188 | "source": [
189 | "#@title ## Copy all generated MIDI files to Google Drive\n",
190 | "\n",
191 | "save_to_drive_dir = \"\" #@param {type: \"string\"}\n",
192 | "\n",
193 | "# Mount Drive\n",
194 | "if mount_drive == True:\n",
195 | " if not os.path.isdir('/content/drive'):\n",
196 | " from google.colab import drive\n",
197 | " drive.mount('/content/drive')\n",
198 | " drive_root = '/content/drive/My Drive'\n",
199 | " if not os.path.isdir('/content/mydrive'):\n",
200 | " os.symlink('/content/drive/My Drive', '/content/mydrive')\n",
201 | " drive_root = '/content/mydrive/'\n",
202 | " drive_root_set = True\n",
203 | "else:\n",
204 | " create_dirs(['/content/faux_drive'])\n",
205 | " drive_root = '/content/faux_drive/'\n",
206 | "\n",
207 | "if save_to_drive_dir == '':\n",
208 | " sys.exit('Please specify a directory in your Google Drive, e.g: midi-files')\n",
209 | "else:\n",
210 | " if not os.path.isdir(drive_root+save_to_drive_dir):\n",
211 | " os.mkdir(drive_root+save_to_drive_dir)\n",
212 | " dir_out = drive_root+save_to_drive_dir\n",
213 | " midi_files = glob(dir_tmp+'*.mid')\n",
214 | " for midi_file in midi_files:\n",
215 | " file_out = dir_out+path_leaf(midi_file)\n",
216 | " if midi_file not in downloaded_files:\n",
217 | " shutil.copy(midi_file, file_out)\n",
218 | " downloaded_files.append(midi_file)\n",
219 | " if os.path.isfile(file_out):\n",
220 | " op(c.ok, 'File saved to', file_out.replace(drive_root, ''), time=True)\n",
221 | " else:\n",
222 | " op(c.fail, 'Error saving file', file_out.replace(drive_root, ''), time=True)\n",
223 | "\n",
224 | "print()\n",
225 | "op(c.ok, 'FIN.')"
226 | ],
227 | "metadata": {
228 | "cellView": "form",
229 | "id": "jc4YJ-Z5ZH5H"
230 | },
231 | "execution_count": null,
232 | "outputs": []
233 | }
234 | ]
235 | }
--------------------------------------------------------------------------------