Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local Transliteration = {}
- Transliteration.__index = Transliteration
- local cyrillicAlphabet = {
- {"А", "а", "A", "a"}, {"Б", "б", "B", "b"}, {"В", "в", "V", "v"}, {"Г", "г", "G", "g"}, {"Д", "д", "D", "d"},
- {"Е", "е", "E", "e"}, {"Ё", "ё", "YO", "yo"}, {"Ж", "ж", "ZH", "zh"}, {"З", "з", "Z", "z"}, {"И", "и", "I", "i"},
- {"Й", "й", "Y", "y"}, {"К", "к", "K", "k"}, {"Л", "л", "L", "l"}, {"М", "м", "M", "m"}, {"Н", "н", "N", "n"},
- {"О", "о", "O", "o"}, {"П", "п", "P", "p"}, {"Р", "р", "R", "r"}, {"С", "с", "S", "s"}, {"Т", "т", "T", "t"},
- {"У", "у", "U", "u"}, {"Ф", "ф", "F", "f"}, {"Х", "х", "KH", "kh"}, {"Ц", "ц", "TS", "ts"}, {"Ч", "ч", "CH", "ch"},
- {"Ш", "ш", "SH", "sh"}, {"Щ", "щ", "SHCH", "shch"}, {"Ъ", "ъ", "", ""}, {"Ы", "ы", "Y", "y"}, {"Ь", "ь", "", ""},
- {"Э", "э", "E", "e"}, {"Ю", "ю", "YU", "yu"}, {"Я", "я", "YA", "ya"}
- }
- function Transliteration.new()
- local self = setmetatable({}, Transliteration)
- self.cyrillicToLatin = {}
- self.latinToCyrillic = {}
- self.isSetup = false
- return self
- end
- function Transliteration:setup()
- if self.isSetup then return end
- for _, pair in ipairs(cyrillicAlphabet) do
- self.cyrillicToLatin[utf8.codepoint(pair[1])] = pair[3]
- self.cyrillicToLatin[utf8.codepoint(pair[2])] = pair[4]
- if pair[3] ~= "" then
- if not self.latinToCyrillic[pair[3]] then self.latinToCyrillic[pair[3]] = {} end
- table.insert(self.latinToCyrillic[pair[3]], pair[1])
- table.insert(self.latinToCyrillic[pair[3]], pair[2])
- end
- if pair[4] ~= "" then
- if not self.latinToCyrillic[pair[4]] then self.latinToCyrillic[pair[4]] = {} end
- table.insert(self.latinToCyrillic[pair[4]], pair[1])
- table.insert(self.latinToCyrillic[pair[4]], pair[2])
- end
- end
- self.isSetup = true
- end
- function Transliteration:translate(str)
- if not self.isSetup then self:setup() end
- local result = ""
- local chars = {}
- for char in str:gmatch(utf8.charpattern) do table.insert(chars, char) end
- for _, char in ipairs(chars) do
- local codepoint = utf8.codepoint(char)
- local latin = self.cyrillicToLatin[codepoint]
- if latin then result = result .. latin else result = result .. char end
- end
- return result
- end
- local translator = Transliteration.new()
- local info_url = "https://server.radio.crysteam.ru/api/nowplaying/dance"
- local streamURL = "https://server.radio.crysteam.ru/listen/dance/stream.dfpwm"
- local speakerList = {}
- local playing = false
- local volume = 1.0
- local currentSong = { artist = "Loading...", title = "Loading...", album = "", duration = 0, elapsed = 0 }
- local nextSong = { artist = "Loading...", title = "Loading...", album = "" }
- local listeners = 0
- local stationName = "RADIO"
- local streamHandle = nil
- local connecting = false
- local animFrame = 0
- local scrollOffset = 0
- local width, height = term.getSize()
- local dfpwm = require("cc.audio.dfpwm")
- local decoder = dfpwm.make_decoder()
- local function formatTime(seconds)
- local mins = math.floor(seconds / 60)
- local secs = seconds % 60
- return string.format("%d:%02d", mins, secs)
- end
- local function scrollText(text, maxWidth, offset)
- if #text <= maxWidth then return text end
- local extended = text .. " *** " .. text
- local start = (offset % (#text + 5)) + 1
- return extended:sub(start, start + maxWidth - 1)
- end
- local function drawBorder(x, y, w, h, color)
- term.setTextColor(color)
- term.setCursorPos(x, y)
- term.write("+" .. string.rep("-", w-2) .. "+")
- for i = 1, h-2 do
- term.setCursorPos(x, y + i)
- term.write("|")
- term.setCursorPos(x + w - 1, y + i)
- term.write("|")
- end
- term.setCursorPos(x, y + h - 1)
- term.write("+" .. string.rep("-", w-2) .. "+")
- end
- local function drawSpectrum(x, y, w, h, color)
- term.setTextColor(color)
- local bars = {"#", "=", "-", "~", "."}
- for i = 1, w do
- local barHeight = math.random(1, h)
- for j = 1, h do
- term.setCursorPos(x + i - 1, y + h - j)
- if j <= barHeight then
- local barIndex = math.min(math.floor((j / h) * #bars) + 1, #bars)
- term.write(bars[barIndex])
- else
- term.write(" ")
- end
- end
- end
- end
- local function drawWaveform(x, y, w, color)
- term.setTextColor(color)
- term.setCursorPos(x, y)
- local wave = ""
- local waveChars = {"~", "^", "-", "v", "_"}
- for i = 1, w do
- local waveIndex = ((i + animFrame) % #waveChars) + 1
- wave = wave .. waveChars[waveIndex]
- end
- term.write(wave)
- end
- local function drawTopBar()
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.cyan)
- term.setCursorPos(1, 1)
- term.clearLine()
- local statusIndicator = playing and ">>> " or "|| "
- term.write(statusIndicator .. stationName)
- term.setTextColor(colors.yellow)
- local listenerText = "[" .. listeners .. " listeners]"
- term.setCursorPos(width - #listenerText + 1, 1)
- term.write(listenerText)
- term.setTextColor(colors.lightGray)
- term.setCursorPos(1, 2)
- term.write(string.rep("=", width))
- end
- local function drawCurrentSong()
- term.setBackgroundColor(colors.black)
- drawBorder(2, 4, width - 2, 8, colors.gray)
- term.setTextColor(colors.white)
- term.setCursorPos(4, 5)
- term.write("NOW PLAYING:")
- term.setTextColor(colors.lightBlue)
- term.setCursorPos(4, 6)
- local titleText = scrollText(currentSong.title, width - 6, scrollOffset)
- term.write(titleText)
- term.setTextColor(colors.yellow)
- term.setCursorPos(4, 7)
- local artistText = scrollText(currentSong.artist, width - 6, scrollOffset)
- term.write(artistText)
- if currentSong.album ~= "" then
- term.setTextColor(colors.lightGray)
- term.setCursorPos(4, 8)
- local albumText = scrollText(currentSong.album, width - 6, scrollOffset)
- term.write(albumText)
- end
- if currentSong.duration > 0 then
- local progress = math.min(currentSong.elapsed / currentSong.duration, 1)
- local barWidth = width - 8
- local filledWidth = math.floor(progress * barWidth)
- term.setCursorPos(4, 10)
- term.setTextColor(colors.green)
- term.write(string.rep("#", filledWidth))
- term.setTextColor(colors.gray)
- term.write(string.rep(".", barWidth - filledWidth))
- term.setCursorPos(4, 11)
- term.setTextColor(colors.white)
- term.write(formatTime(currentSong.elapsed))
- local totalTime = formatTime(currentSong.duration)
- term.setCursorPos(width - #totalTime - 2, 11)
- term.write(totalTime)
- end
- end
- local function drawSpectrogramPanel()
- if playing then
- drawSpectrum(4, 13, width - 6, 4, colors.green)
- else
- term.setTextColor(colors.gray)
- for i = 13, 16 do
- term.setCursorPos(4, i)
- term.write(string.rep(".", width - 6))
- end
- end
- end
- local function drawNextSong()
- term.setBackgroundColor(colors.black)
- drawBorder(2, 18, width - 2, 5, colors.gray)
- term.setTextColor(colors.gray)
- term.setCursorPos(4, 19)
- term.write("UP NEXT:")
- term.setTextColor(colors.lightGray)
- term.setCursorPos(4, 20)
- local nextTitle = scrollText(nextSong.title, width - 6, scrollOffset)
- term.write(nextTitle)
- term.setCursorPos(4, 21)
- local nextArtist = scrollText(nextSong.artist, width - 6, scrollOffset)
- term.write(nextArtist)
- end
- local function drawControls()
- term.setBackgroundColor(colors.black)
- local controlY = height - 6
- drawBorder(2, controlY, width - 2, 6, colors.gray)
- term.setTextColor(colors.white)
- term.setCursorPos(4, controlY + 1)
- local statusText = playing and "STATUS: PLAYING" or "STATUS: PAUSED"
- term.write(statusText)
- if playing then
- drawWaveform(4, controlY + 2, width - 6, colors.cyan)
- else
- term.setTextColor(colors.gray)
- term.setCursorPos(4, controlY + 2)
- term.write(string.rep("-", width - 6))
- end
- term.setTextColor(colors.yellow)
- term.setCursorPos(4, controlY + 3)
- local volText = "VOLUME: " .. math.floor(volume * 100) .. "%"
- term.write(volText)
- local volBarWidth = width - 20
- local volFilled = math.floor(volume * volBarWidth)
- term.setCursorPos(16, controlY + 3)
- term.setTextColor(colors.green)
- term.write(string.rep("#", volFilled))
- term.setTextColor(colors.gray)
- term.write(string.rep(".", volBarWidth - volFilled))
- term.setTextColor(colors.lightGray)
- term.setCursorPos(4, controlY + 4)
- term.write("CONTROLS: [P]lay/Pause [+/-]Volume [Q]uit")
- end
- local function drawUI()
- term.clear()
- drawTopBar()
- drawCurrentSong()
- drawSpectrogramPanel()
- drawNextSong()
- drawControls()
- end
- local function fetch_song_info()
- local res = http.get(info_url)
- if not res then return nil end
- local data, success, parsed
- pcall(function() data = res.readAll(); res.close() end)
- if not data then return nil end
- success, parsed = pcall(textutils.unserializeJSON, data)
- if not success then return nil end
- return parsed
- end
- local function updateSongInfo()
- while true do
- local info = fetch_song_info()
- if info then
- if info.now_playing and info.now_playing.song then
- currentSong.artist = translator:translate(info.now_playing.song.artist or "Unknown Artist")
- currentSong.title = translator:translate(info.now_playing.song.title or "Unknown Title")
- currentSong.album = translator:translate(info.now_playing.song.album or "")
- currentSong.duration = info.now_playing.duration or 0
- currentSong.elapsed = info.now_playing.elapsed or 0
- end
- if info.playing_next and info.playing_next.song then
- nextSong.artist = translator:translate(info.playing_next.song.artist or "Unknown Artist")
- nextSong.title = translator:translate(info.playing_next.song.title or "Unknown Title")
- nextSong.album = translator:translate(info.playing_next.song.album or "")
- end
- if info.listeners then
- local current = info.listeners.current or 0
- local multiplier = math.random(5, 500) / 100
- listeners = math.floor(current * (1 + multiplier))
- end
- if info.station then
- stationName = translator:translate(info.station.name or "RADIO")
- end
- end
- sleep(5)
- end
- end
- local function findSpeakers()
- speakerList = {}
- for _, side in ipairs(peripheral.getNames()) do
- if peripheral.getType(side) == "speaker" then
- table.insert(speakerList, peripheral.wrap(side))
- end
- end
- end
- local function streamLoop()
- while true do
- if playing and not streamHandle and not connecting then
- connecting = true
- http.request(streamURL)
- elseif not playing and streamHandle then
- streamHandle.close()
- streamHandle = nil
- connecting = false
- end
- local event, p1, p2, p3 = os.pullEvent()
- if event == "http_success" and p1 == streamURL then
- streamHandle = p2
- connecting = false
- elseif event == "http_failure" and p1 == streamURL then
- streamHandle = nil
- connecting = false
- sleep(2)
- elseif event == "timer" and playing and streamHandle then
- local chunk = streamHandle.read(8192)
- if chunk then
- local buffer = decoder(chunk)
- if buffer then
- for _, speaker in ipairs(speakerList) do
- while not speaker.playAudio(buffer, volume) do
- os.pullEvent("speaker_audio_empty")
- end
- end
- end
- else
- streamHandle.close()
- streamHandle = nil
- connecting = false
- end
- end
- end
- end
- local function controlLoop()
- while true do
- local event, key = os.pullEvent("key")
- if key == keys.p then
- playing = not playing
- elseif key == keys.equals or key == keys.plus then
- volume = math.min(3.0, volume + 0.1)
- elseif key == keys.minus then
- volume = math.max(0.0, volume - 0.1)
- elseif key == keys.q then
- playing = false
- if streamHandle then
- streamHandle.close()
- end
- term.clear()
- term.setCursorPos(1, 1)
- print("Radio stopped. Goodbye!")
- return
- end
- end
- end
- local function animationLoop()
- while true do
- animFrame = animFrame + 1
- scrollOffset = scrollOffset + 1
- if scrollOffset > 100 then scrollOffset = 0 end
- sleep(0.3)
- end
- end
- local function uiRefreshLoop()
- while true do
- drawUI()
- sleep(0.1)
- end
- end
- local function audioTimerLoop()
- while true do
- if playing and streamHandle then
- os.queueEvent("timer")
- end
- sleep(0.05)
- end
- end
- term.clear()
- findSpeakers()
- if #speakerList == 0 then
- print("No speakers found!")
- return
- end
- local info = fetch_song_info()
- if info then
- if info.now_playing and info.now_playing.song then
- currentSong.artist = translator:translate(info.now_playing.song.artist or "Unknown Artist")
- currentSong.title = translator:translate(info.now_playing.song.title or "Unknown Title")
- currentSong.album = translator:translate(info.now_playing.song.album or "")
- end
- if info.playing_next and info.playing_next.song then
- nextSong.artist = translator:translate(info.playing_next.song.artist or "Unknown Artist")
- nextSong.title = translator:translate(info.playing_next.song.title or "Unknown Title")
- end
- if info.listeners then listeners = info.listeners.current or 0 end
- if info.station then stationName = translator:translate(info.station.name or "RADIO") end
- end
- drawUI()
- parallel.waitForAny(
- streamLoop,
- controlLoop,
- updateSongInfo,
- uiRefreshLoop,
- animationLoop,
- audioTimerLoop
- )
Advertisement
Add Comment
Please, Sign In to add comment