3
3
import os
4
4
import socket
5
5
import json
6
+ import pathlib
7
+ import time
6
8
from XoneK2_DJ .tinytag import TinyTag
7
9
from urllib .parse import unquote
8
10
9
- class BrowserItem ():
10
- def __init__ (self , live_browser_item ):
11
- self ._item = live_browser_item
12
- self ._file_name = self ._uri_to_path (self ._item .uri )
11
+ MUSIC_TO_OPEN_KEY = {
12
+ 'Am' : '1m' ,
13
+ 'C' : '1d' ,
14
+ 'Em' : '2m' ,
15
+ 'G' : '2d' ,
16
+ 'Bm' : '3m' ,
17
+ 'D' : '3d' ,
18
+ 'F#m' : '4m' ,
19
+ 'Gbm' : '4m' ,
20
+ 'A' : '4d' ,
21
+ 'Dbm' : '5m' ,
22
+ 'C#m' : '5m' ,
23
+ 'E' : '5d' ,
24
+ 'Abm' : '6m' ,
25
+ 'G#m' : '6m' ,
26
+ 'B' : '6d' ,
27
+ 'Ebm' : '7m' ,
28
+ 'D#m' : '7m' ,
29
+ 'F#' : '7d' ,
30
+ 'Gb' : '7d' ,
31
+ 'Bbm' : '8m' ,
32
+ 'A#m' : '8m' ,
33
+ 'Db' : '8d' ,
34
+ 'C#' : '8d' ,
35
+ 'Fm' : '9m' ,
36
+ 'Ab' : '9d' ,
37
+ 'G#' : '9d' ,
38
+ 'Cm' : '10m' ,
39
+ 'Eb' : '10d' ,
40
+ 'D#' : '10d' ,
41
+ 'Gm' : '11m' ,
42
+ 'Bb' : '11d' ,
43
+ 'A#' : '11d' ,
44
+ 'Dm' : '12m' ,
45
+ 'F' : '12d'
46
+ }
47
+
48
+ OPEN_TO_MUSICAL_KEY = {
49
+ '1m' : 'Am' ,
50
+ '1d' : 'C' ,
51
+ '2m' : 'Em' ,
52
+ '2d' : 'G' ,
53
+ '3m' : 'Bm' ,
54
+ '3d' : 'D' ,
55
+ '4m' : 'F#m' ,
56
+ '4d' : 'A' ,
57
+ '5m' : 'C#m' ,
58
+ '5d' : 'E' ,
59
+ '6m' : 'G#m' ,
60
+ '6d' : 'B' ,
61
+ '7m' : 'D#m' ,
62
+ '7d' : 'F#' ,
63
+ '8m' : 'A#m' ,
64
+ '8d' : 'C#' ,
65
+ '9m' : 'Fm' ,
66
+ '9d' : 'G#' ,
67
+ '10m' : 'Cm' ,
68
+ '10d' : 'D#' ,
69
+ '11m' : 'Gm' ,
70
+ '11d' : 'A#' ,
71
+ '12m' : 'Dm' ,
72
+ '12d' : 'F'
73
+ }
74
+
75
+ def uri_to_path (uri ):
76
+ path = re .sub ('^query:UserLibrary#' , '~/Music/Ableton/User Library/' , uri )
77
+ path = re .sub (':' ,'/' , path )
78
+ path = unquote (path )
79
+ return path
80
+
81
+ class TaggedFile ():
82
+ def __init__ (self , filename ):
83
+ self ._file_name = filename
13
84
self ._tags = TinyTag .get (self ._file_name )
14
85
self ._duration = "%d:%02d" % (int (self ._tags .duration )/ 60 , int (self ._tags .duration ) % 60 )
15
86
self ._bpm = self ._tags .extra ['bpm' ] if 'bpm' in self ._tags .extra else "none"
16
87
self ._key = self ._tags .extra ['initial_key' ] if 'initial_key' in self ._tags .extra else "none"
17
-
18
- def _uri_to_path (self , uri ):
19
- path = re .sub ('^query:UserLibrary#' , '~/Music/Ableton/User Library/' , uri )
20
- path = re .sub (':' ,'/' , path )
21
- path = unquote (path )
22
- return path
88
+ self ._normaliseKey ()
89
+
90
+ def _normaliseKey (self ):
91
+ self ._open_key = ""
92
+ self ._musical_key = ""
93
+ if re .match ("[0-9]{1,2}[dm]" , self ._key ):
94
+ self ._open_key = self ._key
95
+ self ._musical_key = OPEN_TO_MUSICAL_KEY [self ._key ] if self ._key in OPEN_TO_MUSICAL_KEY .keys () else "?"
96
+ else :
97
+ key = re .sub ('maj' , '' , self ._key )
98
+ key = re .sub ('min' ,'m' , key )
99
+ self ._musical_key = key
100
+ self ._open_key = MUSIC_TO_OPEN_KEY [key ] if key in MUSIC_TO_OPEN_KEY .keys () else "?"
23
101
24
102
@property
25
103
def filename (self ):
26
104
return self ._file_name
27
105
28
- def item (self ):
29
- return self ._item
30
-
31
106
@property
32
107
def artist (self ):
33
108
return self ._tags .artist
@@ -42,7 +117,7 @@ def duration(self):
42
117
43
118
@property
44
119
def key (self ):
45
- return self ._key
120
+ return self ._open_key + " / " + self . _musical_key
46
121
47
122
@property
48
123
def bpm (self ):
@@ -53,6 +128,14 @@ def genre(self):
53
128
return self ._tags .genre or "undefined"
54
129
55
130
131
+ class BrowserItem (TaggedFile ):
132
+ def __init__ (self , live_browser_item ):
133
+ super (BrowserItem , self ).__init__ (uri_to_path (live_browser_item .uri ))
134
+ self ._item = live_browser_item
135
+
136
+ def item (self ):
137
+ return self ._item
138
+
56
139
class BrowserRepresentation ():
57
140
58
141
SOCKET_IN = "/tmp/LiveMusicBrowser.src.socket"
@@ -64,14 +147,15 @@ def __init__(self, browser):
64
147
self ._current = []
65
148
self ._current_index = 0
66
149
self ._iterate_and_find_audio (browser .user_library )
150
+ self ._playing_tracks = {}
67
151
68
152
if os .path .exists (self .SOCKET_IN ):
69
153
os .remove (self .SOCKET_IN )
70
154
71
155
self ._socket = socket .socket (socket .AF_UNIX , socket .SOCK_DGRAM )
72
156
self ._socket .bind (self .SOCKET_IN )
73
157
self ._socket .setsockopt (socket .SOL_SOCKET , socket .SO_SNDBUF , 250 * 1024 )
74
- # os.system("/Users/sam/Projects/GeoLEDic/build/GeoLEDic.app/Contents/MacOS/GeoLEDic&" )
158
+ self . startUi ( )
75
159
self ._update ()
76
160
77
161
def _iterate_and_find_audio (self , node ):
@@ -111,7 +195,8 @@ def _update(self):
111
195
"BPM" ,
112
196
"Key"
113
197
],
114
- "rows" : []
198
+ "rows" : [],
199
+ "playing" : {}
115
200
}
116
201
117
202
for item in self ._current :
@@ -120,7 +205,40 @@ def _update(self):
120
205
r .append (getattr (item , k .lower ()))
121
206
d ["rows" ].append (r )
122
207
208
+ for track_ix in self ._playing_tracks .keys ():
209
+ d ["playing" ][track_ix ] = []
210
+ for k in d ["cols" ]:
211
+ d ["playing" ][track_ix ].append (getattr (self ._playing_tracks [track_ix ], k .lower ()))
212
+
123
213
try :
124
214
self ._socket .sendto (json .dumps (d , indent = 1 ).encode ('utf-8' ), self .SOCKET_OUT )
125
215
except :
126
- pass
216
+ pass
217
+
218
+ def setPlayingTracks (self , playing_tracks ):
219
+ p = {}
220
+ for i in playing_tracks .keys ():
221
+ p [i ] = TaggedFile (playing_tracks [i ])
222
+ self ._playing_tracks = p
223
+ self ._update ()
224
+
225
+ def startUi (self ):
226
+ pwd = pathlib .Path (__file__ ).parent .resolve ()
227
+ os .system ("'%s/build/LiveMusicBrowser' &" % pwd )
228
+ timeout = 10
229
+ while (timeout > 0 and not os .path .exists (self .SOCKET_OUT )):
230
+ time .sleep (0.1 )
231
+ timeout = timeout - 1
232
+
233
+
234
+ def quitUi (self ):
235
+ try :
236
+ self ._socket .sendto (json .dumps ({"quit" : True }).encode ('utf-8' ), self .SOCKET_OUT )
237
+ except :
238
+ pass
239
+
240
+ def __del__ (self ):
241
+ self .quitUi ()
242
+
243
+ def disconnect (self ):
244
+ self .quitUi ()
0 commit comments