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()