mirror of
https://github.com/IgorTimofeev/MineOS.git
synced 2025-12-20 02:59:20 +01:00
1046 lines
28 KiB
Lua
1046 lines
28 KiB
Lua
|
|
local GUI = require("GUI")
|
|
local screen = require("Screen")
|
|
local filesystem = require("Filesystem")
|
|
local color = require("Color")
|
|
local image = require("Image")
|
|
local paths = require("Paths")
|
|
local system = require("System")
|
|
local text = require("Text")
|
|
local internet = require("Internet")
|
|
local event = require("Event")
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local currentScriptDirectory = filesystem.path(system.getCurrentScript())
|
|
|
|
local configPath = paths.user.applicationData .. "Pioneer/Config.cfg"
|
|
local config
|
|
|
|
if filesystem.exists(configPath) then
|
|
config = filesystem.readTable(configPath)
|
|
else
|
|
config = {
|
|
tapes = {
|
|
|
|
},
|
|
lastTape = nil
|
|
}
|
|
end
|
|
|
|
local function saveConfig()
|
|
filesystem.writeTable(configPath, config)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local function loadImage(name)
|
|
local result, reason = image.load(currentScriptDirectory .. "Images/" .. name .. ".pic")
|
|
|
|
if not result then
|
|
GUI.alert(reason)
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
local speedSlider
|
|
|
|
local blinkState = false
|
|
local blinkUptime = 0
|
|
local blinkInterval = 0.5
|
|
|
|
local powerButton
|
|
|
|
local tapes
|
|
local tapeIndex
|
|
local tape, tapeConfig
|
|
local tapeWritingProgress
|
|
|
|
local function invoke(...)
|
|
return component.invoke(tape.address, ...)
|
|
end
|
|
|
|
local function getCurrentTapeSpeed()
|
|
local speed = 2 * speedSlider.value - 1
|
|
|
|
if tapeConfig.speedIndex == 0 then
|
|
speed = speed * 0.25
|
|
elseif tapeConfig.speedIndex == 1 then
|
|
speed = speed * 0.5
|
|
elseif tapeConfig.speedIndex == 2 then
|
|
speed = speed * 0.75
|
|
end
|
|
|
|
return speed
|
|
end
|
|
|
|
local function updateCurrentTapeSpeed()
|
|
local speed = getCurrentTapeSpeed()
|
|
|
|
invoke("setSpeed", 1 + speed * 0.75)
|
|
end
|
|
|
|
local function updateCurrentTape()
|
|
tape = tapes[tapeIndex]
|
|
tapeConfig = config.tapes[tape.address]
|
|
speedSlider.value = tapeConfig.speed
|
|
config.lastTape = tape.address
|
|
|
|
updateCurrentTapeSpeed()
|
|
end
|
|
|
|
local function updateTapes()
|
|
tapes = {}
|
|
tapeIndex = 1
|
|
tape = nil
|
|
tapeConfig = nil
|
|
|
|
local counter = 1
|
|
|
|
for address in component.list("tape_drive") do
|
|
table.insert(tapes, {
|
|
address = address,
|
|
size = component.invoke(address, "getSize")
|
|
})
|
|
|
|
if not config.tapes[address] then
|
|
config.tapes[address] = {
|
|
speed = 0.5,
|
|
speedIndex = 0,
|
|
cue = 0,
|
|
cues = {},
|
|
cueIndex = 1,
|
|
hotCues = {}
|
|
}
|
|
end
|
|
|
|
if config.lastTape == address then
|
|
tapeIndex = counter
|
|
end
|
|
|
|
counter = counter + 1
|
|
end
|
|
|
|
if #tapes > 0 then
|
|
updateCurrentTape()
|
|
end
|
|
end
|
|
|
|
-------------------------------- Round mini button ------------------------------------------------
|
|
|
|
local function roundMiniButtonDraw(button)
|
|
local bg, fg = button.animationCurrentBackground, (powerButton.pressed or button.ignoresPower) and button.animationCurrentText or 0x0
|
|
|
|
-- Background
|
|
screen.drawRectangle(button.x + 1, button.y + 1, button.width - 2, button.height - 2, bg, fg, " ")
|
|
|
|
-- Upper
|
|
screen.drawText(button.x + 1, button.y, bg, string.rep("⣀", button.width - 2))
|
|
|
|
-- Left
|
|
screen.drawText(button.x, button.y + 1, bg, "⢸")
|
|
|
|
-- Middle
|
|
screen.drawText(math.floor(button.x + button.width / 2 - #button.text / 2), button.y + 1, fg, button.text)
|
|
|
|
-- Right
|
|
screen.drawText(button.x + button.width - 1, button.y + 1, bg, "⡇")
|
|
|
|
-- Lower
|
|
screen.drawText(button.x + 1, button.y + button.height - 1, bg, string.rep("⠉", button.width - 2))
|
|
end
|
|
|
|
|
|
local function roundMiniButtonEventHandler(workspace, button, e1, e2, e3, e4, e5)
|
|
if e1 == "touch" then
|
|
button:press()
|
|
end
|
|
end
|
|
|
|
local function newRoundMiniButton(x, y, ...)
|
|
local button = GUI.button(x, y, 4, 3, ...)
|
|
|
|
button.draw = roundMiniButtonDraw
|
|
button.eventHandler = roundMiniButtonEventHandler
|
|
|
|
return button
|
|
end
|
|
|
|
|
|
local function roundTinyButtonDraw(button)
|
|
local bg, fg = button.animationCurrentBackground, (powerButton.pressed or button.ignoresPower) and button.animationCurrentText or 0x0
|
|
|
|
-- Left
|
|
screen.drawText(button.x, button.y, bg, "⢰")
|
|
|
|
-- Middle
|
|
screen.drawRectangle(button.x + 1, button.y, 2, 1, bg, fg, " ")
|
|
screen.drawText(button.x + 1, button.y, fg, button.text)
|
|
|
|
-- Right
|
|
screen.drawText(button.x + 3, button.y, bg, "⡆")
|
|
|
|
-- Lower
|
|
screen.drawText(button.x, button.y + 1, bg, "⠈⠛⠛⠁")
|
|
end
|
|
|
|
local function newRoundTinyButton(x, y, ...)
|
|
local button = GUI.button(x, y, 4, 2, ...)
|
|
|
|
button.draw = roundTinyButtonDraw
|
|
button.eventHandler = roundMiniButtonEventHandler
|
|
|
|
return button
|
|
end
|
|
|
|
-------------------------------- Window ------------------------------------------------
|
|
|
|
local backgroundImage = loadImage("Background")
|
|
|
|
local workspace, window, menu = system.addWindow(GUI.window(1, 1, 78, 49))
|
|
|
|
window.drawShadow = false
|
|
|
|
|
|
-------------------------------- Jog ------------------------------------------------
|
|
|
|
local jogImages = {}
|
|
|
|
for i = 1, 12 do
|
|
jogImages[i] = loadImage("Jog" .. i)
|
|
end
|
|
|
|
-------------------------------- Overlay ------------------------------------------------
|
|
|
|
local overlay = window:addChild(GUI.object(1, 1, window.width, window.height))
|
|
|
|
local currentJogIndex = 1
|
|
local displayWidth, displayHeight = 33, 10
|
|
|
|
local function displayDrawProgressBar(x, y, width, progress)
|
|
local progressActiveWidth = math.floor(progress * width)
|
|
|
|
screen.drawText(x, y, 0xE1E1E1, string.rep("━", progressActiveWidth))
|
|
screen.drawText(x + progressActiveWidth, y, 0x4B4B4B, string.rep("━", width - progressActiveWidth))
|
|
end
|
|
|
|
overlay.draw = function(overlay)
|
|
screen.drawImage(overlay.x, overlay.y, backgroundImage)
|
|
|
|
-- Ignoring if power is off
|
|
if not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
-- Power indicator
|
|
screen.drawText(overlay.x + 73, overlay.y + 3, 0xFF0000, "●")
|
|
|
|
-- Speed slider indicator
|
|
screen.drawText(overlay.x + 68, overlay.y + 39, 0xFFDB40, "⠆")
|
|
|
|
-- Jog
|
|
screen.drawImage(overlay.x + 33, overlay.y + 29, jogImages[currentJogIndex])
|
|
|
|
-- Display
|
|
local displayX, displayY = overlay.x + 22, overlay.y + 3
|
|
|
|
if tapeWritingProgress then
|
|
local progressWidth = displayWidth - 4
|
|
|
|
displayDrawProgressBar(
|
|
math.floor(displayX + displayWidth / 2 - progressWidth / 2),
|
|
math.floor(displayY + displayHeight / 2),
|
|
progressWidth,
|
|
tapeWritingProgress
|
|
)
|
|
|
|
-- UpperText
|
|
local text = "Writing in progress"
|
|
|
|
screen.drawText(
|
|
math.floor(displayX + displayWidth / 2 - #text / 2),
|
|
displayY + 1,
|
|
0xE1E1E1,
|
|
text
|
|
)
|
|
else
|
|
-- Label
|
|
local label = tape and invoke("getLabel") or "No tape"
|
|
|
|
if not label or #label == 0 then
|
|
label = "Untitled tape"
|
|
end
|
|
|
|
screen.drawRectangle(displayX, displayY, displayWidth, 1, 0x004980, 0xE1E1E1, " ")
|
|
screen.drawText(displayX + 1, displayY, 0xE1E1E1, text.limit("♪ " .. label, displayWidth - 3))
|
|
|
|
if tape then
|
|
-- Stats
|
|
local position = invoke("getPosition")
|
|
local statsX = displayX + 2
|
|
local statsY = displayY + displayHeight - 5
|
|
|
|
-- Track index
|
|
screen.drawText(statsX, statsY, 0xE1E1E1, "Track")
|
|
screen.drawText(statsX, statsY + 1, 0xE1E1E1, string.format("%02d", tapeIndex))
|
|
|
|
-- Time
|
|
local timeSecondsTotal = position / (1500 * 4)
|
|
local timeMinutes = math.floor(timeSecondsTotal / 60)
|
|
local timeSeconds, timeMilliseconds = math.modf(timeSecondsTotal - timeMinutes * 60)
|
|
screen.drawText(statsX + 10, statsY + 1, 0xE1E1E1, string.format("%02d", timeMinutes) .. "m:" .. string.format("%02d", timeSeconds) .. "s".. string.format("%03d", math.floor(timeMilliseconds * 1000)))
|
|
|
|
-- Tempo
|
|
screen.drawText(statsX + 24, statsY, 0xE1E1E1, "Tempo")
|
|
screen.drawText(statsX + 26, statsY + 1, 0xE1E1E1, string.format("%02d", math.floor(getCurrentTapeSpeed() * 100)) .. "%")
|
|
|
|
-- Tempo index
|
|
|
|
-- Track
|
|
local trackWidth = displayWidth - 4
|
|
local trackHeight = 3
|
|
statsY = statsY + 2
|
|
|
|
screen.drawRectangle(
|
|
statsX,
|
|
statsY + 1,
|
|
trackWidth,
|
|
trackHeight - 2,
|
|
0x2D2D2D,
|
|
0xE1E1E1,
|
|
" "
|
|
)
|
|
|
|
screen.drawText(
|
|
math.floor(statsX + (tape.size == 0 and 0 or position / tape.size) * trackWidth),
|
|
statsY + 1,
|
|
0xE1E1E1,
|
|
"│"
|
|
)
|
|
|
|
-- Memory cues
|
|
local cueY = statsY
|
|
|
|
for i = 1, #tapeConfig.cues do
|
|
screen.drawText(
|
|
statsX + math.floor(tapeConfig.cues[i] / tape.size * trackWidth),
|
|
cueY,
|
|
i == tapeConfig.cueIndex and 0xE1E1E1 or 0xCC0000,
|
|
"•"
|
|
)
|
|
end
|
|
|
|
-- Hot cues
|
|
for name, position in pairs(tapeConfig.hotCues) do
|
|
screen.drawText(
|
|
statsX + math.floor(position / tape.size * trackWidth),
|
|
cueY,
|
|
0x66FF40,
|
|
"•" .. name
|
|
)
|
|
end
|
|
|
|
-- Current cue
|
|
cueY = statsY + trackHeight - 1
|
|
|
|
screen.drawText(
|
|
statsX + math.floor(tapeConfig.cue / tape.size * trackWidth),
|
|
cueY,
|
|
0xFFB640,
|
|
"•"
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------- Power button ------------------------------------------------
|
|
|
|
powerButton = window:addChild(GUI.object(75, 2, 4, 2))
|
|
|
|
powerButton.pressed = false
|
|
|
|
powerButton.draw = function()
|
|
screen.drawText(powerButton.x, powerButton.y, 0x1E1E1E, powerButton.pressed and "⣠⣤⣄" or "⣸⣿⣇")
|
|
end
|
|
|
|
powerButton.eventHandler = function(workspace, powerButton, e1)
|
|
if e1 == "touch" then
|
|
powerButton.pressed = not powerButton.pressed
|
|
|
|
-- Stopping playback
|
|
if powerButton.pressed then
|
|
currentJogIndex = 1
|
|
else
|
|
for i = 1, #tapes do
|
|
component.invoke(tapes[i].address, "stop")
|
|
end
|
|
end
|
|
|
|
workspace:draw()
|
|
|
|
computer.beep(20, 0.01)
|
|
end
|
|
end
|
|
|
|
-------------------------------- ImageButton ------------------------------------------------
|
|
|
|
local function imageButtonDraw(button)
|
|
screen.drawImage(button.x, button.y, (powerButton.pressed and (not button.blinking or blinkState)) and button.imageOn or button.imageOff)
|
|
end
|
|
|
|
local function newImageButton(x, y, width, height, name)
|
|
local button = GUI.object(x, y, width, height)
|
|
|
|
button.imageOn = loadImage(name .. "On")
|
|
button.imageOff = loadImage(name .. "Off")
|
|
|
|
button.draw = imageButtonDraw
|
|
|
|
return button
|
|
end
|
|
|
|
|
|
-------------------------------- Speed slider ------------------------------------------------
|
|
|
|
local speedSliderImage = loadImage("SpeedSlider")
|
|
|
|
speedSlider = window:addChild(GUI.object(71, 33, 5, 15))
|
|
speedSlider.value = 0.5
|
|
|
|
speedSlider.draw = function(speedSlider)
|
|
-- screen.drawRectangle(speedSlider.x, speedSlider.y, speedSlider.width, speedSlider.height, 0xFF0000, 0x0, " ")
|
|
|
|
local x = speedSlider.x
|
|
local y = speedSlider.y + math.floor((1 - speedSlider.value) * speedSlider.height) - math.floor((1 - speedSlider.value) * 3)
|
|
|
|
screen.drawImage(x, y, speedSliderImage)
|
|
end
|
|
|
|
speedSlider.eventHandler = function(workspace, speedSlider, e1, e2, e3, e4)
|
|
if (e1 == "touch" or e1 == "drag") then
|
|
if e4 == speedSlider.y + speedSlider.height - 1 then
|
|
speedSlider.value = 0
|
|
elseif e4 == math.floor(speedSlider.y + speedSlider.height / 2) then
|
|
speedSlider.value = 0.5
|
|
else
|
|
speedSlider.value = 1 - ((e4 - speedSlider.y) / speedSlider.height)
|
|
end
|
|
|
|
if tape and powerButton.pressed then
|
|
tapeConfig.speed = speedSlider.value
|
|
|
|
updateCurrentTapeSpeed()
|
|
end
|
|
|
|
workspace:draw()
|
|
end
|
|
end
|
|
|
|
-------------------------------- Display buttons ------------------------------------------------
|
|
|
|
local function displayButtonDraw(button)
|
|
local bg, fg = button.animationCurrentBackground, (powerButton.pressed or button.ignoresPower) and button.animationCurrentText or 0x4B4B4B
|
|
|
|
-- Background
|
|
screen.drawRectangle(button.x + 1, button.y + 1, button.width - 2, button.height - 2, bg, fg, " ")
|
|
|
|
-- Upper
|
|
screen.drawText(button.x, button.y, fg, "⢀" .. string.rep("⣀", button.width - 2) .. "⡀")
|
|
|
|
-- Left
|
|
screen.drawText(button.x, button.y + 1, fg, "⢸")
|
|
|
|
-- Middle
|
|
screen.drawText(math.floor(button.x + button.width / 2 - unicode.len(button.text) / 2), button.y + 1, fg, button.text)
|
|
|
|
-- Right
|
|
screen.drawText(button.x + button.width - 1, button.y + 1, fg, "⡇")
|
|
|
|
-- Lower
|
|
screen.drawText(button.x, button.y + button.height - 1, fg, "⠈" .. string.rep("⠉", button.width - 2) .. "⠁")
|
|
|
|
end
|
|
|
|
local function newDisplayButton(x, y, width, ...)
|
|
local button = GUI.button(x, y, width, 3, ...)
|
|
|
|
button.pressed = false
|
|
button.draw = displayButtonDraw
|
|
|
|
return button
|
|
end
|
|
|
|
local helpButton = window:addChild(newDisplayButton(14, 1, 7, 0x0F0F0F, 0xF0F0F0, 0x0, 0xA5A5A5, "Help"))
|
|
helpButton.onTouch = function()
|
|
if not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
local container = GUI.addBackgroundContainer(workspace, true, true, "Help")
|
|
container.layout:removeChildren()
|
|
|
|
local lines = {
|
|
"Pioneer CDJ-2000 nexus",
|
|
" ",
|
|
"Pro-grade digital DJ deck for Computronics",
|
|
"tape drives and DFPWM audio codec.",
|
|
"To convert your favorite tracks, use",
|
|
"https://music.madefor.cc",
|
|
" ",
|
|
"Designed by Pioneer Corporation in Japan",
|
|
" ",
|
|
"Developed and adapted for MineOS by",
|
|
"Igor Timofeev, vk.com/id7799889",
|
|
"Maxim Afonin, @140bpmdubstep"
|
|
}
|
|
|
|
local textBox = container.layout:addChild(GUI.textBox(1, 1, container.layout.width, #lines, nil, 0xB4B4B4, lines, 1, 0, 0))
|
|
textBox:setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
|
|
textBox.eventHandler = container.panel.eventHandler
|
|
|
|
workspace:draw()
|
|
end
|
|
|
|
local closeButton = window:addChild(newDisplayButton(14, 4, 7, 0x0F0F0F, 0x4B4B4B, 0x0, 0x3349FF, "Close"))
|
|
closeButton.onTouch = function()
|
|
window:remove()
|
|
end
|
|
|
|
local wipeButton = window:addChild(newDisplayButton(14, 7, 7, 0x0F0F0F, 0x4B4B4B, 0x0, 0xFFDB40, "Wipe"))
|
|
|
|
local fileButton = window:addChild(newDisplayButton(23, 1, 10, 0x0F0F0F, 0x4B4B4B, 0x0, 0xFFDB40, "File"))
|
|
fileButton.onTouch = function()
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
local filesystemDialog = GUI.addFilesystemDialog(workspace, true, 50, math.floor(window.height * 0.8), "Confirm", "Cancel", "File name", "/")
|
|
|
|
filesystemDialog:setMode(GUI.IO_MODE_OPEN, GUI.IO_MODE_FILE)
|
|
filesystemDialog:addExtensionFilter(".dfpwm")
|
|
filesystemDialog:expandPath(paths.user.desktop)
|
|
filesystemDialog:show()
|
|
|
|
filesystemDialog.onSubmit = function(path)
|
|
local tapeSpaceFree = tape.size - invoke("getPosition")
|
|
local fileSize = filesystem.size(path)
|
|
|
|
if fileSize > tapeSpaceFree then
|
|
GUI.alert("Not enough space on tape")
|
|
return
|
|
end
|
|
|
|
local file = filesystem.open(path, "rb")
|
|
|
|
invoke("stop")
|
|
|
|
local bytesWritten, chunk = 0
|
|
while true do
|
|
chunk = file:read(8192)
|
|
|
|
if not chunk then
|
|
break
|
|
end
|
|
|
|
if not invoke("isReady") then
|
|
GUI.alert("Tape was removed during writing")
|
|
break
|
|
end
|
|
|
|
invoke("write", chunk)
|
|
|
|
bytesWritten = bytesWritten + #chunk
|
|
tapeWritingProgress = bytesWritten / fileSize
|
|
workspace:draw()
|
|
end
|
|
|
|
file:close()
|
|
invoke("seek", -tape.size)
|
|
tapeWritingProgress = nil
|
|
end
|
|
end
|
|
|
|
local urlButton = window:addChild(newDisplayButton(34, 1, 10, 0x0F0F0F, 0x4B4B4B, 0x0, 0xFFDB40, "Url"))
|
|
|
|
local labelButton = window:addChild(newDisplayButton(45, 1, 10, 0x0F0F0F, 0x4B4B4B, 0x0, 0xFFDB40, "Label"))
|
|
labelButton.onTouch = function()
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
local container = GUI.addBackgroundContainer(workspace, true, true, title)
|
|
|
|
local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x2D2D2D, invoke("getLabel") or "", "New label", false))
|
|
|
|
input.onInputFinished = function()
|
|
invoke("setLabel", input.text)
|
|
workspace:draw()
|
|
end
|
|
|
|
container.panel.onTouch = function()
|
|
container:remove()
|
|
workspace:draw()
|
|
end
|
|
|
|
workspace:draw()
|
|
end
|
|
|
|
-------------------------------- Quantize/time buttons ------------------------------------------------
|
|
|
|
local _ = window:addChild(newRoundTinyButton(14, 12, 0x0F0F0F, 0xFF0000, 0x0F0F0F, 0xFF0000, "⢠⡄"))
|
|
local _ = window:addChild(newRoundTinyButton(18, 12, 0x0F0F0F, 0x2D2D2D, 0x0F0F0F, 0x2D2D2D, "⢠⡄"))
|
|
|
|
-------------------------------- Needle search ------------------------------------------------
|
|
|
|
local needleSearch = window:addChild(GUI.object(25, 15, 29, 2))
|
|
|
|
needleSearch.draw = function()
|
|
screen.drawText(needleSearch.x, needleSearch.y, powerButton.pressed and 0xE1E1E1 or 0x0, "▲ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ╷ ▲")
|
|
end
|
|
|
|
needleSearch.eventHandler = function(workspace, needleSearch, e1, e2, e3, e4)
|
|
if (e1 == "touch" or e1 == "drag") and powerButton.pressed and tape then
|
|
local newPosition = math.floor((e3 - needleSearch.x) / needleSearch.width * tape.size)
|
|
|
|
invoke("seek", newPosition - invoke("getPosition"))
|
|
|
|
workspace:draw()
|
|
end
|
|
end
|
|
|
|
-------------------------------- Pref/next tape button ------------------------------------------------
|
|
|
|
local previousTapeButton = window:addChild(newRoundMiniButton(2, 30, 0x2D2D2D, 0xFFB600, 0x0F0F0F, 0xCC9200, "<<"))
|
|
local nextTapeButton = window:addChild(newRoundMiniButton(7, 30, 0x2D2D2D, 0xFFB600, 0x0F0F0F, 0xCC9200, ">>"))
|
|
|
|
local function incrementTape(next)
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
tapeIndex = tapeIndex + (next and 1 or -1)
|
|
|
|
if tapeIndex > #tapes then
|
|
tapeIndex = 1
|
|
elseif tapeIndex < 1 then
|
|
tapeIndex = #tapes
|
|
end
|
|
|
|
updateCurrentTape()
|
|
saveConfig()
|
|
end
|
|
|
|
previousTapeButton.onTouch = function()
|
|
incrementTape(false)
|
|
end
|
|
|
|
nextTapeButton.onTouch = function()
|
|
incrementTape(true)
|
|
end
|
|
|
|
-------------------------------- Pref/next search button ------------------------------------------------
|
|
|
|
local previousSearchButton = window:addChild(newRoundMiniButton(2, 34, 0x2D2D2D, 0xFFB600, 0x0F0F0F, 0xCC9200, "<<"))
|
|
local nextSearchButton = window:addChild(newRoundMiniButton(7, 34, 0x2D2D2D, 0xFFB600, 0x0F0F0F, 0xCC9200, ">>"))
|
|
|
|
previousSearchButton.onTouch = function()
|
|
|
|
end
|
|
|
|
nextSearchButton.onTouch = function()
|
|
|
|
end
|
|
|
|
-------------------------------- Hot cue buttons ------------------------------------------------
|
|
|
|
local hotCueRecCallButton
|
|
|
|
local function hotCueButtonDraw(button)
|
|
local bg = button.animationCurrentBackground
|
|
local fg = (powerButton.pressed and tape and (hotCueRecCallButton.rec or tapeConfig.hotCues[button.text])) and button.animationCurrentText or 0x2D2D2D
|
|
|
|
-- Upper
|
|
screen.drawText(button.x, button.y, bg, "⢀" .. string.rep("⣀", button.width - 2) .. "⡀")
|
|
|
|
-- Left
|
|
screen.drawText(button.x, button.y + 1, bg, "⢸")
|
|
|
|
-- Middle
|
|
screen.set(button.x + 1, button.y + 1, 0x2D2D2D, 0x5A5A5A, "⣤")
|
|
screen.set(button.x + 2, button.y + 1, bg, 0x787878, "⠤")
|
|
|
|
screen.set(button.x + 3, button.y + 1, bg, fg, button.text)
|
|
|
|
screen.set(button.x + 4, button.y + 1, bg, 0x787878, "⠒")
|
|
screen.set(button.x + 5, button.y + 1, 0x2D2D2D, 0x5A5A5A, "⠛")
|
|
|
|
-- Right
|
|
screen.drawText(button.x + button.width - 1, button.y + 1, bg, "⡇")
|
|
|
|
-- Lower
|
|
screen.drawText(button.x, button.y + button.height - 1, bg, "⠈" .. string.rep("⠉", button.width - 2) .. "⠁")
|
|
|
|
end
|
|
|
|
local hotCueButtons = {}
|
|
|
|
local function newHotCueButton(x, y, index, text)
|
|
local button = GUI.button(x, y, 7, 3, 0x1E1E1E, 0x66FF40, 0x0, 0x336D00, text)
|
|
|
|
button.draw = hotCueButtonDraw
|
|
|
|
button.onTouch = function()
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
local hotCuePosition = tapeConfig.hotCues[button.text]
|
|
|
|
if hotCueRecCallButton.rec then
|
|
local position = invoke("getPosition")
|
|
tapeConfig.hotCues[button.text] = (not hotCuePosition or hotCuePosition ~= position) and position or nil
|
|
|
|
workspace:draw()
|
|
saveConfig()
|
|
|
|
elseif hotCuePosition then
|
|
invoke("seek", hotCuePosition - invoke("getPosition"))
|
|
workspace:draw()
|
|
end
|
|
end
|
|
|
|
table.insert(hotCueButtons, button)
|
|
|
|
return button
|
|
end
|
|
|
|
-- local hotCueButtonA = window:addChild(newHotCueButton(3, 13, 0x66FF40, 0x336D00, "A"))
|
|
-- local hotCueButtonB = window:addChild(newHotCueButton(3, 16, 0xFFB600, 0x664900, "B"))
|
|
-- local hotCueButtonB = window:addChild(newHotCueButton(3, 19, 0xFF2440, 0x660000, "C"))
|
|
|
|
local hotCueButtonA = window:addChild(newHotCueButton(3, 13, 1, "A"))
|
|
local hotCueButtonB = window:addChild(newHotCueButton(3, 16, 2, "B"))
|
|
local hotCueButtonB = window:addChild(newHotCueButton(3, 19, 3, "C"))
|
|
|
|
hotCueRecCallButton = window:addChild(GUI.button(3, 23, 7, 3, 0x1E1E1E, 0x1E1E1E, 0x0, 0x0, "⠶"))
|
|
hotCueRecCallButton.rec = false
|
|
hotCueRecCallButton.draw = hotCueButtonDraw
|
|
hotCueRecCallButton.onTouch = function()
|
|
hotCueRecCallButton.rec = not hotCueRecCallButton.rec
|
|
|
|
-- Updating buttons color scheme
|
|
local button
|
|
|
|
for i = 1, #hotCueButtons do
|
|
button = hotCueButtons[i]
|
|
|
|
button.colors.default.text = hotCueRecCallButton.rec and 0xFF2440 or 0x66FF40
|
|
button.colors.pressed.text = hotCueRecCallButton.rec and 0x660000 or 0x336D00
|
|
button.animationCurrentText = button.colors.default.text
|
|
end
|
|
|
|
workspace:draw()
|
|
end
|
|
|
|
-------------------------------- Loop buttons ------------------------------------------------
|
|
|
|
local function loopButtonDraw(button)
|
|
local border, color1, color2, color3, color4
|
|
|
|
if powerButton.pressed then
|
|
if button.pressed then
|
|
border, color1, color2, color3, color4 = 0x332400, 0x996D00, 0x996D00, 0x996D00, 0x996D00
|
|
else
|
|
border, color1, color2, color3, color4 = 0x332400, 0xFFDB80, 0xFFDB40, 0xFFB680, 0xFFB640
|
|
end
|
|
else
|
|
border, color1, color2, color3, color4 = 0x0F0F0F, 0x332400, 0x332400, 0x332400, 0x332400
|
|
end
|
|
|
|
-- 1
|
|
screen.drawText(button.x, button.y, border, "⢰")
|
|
screen.set(button.x + 1, button.y, color1, border, "⠉")
|
|
screen.set(button.x + 2, button.y, color2, border, "⠉")
|
|
screen.set(button.x + 3, button.y, color3, border, "⠉")
|
|
screen.drawText(button.x + 4, button.y, border, "⡆")
|
|
|
|
-- 2
|
|
screen.drawText(button.x, button.y + 1, border, "⠸")
|
|
screen.set(button.x + 1, button.y + 1, color4, border, "⣀")
|
|
screen.set(button.x + 2, button.y + 1, color4, border, "⣀")
|
|
screen.set(button.x + 3, button.y + 1, color3, border, "⣀")
|
|
screen.drawText(button.x + 4, button.y + 1, border, "⠇")
|
|
end
|
|
|
|
local function loopButtonEventHandler(workspace, button, e1, e2, e3, e4, e5)
|
|
if e1 == "touch" then
|
|
button.pressed = true
|
|
workspace:draw()
|
|
|
|
event.sleep(0.2)
|
|
|
|
button.pressed = false
|
|
workspace:draw()
|
|
|
|
if button.onTouch then
|
|
button.onTouch()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function newLoopButton(x, y)
|
|
local button = GUI.object(x, y, 5, 2)
|
|
|
|
button.pressed = false
|
|
button.draw = loopButtonDraw
|
|
button.eventHandler = loopButtonEventHandler
|
|
|
|
return button
|
|
end
|
|
|
|
|
|
local loopButtonIn = window:addChild(newLoopButton(13, 18))
|
|
local loopButtonOut = window:addChild(newLoopButton(19, 18))
|
|
|
|
local reloopButton = window:addChild(newRoundTinyButton(26, 18, 0x2D2D2D, 0xFFB640, 0x1E1E1E, 0x996D00, "⢠⡄"))
|
|
|
|
loopButtonIn.onTouch = function()
|
|
|
|
end
|
|
|
|
-------------------------------- Cue memory buttons ------------------------------------------------
|
|
|
|
local function incrementCueIndex(value)
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
if #tapeConfig.cues == 0 then
|
|
tapeConfig.cueIndex = 1
|
|
return
|
|
end
|
|
|
|
tapeConfig.cueIndex = tapeConfig.cueIndex + value
|
|
|
|
if tapeConfig.cueIndex < 1 then
|
|
tapeConfig.cueIndex = #tapeConfig.cues
|
|
elseif tapeConfig.cueIndex > #tapeConfig.cues then
|
|
tapeConfig.cueIndex = 1
|
|
end
|
|
|
|
tapeConfig.cue = tapeConfig.cues[tapeConfig.cueIndex]
|
|
|
|
-- invoke("stop")
|
|
invoke("seek", tapeConfig.cue - invoke("getPosition"))
|
|
|
|
saveConfig()
|
|
end
|
|
|
|
local cuePrevButton = window:addChild(newRoundTinyButton(50, 18, 0x0F0F0F, 0xFFB640, 0x0, 0x996D00, "⢔ "))
|
|
cuePrevButton.onTouch = function()
|
|
incrementCueIndex(-1)
|
|
end
|
|
|
|
local cueNextButton = window:addChild(newRoundTinyButton(54, 18, 0x0F0F0F, 0xFFB640, 0x0, 0x996D00, " ⡢"))
|
|
cueNextButton.onTouch = function()
|
|
incrementCueIndex(1)
|
|
end
|
|
|
|
local cueDelButton = window:addChild(newRoundTinyButton(59, 18, 0x0F0F0F, 0x4B4B4B, 0x0, 0x2D2D2D, " "))
|
|
cueDelButton.onTouch = function()
|
|
if not tape or not powerButton.pressed or #tapeConfig.cues == 0 then
|
|
return
|
|
end
|
|
|
|
table.remove(tapeConfig.cues, tapeConfig.cueIndex)
|
|
|
|
if tapeConfig.cueIndex > #tapeConfig.cues then
|
|
tapeConfig.cueIndex = math.max(tapeConfig.cueIndex - 1, 1)
|
|
end
|
|
|
|
saveConfig()
|
|
end
|
|
|
|
local cueMemButton = window:addChild(newRoundTinyButton(63, 18, 0x0F0F0F, 0x4B4B4B, 0x0, 0x2D2D2D, " "))
|
|
cueMemButton.onTouch = function()
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
local cue
|
|
|
|
for i = 1, #tapeConfig.cues do
|
|
cue = tapeConfig.cues[i]
|
|
|
|
if cue == tapeConfig.cue then
|
|
return
|
|
end
|
|
end
|
|
|
|
table.insert(tapeConfig.cues, tapeConfig.cue)
|
|
table.sort(tapeConfig.cues)
|
|
|
|
saveConfig()
|
|
end
|
|
|
|
-------------------------------- Cue / play buttons ------------------------------------------------
|
|
|
|
local cueButton = window:addChild(newImageButton(2, window.height - 11, 9, 5, "Cue"))
|
|
|
|
local playButton = window:addChild(newImageButton(2, window.height - 5, 9, 5, "Play"))
|
|
playButton.blinking = true
|
|
|
|
cueButton.eventHandler = function(workspace, cueButton, e1)
|
|
if e1 == "touch" and tape and powerButton.pressed then
|
|
if playButton.blinking then
|
|
tapeConfig.cue = invoke("getPosition")
|
|
cueButton.blinking = false
|
|
|
|
workspace:draw()
|
|
saveConfig()
|
|
else
|
|
invoke("seek", tapeConfig.cue - invoke("getPosition"))
|
|
|
|
workspace:draw()
|
|
end
|
|
end
|
|
end
|
|
|
|
playButton.eventHandler = function(workspace, playButton, e1)
|
|
if e1 == "touch" and tape and powerButton.pressed then
|
|
playButton.blinking = not playButton.blinking
|
|
|
|
if not playButton.blinking then
|
|
cueButton.blinking = false
|
|
end
|
|
|
|
invoke(playButton.blinking and "stop" or "play")
|
|
|
|
workspace:draw()
|
|
end
|
|
end
|
|
|
|
-------------------------------- Jog mode ------------------------------------------------
|
|
|
|
local jogModeDisplay = window:addChild(GUI.object(71, 20, 3, 2))
|
|
jogModeDisplay.draw = function(jogModeDisplay)
|
|
local vinyl = (powerButton.pressed and not config.jogModeCdj) and 0x00B6FF or 0x002440
|
|
local cdj = (powerButton.pressed and config.jogModeCdj) and 0xFFFF40 or 0x332400
|
|
|
|
screen.drawText(jogModeDisplay.x, jogModeDisplay.y, vinyl, "⢀⣀⡀")
|
|
screen.drawRectangle(jogModeDisplay.x, jogModeDisplay.y + 1, 3, 1, vinyl, cdj, "⣤")
|
|
screen.drawText(jogModeDisplay.x, jogModeDisplay.y + 2, cdj, "⠈⠉⠁")
|
|
end
|
|
|
|
local jogModeButton = window:addChild(newRoundMiniButton(74, 20, 0x2D2D2D, 0x696969, 0x1E1E1E, 0x3C3C3C, "JM"))
|
|
jogModeButton.ignoresPower = true
|
|
jogModeButton.onTouch = function()
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
config.jogModeCdj = not config.jogModeCdj
|
|
workspace:draw()
|
|
saveConfig()
|
|
end
|
|
-------------------------------- Right beat buttons ------------------------------------------------
|
|
|
|
local beatSyncButton = window:addChild(newRoundMiniButton(70, 24, 0xB4B4B4, 0x0F0F0F, 0x787878, 0x0F0F0F, "Sy"))
|
|
beatSyncButton.ignoresPower = true
|
|
|
|
local beatSyncMasterButton = window:addChild(newRoundMiniButton(74, 24, 0xB4B4B4, 0x0F0F0F, 0x787878, 0x0F0F0F, "Ms"))
|
|
beatSyncMasterButton.ignoresPower = true
|
|
|
|
-------------------------------- Right tempo buttons ------------------------------------------------
|
|
|
|
local tempoButton = window:addChild(newRoundTinyButton(72, 28, 0x0F0F0F, 0x2D2D2D, 0x0, 0xFF2440, " "))
|
|
|
|
local masterTempoButton = window:addChild(newRoundTinyButton(72, 31, 0x0F0F0F, 0x2D2D2D, 0x0F0F0F, 0xFF0000, "⢠⡄"))
|
|
masterTempoButton.switchMode = true
|
|
masterTempoButton:press()
|
|
|
|
tempoButton.onTouch = function()
|
|
tapeConfig.speedIndex = tapeConfig.speedIndex + 1
|
|
|
|
if tapeConfig.speedIndex > 3 then
|
|
tapeConfig.speedIndex = 1
|
|
end
|
|
|
|
updateCurrentTapeSpeed()
|
|
saveConfig()
|
|
end
|
|
|
|
-------------------------------- Events ------------------------------------------------
|
|
|
|
local jogIncrementSpeedMin = 0.05
|
|
local jogIncrementSpeedMax = 1
|
|
local jogIncrementUptime = 0
|
|
|
|
local overrideWindowEventHandler = window.eventHandler
|
|
|
|
window.eventHandler = function(workspace, window, e1, e2, e3, ...)
|
|
if (e1 == "component_added" or e1 == "component_removed") and e3 == "tape_drive" then
|
|
updateTapes()
|
|
else
|
|
overrideWindowEventHandler(workspace, window, e1, e2, e3, ...)
|
|
|
|
if not tape or not powerButton.pressed then
|
|
return
|
|
end
|
|
|
|
local shouldDraw = false
|
|
local isPlaying = invoke("getState") == "PLAYING"
|
|
local position = invoke("getPosition")
|
|
local uptime = computer.uptime()
|
|
|
|
-- Cheching if play button state was changed
|
|
if isPlaying == playButton.blinking then
|
|
playButton.blinking = not playButton.blinking
|
|
shouldDraw = true
|
|
end
|
|
|
|
-- Cue button
|
|
local cueButtonBlinking = playButton.blinking and tapeConfig.cue ~= invoke("getPosition")
|
|
|
|
if cueButtonBlinking ~= cueButton.blinking then
|
|
cueButton.blinking = cueButtonBlinking
|
|
shouldDraw = true
|
|
end
|
|
|
|
if isPlaying then
|
|
-- Rotating jog
|
|
if uptime > jogIncrementUptime then
|
|
currentJogIndex = currentJogIndex + 1
|
|
|
|
if currentJogIndex > #jogImages then
|
|
currentJogIndex = 1
|
|
end
|
|
|
|
jogIncrementUptime = uptime + (1 - speedSlider.value) * (jogIncrementSpeedMax - jogIncrementSpeedMin)
|
|
shouldDraw = true
|
|
end
|
|
else
|
|
jogIncrementUptime = uptime + (1 - speedSlider.value) * (jogIncrementSpeedMax - jogIncrementSpeedMin)
|
|
end
|
|
|
|
-- Blink
|
|
if uptime > blinkUptime then
|
|
blinkUptime = uptime + blinkInterval
|
|
blinkState = not blinkState
|
|
shouldDraw = true
|
|
end
|
|
|
|
if shouldDraw then
|
|
workspace:draw()
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------
|
|
|
|
updateTapes()
|
|
|
|
workspace:draw()
|