Advertisement
DIXERION

Ring Visualizer

May 4th, 2024
171
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.82 KB | Source Code | 0 0
  1. local ffi = require ("ffi")
  2.  
  3. --
  4.  
  5. ffi.cdef [[
  6.     typedef struct Complex {double X, Y;} Complex;
  7. ]]
  8.  
  9. --
  10.  
  11. App = {Ring = {}, Complex = {}, Offset = {}, Shaders = {}, Meshes = {}, Canvases = {}}
  12.  
  13. App.Sound = nil
  14. App.Audio = nil
  15.  
  16. App.Legend = false
  17.  
  18. App.Bloom = true
  19.  
  20. App.Ring.Count = 48
  21. App.Ring.Order = 2
  22. App.Ring.Color = {1, 0.4, 0.05}
  23. App.Ring.Data = love.data.newByteData (ffi.sizeof ("float")*App.Ring.Count)
  24. App.Ring.Waves = {[App.Ring.Order] = ffi.cast ("float *", App.Ring.Data:getFFIPointer ())}
  25.  
  26. App.Complex.Count = 1024
  27. App.Complex.Wave = ffi.new ("Complex [?]", App.Complex.Count)
  28.  
  29. App.Offset.Order = 3
  30. App.Offset.Frequency = 4
  31. App.Offset.X = ffi.new ("double [?]", App.Offset.Order + 1)
  32. App.Offset.Y = ffi.new ("double [?]", App.Offset.Order + 1)
  33.  
  34. App.Shaders.Ring = love.graphics.newShader [[
  35.     #define COUNT 48
  36.     #define SIZE 0.8
  37.     #define SMOOTH 0.04
  38.     //
  39.     const float PI = acos (-1.0);
  40.     //
  41.     uniform vec3 Color;
  42.     uniform float Data [COUNT];
  43.     uniform vec2 Offset;
  44.     //
  45.     void effect () {
  46.         vec2 FP = (VaryingTexCoord.xy + Offset - 0.5)*2.0;
  47.         float FD = length (FP);
  48.         float FA = atan (FP.y, FP.x);
  49.         //
  50.         float RI = (FA/PI*0.5 - 0.25 - 0.5/COUNT)*COUNT;
  51.         float RF = floor (RI);
  52.         float RC = ceil (RI);
  53.         float RA = Data [int (mod (RF, COUNT))];
  54.         float RB = Data [int (mod (RC, COUNT))];
  55.         float RV = RF != RC ? mix (RA, RB, smoothstep (RF, RC, RI)) : RA;
  56.         float RE = RV - abs (FD - SIZE)/(1.0 - SIZE);
  57.         //
  58.         if (RE <= 0.0) {discard;}
  59.         //
  60.         love_PixelColor = vec4 (Color*(RV*3.5 + 0.8), min (RE/SMOOTH, 1.0));
  61.     }
  62. ]]
  63.  
  64. App.Shaders.Blur = love.graphics.newShader [[
  65.     #pragma language glsl3
  66.     //
  67.     const vec3 Kernel [] = vec3 [13] (
  68.         vec3 (-2.0, -2.0, 1.0/30.0),
  69.         vec3 (+0.0, -2.0, 2.0/30.0),
  70.         vec3 (+2.0, -2.0, 1.0/30.0),
  71.         vec3 (-1.0, -1.0, 4.0/30.0),
  72.         vec3 (+1.0, -1.0, 4.0/30.0),
  73.         vec3 (-2.0, +0.0, 2.0/30.0),
  74.         vec3 (+0.0, +0.0, 2.0/30.0),
  75.         vec3 (+2.0, +0.0, 2.0/30.0),
  76.         vec3 (-1.0, +1.0, 4.0/30.0),
  77.         vec3 (+1.0, +1.0, 4.0/30.0),
  78.         vec3 (-2.0, +2.0, 1.0/30.0),
  79.         vec3 (+0.0, +2.0, 2.0/30.0),
  80.         vec3 (+2.0, +2.0, 1.0/30.0)
  81.     );
  82.     //
  83.     uniform Image MainTex;
  84.     //
  85.     void effect () {
  86.         vec2 Dimensions = vec2 (textureSize (MainTex, 0));
  87.         //
  88.         vec3 Color = vec3 (0.0);
  89.         //
  90.         for (int Index = 0; Index < 13; ++ Index) {
  91.             vec3 Info = Kernel [Index];
  92.             //
  93.             vec3 TextureColor = Texel (MainTex, VaryingTexCoord.xy + Info.xy/Dimensions).rgb;
  94.             //
  95.             Color += TextureColor*Info.z;
  96.         }
  97.         //
  98.         love_PixelColor = vec4 (Color, 1.0);
  99.     }
  100. ]]
  101.  
  102. App.Meshes.Ring = love.graphics.newMesh (
  103.     {
  104.         {"VertexPosition", "float", 2},
  105.         {"VertexTexCoord", "float", 2}
  106.     }, {
  107.         {0, 0, 0, 0},
  108.         {1, 0, 1, 0},
  109.         {1, 1, 1, 1},
  110.         {0, 1, 0, 1}
  111.     }
  112. )
  113.  
  114. App.Canvases.Index = 0
  115.  
  116. --
  117.  
  118. for Index = 0, App.Ring.Order - 1 do App.Ring.Waves [Index] = ffi.new ("float [?]", App.Ring.Count) end
  119.  
  120. --
  121.  
  122. io.stdout:setvbuf ("no")
  123.  
  124. --
  125.  
  126. function App.map (V, A, B, X, Y)
  127.     return X + (Y - X)*(V - A)/(B - A)
  128. end
  129.  
  130. function App.emap (V, A, B, X, Y)
  131.     return math.pow (Y/X, (V - A)/(B - A))*X
  132. end
  133.  
  134. function App.slope (V, A, B, X, Y)
  135.     return math.pow (V/A, math.log (Y, X))*B
  136. end
  137.  
  138. function App.volume (D)
  139.     return math.pow (10, D/20)
  140. end
  141.  
  142. function App.abs (C)
  143.     return math.sqrt (C.X*C.X + C.Y*C.Y)
  144. end
  145.  
  146. function App.smooth (A, B, F, DT)
  147.     local R = F*DT*math.pi*2
  148.     --
  149.     return A + (B - A)/(R + 1)*R
  150. end
  151.  
  152. function App.mratio (R, X, Y)
  153.     if not Y then Y = X end
  154.     --
  155.     local W = math.min (Y*R, X)
  156.     local H = math.min (X/R, Y)
  157.     --
  158.     return W, H
  159. end
  160.  
  161. function App.complexTransform (W, C, I)
  162.     if C == 1 then return W end
  163.     --
  164.     local D, S = C/2, I and 1 or -1
  165.     --
  166.     local A = ffi.new ("Complex [?]", D)
  167.     local B = ffi.new ("Complex [?]", D)
  168.     --
  169.     for J = 0, D - 1 do A [J], B [J] = W [J*2], W [J*2 + 1] end
  170.     --
  171.     local X = App.complexTransform (A, D)
  172.     local Y = App.complexTransform (B, D)
  173.     --
  174.     local T = ffi.new ("Complex [?]", C)
  175.     --
  176.     for J = 0, D - 1 do
  177.         local EA = S*J/D*math.pi
  178.         --
  179.         local EX = math.cos (EA)
  180.         local EY = math.sin (EA)
  181.         --
  182.         local FX = Y [J].X*EX - Y [J].Y*EY
  183.         local FY = Y [J].X*EY + Y [J].Y*EX
  184.         --
  185.         T [J].X = X [J].X + FX
  186.         T [J].Y = X [J].Y + FY
  187.         --
  188.         T [D + J].X = X [J].X - FX
  189.         T [D + J].Y = X [J].Y - FY
  190.     end
  191.     --
  192.     return T
  193. end
  194.  
  195. function App.updateCanvases (MW, MH)
  196.     for Index = 0, #App.Canvases do App.Canvases [Index] = nil end
  197.     --
  198.     for Index = 0, math.huge do
  199.         local CW = math.floor (MW/2^Index)
  200.         local CH = math.floor (MH/2^Index)
  201.         --
  202.         if CW < 8 or CH < 8 then break end
  203.         --
  204.         local Samples = Index == 0 and 4 or 0
  205.         --
  206.         App.Canvases [Index] = love.graphics.newCanvas (CW, CH, {format = "rg11b10f", msaa = Samples})
  207.     end
  208.     --
  209.     App.Canvases.Index = math.min (App.Canvases.Index, #App.Canvases)
  210.     --
  211.     return true
  212. end
  213.  
  214. --
  215.  
  216. function love.update (DT)
  217.     if App.Audio and App.Sound and App.Audio:isPlaying () then
  218.         local AI = App.Audio:tell ("samples")
  219.         local SC = App.Sound:getSampleCount ()
  220.         local CC = App.Sound:getChannelCount ()
  221.         --
  222.         for Index = 0, App.Complex.Count - 1 do
  223.             local SI = AI - App.Complex.Count + Index + 1
  224.             --
  225.             if CC == 1 then
  226.                 local MV = SI >= 0 and SI < SC and App.Sound:getSample (SI) or 0
  227.                 --
  228.                 App.Complex.Wave [Index] = {MV, MV}
  229.             else
  230.                 local LV = SI >= 0 and SI < SC and App.Sound:getSample (SI, 1) or 0
  231.                 local RV = SI >= 0 and SI < SC and App.Sound:getSample (SI, 2) or 0
  232.                 --
  233.                 App.Complex.Wave [Index] = {LV, RV}
  234.             end
  235.         end
  236.     else
  237.         for Index = 0, App.Complex.Count - 1 do App.Complex.Wave [Index] = {0, 0} end
  238.     end
  239.     --
  240.     local FrequencyWave = App.complexTransform (App.Complex.Wave, App.Complex.Count)
  241.     --
  242.     local SR = App.Sound and App.Sound:getSampleRate () or 1
  243.     --
  244.     for Index = 0, App.Ring.Count - 1 do
  245.         local FR = App.emap (Index, 0, App.Ring.Count - 1, 20, 20000)
  246.         local FS = App.slope (FR, 20, 1, 2, App.volume (3.8))
  247.         local FI = FR/SR*App.Complex.Count
  248.         --
  249.         local FF, FC = math.floor (FI), math.ceil (FI)
  250.         local FA, FB = FrequencyWave [FF % App.Complex.Count], FrequencyWave [FC % App.Complex.Count]
  251.         --
  252.         local FV = FF ~= FC and App.map (FI, FF, FC, App.abs (FA), App.abs (FB)) or App.abs (FA)
  253.         --
  254.         App.Ring.Waves [0] [Index] = FV*FS/App.Complex.Count*0.9 + 0.06
  255.         --
  256.         for OI = 1, App.Ring.Order do
  257.             local SA = App.Ring.Waves [OI] [Index]
  258.             local SB = App.Ring.Waves [OI - 1] [Index]
  259.             --
  260.             App.Ring.Waves [OI] [Index] = App.smooth (SA, SB, 6, DT)
  261.         end
  262.     end
  263.     --
  264.     local OV, OC = 0, 32
  265.     --
  266.     for OI = 1, OC do
  267.         local FR = App.emap (OI, 1, OC, 20, 20000)
  268.         local FI = FR/SR*App.Complex.Count
  269.         --
  270.         local FF, FC = math.floor (FI), math.ceil (FI)
  271.         local FA, FB = FrequencyWave [FF % App.Complex.Count], FrequencyWave [FC % App.Complex.Count]
  272.         --
  273.         local FV = FF ~= FC and App.map (FI, FF, FC, App.abs (FA), App.abs (FB)) or App.abs (FA)
  274.         --
  275.         OV = OV + FV/FR/FR/App.Complex.Count
  276.     end
  277.     --
  278.     App.Offset.X [0] = (math.random ()*2 - 1)*OV*32
  279.     App.Offset.Y [0] = (math.random ()*2 - 1)*OV*32
  280.     --
  281.     for OI = 1, App.Offset.Order do
  282.         App.Offset.X [OI] = App.smooth (App.Offset.X [OI], App.Offset.X [OI - 1], App.Offset.Frequency, DT)
  283.         App.Offset.Y [OI] = App.smooth (App.Offset.Y [OI], App.Offset.Y [OI - 1], App.Offset.Frequency, DT)
  284.     end
  285. end
  286.  
  287. function love.draw ()
  288.     if not App.Canvases [0] then return end
  289.     --
  290.     local WW, WH = love.graphics.getDimensions ()
  291.     --
  292.     local RW, RH = App.mratio (1, WW*0.75, WH*0.75)
  293.     local RX, RY = WW/2 - RW/2, WH/2 - RH/2
  294.     --
  295.     App.Shaders.Ring:send ("Color", App.Ring.Color)
  296.     App.Shaders.Ring:send ("Data", App.Ring.Data)
  297.     App.Shaders.Ring:send ("Offset", {App.Offset.X [App.Offset.Order], App.Offset.Y [App.Offset.Order]})
  298.     --
  299.     love.graphics.push ("all")
  300.     love.graphics.setCanvas (App.Canvases [0])
  301.     love.graphics.setShader (App.Shaders.Ring)
  302.     love.graphics.clear ()
  303.     love.graphics.setBlendMode ("replace", "alphamultiply")
  304.     love.graphics.draw (App.Meshes.Ring, RX, RY, 0, RW, RH)
  305.     love.graphics.pop ()
  306.     --
  307.     if App.Bloom then
  308.         love.graphics.push ("all")
  309.         love.graphics.setBlendMode ("replace", "premultiplied")
  310.         love.graphics.setShader (App.Shaders.Blur)
  311.         --
  312.         for Index = 1, #App.Canvases do
  313.             local Current, Previous = App.Canvases [Index], App.Canvases [Index - 1]
  314.             --
  315.             local CW, CH = Current:getDimensions ()
  316.             local PW, PH = Previous:getDimensions ()
  317.             --
  318.             Previous:setFilter ("linear")
  319.             --
  320.             love.graphics.setCanvas (Current)
  321.             love.graphics.draw (Previous, 0, 0, 0, CW/PW, CH/PH)
  322.         end
  323.         --
  324.         love.graphics.setBlendMode ("add", "premultiplied")
  325.         --
  326.         for Index = #App.Canvases - 1, 0, -1 do
  327.             local Current, Next = App.Canvases [Index], App.Canvases [Index + 1]
  328.             --
  329.             local CW, CH = Current:getDimensions ()
  330.             local NW, NH = Next:getDimensions ()
  331.             --
  332.             Next:setFilter ("linear")
  333.             --
  334.             love.graphics.setCanvas (Current)
  335.             love.graphics.draw (Next, 0, 0, 0, CW/NW, CH/NH)
  336.         end
  337.         --
  338.         love.graphics.pop ()
  339.     end
  340.     --
  341.     local Canvas = App.Canvases [App.Canvases.Index]
  342.     --
  343.     local CW, CH = Canvas:getDimensions ()
  344.     --
  345.     Canvas:setFilter ("nearest")
  346.     --
  347.     love.graphics.push ("all")
  348.     love.graphics.setBlendMode ("replace", "premultiplied")
  349.     love.graphics.draw (Canvas, 0, 0, 0, WW/CW, WH/CH)
  350.     love.graphics.pop ()
  351.     --
  352.     if App.Legend then
  353.         local Info = string.format ("Mip level: %i/%i (%i, %i)", App.Canvases.Index, #App.Canvases, CW, CH)
  354.         --
  355.         love.graphics.print (string.format ("FPS: %i", love.timer.getFPS ()), 10, 10)
  356.         love.graphics.print (Info, 10, 25)
  357.         --
  358.         if App.Audio then
  359.             local AP, AD = App.Audio:tell (), App.Audio:getDuration ()
  360.             --
  361.             local Position = string.format ("%i:%02i", AP/60, AP % 60)
  362.             local Duration = string.format ("%i:%02i", AD/60, AD % 60)
  363.             --
  364.             love.graphics.print (string.format ("Audio position: %s/%s", Position, Duration), 10, 40)
  365.         else
  366.             love.graphics.print ("No audio. Drag and drop an audio file on this window.", 10, 40)
  367.         end
  368.         --
  369.         love.graphics.print ("Keyboard:", 10, 70)
  370.         love.graphics.print ("[ESCAPE] Quit.", 10, 85)
  371.         love.graphics.print ("[F] Toggle fullscreen.", 10, 100)
  372.         love.graphics.print ("[L] Toggle this legend.", 10, 115)
  373.         love.graphics.print ("[B] Toggle bloom effect.", 10, 130)
  374.         love.graphics.print ("[Q/E] Change mip level (requires bloom enabled).", 10, 145)
  375.         love.graphics.print ("[S] Stop audio.", 10, 160)
  376.         love.graphics.print ("[A/D] Rewind or advance audio 5 seconds.", 10, 175)
  377.         love.graphics.print ("[SPACE BAR] Play or pause audio.", 10, 190)
  378.         love.graphics.print ("[0-9] Seek audio.", 10, 205)
  379.     end
  380. end
  381.  
  382. function love.resize (WW, WH)
  383.     App.updateCanvases (WW, WH)
  384. end
  385.  
  386. function love.keypressed (Button)
  387.     if Button == "escape" then
  388.         love.event.quit ()
  389.     elseif Button == "f" then
  390.         love.window.setFullscreen (not love.window.getFullscreen ())
  391.     elseif Button == "l" then
  392.         App.Legend = not App.Legend
  393.     elseif Button == "b" then
  394.         App.Bloom = not App.Bloom
  395.         --
  396.         if not App.Bloom then App.Canvases.Index = 0 end
  397.     elseif Button == "q" then
  398.         App.Canvases.Index = math.max (App.Canvases.Index - 1, 0)
  399.     elseif Button == "e" then
  400.         if App.Bloom then App.Canvases.Index = math.min (App.Canvases.Index + 1, #App.Canvases) end
  401.     elseif App.Audio then
  402.         if Button == "s" then
  403.             App.Audio:stop ()
  404.         elseif Button == "a" then
  405.             App.Audio:seek (math.max (App.Audio:tell () - 5, 0))
  406.         elseif Button == "d" then
  407.             App.Audio:seek (math.min (App.Audio:tell () + 5, App.Audio:getDuration ()))
  408.         elseif Button == "space" then
  409.             if App.Audio:isPlaying () then App.Audio:pause () else App.Audio:play () end
  410.         else
  411.             for Index = 0, 9 do
  412.                 if Button == tostring (Index) or Button == "kp" .. Index then
  413.                     App.Audio:seek (App.Audio:getDuration ()*Index/10)
  414.                     --
  415.                     break
  416.                 end
  417.             end
  418.         end
  419.     end
  420. end
  421.  
  422. function love.filedropped (File)
  423.     pcall (function ()
  424.         if App.Audio then App.Audio:stop () end
  425.         --
  426.         App.Sound = love.sound.newSoundData (File)
  427.         App.Audio = love.audio.newSource (App.Sound)
  428.         --
  429.         App.Audio:play ()
  430.     end)
  431. end
  432.  
  433. --
  434.  
  435. App.updateCanvases (love.graphics.getDimensions ())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement