aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in1
-rw-r--r--README14
-rw-r--r--music_sampler.spec4
-rw-r--r--music_sampler/__init__.py193
-rw-r--r--music_sampler/action.py2
-rw-r--r--music_sampler/app.py (renamed from music_sampler.py)18
-rw-r--r--music_sampler/helpers.py192
-rw-r--r--music_sampler/key.py2
-rw-r--r--music_sampler/lock.py2
-rw-r--r--music_sampler/mapping.py2
-rw-r--r--music_sampler/mixer.py2
-rw-r--r--music_sampler/music_file.py2
-rw-r--r--music_sampler/music_sampler.kv (renamed from music_sampler.kv)0
-rw-r--r--run.py3
-rw-r--r--setup.py40
15 files changed, 268 insertions, 209 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..5dab67a
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
include music_sampler/music_sampler.kv
diff --git a/README b/README
new file mode 100644
index 0000000..6017dcd
--- /dev/null
+++ b/README
@@ -0,0 +1,14 @@
1Music Sampler is a music player which associates each key on the keyboard to a
2set of actions to run.
3
4See full documentation in french in documentation_fr.md
5
6Git repository:
7https://git.immae.eu/?p=perso/Immae/Projets/Python/MusicSampler.git
8
9Bug Tracker:
10https://git.immae.eu/mantisbt/view_all_bug_page.php?project_id=1
11
12Contributors:
13Ismaël Bouya
14Denise Maurice
diff --git a/music_sampler.spec b/music_sampler.spec
index 3794397..791cd68 100644
--- a/music_sampler.spec
+++ b/music_sampler.spec
@@ -28,11 +28,11 @@ pyinstaller_file.write(commit_message)
28pyinstaller_file.close() 28pyinstaller_file.close()
29 29
30data = [ 30data = [
31 ('music_sampler.kv', '.'), 31 ('music_sampler/music_sampler.kv', '.'),
32 ('.pyinstaller_commit', '.') 32 ('.pyinstaller_commit', '.')
33] 33]
34 34
35a = Analysis(['music_sampler.py'], 35a = Analysis(['run.py'],
36 binaries=None, 36 binaries=None,
37 datas=data, 37 datas=data,
38 hookspath=hookspath(), 38 hookspath=hookspath(),
diff --git a/music_sampler/__init__.py b/music_sampler/__init__.py
index 4827e6c..aa551fb 100644
--- a/music_sampler/__init__.py
+++ b/music_sampler/__init__.py
@@ -1,192 +1 @@
1# -*- coding: utf-8 -*- from . import app
2import argparse
3import sys
4import os
5import math
6import sounddevice as sd
7import logging
8
9from . import sysfont
10
11class Config:
12 pass
13
14def find_font(name, style=sysfont.STYLE_NONE):
15 if getattr(sys, 'frozen', False):
16 font = sys._MEIPASS + "/fonts/{}_{}.ttf".format(name, style)
17 else:
18 font = sysfont.get_font(name, style=style)
19 if font is not None:
20 font = font[4]
21 return font
22
23def register_fonts():
24 from kivy.core.text import LabelBase
25
26 ubuntu_regular = find_font("Ubuntu", style=sysfont.STYLE_NORMAL)
27 ubuntu_bold = find_font("Ubuntu", style=sysfont.STYLE_BOLD)
28 symbola = find_font("Symbola")
29
30 if ubuntu_regular is None:
31 error_print("Font Ubuntu regular could not be found, please install it.")
32 sys.exit()
33 if symbola is None:
34 error_print("Font Symbola could not be found, please install it.")
35 sys.exit()
36 if ubuntu_bold is None:
37 warn_print("Font Ubuntu Bold could not be found.")
38
39 LabelBase.register(name="Ubuntu",
40 fn_regular=ubuntu_regular,
41 fn_bold=ubuntu_bold)
42 LabelBase.register(name="Symbola",
43 fn_regular=symbola)
44
45
46def path():
47 if getattr(sys, 'frozen', False):
48 return sys._MEIPASS + "/"
49 else:
50 path = os.path.dirname(os.path.realpath(__file__))
51 return path + "/../"
52
53def parse_args():
54 argv = sys.argv[1 :]
55 sys.argv = sys.argv[: 1]
56 if "--" in argv:
57 index = argv.index("--")
58 kivy_args = argv[index+1 :]
59 argv = argv[: index]
60
61 sys.argv.extend(kivy_args)
62
63 parser = argparse.ArgumentParser(
64 description="A Music Sampler application.",
65 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
66 parser.add_argument("-c", "--config",
67 default="config.yml",
68 required=False,
69 help="Config file to load")
70 parser.add_argument("-p", "--music-path",
71 default=".",
72 required=False,
73 help="Folder in which to find the music files")
74 parser.add_argument("-d", "--debug",
75 nargs=0,
76 action=DebugModeAction,
77 help="Print messages in console")
78 parser.add_argument("-m", "--builtin-mixing",
79 action="store_true",
80 help="Make the mixing of sounds manually\
81 (do it if the system cannot handle it correctly)")
82 parser.add_argument("-l", "--latency",
83 default="high",
84 required=False,
85 help="Latency: low, high or number of seconds")
86 parser.add_argument("-b", "--blocksize",
87 default=0,
88 type=int,
89 required=False,
90 help="Blocksize: If not 0, the number of frames to take\
91 at each step for the mixer")
92 parser.add_argument("-f", "--frame-rate",
93 default=44100,
94 type=int,
95 required=False,
96 help="Frame rate to play the musics")
97 parser.add_argument("-x", "--channels",
98 default=2,
99 type=int,
100 required=False,
101 help="Number of channels to use")
102 parser.add_argument("-s", "--sample-width",
103 default=2,
104 type=int,
105 required=False,
106 help="Sample width (number of bytes for each frame)")
107 parser.add_argument("-V", "--version",
108 action="version",
109 help="Displays the current version and exits. Only use\
110 in bundled package",
111 version=show_version())
112 parser.add_argument("--device",
113 action=SelectDeviceAction,
114 help="Select this sound device"
115 )
116 parser.add_argument("--list-devices",
117 nargs=0,
118 action=ListDevicesAction,
119 help="List available sound devices"
120 )
121 parser.add_argument('--',
122 dest="args",
123 help="Kivy arguments. All arguments after this are interpreted\
124 by Kivy. Pass \"-- --help\" to get Kivy's usage.")
125
126 from kivy.logger import Logger
127 Logger.setLevel(logging.WARN)
128
129 args = parser.parse_args(argv)
130
131 Config.yml_file = args.config
132
133 Config.latency = args.latency
134 Config.blocksize = args.blocksize
135 Config.frame_rate = args.frame_rate
136 Config.channels = args.channels
137 Config.sample_width = args.sample_width
138 Config.builtin_mixing = args.builtin_mixing
139 if args.music_path.endswith("/"):
140 Config.music_path = args.music_path
141 else:
142 Config.music_path = args.music_path + "/"
143
144class DebugModeAction(argparse.Action):
145 def __call__(self, parser, namespace, values, option_string=None):
146 from kivy.logger import Logger
147 Logger.setLevel(logging.DEBUG)
148
149class SelectDeviceAction(argparse.Action):
150 def __call__(self, parser, namespace, values, option_string=None):
151 sd.default.device = values
152
153class ListDevicesAction(argparse.Action):
154 nargs = 0
155 def __call__(self, parser, namespace, values, option_string=None):
156 print(sd.query_devices())
157 sys.exit()
158
159def show_version():
160 if getattr(sys, 'frozen', False):
161 with open(path() + ".pyinstaller_commit", "r") as f:
162 return f.read()
163 else:
164 return "option '-v' can only be used in bundled package"
165
166def duration_to_min_sec(duration):
167 minutes = int(duration / 60)
168 seconds = int(duration) % 60
169 if minutes < 100:
170 return "{:2}:{:0>2}".format(minutes, seconds)
171 else:
172 return "{}:{:0>2}".format(minutes, seconds)
173
174def gain(volume, old_volume=None):
175 if old_volume is None:
176 return 20 * math.log10(max(volume, 0.1) / 100)
177 else:
178 return [
179 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
180 max(volume, 0)]
181
182def debug_print(message, with_trace=False):
183 from kivy.logger import Logger
184 Logger.debug('MusicSampler: ' + message, exc_info=with_trace)
185
186def error_print(message, with_trace=False):
187 from kivy.logger import Logger
188 Logger.error('MusicSampler: ' + message, exc_info=with_trace)
189
190def warn_print(message, with_trace=False):
191 from kivy.logger import Logger
192 Logger.warn('MusicSampler: ' + message, exc_info=with_trace)
diff --git a/music_sampler/action.py b/music_sampler/action.py
index 4b5a71d..ef56b7c 100644
--- a/music_sampler/action.py
+++ b/music_sampler/action.py
@@ -1,5 +1,5 @@
1from transitions.extensions import HierarchicalMachine as Machine 1from transitions.extensions import HierarchicalMachine as Machine
2from . import debug_print, error_print 2from .helpers import debug_print, error_print
3from . import actions 3from . import actions
4 4
5class Action: 5class Action:
diff --git a/music_sampler.py b/music_sampler/app.py
index 714598a..81c47a7 100644
--- a/music_sampler.py
+++ b/music_sampler/app.py
@@ -1,6 +1,6 @@
1import music_sampler 1from .helpers import parse_args, register_fonts, duration_to_min_sec, path
2 2
3music_sampler.parse_args() 3parse_args()
4 4
5import kivy 5import kivy
6kivy.require("1.9.1") 6kivy.require("1.9.1")
@@ -11,10 +11,10 @@ from kivy.properties import ListProperty, StringProperty
11from kivy.clock import Clock 11from kivy.clock import Clock
12from kivy.core.window import Window 12from kivy.core.window import Window
13from kivy.lang import Builder 13from kivy.lang import Builder
14from music_sampler.key import Key 14from .key import Key
15from music_sampler.mapping import Mapping 15from .mapping import Mapping
16 16
17music_sampler.register_fonts() 17register_fonts()
18 18
19class KeyList(RelativeLayout): 19class KeyList(RelativeLayout):
20 keylist = ListProperty([]) 20 keylist = ListProperty([])
@@ -51,8 +51,8 @@ class PlayList(RelativeLayout):
51 continue 51 continue
52 52
53 text = "{}/{}".format( 53 text = "{}/{}".format(
54 music_sampler.duration_to_min_sec(music_file.sound_position), 54 duration_to_min_sec(music_file.sound_position),
55 music_sampler.duration_to_min_sec(music_file.sound_duration)) 55 duration_to_min_sec(music_file.sound_duration))
56 56
57 if music_file.is_loaded_paused(): 57 if music_file.is_loaded_paused():
58 self.playlist.append(["⏸", music_file.name, text, False]) 58 self.playlist.append(["⏸", music_file.name, text, False])
@@ -86,6 +86,6 @@ class MusicSamplerApp(App):
86 86
87 return Screen() 87 return Screen()
88 88
89if __name__ == '__main__': 89def main():
90 Builder.load_file(music_sampler.path() + "/music_sampler.kv") 90 Builder.load_file(path() + "/music_sampler.kv")
91 MusicSamplerApp().run() 91 MusicSamplerApp().run()
diff --git a/music_sampler/helpers.py b/music_sampler/helpers.py
new file mode 100644
index 0000000..1788084
--- /dev/null
+++ b/music_sampler/helpers.py
@@ -0,0 +1,192 @@
1# -*- coding: utf-8 -*-
2import argparse
3import sys
4import os
5import math
6import sounddevice as sd
7import logging
8
9from . import sysfont
10
11class Config:
12 pass
13
14def find_font(name, style=sysfont.STYLE_NONE):
15 if getattr(sys, 'frozen', False):
16 font = sys._MEIPASS + "/fonts/{}_{}.ttf".format(name, style)
17 else:
18 font = sysfont.get_font(name, style=style)
19 if font is not None:
20 font = font[4]
21 return font
22
23def register_fonts():
24 from kivy.core.text import LabelBase
25
26 ubuntu_regular = find_font("Ubuntu", style=sysfont.STYLE_NORMAL)
27 ubuntu_bold = find_font("Ubuntu", style=sysfont.STYLE_BOLD)
28 symbola = find_font("Symbola")
29
30 if ubuntu_regular is None:
31 error_print("Font Ubuntu regular could not be found, please install it.")
32 sys.exit()
33 if symbola is None:
34 error_print("Font Symbola could not be found, please install it.")
35 sys.exit()
36 if ubuntu_bold is None:
37 warn_print("Font Ubuntu Bold could not be found.")
38
39 LabelBase.register(name="Ubuntu",
40 fn_regular=ubuntu_regular,
41 fn_bold=ubuntu_bold)
42 LabelBase.register(name="Symbola",
43 fn_regular=symbola)
44
45
46def path():
47 if getattr(sys, 'frozen', False):
48 return sys._MEIPASS + "/"
49 else:
50 return os.path.dirname(os.path.realpath(__file__))
51
52def parse_args():
53 argv = sys.argv[1 :]
54 sys.argv = sys.argv[: 1]
55 if "--" in argv:
56 index = argv.index("--")
57 kivy_args = argv[index+1 :]
58 argv = argv[: index]
59
60 sys.argv.extend(kivy_args)
61
62 parser = argparse.ArgumentParser(
63 description="A Music Sampler application.",
64 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
65 parser.add_argument("-c", "--config",
66 default="config.yml",
67 required=False,
68 help="Config file to load")
69 parser.add_argument("-p", "--music-path",
70 default=".",
71 required=False,
72 help="Folder in which to find the music files")
73 parser.add_argument("-d", "--debug",
74 nargs=0,
75 action=DebugModeAction,
76 help="Print messages in console")
77 parser.add_argument("-m", "--builtin-mixing",
78 action="store_true",
79 help="Make the mixing of sounds manually\
80 (do it if the system cannot handle it correctly)")
81 parser.add_argument("-l", "--latency",
82 default="high",
83 required=False,
84 help="Latency: low, high or number of seconds")
85 parser.add_argument("-b", "--blocksize",
86 default=0,
87 type=int,
88 required=False,
89 help="Blocksize: If not 0, the number of frames to take\
90 at each step for the mixer")
91 parser.add_argument("-f", "--frame-rate",
92 default=44100,
93 type=int,
94 required=False,
95 help="Frame rate to play the musics")
96 parser.add_argument("-x", "--channels",
97 default=2,
98 type=int,
99 required=False,
100 help="Number of channels to use")
101 parser.add_argument("-s", "--sample-width",
102 default=2,
103 type=int,
104 required=False,
105 help="Sample width (number of bytes for each frame)")
106 parser.add_argument("-V", "--version",
107 action="version",
108 help="Displays the current version and exits. Only use\
109 in bundled package",
110 version=show_version())
111 parser.add_argument("--device",
112 action=SelectDeviceAction,
113 help="Select this sound device"
114 )
115 parser.add_argument("--list-devices",
116 nargs=0,
117 action=ListDevicesAction,
118 help="List available sound devices"
119 )
120 parser.add_argument('--',
121 dest="args",
122 help="Kivy arguments. All arguments after this are interpreted\
123 by Kivy. Pass \"-- --help\" to get Kivy's usage.")
124
125 from kivy.logger import Logger
126 Logger.setLevel(logging.WARN)
127
128 args = parser.parse_args(argv)
129
130 Config.yml_file = args.config
131
132 Config.latency = args.latency
133 Config.blocksize = args.blocksize
134 Config.frame_rate = args.frame_rate
135 Config.channels = args.channels
136 Config.sample_width = args.sample_width
137 Config.builtin_mixing = args.builtin_mixing
138 if args.music_path.endswith("/"):
139 Config.music_path = args.music_path
140 else:
141 Config.music_path = args.music_path + "/"
142
143class DebugModeAction(argparse.Action):
144 def __call__(self, parser, namespace, values, option_string=None):
145 from kivy.logger import Logger
146 Logger.setLevel(logging.DEBUG)
147
148class SelectDeviceAction(argparse.Action):
149 def __call__(self, parser, namespace, values, option_string=None):
150 sd.default.device = values
151
152class ListDevicesAction(argparse.Action):
153 nargs = 0
154 def __call__(self, parser, namespace, values, option_string=None):
155 print(sd.query_devices())
156 sys.exit()
157
158def show_version():
159 if getattr(sys, 'frozen', False):
160 with open(path() + ".pyinstaller_commit", "r") as f:
161 return f.read()
162 else:
163 return "option '-v' can only be used in bundled package"
164
165def duration_to_min_sec(duration):
166 minutes = int(duration / 60)
167 seconds = int(duration) % 60
168 if minutes < 100:
169 return "{:2}:{:0>2}".format(minutes, seconds)
170 else:
171 return "{}:{:0>2}".format(minutes, seconds)
172
173def gain(volume, old_volume=None):
174 if old_volume is None:
175 return 20 * math.log10(max(volume, 0.1) / 100)
176 else:
177 return [
178 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
179 max(volume, 0)]
180
181def debug_print(message, with_trace=False):
182 from kivy.logger import Logger
183 Logger.debug('MusicSampler: ' + message, exc_info=with_trace)
184
185def error_print(message, with_trace=False):
186 from kivy.logger import Logger
187 Logger.error('MusicSampler: ' + message, exc_info=with_trace)
188
189def warn_print(message, with_trace=False):
190 from kivy.logger import Logger
191 Logger.warn('MusicSampler: ' + message, exc_info=with_trace)
192
diff --git a/music_sampler/key.py b/music_sampler/key.py
index 66e792d..b05de4c 100644
--- a/music_sampler/key.py
+++ b/music_sampler/key.py
@@ -4,7 +4,7 @@ from kivy.properties import AliasProperty, BooleanProperty, \
4from kivy.uix.behaviors import ButtonBehavior 4from kivy.uix.behaviors import ButtonBehavior
5 5
6from .action import Action 6from .action import Action
7from . import debug_print 7from .helpers import debug_print
8import time 8import time
9import threading 9import threading
10from transitions.extensions import HierarchicalMachine as Machine 10from transitions.extensions import HierarchicalMachine as Machine
diff --git a/music_sampler/lock.py b/music_sampler/lock.py
index 9beafcd..5befe33 100644
--- a/music_sampler/lock.py
+++ b/music_sampler/lock.py
@@ -1,6 +1,6 @@
1import threading 1import threading
2 2
3from . import debug_print 3from .helpers import debug_print
4 4
5class Lock: 5class Lock:
6 def __init__(self, lock_type): 6 def __init__(self, lock_type):
diff --git a/music_sampler/mapping.py b/music_sampler/mapping.py
index bb20e67..ca471ef 100644
--- a/music_sampler/mapping.py
+++ b/music_sampler/mapping.py
@@ -12,7 +12,7 @@ from transitions.extensions import HierarchicalMachine as Machine
12 12
13from .music_file import MusicFile 13from .music_file import MusicFile
14from .mixer import Mixer 14from .mixer import Mixer
15from . import Config, gain, error_print, warn_print 15from .helpers import Config, gain, error_print, warn_print
16from .action import Action 16from .action import Action
17 17
18class Mapping(RelativeLayout): 18class Mapping(RelativeLayout):
diff --git a/music_sampler/mixer.py b/music_sampler/mixer.py
index 9242b61..c8ec907 100644
--- a/music_sampler/mixer.py
+++ b/music_sampler/mixer.py
@@ -2,7 +2,7 @@ import sounddevice as sd
2import audioop 2import audioop
3import time 3import time
4 4
5from . import Config 5from .helpers import Config
6 6
7sample_width = Config.sample_width 7sample_width = Config.sample_width
8 8
diff --git a/music_sampler/music_file.py b/music_sampler/music_file.py
index 2d3ba72..fa6293d 100644
--- a/music_sampler/music_file.py
+++ b/music_sampler/music_file.py
@@ -8,7 +8,7 @@ import os.path
8import audioop 8import audioop
9 9
10from .lock import Lock 10from .lock import Lock
11from . import Config, gain, debug_print, error_print 11from .helpers import Config, gain, debug_print, error_print
12from .mixer import Mixer 12from .mixer import Mixer
13from .music_effect import GainEffect 13from .music_effect import GainEffect
14 14
diff --git a/music_sampler.kv b/music_sampler/music_sampler.kv
index 9057532..9057532 100644
--- a/music_sampler.kv
+++ b/music_sampler/music_sampler.kv
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..b486c50
--- /dev/null
+++ b/run.py
@@ -0,0 +1,3 @@
1import music_sampler
2
3music_sampler.app.main()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..a4cdb3c
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,40 @@
1from setuptools import setup
2
3def readme():
4 with open('README') as f:
5 return f.read()
6
7setup(name='music_sampler',
8 setup_requires=['setuptools_scm'],
9 use_scm_version=True,
10 description='A music player which associates each key on the keyboard '
11 'to a set of actions to run',
12 long_description=readme(),
13 classifiers= [
14 'Development Status :: 4 - Beta',
15 'Intended Audience :: End Users/Desktop',
16 'License :: OSI Approved :: MIT License',
17 'Operating System :: POSIX :: Linux',
18 'Programming Language :: Python :: 3.5',
19 'Topic :: Multimedia :: Sound/Audio :: Players'
20 ],
21 keywords='music sampler keyboard',
22 url='https://git.immae.eu/?p=perso/Immae/Projets/Python/MusicSampler.git',
23 author='Ismaël Bouya',
24 author_email='ismael.bouya@normalesup.org',
25 license='MIT',
26 packages=['music_sampler', 'music_sampler.actions'],
27 install_requires=[
28 'Kivy>=1.9.1',
29 'pydub>=0.16.4',
30 'Pygame>=1.9.2.dev1',
31 'sounddevice>=0.3.3',
32 'TRANSITIONS>=0.4.1',
33 'PyYAML>=3.11'
34 ],
35 entry_points={
36 'console_scripts': ['music_sampler=music_sampler.app:main'],
37 },
38 include_package_data=True,
39 zip_safe=False)
40