-
Notifications
You must be signed in to change notification settings - Fork 0
/
create_device.lua
251 lines (221 loc) · 8.31 KB
/
create_device.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
local createDevice = {}
local log = hs.logger.new('createDev', 'debug')
createDevice.hotkeys = {}
createDevice.dataFile = 'bce_data.json'
createDevice.freqFile = 'bce_freq.json'
-----------
-- setup --
-----------
function createDevice:start()
createDevice.deviceData = hs.json.read(createDevice.dataFile)
createDevice.freqData = hs.json.read(createDevice.freqFile)
createDevice.chooser = hs.chooser.new(function(choice)
return createDevice:select(choice)
end)
if not (createDevice.presetCommand or createDevice.presetFolders) then
log.i('no preset folders or command provided')
log.i('consider running spoon.Reason:setPresetFolders() in your init.lua')
end
end
function createDevice:bindHotkeys(maps)
table.insert(createDevice.hotkeys, hs.hotkey.new(maps.createDevice[1], maps.createDevice[2], createDevice.show))
end
function createDevice:activate(app)
for _, v in pairs(createDevice.hotkeys) do v:enable() end
createDevice.app = app
end
function createDevice:deactivate()
for _, v in pairs(createDevice.hotkeys) do v:disable() end
end
--------------------
-- implementation --
--------------------
-- createDevice:show()
-- Method
-- Shows the device chooser
-- If the device chooser is already active (double press) then it calls createDevice:rebuild()
function createDevice:show()
if createDevice.chooser:isVisible() then
createDevice:rebuild()
else
createDevice.chooser:choices(createDevice.deviceData)
createDevice.chooser:show()
end
end
-- createDevice:select(choice)
-- Method
-- Creates an instance of the selected device/preset
-- Writes the updated frequency data to createDevice.freqFile
--
-- Parameters:
-- * choice - A choice from the chooser's choices table
function createDevice:select(choice)
if not choice then return end
if choice['menuSelector'] == nil then
-- open preset
local openFilename = choice['subText']
local openCommand = string.format('open -a Reason\\ 12 "%s"', openFilename)
log.d(openCommand)
hs.execute(openCommand)
else
-- create device
log.d(string.format('selected %s', choice['text']))
createDevice.app:selectMenuItem(choice['menuSelector'])
end
if createDevice.freqData[choice['text']] == nil then
createDevice.freqData[choice['text']] = 0
else
createDevice.freqData[choice['text']] = createDevice.freqData[choice['text']] + 1
end
-- write freq data. also writes to bce_data.json since createDeviceRefresh() is called
hs.json.write(createDevice.freqData, createDevice.freqFile, true, true)
createDevice:refresh()
end
-- createDevice:refresh()
-- Method
-- Sorts the device table using the frequency data from createDevice.freqData
-- Writes the list of devices to createDevice.dataFile
function createDevice:refresh()
table.sort(createDevice.deviceData, function(left, right)
if createDevice.freqData[left['text']] == nil then
createDevice.freqData[left['text']] = 0
end
if createDevice.freqData[right['text']] == nil then
createDevice.freqData[right['text']] = 0
end
return createDevice.freqData[left['text']] > createDevice.freqData[right['text']]
end)
hs.json.write(createDevice.deviceData, createDevice.dataFile, true, true)
createDevice.chooser:choices(createDevice.deviceData)
end
function createDevice:_rebuildPresets()
local presets = {}
-- default to preset command
if createDevice.presetCommand then
fileList = hs.execute(createDevice.presetCommand)
for abspath in string.gmatch(fileList, '[^\r\n]+') do
local filename = abspath:match('^.+/(.+)$')
table.insert(presets, {
['text'] = filename,
['subText'] = abspath,
})
log.d(filename)
end
elseif createDevice.presetFolders then
for _, dir in pairs(createDevice.presetFolders) do
presets = createDevice:_presetWalk(dir, presets)
end
else
log.e('no presets specified! cannot rebuild preset list')
end
return presets
end
function createDevice:_presetWalk(dir, presets)
for filename in hs.fs.dir(dir) do
local abspath = string.format('%s/%s', dir, filename)
local uti = hs.fs.fileUTI(abspath)
if (string.find(uti, 'se.propellerheads.reason') or string.find(uti, 'se.propellerheads.rackextension')) then
table.insert(presets, {
['text'] = filename,
['subText'] = abspath,
})
log.d(filename)
elseif string.find(uti, 'public.folder') then
if not (filename == '.' or filename == '..') then
createDevice:_presetWalk(abspath, presets)
end
end
end
return presets
end
function createDevice:_rebuildDevices()
local devices = {}
if createDevice.app:getMenuItems() == nil then return devices end -- quit if no menus are up yet
local menus = createDevice.app:getMenuItems()[4]['AXChildren'][1] -- children of "Create" menu
-- build Instruments, Effects, and Utilities
for i = 7, 9 do
local foundSubmenu = false
for j = 1, #menus[i]['AXChildren'][1] do
-- iterate until we find Reason Studios
local subtitle = menus[i]['AXChildren'][1][j]['AXTitle']
log.d(subtitle)
if subtitle == 'Reason Studios' then foundSubmenu = true end
-- iterate thru this submenu and the successive submenus
if foundSubmenu then
local submenu = menus[i]['AXChildren'][1][j]['AXChildren'][1]
for k = 1, #submenu do
if not (submenu[k]['AXTitle'] == '') then -- table contains divider bars, which have a blank title
local title = submenu[k]['AXTitle']
log.d(title)
table.insert(devices, {
['text'] = title,
['subText'] = string.format('%s - %s',
menus[i]['AXTitle'],
subtitle
),
['menuSelector'] = {
'Create',
menus[i]['AXTitle'],
subtitle,
submenu[k]['AXTitle'],
}
})
end
end
end
end
end
-- build Players
for i = 1, #menus[10]['AXChildren'][1] do
if not (menus[10]['AXChildren'][1][i]['AXTitle'] == '') then -- table may contain divider bars in the future
local title = menus[10]['AXChildren'][1][i]['AXTitle']
log.d(title)
table.insert(devices, {
['text'] = title,
['subText'] = 'Players',
['menuSelector'] = {
'Create', 'Players',
title,
}
})
end
end
return devices
end
-- createDevice:rebuild()
-- Method
-- Rebuilds the device list
-- Scrapes the Create menus for available devices
-- Also scans preset directories for available presets
function createDevice:rebuild()
-- update table and refresh
local newData = {}
-- build devices
for _, v in pairs(createDevice:_rebuildDevices()) do
table.insert(newData, v)
end
-- build presets
if (createDevice.presetCommand or createDevice.presetFolders) then
for _, v in pairs(createDevice:_rebuildPresets()) do
table.insert(newData, v)
end
end
createDevice.deviceData = newData
createDevice:refresh()
hs.alert('rebuilt device list')
end
-- createDevice:setPresetCommand()
-- Method
-- Sets the command used to find presets for the device chooser
-- Any command that returns a list of files separated by newlines (e.g. find, fd) will work
-- Note that any commands must be absolute paths, for example "/usr/bin/find" instead of "find"
--
-- Parameters
-- * presetCommand - A string containing a shell command
function createDevice:setPresetCommand(presetCommand)
createDevice.presetCommand = presetCommand
end
function createDevice:setPresetFolders(presetFolders)
createDevice.presetFolders = presetFolders
end
return createDevice