nonogamer9

Nono3D Demo for CC!

Jun 5th, 2026 (edited)
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 25.59 KB | Source Code | 0 0
  1. local term = term
  2. local baseWidth, baseHeight = term.getSize()
  3.  
  4. local width = baseWidth * 2
  5. local height = baseHeight * 3
  6.  
  7. local centerX, centerY = width / 2, height / 2 - 12
  8. local scale = math.min(width, height) * 0.38
  9.  
  10. local modFilename = "music.mod"
  11. if not fs.exists(modFilename) then
  12.     term.clear()
  13.     term.setCursorPos(1,1)
  14.     print("Downloading track via wget...")
  15.     shell.run("wget", "https://files.catbox.moe/n2aqgx.mod", modFilename)
  16.     if not fs.exists(modFilename) then
  17.         error("Download failed! Please check internet settings.")
  18.     end
  19. end
  20.  
  21. local speaker = peripheral.find("speaker")
  22. if not speaker then error("No speaker attached to the computer!") end
  23.  
  24. local AMIGA_CLOCK = 3579545
  25. local INTERNAL_MIX_RATE = 44100
  26. local SPEAKER_RATE = 48000
  27. local CHANNELS = 4
  28.  
  29. local proPeriods = {
  30.     856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
  31.     428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
  32.     214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113
  33. }
  34.  
  35. local samples, songOrder, patterns = {}, {}, {}
  36. local songLength, restartPos = 0, 0
  37. local curOrder, curRow = 1, 1
  38. local speed, bpm = 6, 125
  39. local jumpToRow, jumpToOrder = -1, -1
  40.  
  41. local rawBuffer = {}
  42. local outputBuffer = {}
  43. local tracks = {}
  44. local currentTick = 0
  45. local sampleAccumulator = 0
  46.  
  47. local math_floor = math.floor
  48. local math_min = math.min
  49. local math_max = math.max
  50. local math_abs = math.abs
  51. local math_sin = math.sin
  52. local math_sqrt = math.sqrt
  53. local bit32_rshift = bit32.rshift
  54. local bit32_band = bit32.band
  55. local bit32_lshift = bit32.lshift -- FIXED TYPO HERE
  56. local bit32_bor = bit32.bor
  57.  
  58. local pixelBuffer = {}
  59.  
  60. local colorToHex = {
  61.     [1] = "0", [2] = "1", [4] = "2", [8] = "3",
  62.     [16] = "4", [32] = "5", [64] = "6", [128] = "7",
  63.     [256] = "8", [512] = "9", [1024] = "a", [2048] = "b",
  64.     [4096] = "c", [8192] = "d", [16384] = "e", [32768] = "f"
  65. }
  66.  
  67. local plasmaColors = {
  68.     colors.black, colors.blue, colors.purple, colors.magenta, colors.cyan, colors.white
  69. }
  70.  
  71. local bayer2x2 = {
  72.     { 0.25, 0.75 },
  73.     { 1.00, 0.50 }
  74. }
  75.  
  76. local font = {
  77.     ["A"]={ 6, 9,15, 9, 9}, ["B"]={14, 9,14, 9,14}, ["C"]={ 6, 9, 8, 9, 6}, ["D"]={14, 9, 9, 9,14},
  78.     ["E"]={15, 8,14, 8,15}, ["F"]={15, 8,14, 8, 8}, ["G"]={ 6, 9,11, 9, 6}, ["H"]={ 9, 9,15, 9, 9},
  79.     ["I"]={14, 4, 4, 4,14}, ["J"]={ 2, 2, 2,10, 4}, ["K"]={ 9,10,12,10, 9}, ["L"]={ 8, 8, 8, 8,15},
  80.     ["M"]={ 9,15,15, 9, 9}, ["N"]={ 9,13,11, 9, 9}, ["O"]={ 6, 9, 9, 9, 6}, ["P"]={14, 9,14, 8, 8},
  81.     ["Q"]={ 6, 9, 9,11, 5}, ["R"]={14, 9,14,10, 9}, ["S"]={ 7, 8, 6, 1,14}, ["T"]={15, 4, 4, 4, 4},
  82.     ["U"]={ 9, 9, 9, 9, 6}, ["V"]={ 9, 9, 9, 6, 2}, ["W"]={ 9, 9,15,15, 9}, ["X"]={ 9, 9, 6, 9, 9},
  83.     ["Y"]={ 9, 9, 6, 2, 2}, ["Z"]={15, 2, 4, 8,15},
  84.     ["0"]={ 6, 9, 9, 9, 6}, ["1"]={ 4,12, 4, 4,14}, ["2"]={14, 1, 6, 8,15}, ["3"]={14, 1, 6, 1,14},
  85.     ["4"]={ 9, 9,15, 1, 1}, ["5"]={15, 8,14, 1,14}, ["6"]={ 6, 8,14, 9, 6}, ["7"]={15, 1, 2, 4, 4},
  86.     ["8"]={ 6, 9, 6, 9, 6}, ["9"]={ 6, 9, 7, 1, 6},
  87.     [" "]={ 0, 0, 0, 0, 0}, ["."]={ 0, 0, 0, 0, 4}, [","]={ 0, 0, 0, 4, 8}, ["!"]={ 4, 4, 4, 0, 4},
  88.     ["?"]={14, 1, 2, 0, 4}, ["'"]={ 4, 4, 0, 0, 0}, ["-"]={ 0, 0,15, 0, 0}
  89. }
  90.  
  91. local rawText = "Wow?? Fast 3D With Good Music on ComputerCraft? How is that even possible??? It's thanks to my new LUA Written engine called 'nono3D', It Supports Protracker MOD files, 3D, Plasmas and Much More! It's Still In Development but i hope i would finish it!      "
  92. local scrollText = string.upper(rawText)
  93. local scrollPixelX = width
  94.  
  95. local scrollerHeightSubPixels = 7
  96. local scrollerStartRow = height - scrollerHeightSubPixels - 1
  97.  
  98. local function updateStep(t)
  99.     local s = samples[t.sampleIdx]
  100.     if not s or t.period <= 0 then
  101.         t.step = 0
  102.         return
  103.     end
  104.     local prd = math_max(113, math_min(856, t.period))
  105.     local frequency = AMIGA_CLOCK / prd
  106.     if s.fine ~= 0 then
  107.         frequency = frequency * (2 ^ (s.fine / 96))
  108.     end
  109.     t.step = frequency / INTERNAL_MIX_RATE
  110.     t.volumeScale = t.volume / 64
  111. end
  112.  
  113. local function getArpeggioPeriod(basePeriod, noteOffset)
  114.     local bestIdx, minDiff = 1, 1e9
  115.     for i = 1, 36 do
  116.         local diff = math_abs(proPeriods[i] - basePeriod)
  117.         if diff < minDiff then minDiff = diff bestIdx = i end
  118.     end
  119.     local targetIdx = math_max(1, math_min(36, bestIdx + noteOffset))
  120.     return proPeriods[targetIdx]
  121. end
  122.  
  123. local function processEffects(tick)
  124.     for i = 1, CHANNELS do
  125.         local t = tracks[i]
  126.         local cmd, data = t.effect, t.eData
  127.         if tick == 0 then
  128.             if cmd == 0xC then
  129.                 t.volume = math_min(data, 64)
  130.                 t.volumeScale = t.volume / 64
  131.             elseif cmd == 0xF then
  132.                 if data > 0 and data < 32 then speed = data
  133.                 elseif data >= 32 then bpm = math_max(32, math_min(255, data)) end
  134.             elseif cmd == 0xD then
  135.                 local newRow = math_floor(data / 16) * 10 + (data % 16)
  136.                 if newRow < 64 then jumpToRow = newRow + 1 jumpToOrder = curOrder + 1 end
  137.             elseif cmd == 0xB then
  138.                 if data < 128 then jumpToOrder = data + 1 jumpToRow = 1 end
  139.             elseif cmd == 0x3 or cmd == 0x5 then
  140.                 if data > 0 then t.portamentoSpeed = data end
  141.             elseif cmd == 0x4 or cmd == 0x6 then
  142.                 if data > 0 then
  143.                     local s, d = bit32_rshift(data, 4), bit32_band(data, 0x0F)
  144.                     if s > 0 then t.vibratoSpeed = s end
  145.                     if d > 0 then t.vibratoDepth = d end
  146.                 end
  147.             elseif cmd == 0xE then
  148.                 local subCmd, subData = bit32_rshift(data, 4), bit32_band(data, 0x0F)
  149.                 if subCmd == 0x1 then t.period = math_max(113, t.period - subData) updateStep(t)
  150.                 elseif subCmd == 0x2 then t.period = math_min(856, t.period + subData) updateStep(t)
  151.                 elseif subCmd == 0xA then t.volume = math_min(64, t.volume + subData) t.volumeScale = t.volume / 64
  152.                 elseif subCmd == 0xB then t.volume = math_max(0, t.volume - subData) t.volumeScale = t.volume / 64
  153.                 elseif subCmd == 0xC then t.active = false end
  154.             end
  155.         else
  156.             if cmd == 0x0 and data > 0 then
  157.                 local phase = tick % 3
  158.                 if phase == 1 then t.period = getArpeggioPeriod(t.targetPeriod, bit32_rshift(data, 4))
  159.                 elseif phase == 2 then t.period = getArpeggioPeriod(t.targetPeriod, bit32_band(data, 0x0F))
  160.                 else t.period = t.targetPeriod end
  161.                 updateStep(t)
  162.             elseif cmd == 0x1 then t.period = math_max(113, t.period - data) updateStep(t)
  163.             elseif cmd == 0x2 then t.period = math_min(856, t.period + data) updateStep(t)
  164.             elseif cmd == 0x3 or cmd == 0x5 then
  165.                 if t.period < t.targetPeriod then t.period = math_min(t.targetPeriod, t.period + t.portamentoSpeed)
  166.                 elseif t.period > t.targetPeriod then t.period = math_max(t.targetPeriod, t.period - t.portamentoSpeed) end
  167.                 updateStep(t)
  168.                 if cmd == 0x5 and data > 0 then
  169.                     local u, d = bit32_rshift(data, 4), bit32_band(data, 0x0F)
  170.                     t.volume = math_max(0, math_min(64, t.volume + u - d))
  171.                     t.volumeScale = t.volume / 64
  172.                 end
  173.             elseif cmd == 0x4 or cmd == 0x6 then
  174.                 local vib = math_sin(t.vibratoPos * 0.09817477) * t.vibratoDepth * 2
  175.                 t.period = math_max(113, math_min(856, t.targetPeriod + vib))
  176.                 t.vibratoPos = (t.vibratoPos + t.vibratoSpeed) % 64
  177.                 updateStep(t)
  178.                 if cmd == 0x6 and data > 0 then
  179.                     local u, d = bit32_rshift(data, 4), bit32_band(data, 0x0F)
  180.                     t.volume = math_max(0, math_min(64, t.volume + u - d))
  181.                     t.volumeScale = t.volume / 64
  182.                 end
  183.             elseif cmd == 0xA then
  184.                 local u, d = bit32_rshift(data, 4), bit32_band(data, 0x0F)
  185.                 t.volume = math_max(0, math_min(64, t.volume + u - d))
  186.                 t.volumeScale = t.volume / 64
  187.             elseif cmd == 0xE then
  188.                 local subCmd, subData = bit32_rshift(data, 4), bit32_band(data, 0x0F)
  189.                 if subCmd == 0x9 and subData > 0 and tick % subData == 0 then t.cursor = 0 end
  190.             end
  191.         end
  192.     end
  193. end
  194.  
  195. local mixCache = {}
  196. for i = 1, 32 do
  197.     mixCache[i] = { track = {}, data = {}, len = 0, loopStart = 0, loopLen = 0, hasLoop = false, loopEnd = 0 }
  198. end
  199.  
  200. local function mixSamples(count)
  201.     local normalizationFactor = math_sqrt(CHANNELS) * 1.1
  202.     local activeCount = 0
  203.  
  204.     for c = 1, CHANNELS do
  205.         local t = tracks[c]
  206.         if t and t.active then
  207.             local s = samples[t.sampleIdx]
  208.             if s and s.len > 0 then
  209.                 activeCount = activeCount + 1
  210.                 local mc = mixCache[activeCount]
  211.                 mc.track = t; mc.data = s.data; mc.len = s.len
  212.                 mc.loopStart = s.loopStart; mc.loopLen = s.loopLen
  213.                 mc.hasLoop = s.loopLen > 2; mc.loopEnd = s.loopStart + s.loopLen
  214.             end
  215.         end
  216.     end
  217.  
  218.     for i = 1, count do
  219.         local out = 0
  220.         for c = 1, activeCount do
  221.             local at = mixCache[c]
  222.             local t = at.track
  223.             if t.active then
  224.                 if at.hasLoop then
  225.                     if t.cursor >= at.loopEnd then
  226.                         t.cursor = at.loopStart + ((t.cursor - at.loopEnd) % at.loopLen)
  227.                     end
  228.                 elseif t.cursor >= at.len then t.active = false end
  229.  
  230.                 if t.active then
  231.                     local idx = math_floor(t.cursor) + 1
  232.                     local val = at.data[idx]
  233.                     if val then
  234.                         out = out + (val * t.volumeScale)
  235.                         t.cursor = t.cursor + t.step
  236.                     else t.active = false end
  237.                 end
  238.             end
  239.         end
  240.         rawBuffer[i] = out / normalizationFactor
  241.     end
  242.  
  243.     local ratio = SPEAKER_RATE / INTERNAL_MIX_RATE
  244.     local outSize = math_floor(count * ratio + 0.5)
  245.     local invRatio = 1 / ratio
  246.    
  247.     local src_acc = 1
  248.     for i = 1, outSize do
  249.         local idx = math_floor(src_acc)
  250.         local s1 = rawBuffer[idx] or 0
  251.         local s2 = rawBuffer[idx + 1] or s1
  252.         outputBuffer[i] = math_max(-128, math_min(127, math_floor(s1 + (s2 - s1) * (src_acc - idx))))
  253.         src_acc = src_acc + invRatio
  254.     end
  255.     return outputBuffer, outSize
  256. end
  257.  
  258. local function loadMOD(filename)
  259.     local f = fs.open(filename, "rb")
  260.     if not f then error("File not found: " .. filename) end
  261.  
  262.     local success, err = pcall(function()
  263.         f.read(20)
  264.         for i = 1, 31 do
  265.             f.read(22)
  266.             local len = (f.read() * 256 + f.read()) * 2
  267.             local fine = f.read()
  268.             if fine > 7 then fine = fine - 16 end
  269.             local vol = f.read()
  270.             local lStart = (f.read() * 256 + f.read()) * 2
  271.             local lLen = (f.read() * 256 + f.read()) * 2
  272.            
  273.             if len > 0 and vol > 0 then
  274.                 samples[i] = { len = len, vol = math_min(64, vol), loopStart = lStart, loopLen = lLen, fine = fine, data = {} }
  275.             else
  276.                 samples[i] = { len = 0, data = {} }
  277.             end
  278.         end
  279.  
  280.         songLength = f.read()
  281.         restartPos = f.read()
  282.  
  283.         local maxPattern = 0
  284.         for i = 1, 128 do
  285.             songOrder[i] = f.read() or 0
  286.             if songOrder[i] > maxPattern then maxPattern = songOrder[i] end
  287.         end
  288.  
  289.         local m1, m2, m3, m4 = f.read(), f.read(), f.read(), f.read()
  290.         local tag = string.char(m1 or 0, m2 or 0, m3 or 0, m4 or 0)
  291.        
  292.         CHANNELS = 4
  293.         if tag == "M.K." or tag == "M!K!" or tag == "FLT4" or tag == "4CHN" then CHANNELS = 4
  294.         elseif tag == "6CHN" or tag == "6CNM" then CHANNELS = 6
  295.         elseif tag == "8CHN" or tag == "8CNM" or tag == "CD81" or tag == "OKTA" then CHANNELS = 8
  296.         elseif tag:match("^%d+CH") or tag:match("^%d+CN") then
  297.             local digits = tag:match("^(%d+)CH") or tag:match("^(%d+)CN")
  298.             if digits then CHANNELS = tonumber(digits) end
  299.         end
  300.  
  301.         for i = 1, CHANNELS do
  302.             tracks[i] = {
  303.                 sampleIdx = 0, cursor = 0, period = 0, step = 0, targetPeriod = 0, volume = 64, active = false,
  304.                 effect = 0, eData = 0, portamentoSpeed = 0, vibratoSpeed = 0, vibratoDepth = 0, vibratoPos = 0, volumeScale = 1
  305.             }
  306.         end
  307.  
  308.         for p = 0, maxPattern do
  309.             patterns[p] = {}
  310.             for row = 1, 64 do
  311.                 patterns[p][row] = {}
  312.                 for chan = 1, CHANNELS do
  313.                     local b1, b2, b3, b4 = f.read(), f.read(), f.read(), f.read()
  314.                     if b1 and b2 and b3 and b4 then
  315.                         local sIdx = bit32_bor(bit32_band(b1, 0xF0), bit32_rshift(b3, 4))
  316.                         local prd = bit32_bor(bit32_lshift(bit32_band(b1, 0x0F), 8), b2)
  317.                         patterns[p][row][chan] = { samp = sIdx, period = prd, effect = bit32_band(b3, 0x0F), eData = b4 }
  318.                     else
  319.                         patterns[p][row][chan] = { samp = 0, period = 0, effect = 0, eData = 0 }
  320.                     end
  321.                 end
  322.             end
  323.         end
  324.  
  325.         for i = 1, 31 do
  326.             if samples[i] and samples[i].len > 0 then
  327.                 local data = samples[i].data
  328.                 for j = 1, samples[i].len do
  329.                     local b = f.read()
  330.                     data[j] = b and ((b > 127) and (b - 256) or b) or 0
  331.                 end
  332.             end
  333.         end
  334.     end)
  335.     f.close()
  336.     if not success then error("Failed to load MOD: " .. tostring(err)) end
  337. end
  338.  
  339. local function getPlasmaColor(x, y, time)
  340.     local v1 = math.sin(x / 12 + time)
  341.     local v2 = math.sin(y / 12 + time * 1.2)
  342.     local v3 = math.sin((x + y) / 18 + time)
  343.     local cx = x - width / 2
  344.     local cy = y - height / 2
  345.     local v4 = math.sin(math.sqrt(cx*cx + cy*cy) / 10 - time)
  346.    
  347.     local total = (v1 + v2 + v3 + v4) / 4
  348.     local factor = (total + 1) / 2
  349.    
  350.     local rawIdx = factor * (#plasmaColors - 1) + 1
  351.     local baseIdx = math.floor(rawIdx)
  352.     local fraction = rawIdx - baseIdx
  353.    
  354.     local mX = (x % 2) + 1
  355.     local mY = (y % 2) + 1
  356.     local threshold = bayer2x2[mY][mX]
  357.    
  358.     local finalIdx = baseIdx
  359.     if fraction > threshold and baseIdx < #plasmaColors then finalIdx = baseIdx + 1 end
  360.     if finalIdx < 1 then finalIdx = 1 elseif finalIdx > #plasmaColors then finalIdx = #plasmaColors end
  361.     return plasmaColors[finalIdx]
  362. end
  363.  
  364. local function clearBuffers(time)
  365.     for y = 1, height do
  366.         local rowOffset = (y - 1) * width
  367.         if y >= scrollerStartRow then
  368.             for x = 1, width do pixelBuffer[rowOffset + x] = colors.black end
  369.         else
  370.             for x = 1, width do pixelBuffer[rowOffset + x] = getPlasmaColor(x, y, time) end
  371.         end
  372.     end
  373. end
  374.  
  375. local function drawChar(char, startX, startY, textColor)
  376.     local glyph = font[char] or font["?"]
  377.     for row = 1, 5 do
  378.         local bitmask = glyph[row]
  379.         local currentY = startY + row - 1
  380.         if currentY >= 1 and currentY <= height then
  381.             local rowOffset = (currentY - 1) * width
  382.             for col = 1, 4 do
  383.                 local currentX = startX + col - 1
  384.                 if currentX >= 1 and currentX <= width then
  385.                     local bit = math.floor(bitmask / (2 ^ (4 - col))) % 2
  386.                     if bit == 1 then pixelBuffer[rowOffset + currentX] = textColor end
  387.                 end
  388.             end
  389.         end
  390.     end
  391. end
  392.  
  393. local function drawScroller()
  394.     local currentX = math.floor(scrollPixelX)
  395.     local charSpacing = 5
  396.     local textOffsetY = scrollerStartRow + 1
  397.    
  398.     for i = 1, #scrollText do
  399.         local char = scrollText:sub(i, i)
  400.         if currentX >= -4 and currentX <= width then
  401.             local colorCycle = math_floor((currentX / 12) % 3)
  402.             local txtColor = colors.white
  403.             if colorCycle == 1 then txtColor = colors.cyan
  404.             elseif colorCycle == 2 then txtColor = colors.purple end
  405.             drawChar(char, currentX, textOffsetY, txtColor)
  406.         end
  407.         currentX = currentX + charSpacing
  408.     end
  409. end
  410.  
  411. local function edgeFunction(ax, ay, bx, by, cx, cy)
  412.     return (cx - ax) * (by - ay) - (cy - ay) * (bx - ax)
  413. end
  414.  
  415. local function drawTriangle(p1, p2, p3, color)
  416.     local minX = math.max(1, math.floor(math.min(p1.x, p2.x, p3.x)))
  417.     local maxX = math.min(width, math.floor(math.max(p1.x, p2.x, p3.x)))
  418.     local minY = math.max(1, math.floor(math.min(p1.y, p2.y, p3.y)))
  419.     local maxY = math.min(height, math.floor(math.max(p1.y, p2.y, p3.y)))
  420.  
  421.     maxY = math.min(maxY, scrollerStartRow - 1)
  422.  
  423.     for y = minY, maxY do
  424.         local rowOffset = (y - 1) * width
  425.         for x = minX, maxX do
  426.             local w0 = edgeFunction(p2.x, p2.y, p3.x, p3.y, x, y)
  427.             local w1 = edgeFunction(p3.x, p3.y, p1.x, p1.y, x, y)
  428.             local w2 = edgeFunction(p1.x, p1.y, p2.x, p2.y, x, y)
  429.             if (w0 >= 0 and w1 >= 0 and w2 >= 0) or (w0 <= 0 and w1 <= 0 and w2 <= 0) then
  430.                 pixelBuffer[rowOffset + x] = color
  431.             end
  432.         end
  433.     end
  434. end
  435.  
  436. local function drawQuad(p1, p2, p3, p4, color)
  437.     drawTriangle(p1, p2, p3, color)
  438.     drawTriangle(p1, p3, p4, color)
  439. end
  440.  
  441. local function renderScreen()
  442.     for y = 1, baseHeight do
  443.         local charRow, textColRow, bgColRow = {}, {}, {}
  444.         for x = 1, baseWidth do
  445.             local hX = (x - 1) * 2
  446.             local hY = (y - 1) * 3
  447.  
  448.             local c1 = pixelBuffer[(hY * width) + hX + 1] or colors.black
  449.             local c2 = pixelBuffer[(hY * width) + hX + 2] or colors.black
  450.             local c3 = pixelBuffer[((hY + 1) * width) + hX + 1] or colors.black
  451.             local c4 = pixelBuffer[((hY + 1) * width) + hX + 2] or colors.black
  452.             local c5 = pixelBuffer[((hY + 2) * width) + hX + 1] or colors.black
  453.             local c6 = pixelBuffer[((hY + 2) * width) + hX + 2] or colors.black
  454.  
  455.             local primaryColor, secondaryColor = c1, c1
  456.             local counts, maxCount = {}, 0
  457.            
  458.             if (hY + 1) >= scrollerStartRow then
  459.                 for _, c in ipairs({c1, c2, c3, c4, c5, c6}) do
  460.                     counts[c] = (counts[c] or 0) + ((c == colors.white or c == colors.cyan or c == colors.purple) and 20 or 1)
  461.                     if counts[c] > maxCount then maxCount = counts[c]; primaryColor = c end
  462.                 end
  463.                 secondaryColor = colors.black
  464.             else
  465.                 for _, c in ipairs({c1, c2, c3, c4, c5, c6}) do
  466.                     counts[c] = (counts[c] or 0) + 1
  467.                     if counts[c] > maxCount then maxCount = counts[c]; primaryColor = c end
  468.                 end
  469.                 for _, c in ipairs({c1, c2, c3, c4, c5, c6}) do
  470.                     if c ~= primaryColor then secondaryColor = c break end
  471.                 end
  472.             end
  473.  
  474.             local p1 = (c1 == primaryColor) and 1 or 0
  475.             local p2 = (c2 == primaryColor) and 1 or 0
  476.             local p3 = (c3 == primaryColor) and 1 or 0
  477.             local p4 = (c4 == primaryColor) and 1 or 0
  478.             local p5 = (c5 == primaryColor) and 1 or 0
  479.             local p6 = (c6 == primaryColor)
  480.  
  481.             local textHex = colorToHex[primaryColor] or "f"
  482.             local bgHex = colorToHex[secondaryColor] or "f"
  483.  
  484.             if p6 then
  485.                 p1, p2, p3, p4, p5 = 1 - p1, 1 - p2, 1 - p3, 1 - p4, 1 - p5
  486.                 textHex, bgHex = bgHex, textHex
  487.             end
  488.  
  489.             table.insert(charRow, string.char(128 + p1 + p2 * 2 + p3 * 4 + p4 * 8 + p5 * 16))
  490.             table.insert(textColRow, textHex)
  491.             table.insert(bgColRow, bgHex)
  492.         end
  493.         term.setCursorPos(1, y)
  494.         term.blit(table.concat(charRow), table.concat(textColRow), table.concat(bgColRow))
  495.     end
  496. end
  497.  
  498. local vertices = {
  499.     { -1, -1, -1 }, {  1, -1, -1 }, {  1,  1, -1 }, { -1,  1, -1 },
  500.     { -1, -1,  1 }, {  1, -1,  1 }, {  1,  1,  1 }, { -1,  1,  1 }
  501. }
  502.  
  503. local faces = {
  504.     { vertices = {1, 4, 3, 2}, color = colors.red },
  505.     { vertices = {5, 6, 7, 8}, color = colors.orange },
  506.     { vertices = {1, 2, 6, 5}, color = colors.yellow }, -- FIXED PROPERTY PREFIX HERE
  507.     { vertices = {4, 8, 7, 3}, color = colors.green },
  508.     { vertices = {1, 5, 8, 4}, color = colors.blue },
  509.     { vertices = {2, 3, 7, 6}, color = colors.magenta }
  510. }
  511.  
  512. loadMOD(modFilename)
  513.  
  514. local angleX, angleY = 0, 0
  515. local time = 0
  516.  
  517. local function pumpAudioTick()
  518.     if curOrder > songLength then return false end
  519.  
  520.     local pIdx = songOrder[curOrder]
  521.     if not pIdx or not patterns[pIdx] then
  522.         curOrder = curOrder + 1; curRow = 1; return true
  523.     end
  524.  
  525.     local rowData = patterns[pIdx][curRow]
  526.     if not rowData then curRow = curRow + 1; return true end
  527.  
  528.     if currentTick == 0 then
  529.         for c = 1, CHANNELS do
  530.             local cell = rowData[c]
  531.             if cell then
  532.                 if cell.samp > 0 then
  533.                     local s = samples[cell.samp]
  534.                     if s and s.len > 0 then
  535.                         tracks[c].sampleIdx = cell.samp
  536.                         if s.vol > 0 then tracks[c].volume = s.vol; tracks[c].volumeScale = s.vol / 64 end
  537.                     end
  538.                 end
  539.                 if cell.period > 0 then
  540.                     if cell.effect == 0x3 or cell.effect == 0x5 then tracks[c].targetPeriod = cell.period
  541.                     else
  542.                         tracks[c].period = cell.period; tracks[c].targetPeriod = cell.period
  543.                         tracks[c].cursor = 0; tracks[c].vibratoPos = 0
  544.                         updateStep(tracks[c]); tracks[c].active = true
  545.                     end
  546.                 elseif cell.period == 0 and cell.samp > 0 then
  547.                     if tracks[c].targetPeriod > 0 then
  548.                         tracks[c].cursor = 0; tracks[c].active = true; updateStep(tracks[c])
  549.                     end
  550.                 end
  551.                 tracks[c].effect = cell.effect; tracks[c].eData = cell.eData
  552.             end
  553.         end
  554.     end
  555.  
  556.     processEffects(currentTick)
  557.  
  558.     speed = math_max(1, speed)
  559.     bpm = math_max(32, math_min(255, bpm))
  560.     local rowTime = (speed * 2.5) / bpm
  561.     local totalSamplesNeeded = math_floor(INTERNAL_MIX_RATE * rowTime + 0.5)
  562.     local samplesPerTickBase = totalSamplesNeeded / speed
  563.    
  564.     sampleAccumulator = sampleAccumulator + samplesPerTickBase
  565.     local tickSamples = math_floor(sampleAccumulator)
  566.     sampleAccumulator = sampleAccumulator - tickSamples
  567.  
  568.     local accepted = true
  569.     if tickSamples > 0 then
  570.         local audio, n = mixSamples(tickSamples)
  571.         accepted = speaker.playAudio(audio)
  572.     end
  573.  
  574.     if accepted then
  575.         currentTick = currentTick + 1
  576.         if currentTick >= speed then
  577.             currentTick = 0
  578.             if jumpToOrder ~= -1 then
  579.                 curOrder = jumpToOrder; curRow = jumpToRow; jumpToOrder = -1; jumpToRow = -1
  580.             else
  581.                 curRow = curRow + 1
  582.                 if curRow > 64 then
  583.                     curRow = 1; curOrder = curOrder + 1
  584.                     if curOrder > songLength then curOrder = restartPos + 1; if curOrder < 1 then curOrder = 1 end; curRow = 1 end
  585.                 end
  586.             end
  587.         end
  588.     end
  589.  
  590.     return accepted
  591. end
  592.  
  593. local function audioWorker()
  594.     while true do
  595.         local event, side = os.pullEvent()
  596.         if event == "speaker_audio_empty" then
  597.             local accepted = true
  598.             while accepted do
  599.                 accepted = pumpAudioTick()
  600.             end
  601.         end
  602.     end
  603. end
  604.  
  605. local function graphicsWorker()
  606.     while true do
  607.         clearBuffers(time)
  608.  
  609.         local rotated, projected = {}, {}
  610.         local radX, radY = math.rad(angleX), math.rad(angleY)
  611.         local cosX, sinX = math.cos(radX), math.sin(radX)
  612.         local cosY, sinY = math.cos(radY), math.sin(radY)
  613.  
  614.         for i, v in ipairs(vertices) do
  615.             local y1 = v[2] * cosX - v[3] * sinX
  616.             local z1 = v[2] * sinX + v[3] * cosX
  617.             local x2 = v[1] * cosY + z1 * sinY
  618.             local z2 = -v[1] * sinY + z1 * cosY
  619.             rotated[i] = { x = x2, y = y1, z = z2 }
  620.  
  621.             local distance = 3.2
  622.             local rz = 1 / (distance - z2)
  623.             projected[i] = { x = centerX + (x2 * scale * rz) * 1.15, y = centerY + (y1 * scale * rz) }
  624.         end
  625.  
  626.         for _, face in ipairs(faces) do
  627.             local r1, r2, r3 = rotated[face.vertices[1]], rotated[face.vertices[2]], rotated[face.vertices[3]]
  628.             local v1x, v1y, v1z = r2.x - r1.x, r2.y - r1.y, r2.z - r1.z
  629.             local v2x, v2y, v2z = r3.x - r1.x, r3.y - r1.y, r3.z - r1.z
  630.             local nx = v1y * v2z - v1z * v2y
  631.             local ny = v1z * v2x - v1x * v2z
  632.             local nz = v1x * v2y - v1y * v2x
  633.  
  634.             if (nx * r1.x + ny * r1.y + nz * (r1.z - 3.2)) < 0 then
  635.                 drawQuad(projected[face.vertices[1]], projected[face.vertices[2]], projected[face.vertices[3]], projected[face.vertices[4]], face.color)
  636.             end
  637.         end
  638.  
  639.         drawScroller()
  640.         renderScreen()
  641.  
  642.         angleX = (angleX + 3) % 360
  643.         angleY = (angleY + 2) % 360
  644.         time = time + 0.04
  645.        
  646.         scrollPixelX = scrollPixelX - 1.4
  647.         if scrollPixelX < -(#scrollText * 5) then scrollPixelX = width end
  648.  
  649.         sleep(0.05)
  650.     end
  651. end
  652.  
  653. term.clear()
  654.  
  655. local prefill = true
  656. while prefill do
  657.     prefill = pumpAudioTick()
  658. end
  659.  
  660. parallel.waitForAny(audioWorker, graphicsWorker)
Add Comment
Please, Sign In to add comment