TIMAS_Bro

Untitled

Jul 8th, 2025 (edited)
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.48 KB | None | 0 0
  1. local Transliteration = {}
  2. Transliteration.__index = Transliteration
  3. local cyrillicAlphabet = {
  4. {"А", "а", "A", "a"}, {"Б", "б", "B", "b"}, {"В", "в", "V", "v"}, {"Г", "г", "G", "g"}, {"Д", "д", "D", "d"},
  5. {"Е", "е", "E", "e"}, {"Ё", "ё", "YO", "yo"}, {"Ж", "ж", "ZH", "zh"}, {"З", "з", "Z", "z"}, {"И", "и", "I", "i"},
  6. {"Й", "й", "Y", "y"}, {"К", "к", "K", "k"}, {"Л", "л", "L", "l"}, {"М", "м", "M", "m"}, {"Н", "н", "N", "n"},
  7. {"О", "о", "O", "o"}, {"П", "п", "P", "p"}, {"Р", "р", "R", "r"}, {"С", "с", "S", "s"}, {"Т", "т", "T", "t"},
  8. {"У", "у", "U", "u"}, {"Ф", "ф", "F", "f"}, {"Х", "х", "KH", "kh"}, {"Ц", "ц", "TS", "ts"}, {"Ч", "ч", "CH", "ch"},
  9. {"Ш", "ш", "SH", "sh"}, {"Щ", "щ", "SHCH", "shch"}, {"Ъ", "ъ", "", ""}, {"Ы", "ы", "Y", "y"}, {"Ь", "ь", "", ""},
  10. {"Э", "э", "E", "e"}, {"Ю", "ю", "YU", "yu"}, {"Я", "я", "YA", "ya"}
  11. }
  12. function Transliteration.new()
  13. local self = setmetatable({}, Transliteration)
  14. self.cyrillicToLatin = {}
  15. self.latinToCyrillic = {}
  16. self.isSetup = false
  17. return self
  18. end
  19. function Transliteration:setup()
  20. if self.isSetup then return end
  21. for _, pair in ipairs(cyrillicAlphabet) do
  22. self.cyrillicToLatin[utf8.codepoint(pair[1])] = pair[3]
  23. self.cyrillicToLatin[utf8.codepoint(pair[2])] = pair[4]
  24. if pair[3] ~= "" then
  25. if not self.latinToCyrillic[pair[3]] then self.latinToCyrillic[pair[3]] = {} end
  26. table.insert(self.latinToCyrillic[pair[3]], pair[1])
  27. table.insert(self.latinToCyrillic[pair[3]], pair[2])
  28. end
  29. if pair[4] ~= "" then
  30. if not self.latinToCyrillic[pair[4]] then self.latinToCyrillic[pair[4]] = {} end
  31. table.insert(self.latinToCyrillic[pair[4]], pair[1])
  32. table.insert(self.latinToCyrillic[pair[4]], pair[2])
  33. end
  34. end
  35. self.isSetup = true
  36. end
  37. function Transliteration:translate(str)
  38. if not self.isSetup then self:setup() end
  39. local result = ""
  40. local chars = {}
  41. for char in str:gmatch(utf8.charpattern) do table.insert(chars, char) end
  42. for _, char in ipairs(chars) do
  43. local codepoint = utf8.codepoint(char)
  44. local latin = self.cyrillicToLatin[codepoint]
  45. if latin then result = result .. latin else result = result .. char end
  46. end
  47. return result
  48. end
  49. local translator = Transliteration.new()
  50.  
  51. local info_url = "https://server.radio.crysteam.ru/api/nowplaying/dance"
  52. local streamURL = "https://server.radio.crysteam.ru/listen/dance/stream.dfpwm"
  53.  
  54. local speakerList = {}
  55. local playing = false
  56. local volume = 1.0
  57. local currentSong = { artist = "Loading...", title = "Loading...", album = "", duration = 0, elapsed = 0 }
  58. local nextSong = { artist = "Loading...", title = "Loading...", album = "" }
  59. local listeners = 0
  60. local stationName = "RADIO"
  61. local streamHandle = nil
  62. local connecting = false
  63. local animFrame = 0
  64. local scrollOffset = 0
  65.  
  66. local width, height = term.getSize()
  67. local dfpwm = require("cc.audio.dfpwm")
  68. local decoder = dfpwm.make_decoder()
  69.  
  70. local function formatTime(seconds)
  71. local mins = math.floor(seconds / 60)
  72. local secs = seconds % 60
  73. return string.format("%d:%02d", mins, secs)
  74. end
  75.  
  76. local function scrollText(text, maxWidth, offset)
  77. if #text <= maxWidth then return text end
  78. local extended = text .. " *** " .. text
  79. local start = (offset % (#text + 5)) + 1
  80. return extended:sub(start, start + maxWidth - 1)
  81. end
  82.  
  83. local function drawBorder(x, y, w, h, color)
  84. term.setTextColor(color)
  85. term.setCursorPos(x, y)
  86. term.write("+" .. string.rep("-", w-2) .. "+")
  87. for i = 1, h-2 do
  88. term.setCursorPos(x, y + i)
  89. term.write("|")
  90. term.setCursorPos(x + w - 1, y + i)
  91. term.write("|")
  92. end
  93. term.setCursorPos(x, y + h - 1)
  94. term.write("+" .. string.rep("-", w-2) .. "+")
  95. end
  96.  
  97. local function drawSpectrum(x, y, w, h, color)
  98. term.setTextColor(color)
  99. local bars = {"#", "=", "-", "~", "."}
  100. for i = 1, w do
  101. local barHeight = math.random(1, h)
  102. for j = 1, h do
  103. term.setCursorPos(x + i - 1, y + h - j)
  104. if j <= barHeight then
  105. local barIndex = math.min(math.floor((j / h) * #bars) + 1, #bars)
  106. term.write(bars[barIndex])
  107. else
  108. term.write(" ")
  109. end
  110. end
  111. end
  112. end
  113.  
  114. local function drawWaveform(x, y, w, color)
  115. term.setTextColor(color)
  116. term.setCursorPos(x, y)
  117. local wave = ""
  118. local waveChars = {"~", "^", "-", "v", "_"}
  119. for i = 1, w do
  120. local waveIndex = ((i + animFrame) % #waveChars) + 1
  121. wave = wave .. waveChars[waveIndex]
  122. end
  123. term.write(wave)
  124. end
  125.  
  126. local function drawTopBar()
  127. term.setBackgroundColor(colors.black)
  128. term.setTextColor(colors.cyan)
  129. term.setCursorPos(1, 1)
  130. term.clearLine()
  131.  
  132. local statusIndicator = playing and ">>> " or "|| "
  133. term.write(statusIndicator .. stationName)
  134.  
  135. term.setTextColor(colors.yellow)
  136. local listenerText = "[" .. listeners .. " listeners]"
  137. term.setCursorPos(width - #listenerText + 1, 1)
  138. term.write(listenerText)
  139.  
  140. term.setTextColor(colors.lightGray)
  141. term.setCursorPos(1, 2)
  142. term.write(string.rep("=", width))
  143. end
  144.  
  145. local function drawCurrentSong()
  146. term.setBackgroundColor(colors.black)
  147.  
  148. drawBorder(2, 4, width - 2, 8, colors.gray)
  149.  
  150. term.setTextColor(colors.white)
  151. term.setCursorPos(4, 5)
  152. term.write("NOW PLAYING:")
  153.  
  154. term.setTextColor(colors.lightBlue)
  155. term.setCursorPos(4, 6)
  156. local titleText = scrollText(currentSong.title, width - 6, scrollOffset)
  157. term.write(titleText)
  158.  
  159. term.setTextColor(colors.yellow)
  160. term.setCursorPos(4, 7)
  161. local artistText = scrollText(currentSong.artist, width - 6, scrollOffset)
  162. term.write(artistText)
  163.  
  164. if currentSong.album ~= "" then
  165. term.setTextColor(colors.lightGray)
  166. term.setCursorPos(4, 8)
  167. local albumText = scrollText(currentSong.album, width - 6, scrollOffset)
  168. term.write(albumText)
  169. end
  170.  
  171. if currentSong.duration > 0 then
  172. local progress = math.min(currentSong.elapsed / currentSong.duration, 1)
  173. local barWidth = width - 8
  174. local filledWidth = math.floor(progress * barWidth)
  175.  
  176. term.setCursorPos(4, 10)
  177. term.setTextColor(colors.green)
  178. term.write(string.rep("#", filledWidth))
  179. term.setTextColor(colors.gray)
  180. term.write(string.rep(".", barWidth - filledWidth))
  181.  
  182. term.setCursorPos(4, 11)
  183. term.setTextColor(colors.white)
  184. term.write(formatTime(currentSong.elapsed))
  185. local totalTime = formatTime(currentSong.duration)
  186. term.setCursorPos(width - #totalTime - 2, 11)
  187. term.write(totalTime)
  188. end
  189. end
  190.  
  191. local function drawSpectrogramPanel()
  192. if playing then
  193. drawSpectrum(4, 13, width - 6, 4, colors.green)
  194. else
  195. term.setTextColor(colors.gray)
  196. for i = 13, 16 do
  197. term.setCursorPos(4, i)
  198. term.write(string.rep(".", width - 6))
  199. end
  200. end
  201. end
  202.  
  203. local function drawNextSong()
  204. term.setBackgroundColor(colors.black)
  205. drawBorder(2, 18, width - 2, 5, colors.gray)
  206.  
  207. term.setTextColor(colors.gray)
  208. term.setCursorPos(4, 19)
  209. term.write("UP NEXT:")
  210.  
  211. term.setTextColor(colors.lightGray)
  212. term.setCursorPos(4, 20)
  213. local nextTitle = scrollText(nextSong.title, width - 6, scrollOffset)
  214. term.write(nextTitle)
  215.  
  216. term.setCursorPos(4, 21)
  217. local nextArtist = scrollText(nextSong.artist, width - 6, scrollOffset)
  218. term.write(nextArtist)
  219. end
  220.  
  221. local function drawControls()
  222. term.setBackgroundColor(colors.black)
  223. local controlY = height - 6
  224.  
  225. drawBorder(2, controlY, width - 2, 6, colors.gray)
  226.  
  227. term.setTextColor(colors.white)
  228. term.setCursorPos(4, controlY + 1)
  229. local statusText = playing and "STATUS: PLAYING" or "STATUS: PAUSED"
  230. term.write(statusText)
  231.  
  232. if playing then
  233. drawWaveform(4, controlY + 2, width - 6, colors.cyan)
  234. else
  235. term.setTextColor(colors.gray)
  236. term.setCursorPos(4, controlY + 2)
  237. term.write(string.rep("-", width - 6))
  238. end
  239.  
  240. term.setTextColor(colors.yellow)
  241. term.setCursorPos(4, controlY + 3)
  242. local volText = "VOLUME: " .. math.floor(volume * 100) .. "%"
  243. term.write(volText)
  244.  
  245. local volBarWidth = width - 20
  246. local volFilled = math.floor(volume * volBarWidth)
  247. term.setCursorPos(16, controlY + 3)
  248. term.setTextColor(colors.green)
  249. term.write(string.rep("#", volFilled))
  250. term.setTextColor(colors.gray)
  251. term.write(string.rep(".", volBarWidth - volFilled))
  252.  
  253. term.setTextColor(colors.lightGray)
  254. term.setCursorPos(4, controlY + 4)
  255. term.write("CONTROLS: [P]lay/Pause [+/-]Volume [Q]uit")
  256. end
  257.  
  258. local function drawUI()
  259. term.clear()
  260. drawTopBar()
  261. drawCurrentSong()
  262. drawSpectrogramPanel()
  263. drawNextSong()
  264. drawControls()
  265. end
  266.  
  267. local function fetch_song_info()
  268. local res = http.get(info_url)
  269. if not res then return nil end
  270. local data, success, parsed
  271. pcall(function() data = res.readAll(); res.close() end)
  272. if not data then return nil end
  273. success, parsed = pcall(textutils.unserializeJSON, data)
  274. if not success then return nil end
  275. return parsed
  276. end
  277.  
  278. local function updateSongInfo()
  279. while true do
  280. local info = fetch_song_info()
  281. if info then
  282. if info.now_playing and info.now_playing.song then
  283. currentSong.artist = translator:translate(info.now_playing.song.artist or "Unknown Artist")
  284. currentSong.title = translator:translate(info.now_playing.song.title or "Unknown Title")
  285. currentSong.album = translator:translate(info.now_playing.song.album or "")
  286. currentSong.duration = info.now_playing.duration or 0
  287. currentSong.elapsed = info.now_playing.elapsed or 0
  288. end
  289. if info.playing_next and info.playing_next.song then
  290. nextSong.artist = translator:translate(info.playing_next.song.artist or "Unknown Artist")
  291. nextSong.title = translator:translate(info.playing_next.song.title or "Unknown Title")
  292. nextSong.album = translator:translate(info.playing_next.song.album or "")
  293. end
  294. if info.listeners then
  295. local current = info.listeners.current or 0
  296. local multiplier = math.random(5, 500) / 100
  297. listeners = math.floor(current * (1 + multiplier))
  298. end
  299. if info.station then
  300. stationName = translator:translate(info.station.name or "RADIO")
  301. end
  302. end
  303. sleep(5)
  304. end
  305. end
  306.  
  307. local function findSpeakers()
  308. speakerList = {}
  309. for _, side in ipairs(peripheral.getNames()) do
  310. if peripheral.getType(side) == "speaker" then
  311. table.insert(speakerList, peripheral.wrap(side))
  312. end
  313. end
  314. end
  315.  
  316. local function streamLoop()
  317. while true do
  318. if playing and not streamHandle and not connecting then
  319. connecting = true
  320. http.request(streamURL)
  321. elseif not playing and streamHandle then
  322. streamHandle.close()
  323. streamHandle = nil
  324. connecting = false
  325. end
  326.  
  327. local event, p1, p2, p3 = os.pullEvent()
  328.  
  329. if event == "http_success" and p1 == streamURL then
  330. streamHandle = p2
  331. connecting = false
  332. elseif event == "http_failure" and p1 == streamURL then
  333. streamHandle = nil
  334. connecting = false
  335. sleep(2)
  336. elseif event == "timer" and playing and streamHandle then
  337. local chunk = streamHandle.read(8192)
  338. if chunk then
  339. local buffer = decoder(chunk)
  340. if buffer then
  341. for _, speaker in ipairs(speakerList) do
  342. while not speaker.playAudio(buffer, volume) do
  343. os.pullEvent("speaker_audio_empty")
  344. end
  345. end
  346. end
  347. else
  348. streamHandle.close()
  349. streamHandle = nil
  350. connecting = false
  351. end
  352. end
  353. end
  354. end
  355.  
  356. local function controlLoop()
  357. while true do
  358. local event, key = os.pullEvent("key")
  359. if key == keys.p then
  360. playing = not playing
  361. elseif key == keys.equals or key == keys.plus then
  362. volume = math.min(3.0, volume + 0.1)
  363. elseif key == keys.minus then
  364. volume = math.max(0.0, volume - 0.1)
  365. elseif key == keys.q then
  366. playing = false
  367. if streamHandle then
  368. streamHandle.close()
  369. end
  370. term.clear()
  371. term.setCursorPos(1, 1)
  372. print("Radio stopped. Goodbye!")
  373. return
  374. end
  375. end
  376. end
  377.  
  378. local function animationLoop()
  379. while true do
  380. animFrame = animFrame + 1
  381. scrollOffset = scrollOffset + 1
  382. if scrollOffset > 100 then scrollOffset = 0 end
  383. sleep(0.3)
  384. end
  385. end
  386.  
  387. local function uiRefreshLoop()
  388. while true do
  389. drawUI()
  390. sleep(0.1)
  391. end
  392. end
  393.  
  394. local function audioTimerLoop()
  395. while true do
  396. if playing and streamHandle then
  397. os.queueEvent("timer")
  398. end
  399. sleep(0.05)
  400. end
  401. end
  402.  
  403. term.clear()
  404. findSpeakers()
  405.  
  406. if #speakerList == 0 then
  407. print("No speakers found!")
  408. return
  409. end
  410.  
  411. local info = fetch_song_info()
  412. if info then
  413. if info.now_playing and info.now_playing.song then
  414. currentSong.artist = translator:translate(info.now_playing.song.artist or "Unknown Artist")
  415. currentSong.title = translator:translate(info.now_playing.song.title or "Unknown Title")
  416. currentSong.album = translator:translate(info.now_playing.song.album or "")
  417. end
  418. if info.playing_next and info.playing_next.song then
  419. nextSong.artist = translator:translate(info.playing_next.song.artist or "Unknown Artist")
  420. nextSong.title = translator:translate(info.playing_next.song.title or "Unknown Title")
  421. end
  422. if info.listeners then listeners = info.listeners.current or 0 end
  423. if info.station then stationName = translator:translate(info.station.name or "RADIO") end
  424. end
  425.  
  426. drawUI()
  427.  
  428. parallel.waitForAny(
  429. streamLoop,
  430. controlLoop,
  431. updateSongInfo,
  432. uiRefreshLoop,
  433. animationLoop,
  434. audioTimerLoop
  435. )
Advertisement
Add Comment
Please, Sign In to add comment