├── .cache └── v │ └── cache │ └── lastfailed ├── MANIFEST.in ├── CHANGES.txt ├── examples ├── scores │ ├── mary.mlp │ ├── furelise.mlp │ ├── menuet.mlp │ └── entertainer.mlp ├── mary.py ├── menuet.py ├── furelise.py ├── entertainer.py ├── parser.py ├── fifths.py ├── canon.py └── twinkle.py ├── tests ├── __init__.py └── melopy_tests.py ├── .gitignore ├── melopy ├── exceptions.py ├── __init__.py ├── chords.py ├── scales.py ├── utility.py └── melopy.py ├── MANIFEST ├── setup.py ├── LICENSE.txt ├── README.md └── README.txt /.cache/v/cache/lastfailed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.1, 2012-09-17 -- Initial release. -------------------------------------------------------------------------------- /examples/scores/mary.mlp: -------------------------------------------------------------------------------- 1 | 4EDCDEE((EFEF))_DDD_EGG_EDCDEEEEDDED[C] -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under The MIT License (MIT) 2 | # See LICENSE file for more -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.py[co] 3 | *.wav 4 | build/ 5 | dist/ 6 | *.egg-info 7 | *.egg 8 | -------------------------------------------------------------------------------- /melopy/exceptions.py: -------------------------------------------------------------------------------- 1 | class MelopyGenericError(Exception): pass 2 | class MelopyValueError(ValueError): pass 3 | -------------------------------------------------------------------------------- /melopy/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from melopy import * 5 | 6 | # Licensed under The MIT License (MIT) 7 | # See LICENSE file for more -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | CHANGES.txt 2 | LICENSE.txt 3 | README.txt 4 | requirements.txt 5 | setup.py 6 | melopy/__init__.py 7 | melopy/melopy.py 8 | melopy/scales.py 9 | melopy/utility.py 10 | melopy/chords.py 11 | melopy/exceptions.py 12 | -------------------------------------------------------------------------------- /examples/mary.py: -------------------------------------------------------------------------------- 1 | from melopy import Melopy 2 | import os 3 | 4 | def main(): 5 | m = Melopy('mary') 6 | d = os.path.dirname(__file__) 7 | if len(d): 8 | m.parsefile(d + '/scores/mary.mlp') 9 | else: 10 | m.parsefile('scores/mary.mlp') 11 | m.render() 12 | 13 | if __name__ == '__main__': 14 | main() 15 | -------------------------------------------------------------------------------- /examples/menuet.py: -------------------------------------------------------------------------------- 1 | from melopy import Melopy 2 | import os 3 | 4 | def main(): 5 | m = Melopy('menuet') 6 | d = os.path.dirname(__file__) 7 | if len(d): 8 | m.parsefile(d + '/scores/menuet.mlp') 9 | else: 10 | m.parsefile('scores/menuet.mlp') 11 | m.render() 12 | 13 | if __name__ == '__main__': 14 | main() 15 | -------------------------------------------------------------------------------- /examples/furelise.py: -------------------------------------------------------------------------------- 1 | from melopy import Melopy 2 | import os 3 | 4 | def main(): 5 | m = Melopy('furelise') 6 | d = os.path.dirname(__file__) 7 | if len(d): 8 | m.parsefile(d + '/scores/furelise.mlp') 9 | else: 10 | m.parsefile('scores/furelise.mlp') 11 | m.render() 12 | 13 | if __name__ == '__main__': 14 | main() 15 | -------------------------------------------------------------------------------- /examples/entertainer.py: -------------------------------------------------------------------------------- 1 | from melopy import Melopy 2 | import os 3 | 4 | def main(): 5 | m = Melopy('entertainer') 6 | m.tempo = 140 7 | d = os.path.dirname(__file__) 8 | if len(d): 9 | m.parsefile(d + '/scores/entertainer.mlp') 10 | else: 11 | m.parsefile('scores/entertainer.mlp') 12 | m.render() 13 | 14 | if __name__ == '__main__': 15 | main() 16 | -------------------------------------------------------------------------------- /examples/scores/furelise.mlp: -------------------------------------------------------------------------------- 1 | 5 2 | (ED#ED#E 3 | vB 4 | ^DC) 5 | vA(_) 6 | 7 | 4(CEA)B(_) 8 | (EG#B)^C(_vE) 9 | 10 | 5(ED#ED#E 11 | vB 12 | ^DC) 13 | vA(_) 14 | 15 | 4(CEA)B(_) 16 | (E^CvB)A 17 | 18 | &&& 19 | 20 | [[_]] 21 | 3(A^4EA) 22 | (_)_ 23 | 24 | 3(E^EG#) 25 | (_)_ 26 | 27 | 3(A^EA) 28 | (_)_ 29 | 30 | [_]_ 31 | 3(A^EA) 32 | (_)_ 33 | 34 | 3(E^EG#) 35 | (_)_ 36 | 37 | 3(A^EA) 38 | -------------------------------------------------------------------------------- /examples/parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # generic parser for Melopy 4 | # 5 | # $ python examples/parser.py entertainer < examples/scores/entertainer.mlp 6 | 7 | from melopy import * 8 | from sys import argv, exit 9 | 10 | if len(argv) < 2: 11 | fn = 'melody' 12 | else: 13 | fn = argv[1] 14 | 15 | m = Melopy(fn) 16 | buff = raw_input() 17 | data = buff 18 | 19 | while True: 20 | try: 21 | buff = raw_input() 22 | data += '\n' + buff 23 | except EOFError: 24 | break 25 | 26 | m.parse(data) 27 | m.render() 28 | -------------------------------------------------------------------------------- /examples/scores/menuet.mlp: -------------------------------------------------------------------------------- 1 | 5D 2 | v(GAB^C)D 3 | vGG 4 | 5 | 5E 6 | (((CDvB^C))DEF#)G 7 | vGG 8 | 9 | 5((CDvB^C))(DCvBA) 10 | B(5CvBAG) 11 | F#(GABG) 12 | (BA)[A] 13 | 14 | 5D 15 | v(GAB5C)D 16 | vGG 17 | 18 | 5E 19 | 5(((CDvB5C))DEF#)G 20 | vGG 21 | 22 | 5((CDvB5C))(DCvBA) 23 | B(5CvBAG) 24 | A(BAGF#) 25 | [G]G 26 | 27 | &&& 28 | 29 | 3[G]A 30 | 3[B]B 31 | 4[C]C 32 | 3[B]B 33 | 3[A]A 34 | 3[G]G 35 | 36 | 4DvBG 37 | 4Dv(D^CvBA) 38 | 39 | 3[B]A 40 | 3GBG 41 | 4[C]C 42 | 3B 43 | ^(CvBAG) 44 | 3[A]F# 45 | 3[G]B 46 | 4CD3D 47 | 3[G]2[G] 48 | -------------------------------------------------------------------------------- /examples/fifths.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from melopy import * 4 | 5 | def frequency_up_fifth(f): 6 | # returns a frequency of the fifth note up from the given frequency 7 | return key_to_frequency(frequency_to_key(f) + 5) 8 | 9 | def fifth_saw(f, t): 10 | # sawtooth wave representing a note and its fifth 11 | return sawtooth(f, t) + sawtooth(frequency_up_fifth(f), t) 12 | 13 | if __name__ == '__main__': 14 | m = Melopy('fifths') 15 | 16 | # change the wave_type 17 | m.wave_type = fifth_saw 18 | 19 | m.parse('C Eb G G Eb [C]') 20 | m.render() 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils.core import setup 5 | 6 | config = { 7 | 'name': 'Melopy', 8 | 'author': 'Jordan Scales', 9 | 'author_email': 'scalesjordan@gmail.com', 10 | 'description': 'Python music library', 11 | 'long_description': open('README.txt').read(), 12 | 'packages': ['melopy'], 13 | 'version': '0.1.1', 14 | 'url': 'https://github.com/jdan/Melopy', 15 | 'license': 'LICENSE.txt', 16 | 'classifiers': [] 17 | } 18 | 19 | setup(**config) 20 | 21 | # Licensed under The MIT License (MIT) 22 | # See LICENSE file for more 23 | -------------------------------------------------------------------------------- /examples/canon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys; sys.path.append(sys.path[0] + '/../melopy/') 5 | 6 | from melopy import * 7 | 8 | if __name__ == "__main__": 9 | m = Melopy('canon', 50) 10 | melody = [] 11 | 12 | for start in ['d4', 'a3', 'b3m', 'f#3m', 'g3', 'd3', 'g3', 'a3']: 13 | if start.endswith('m'): 14 | scale = minor_scale(start[:-1]) 15 | else: 16 | scale = major_scale(start) 17 | 18 | scale.insert(0, scale[0][:-1] + str(int(scale[0][-1]) - 1)) 19 | 20 | [melody.append(note) for note in scale] 21 | 22 | m.add_melody(melody, 0.2) 23 | m.add_rest(0.4) 24 | m.add_note('d4', 0.4) 25 | m.add_rest(0.1) 26 | m.add_note(['d4', 'a4', 'd5'], 0.8) 27 | 28 | m.render() 29 | 30 | # Licensed under The MIT License (MIT) 31 | # See LICENSE file for more 32 | 33 | -------------------------------------------------------------------------------- /examples/twinkle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys; sys.path.append(sys.path[0] + '/../melopy/') 5 | 6 | from melopy import * 7 | 8 | if __name__ == "__main__": 9 | song = Melopy('twinkle', 50) 10 | 11 | song.tempo = 160 12 | song.wave_type = square 13 | 14 | part1notes = ['C', 'G', 'A', 'G', 'F', 'E', 'D', 'C'] 15 | part2notes = ['G', 'F', 'E', 'D'] 16 | 17 | def twinkle(notes): 18 | for i in range(len(notes)): 19 | song.add_quarter_note(notes[i]) 20 | if i % 4 == 3: 21 | song.add_quarter_rest() 22 | else: 23 | song.add_quarter_note(notes[i]) 24 | 25 | twinkle(part1notes) 26 | twinkle(part2notes) 27 | twinkle(part2notes) 28 | twinkle(part1notes) 29 | 30 | song.render() 31 | 32 | # Licensed under The MIT License (MIT) 33 | # See LICENSE file for more 34 | -------------------------------------------------------------------------------- /melopy/chords.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from utility import note_to_key, key_to_note, iterate 5 | from exceptions import MelopyGenericError 6 | 7 | CHORD_INTERVALS = { 8 | 'maj': [4,3], 9 | 'min': [3,4], 10 | 'aug' : [4,4], 11 | 'dim' : [3,3], 12 | '7': [4,3,3], 13 | 'maj7': [4,3,4], 14 | 'min7': [3,4,3], 15 | 'minmaj7': [3,4,4], 16 | 'dim7': [3,3,3,3] 17 | } 18 | 19 | def _get_inversion(chord, inversion): 20 | return chord[inversion:] + chord[:inversion] 21 | 22 | def generateChord(name, tonic, inversion=0, rType='list', octaves=True): 23 | if name in CHORD_INTERVALS: 24 | steps = CHORD_INTERVALS[name] 25 | return _get_inversion(iterate(tonic, steps, rType, octaves),inversion) 26 | else: 27 | raise MelopyGenericError("Unknown Chord:"+str(name)) 28 | 29 | # Licensed under The MIT License (MIT) 30 | # See LICENSE file for more 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jordan Scales 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /melopy/scales.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from utility import note_to_key, key_to_note, iterate 5 | from exceptions import MelopyGenericError 6 | 7 | SCALE_STEPS = { 8 | "major":[2,2,1,2,2,2,1], 9 | "melodic_minor":[2,1,2,2,2,2,1], 10 | "harmonic_minor":[2,1,2,2,2,1,2], 11 | "chromatic":[1,1,1,1,1,1,1,1,1,1,1], 12 | "major_pentatonic":[2,2,3,2], 13 | "minor_pentatonic":[3,2,2,3] 14 | } 15 | 16 | def _get_mode(steps, mode): 17 | """ Gets the correct mode step list by rotating the list """ 18 | mode = mode - 1 19 | res = steps[mode:] + steps[:mode] 20 | return res 21 | 22 | def generateScale(scale, note, mode=1, rType="list", octaves=True): #scale, start, type 23 | """ 24 | Generate a scale 25 | scale (string): major, melodic_minor, harmonic_minor, chromatic, major_pentatonic 26 | note: start note 27 | """ 28 | if scale in SCALE_STEPS: 29 | steps = _get_mode(SCALE_STEPS[scale], mode) 30 | return iterate(note, steps, rType, octaves) 31 | else: 32 | raise MelopyGenericError("Unknown scale type:" + str(scale)) 33 | 34 | # Licensed under The MIT License (MIT) 35 | # See LICENSE file for more 36 | -------------------------------------------------------------------------------- /examples/scores/entertainer.mlp: -------------------------------------------------------------------------------- 1 | 6 (DEC)vA(B)G 2 | (DEC)vA(B)G 3 | (DEC)vA(BAAb)G_^G 4 | 5 | 4 (DD#) 6 | (E)^Cv 7 | (E)^Cv 8 | (E)^[C]_ 9 | 10 | 5 (CDD#E CD)Ev(B)^D[C]_ 11 | 12 | 4 (DD#) 13 | (E)^Cv 14 | (E)^Cv 15 | (E)^[C]_(_) 16 | 17 | 4 (AGF#A^C)E(DCvA)^[D]_ 18 | 19 | 4 (DD#) 20 | (E)^Cv 21 | (E)^Cv 22 | (E)^[C]_ 23 | 24 | 5 (CDD#E CD)Ev(B)^D[C]_ 25 | 26 | 5 (CDECD)E (CDC) 27 | (ECD)E (CDC) 28 | (ECD)E (vB)^D[C]_ 29 | 30 | / ======== repeat ======== / 31 | 32 | 4 (DD#) 33 | (E)^Cv 34 | (E)^Cv 35 | (E)^[C]_ 36 | 37 | 5 (CDD#E CD)Ev(B)^D[C]_ 38 | 39 | 4 (DD#) 40 | (E)^Cv 41 | (E)^Cv 42 | (E)^[C]_(_) 43 | 44 | 4 (AGF#A^C)E(DCvA)^[D]_ 45 | 46 | 4 (DD#) 47 | (E)^Cv 48 | (E)^Cv 49 | (E)^[C]_ 50 | 51 | 5 (CDD#E CD)Ev(B)^D[C]_ 52 | 53 | 5 (CDECD)E (CDC) 54 | (ECD)E (CDC) 55 | (ECD)E (vB)^D[C]_ 56 | 57 | &&& 58 | 59 | 5 (DEC)vA(B)G 60 | (DEC)vA(B)G 61 | (DEC)vA(BAAb)G_G_ 62 | 63 | 3 C^C 64 | 2 G^Bb 65 | 2 F^A 66 | 2 E^G 67 | 68 | 2 G4C 69 | 2 G^B 70 | 2 C4CCvB 71 | 72 | 3 C^C 73 | 2 G^Bb 74 | 2 F^A 75 | 3 EEb 76 | 77 | 2 D^F# 78 | 2 D^F# 79 | 80 | 3 BGAB 81 | 82 | 3 C^C 83 | 2 G^Bb 84 | 2 F^A 85 | 2 E^G 86 | 87 | 2 G4C 88 | 2 G^B 89 | 2 C4CE_ 90 | 91 | 3 C 4E 92 | 2 Bb4E 93 | 2 A 4F 94 | 2 Ab4F 95 | 2 G 4E 96 | 2 G 4B 97 | 3 CGAB 98 | 99 | / ======== repeat ======== / 100 | 101 | 3 C^C 102 | 2 G^Bb 103 | 2 F^A 104 | 2 E^G 105 | 106 | 2 G4C 107 | 2 G^B 108 | 2 C4CCvB 109 | 110 | 3 C^C 111 | 2 G^Bb 112 | 2 F^A 113 | 3 EEb 114 | 115 | 2 D^F# 116 | 2 D^F# 117 | 118 | 3 BGAB 119 | 120 | 3 C^C 121 | 2 G^Bb 122 | 2 F^A 123 | 2 E^G 124 | 125 | 2 G4C 126 | 2 G^B 127 | 2 C4CE_ 128 | 129 | 3 C 4E 130 | 2 Bb4E 131 | 2 A 4F 132 | 2 Ab4F 133 | 2 G 4E 134 | 2 G 4B 135 | 3 C2GC_ -------------------------------------------------------------------------------- /melopy/utility.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from math import log 5 | from exceptions import MelopyGenericError, MelopyValueError 6 | 7 | def key_to_frequency(key): 8 | """Returns the frequency of the note (key) keys from A0""" 9 | return 440 * 2 ** ((key - 49) / 12.0) 10 | 11 | def key_to_note(key, octaves=True): 12 | """Returns a string representing a note which is (key) keys from A0""" 13 | notes = ['a','a#','b','c','c#','d','d#','e','f','f#','g','g#'] 14 | octave = (key + 8) / 12 15 | note = notes[(key - 1) % 12] 16 | 17 | if octaves: 18 | return note.upper() + str(octave) 19 | else: 20 | return note.upper() 21 | 22 | def note_to_frequency(note, default=4): 23 | """Returns the frequency of a note represented by a string""" 24 | return key_to_frequency(note_to_key(note, default)) 25 | 26 | def note_to_key(note, default=4): 27 | """Returns the key number (keys from A0) from a note represented by a string""" 28 | indices = { 'C':0, 'D':2, 'E':4, 'F':5, 'G':7, 'A':9, 'B':11 } 29 | 30 | octave = default 31 | 32 | if note[-1] in '012345678': 33 | octave = int(note[-1]) 34 | 35 | tone = indices[note[0].upper()] 36 | key = 12 * octave + tone 37 | 38 | if len(note) > 1 and note[1] == '#': 39 | key += 1 40 | elif len(note) > 1 and note[1] == 'b': 41 | key -= 1 42 | 43 | return key - 8; 44 | 45 | def frequency_to_key(frequency): 46 | return int(12 * log(frequency/440.0) / log(2) + 49) 47 | 48 | def frequency_to_note(frequency): 49 | return key_to_note(frequency_to_key(frequency)) 50 | 51 | def bReturn(output, Type): 52 | """Returns a selected output assuming input is a list""" 53 | if isinstance(output, list): 54 | if Type.lower() == "list": 55 | return output 56 | elif Type.lower() == "tuple": 57 | return tuple([i for i in output]) 58 | elif Type.lower() == "dict": 59 | O = {} 60 | for i in range(len(output)): 61 | O[i] = output[i] 62 | return O 63 | elif Type.lower() == "string": 64 | return ''.join(output) 65 | elif Type.lower() == "stringspace": 66 | return ' '.join(output) 67 | elif Type.lower() == "delemiter": 68 | return ','.join(output) 69 | else: 70 | raise MelopyGenericError("Unknown type: " + Type) 71 | else: 72 | raise MelopyGenericError("Input to bReturn is not a list! Input: " + str(output)) 73 | 74 | def iterate(start, pattern, rType="list", octaves=True): 75 | """Iterates over a pattern starting at a given note""" 76 | start_key = note_to_key(start) 77 | ret = [start_key] 78 | for step in pattern: 79 | ret.append(ret[-1] + step) 80 | 81 | for i, item in enumerate(ret): 82 | ret[i] = key_to_note(ret[i], octaves) 83 | 84 | return bReturn(ret, rType) 85 | 86 | 87 | # Licensed under The MIT License (MIT) 88 | # See LICENSE file for more 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Melopy 2 | 3 | http://jdan.github.io/Melopy 4 | 5 | A python library for playing with sound.
6 | *by [Jordan Scales](http://jordanscales.com) and friends* 7 | 8 | ### Install it 9 | 10 | You may need to use `sudo` for this to work. 11 | 12 | $ pip install melopy 13 | 14 | ### Load it 15 | 16 | ```python 17 | $ python 18 | Python 2.7.2 (default, Jun 20 2012, 16:23:33) 19 | [GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin 20 | Type "help", "copyright", "credits" or "license" for more information. 21 | >>> import melopy 22 | >>> melopy.major_scale('C5') 23 | ['C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5'] 24 | >>> 25 | ``` 26 | 27 | ### Development 28 | 29 | To install locally: 30 | 31 | $ git clone git://github.com/jdan/Melopy 32 | $ cd Melopy 33 | $ python setup.py install 34 | 35 | For examples, check out the `examples` directory: 36 | 37 | $ python examples/canon.py 38 | $ python examples/parser.py entertainer < examples/scores/entertainer.mlp 39 | 40 | To run the tests: 41 | 42 | $ python tests/melopy_tests.py 43 | 44 | ### Organization 45 | 46 | Melopy is broken down into 3 subcategories - `melopy`, `scales`, and `utility`. 47 | 48 | * `melopy.py` contains the Melopy class 49 | * this is used for creating a Melopy and adding notes to it, rendering, etc 50 | * `scales.py` contains methods for generating scales 51 | * for instance, if you want to store the C major scale in an array 52 | * `utility.py` contains methods for finding frequencies of notes, etc 53 | 54 | ### melopy.py 55 | 56 | ```python 57 | >>> from melopy import Melopy 58 | >>> m = Melopy('mysong') 59 | >>> m.add_quarter_note('A4') 60 | >>> m.add_quarter_note('C#5') 61 | >>> m.add_quarter_note('E5') 62 | >>> m.render() 63 | [==================================================] 100% 64 | Done 65 | ``` 66 | 67 | ### scales.py 68 | 69 | * chromatic_scale 70 | * harmonic_minor_scale 71 | * major_pentatonic_scale 72 | * major_scale 73 | * minor_scale 74 | * major_triad 75 | * minor_triad 76 | * melodic_minor_scale 77 | * minor_pentatonic_scale 78 | 79 | ```python 80 | >>> from melopy.scales import * 81 | >>> major_scale('C4') 82 | ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4'] 83 | >>> major_scale('C4','dict') 84 | {0: 'C4', 1: 'D4', 2: 'E4', 3: 'F4', 4: 'G4', 5: 'A4', 6: 'B4'} 85 | >>> major_scale('C4','tuple') 86 | ('C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4') 87 | >>> minor_scale('D#5') # has some bugs 88 | ['D#5', 'F5', 'F#5', 'G#5', 'A#5', 'B5', 'C#6'] 89 | >>> major_triad('A4') 90 | ['A4', 'C#5', 'E5'] 91 | >>> major_triad('A4', 'tuple') 92 | ('A4', 'C#5', 'E5') 93 | ``` 94 | 95 | ### utility.py 96 | 97 | * key_to_frequency 98 | * key_to_note 99 | * note_to_frequency 100 | * note_to_key 101 | * frequency_to_key 102 | * frequency_to_note 103 | 104 | ```python 105 | >>> from melopy.utility import * 106 | >>> key_to_frequency(49) 107 | 440.0 108 | >>> note_to_frequency('A4') 109 | 440.0 110 | >>> note_to_frequency('C5') 111 | 523.2511306011972 112 | >>> note_to_key('Bb5') 113 | 62 114 | >>> key_to_note(65) 115 | 'C#6' 116 | >>> key_to_note(304) # even something stupid 117 | 'C26' 118 | >>> frequency_to_key(660) 119 | 56 120 | >>> frequency_to_note(660) 121 | 'E5' 122 | ``` 123 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ====== 2 | Melopy 3 | ====== 4 | 5 | A python library for playing with sound. 6 | by Jordan Scales (http://jordanscales.com) and friends 7 | on Github: http://jdan.github.io/Melopy 8 | 9 | Install it 10 | ========== 11 | 12 | You may need to use `sudo` for this to work. 13 | 14 | :: 15 | 16 | $ pip install melopy 17 | 18 | Load it 19 | ======= 20 | 21 | :: 22 | 23 | $ python 24 | Python 2.7.2 (default, Jun 20 2012, 16:23:33) 25 | [GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin 26 | Type "help", "copyright", "credits" or "license" for more information. 27 | >>> import melopy 28 | >>> melopy.major_scale('C5') 29 | ['C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5'] 30 | >>> 31 | 32 | Develop 33 | ======= 34 | 35 | To install locally: 36 | 37 | :: 38 | 39 | $ git clone git://github.com/jdan/Melopy 40 | $ cd Melopy 41 | $ python setup.py install 42 | 43 | For examples, check out the `examples` directory: 44 | 45 | :: 46 | 47 | $ python examples/canon.py 48 | $ python examples/parser.py entertainer < examples/scores/entertainer.mlp 49 | 50 | To run the tests: 51 | 52 | :: 53 | 54 | $ python tests/melopy_tests.py 55 | 56 | Organization 57 | ============ 58 | 59 | Melopy is broken down into 3 subcategories - `melopy`, `scales`, and `utility`. 60 | 61 | * `melopy.py` contains the Melopy class 62 | * this is used for creating a Melopy and adding notes to it, rendering, etc 63 | * `scales.py` contains methods for generating scales 64 | * for instance, if you want to store the C major scale in an array 65 | * `utility.py` contains methods for finding frequencies of notes, etc 66 | 67 | melopy.py 68 | ========= 69 | 70 | >>> from melopy import Melopy 71 | >>> m = Melopy('mysong') 72 | >>> m.add_quarter_note('A4') 73 | >>> m.add_quarter_note('C#5') 74 | >>> m.add_quarter_note('E5') 75 | >>> m.render() 76 | [==================================================] 100% 77 | Done 78 | 79 | scales.py 80 | ========= 81 | 82 | * chromatic_scale 83 | * harmonic_minor_scale 84 | * major_pentatonic_scale 85 | * major_scale 86 | * minor_scale 87 | * major_triad 88 | * minor_triad 89 | * melodic_minor_scale 90 | * minor_pentatonic_scale 91 | 92 | >>> from melopy.scales import * 93 | >>> major_scale('C4') 94 | ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4'] 95 | >>> major_scale('C4','dict') 96 | {0: 'C4', 1: 'D4', 2: 'E4', 3: 'F4', 4: 'G4', 5: 'A4', 6: 'B4'} 97 | >>> major_scale('C4','tuple') 98 | ('C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4') 99 | >>> minor_scale('D#5') # has some bugs 100 | ['D#5', 'F5', 'F#5', 'G#5', 'A#5', 'B5', 'C#6'] 101 | >>> major_triad('A4') 102 | ['A4', 'C#5', 'E5'] 103 | >>> major_triad('A4', 'tuple') 104 | ('A4', 'C#5', 'E5') 105 | 106 | utility.py 107 | ========== 108 | 109 | * key_to_frequency 110 | * key_to_note 111 | * note_to_frequency 112 | * note_to_key 113 | * frequency_to_key 114 | * frequency_to_note 115 | 116 | >>> from melopy.utility import * 117 | >>> key_to_frequency(49) 118 | 440.0 119 | >>> note_to_frequency('A4') 120 | 440.0 121 | >>> note_to_frequency('C5') 122 | 523.2511306011972 123 | >>> note_to_key('Bb5') 124 | 62 125 | >>> key_to_note(65) 126 | 'C#6' 127 | >>> key_to_note(304) # even something stupid 128 | 'C26' 129 | >>> frequency_to_key(660) 130 | 56 131 | >>> frequency_to_note(660) 132 | 'E5' 133 | -------------------------------------------------------------------------------- /tests/melopy_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | from melopy import chords, scales, utility, exceptions 7 | 8 | def data_provider(data): 9 | def decorator(fn): 10 | def repl(self, *args): 11 | for i in data(): 12 | fn(self, *i) 13 | return repl 14 | return decorator 15 | 16 | class LibraryFunctionsTests(unittest.TestCase): 17 | def test_key_to_frequency(self): 18 | key = 49 19 | self.assertEqual(440, utility.key_to_frequency(key)) 20 | 21 | def test_note_to_frequency(self): 22 | note = 'A4' 23 | self.assertEqual(440, utility.note_to_frequency(note)) 24 | 25 | def test_note_to_key(self): 26 | note = 'A4' 27 | self.assertEqual(49, utility.note_to_key(note)) 28 | 29 | def test_key_to_note(self): 30 | key = 49 31 | self.assertEqual('A4', utility.key_to_note(key)) 32 | 33 | def test_iterate(self): 34 | start = 'D4' 35 | pattern = [2, 2, 1, 2, 2, 2] 36 | should_be = ['D4', 'E4', 'F#4', 'G4', 'A4', 'B4', 'C#5'] 37 | self.assertEqual(should_be, utility.iterate(start, pattern)) 38 | 39 | def test_generate_major_scales(self): 40 | start = 'D4' 41 | should_be = ['D4', 'E4', 'F#4', 'G4', 'A4', 'B4', 'C#5','D5'] 42 | self.assertEqual(should_be, scales.generateScale('major', start)) 43 | 44 | def test_generate_chromatic_scales(self): 45 | start = 'C5' 46 | should_be= ['C5', 'C#5', 'D5', 'D#5', 'E5', 'F5', 'F#5', 'G5', 'G#5', 'A5', 'A#5', 'B5'] 47 | self.assertEqual(should_be, scales.generateScale('chromatic', start)) 48 | 49 | def test_generate_major_pentatonic_scales(self): 50 | start = 'C5' 51 | should_be = ['C5', 'D5', 'E5', 'G5', 'A5'] 52 | self.assertEqual(should_be, scales.generateScale('major_pentatonic', start)) 53 | 54 | def test_generate_minor_pentatonic_scales(self): 55 | start = 'A5' 56 | should_be = ['A5', 'C6', 'D6', 'E6', 'G6'] 57 | self.assertEqual(should_be, scales.generateScale('minor_pentatonic', start)) 58 | 59 | def test_generate_dorian_mode(self): 60 | start = 'D5' 61 | should_be = ['D5','E5','F5','G5','A5','B5','C6','D6'] 62 | self.assertEqual(should_be, scales.generateScale('major', start, mode=2)) 63 | 64 | def test_generate_phrygian_mode(self): 65 | start = 'E5' 66 | should_be = ['E5','F5','G5','A5','B5','C6','D6','E6'] 67 | self.assertEqual(should_be, scales.generateScale('major', start, mode=3)) 68 | 69 | def test_generate_lydian_mode(self): 70 | start = 'C5' 71 | should_be = ['C5','D5','E5','F#5','G5','A5','B5','C6'] 72 | self.assertEqual(should_be, scales.generateScale('major', start, mode=4)) 73 | 74 | def test_generate_mixolydian_mode(self): 75 | start = 'C5' 76 | should_be = ['C5','D5','E5','F5','G5','A5','A#5','C6'] 77 | self.assertEqual(should_be, scales.generateScale('major', start, mode=5)) 78 | 79 | def test_generate_dorian_flat_nine(self): 80 | start = 'D5' 81 | should_be = ['D5','D#5','F5','G5','A5','B5','C6','D6'] 82 | self.assertEqual(should_be, scales.generateScale('melodic_minor', start, mode=2)) 83 | 84 | def test_generate_lydian_augmented(self): 85 | start = 'C5' 86 | should_be = ['C5','D5','E5','F#5','G#5','A5','B5','C6'] 87 | self.assertEqual(should_be, scales.generateScale('melodic_minor', start, mode=3)) 88 | 89 | def test_generate_lydian_dominant(self): 90 | start = 'C5' 91 | should_be = ['C5','D5','E5','F#5','G5','A5','A#5','C6'] 92 | self.assertEqual(should_be, scales.generateScale('melodic_minor', start, mode=4)) 93 | 94 | def test_generate_major_triad(self): 95 | start = 'C5' 96 | should_be = ['C5','E5','G5'] 97 | self.assertEqual(should_be, chords.generateChord('maj', start)) 98 | 99 | def test_generate_min_triad(self): 100 | start = 'C5' 101 | should_be = ['C5','D#5','G5'] 102 | self.assertEqual(should_be, chords.generateChord('min', start)) 103 | 104 | def test_generate_maj_first_inversion(self): 105 | start = 'C5' 106 | should_be = ['E5','G5','C5'] 107 | self.assertEqual(should_be, chords.generateChord('maj', start, inversion=1)) 108 | 109 | def test_generate_maj_second_inversion(self): 110 | start = 'C5' 111 | should_be = ['G5','C5','E5'] 112 | self.assertEqual(should_be, chords.generateChord('maj', start, inversion=2)) 113 | 114 | def test_generate_maj_seven(self): 115 | start = 'C5' 116 | should_be = ['C5','E5','G5','B5'] 117 | self.assertEqual(should_be, chords.generateChord('maj7', start)) 118 | 119 | def test_generate_maj_seven(self): 120 | start = 'C5' 121 | should_be = ['C5','E5','G5','B5'] 122 | self.assertEqual(should_be, chords.generateChord('maj7', start)) 123 | 124 | def test_generate_aug(self): 125 | start = 'C5' 126 | should_be = ['C5','E5','G#5'] 127 | self.assertEqual(should_be, chords.generateChord('aug', start)) 128 | 129 | def test_generate_dim(self): 130 | start = 'C5' 131 | should_be = ['C5','D#5','F#5'] 132 | self.assertEqual(should_be, chords.generateChord('dim', start)) 133 | 134 | def test_generate_seven(self): 135 | start = 'C5' 136 | should_be = ['C5','E5','G5','A#5'] 137 | self.assertEqual(should_be, chords.generateChord('7', start)) 138 | 139 | 140 | if __name__ == '__main__': 141 | unittest.main() 142 | 143 | # Licensed under The MIT License (MIT) 144 | # See LICENSE file for more 145 | -------------------------------------------------------------------------------- /melopy/melopy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import wave, struct, random, math 5 | import os, sys 6 | 7 | from utility import * 8 | from scales import * 9 | 10 | # same included wave functions 11 | # a function of frequency and tick 12 | # each function accepts the frequency and tick, 13 | # and returns a value from -1 to 1 14 | 15 | sine = lambda f, t: math.sin(2 * math.pi * t * f / 44100.0) 16 | square = lambda f, t: 0.6 * ((t % (44100 / f) >= ((44100 / f)/2)) * 2 - 1) 17 | sawtooth = lambda f, t: (t % (44100 / f)) / (44100 / f) * 2 - 1 18 | def triangle(f, t): 19 | v = 2 * (t % (44100 / f)) / (44100 / f) 20 | if t % (44100 / f) >= (44100 / (2 * f)): 21 | v = 2 * 1 - v 22 | v = 2 * v - 1 23 | return v 24 | 25 | class Melopy: 26 | def __init__(self, title='sound', volume=20, tempo=120, octave=4): 27 | self.title = title.lower() 28 | self.rate = 44100 29 | self.volume = volume 30 | self.data = [] 31 | 32 | self.tempo = tempo 33 | self.octave = octave 34 | self.wave_type = sine 35 | 36 | def add_wave(self, frequency, length, location='END', level=None): 37 | if location == 'END': 38 | location = len(self.data) 39 | elif location < 0: 40 | location = 0 41 | elif location * 44100 > len(self.data): 42 | location = len(self.data) / 44100.0 43 | 44 | # location is a time, so let's adjust 45 | location = int(location * 44100) 46 | 47 | if level == None: 48 | level = self.volume 49 | elif level > 100: 50 | level = 100 51 | elif level < 0: 52 | level = 0 53 | 54 | num_samples = int(44100 * length) 55 | fade_in_count = 0.005 * 44100 56 | fade_out_count = 0.005 * 44100 57 | fade_level = level 58 | 59 | #reduce the fade counts if the note isn't at least 8 times longer than the fade length 60 | if (fade_in_count * 8) > num_samples: 61 | fade_in_count = num_samples / 8 62 | if (fade_out_count * 8) > num_samples: 63 | fade_out_count = num_samples / 8 64 | 65 | for n in range(0, num_samples): 66 | fade_level = level 67 | 68 | #fade in 69 | if (n < fade_in_count): 70 | fade_level = n * level / fade_in_count 71 | 72 | #fade out 73 | samples_left = num_samples - n 74 | if samples_left < fade_out_count: 75 | fade_level = samples_left * level / fade_out_count 76 | 77 | wave_val = self.wave_type(frequency, n) 78 | val = wave_val * (fade_level / 100.0 * 32767) 79 | 80 | new_location = location + n 81 | 82 | if new_location >= len(self.data): 83 | self.data.append(val) 84 | else: 85 | current_val = self.data[new_location] 86 | if current_val + val > 32767: 87 | val = 32767 88 | elif current_val + val < -32768: 89 | val = -32768 90 | else: 91 | val += current_val 92 | 93 | self.data[new_location] = val 94 | 95 | def add_note(self, note, length, location='END', volume=None): 96 | """Add a note, or if a list, add a chord.""" 97 | if not isinstance(note, list): 98 | note = [note] 99 | 100 | if location == 'END': 101 | location = len(self.data) / 44100.0 102 | 103 | if not isinstance(volume, list): 104 | volume = [volume] 105 | if volume[0] == None: 106 | volume = [float(self.volume)/len(note)] * len(note) 107 | #By default, when adding a chord, set equal level for each 108 | #component note, such that the sum volume is self.volume 109 | else: 110 | volume = volume + [volume[-1]]*(len(note) - len(volume)) 111 | #Otherwise, pad volume by repeating the final level so that we have 112 | #enough level values for each note 113 | 114 | for item, level in zip(note, volume): 115 | if not item[-1].isdigit(): 116 | item += str(self.octave) 117 | 118 | self.add_wave(note_to_frequency(item, self.octave), length, location, level) 119 | 120 | def add_melody(self, melody, length): 121 | for note in melody: 122 | if not note[-1].isdigit(): 123 | note += self.octave 124 | self.add_wave(note_to_frequency(note), length) 125 | 126 | def add_whole_note(self, note, location='END', volume=None): 127 | """Add a whole note""" 128 | self.add_fractional_note(note, 1.0, location, volume) 129 | 130 | def add_half_note(self, note, location='END', volume=None): 131 | """Add a half note""" 132 | self.add_fractional_note(note, 1.0 / 2, location, volume) 133 | 134 | def add_quarter_note(self, note, location='END', volume=None): 135 | """Add a quarter note""" 136 | self.add_fractional_note(note, 1.0 / 4, location, volume) 137 | 138 | def add_eighth_note(self, note, location='END', volume=None): 139 | """Add a eighth note""" 140 | self.add_fractional_note(note, 1.0 / 8, location, volume) 141 | 142 | def add_sixteenth_note(self, note, location='END', volume=None): 143 | """Add a sixteenth note""" 144 | self.add_fractional_note(note, 1.0 / 16, location, volume) 145 | 146 | def add_fractional_note(self, note, fraction, location='END', volume=None): 147 | """Add a fractional note (smaller then 1/16 notes)""" 148 | self.add_note(note, 60.0 / self.tempo * (fraction * 4), location, volume) 149 | 150 | def add_rest(self, length): 151 | for i in range(int(self.rate * length)): 152 | self.data.append(0) 153 | 154 | def add_whole_rest(self): 155 | self.add_fractional_rest(1.0) 156 | 157 | def add_half_rest(self): 158 | self.add_fractional_rest(1.0 / 2) 159 | 160 | def add_quarter_rest(self): 161 | self.add_fractional_rest(1.0 / 4) 162 | 163 | def add_eighth_rest(self): 164 | self.add_fractional_rest(1.0 / 8) 165 | 166 | def add_sixteenth_rest(self): 167 | self.add_fractional_rest(1.0 / 16) 168 | 169 | def add_fractional_rest(self, fraction): 170 | self.add_rest(60.0 / self.tempo * (fraction * 4)) 171 | 172 | def parse(self, string, location='END'): 173 | tracks = string.split('&&&') 174 | 175 | # special case for multiple tracks 176 | if len(tracks) > 1: 177 | t = len(self.data) / 44100.0 178 | for track in tracks: 179 | self.parse(track, t) 180 | return 181 | 182 | cf = 0.25 # start with a quarter note, change accordingly 183 | in_comment = False 184 | 185 | for i, char in enumerate(string): # divide melody into fragments 186 | # / this is a comment / 187 | if char == '/': 188 | in_comment = not in_comment 189 | 190 | if in_comment: 191 | continue 192 | elif char in 'ABCDEFG': 193 | if (i+1 < len(string)) and (string[i+1] in '#b'): 194 | # check if the next item in the array is 195 | # a sharp or flat, make sure we include it 196 | char += string[i+1] 197 | 198 | self.add_fractional_note(char, cf, location) 199 | if location != 'END': 200 | location += (60.0 / self.tempo * (cf * 4)) 201 | elif char in map(str, range(0, 20)): 202 | self.octave = int(char) 203 | elif char == '+' or char == '^': 204 | self.octave += 1 205 | elif char == 'V' or char == 'v' or char == '-': 206 | self.octave -= 1 207 | elif char == '(' or char == ']': 208 | cf /= 2 209 | elif char == ')' or char == '[': 210 | cf *= 2 211 | elif char == '_': 212 | self.add_fractional_rest(cf) 213 | if location != 'END': 214 | location += (60.0 / self.tempo * (cf * 4)) 215 | 216 | def parsefile(self, filename, location='END'): 217 | fr = open(filename, 'r') 218 | s = fr.read() 219 | fr.close() 220 | 221 | self.parse(s, location) 222 | 223 | def stdout_progress(percent): 224 | sys.stdout.write("\r[%s] %d%%" % (('='*int(percent * 0.5)+'>').ljust(50), percent)) 225 | sys.stdout.flush() 226 | if (percent >= 100): 227 | sys.stdout.write("\r[%s] 100%%" % ('='*51)) 228 | sys.stdout.flush() 229 | sys.stdout.write("\nDone\n") 230 | 231 | def render(self, update_callback=stdout_progress): 232 | """Render a playable song out to a .wav file""" 233 | melopy_writer = wave.open(self.title + '.wav', 'w') 234 | melopy_writer.setparams((2, 2, 44100, 0, 'NONE', 'not compressed')) 235 | last_percent = -1 236 | data_frames = [] 237 | callback_present = callable(update_callback) 238 | 239 | for i in range(len(self.data)): 240 | percent = 100 * i // len(self.data) 241 | if callback_present and last_percent != percent: 242 | update_callback(percent) 243 | last_percent = percent 244 | packed_val = struct.pack('h', int(self.data[i])) 245 | data_frames.append(packed_val) 246 | data_frames.append(packed_val) 247 | 248 | melopy_writer.writeframes(b''.join(data_frames)) 249 | 250 | if callback_present: 251 | update_callback(100) 252 | 253 | melopy_writer.close() 254 | 255 | def play(self): 256 | """Opens the song in the os default program""" 257 | os.startfile(self.title + '.wav') 258 | 259 | # Licensed under The MIT License (MIT) 260 | # See LICENSE file for more 261 | --------------------------------------------------------------------------------