Guest User

Untitled

a guest
Dec 9th, 2022
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.74 KB | None | 0 0
  1. import launchpad_py as launchpad
  2. import time, ast, os
  3. from pygame import mixer
  4. import pygame, pygame_menu
  5.  
  6. mixer.init()
  7.  
  8. """
  9. ================MENU FUNCTIONS============================
  10. """
  11.  
  12.  
  13. def initmenu():
  14. """
  15. Returns 2 Lists:
  16. infolist contains a list of the info.dat files of each song as a dict
  17. songlist contains 'nameofsong', songdifficulties, 'folderofSong'
  18. :return: infoList = [song1info dict, song2infodict...]
  19. :return: songlist = [('songName', [('diffname', 0), ('diffname2', 0)...], 'foldername'), ...]
  20. """
  21. infolist = []
  22. songlist = []
  23. for songfolder in os.listdir('Songs'):
  24. file = open('Songs/{}/info.dat'.format(songfolder))
  25. info = ast.literal_eval(file.read())
  26. infolist.append(info)
  27.  
  28. songdifs = []
  29. for difsets in info['_difficultyBeatmapSets']:
  30. if difsets['_beatmapCharacteristicName'] == 'Standard':
  31. for dif in difsets['_difficultyBeatmaps']:
  32. songdifs.append((dif['_difficulty'],0))
  33. songlist.append((info['_songName'], songdifs, songfolder))
  34.  
  35. return songlist, infolist
  36.  
  37.  
  38. def main_background() -> None:
  39. """
  40. Converts cover art to the background of the menu
  41. :return:
  42. """
  43. background = pygame_menu.baseimage.BaseImage(image_path='Songs/{}/{}'.format(FILE[0], COVERJPG[0]))
  44. background.draw(surface)
  45.  
  46.  
  47. def setdif(value, item):
  48. """
  49. Sets the difficulty of the level when difficulty selector is adjusted
  50. """
  51. print(value)
  52. DIFCHOICE[0] = value[1]
  53. pass
  54.  
  55.  
  56. def setlevel(value, difset, file):
  57. """
  58. When Level selector is adjusted:
  59. Set FILE[] to correct song folder
  60. Set background image to correct image, reads from File[0]'s info.dat
  61. Updates difficulty selector with the right difficulty options
  62. plays a preview of the song
  63.  
  64. :param difset: [('difficultyname', 0), ('difficultyname2', 0)...]
  65. :param file: String 'folderinSongs'
  66. :return:ButtonStateXY()
  67. """
  68. FILE[0] = file
  69. COVERJPG[0] = infolist[value[1]]['_coverImageFilename']
  70. d1.update_elements(difset)
  71. print('Filename: {}'.format(file))
  72. print('Diffs: {}'.format(difset))
  73. if mixer.music.get_busy():
  74. mixer.music.unload()
  75. mixer.music.load('Songs/{}/{}'.format(file, infolist[value[1]]['_songFilename']))
  76. mixer.music.play(start=infolist[value[1]]['_previewStartTime'], fade_ms= 1000)
  77. pass
  78.  
  79.  
  80. def startsong():
  81. """
  82. When Start button triggered, clear music, start the level
  83. :return:
  84. """
  85. mixer.music.unload()
  86. print('Selection: {}\n{}'.format(FILE[0], DIFCHOICE[0]))
  87. playSong('Songs/{}'.format(FILE[0]), DIFCHOICE[0], lp)
  88. pass
  89.  
  90. """
  91. ==============Beatsaber data Conversion functions===========
  92. """
  93.  
  94. def sortKeyt(e):
  95. return e[2]
  96.  
  97. def sortKeyb(e):
  98. return e[1]
  99.  
  100. """
  101. Sort keys used in initBeatmap for beatmap and notetime
  102. not be needed because of how playSong() processes the notes, but may improve complexity
  103. """
  104.  
  105. def beatsaberConverter(folder, dif):
  106. """
  107. Looks for beatsaber notes dat from info.dat
  108. Beatsaber notes use a 4X3 grid, convert to 8X6 grid depending on cut direction
  109. Depending if it was a left hand/right hand note, create a red/blue color for each note
  110.  
  111. Resources:
  112. info.dat: https://bsmg.wiki/mapping/map-format.html
  113. difficulty.dat: https://bsmg.wiki/mapping/map-format.html#difficulty-file
  114.  
  115. :param folder: 'foldername'
  116. :param dif: int
  117. :return:conversion = [[x, y, time*16, r,g,b], ...]
  118. """
  119. file = open('{}/info.dat'.format(folder)) #Read info.dat to find difficulty.dat
  120. info = ast.literal_eval(file.read())
  121. noteFile1 = info['_difficultyBeatmapSets']
  122. noteFile = ''
  123. for difsets in noteFile1:
  124. if difsets['_beatmapCharacteristicName'] == 'Standard':
  125. noteFile = difsets['_difficultyBeatmaps'][dif]["_beatmapFilename"]
  126. file.close()
  127.  
  128. file = open('{}/{}'.format(folder, noteFile)) #Read difficulty.dat to extract list of notes
  129. notes = ast.literal_eval(file.read())['_notes']
  130. file.close()
  131.  
  132. conversion = []
  133. for i in notes:
  134. if i['_cutDirection'] in (1,6,7):
  135. ypos = (i['_lineLayer']+1)*2+2
  136. else:
  137. ypos = (i['_lineLayer']+1)*2+1
  138. if i['_cutDirection'] in (3,5,7):
  139. xpos = (i['_lineIndex'])*2+1
  140. else:
  141. xpos = (i['_lineIndex'])*2
  142. if i['_type'] == 0:
  143. r,g,b = 63,0,20
  144. else:
  145. r,g,b, = 1,10,63
  146.  
  147. conversion.append([xpos, ypos, round(i['_time']*16,0), r,g,b])
  148. # Prescalar = 16, effects how early lights start glowing before the note,
  149. # if you change this also change it in initBeatmap() (for j in range), and playSong()(while going)
  150.  
  151. return conversion
  152.  
  153.  
  154. def initBeatmap(notemap):
  155. """
  156. Creates array for lightshow of the song (beatmap), independent of user input
  157. creates timing for notes(notetiming), used for checking if user input is early/late compared to note time
  158. :param notemap: [[xpos, ypos, round(i['_time']*16,0), r,g,b], ...]
  159. :return beatmap: [ [[x, y, r, g, b], time], ...]
  160. :return notetime: notemap (may be changed in future for complexity)
  161.  
  162. """
  163. notetime = notemap
  164. beatmap = []
  165.  
  166. for i in notemap:
  167. r = i[3]
  168. g = i[4]
  169. b = i[5]
  170. for j in range(16-1, 0,-1):
  171. """
  172. Determines colors leading up to the note
  173. """
  174. # Prescalar = 16, effects how early lights start glowing before the note,
  175. # if you change this also change it in Beatsaberconverter() (conversion.append), and playSong()(while going)
  176. if j > 5:
  177. beatmap.append([[i[0], i[1], round(r/j), round(g/j), round(b/j)], i[2]-j])
  178. elif j > 0:
  179. beatmap.append([[i[0], i[1], round(r/j), round(g/j), round(b/j)], i[2]-j])
  180. else:
  181. beatmap.append([[i[0], i[1], 0, 0, 0], i[2]-j])
  182. """
  183. Determines color of actual note
  184. """
  185. beatmap.append([[i[0], i[1], i[3], i[4], i[5]], i[2]])
  186. beatmap.append([[i[0], i[1], 0, 0, 0], i[2] + 1])
  187.  
  188. #beatmap.append([[[i[0]+1, i[1], i[3], i[4]+10*j, i[5]]], i[2]-j])
  189. #beatmap.append([[[i[0]-1, i[1], i[3], i[4]+10*j, i[5]]], i[2]-j])
  190. #beatmap.append([[[i[0], i[1]+1, i[3], i[4]+10*j, i[5]]], i[2]-j])
  191. #beatmap.append([[[i[0], i[1]-j, round(63/j), round(63/j), round(63/j)]], i[2]-j-1])
  192.  
  193. # beatmap.append([[[i[0]+j, i[1], 0, 0, 0]], i[2]+1])
  194. # beatmap.append([[[i[0], i[1]+j, 0, 0, 0]], i[2]+1])
  195. #beatmap.append([[[i[0], i[1]-j, 0, 0, 0]], i[2]+1-j])
  196. # beatmap.append([[[i[0]-j, i[1], 0, 0, 0]], i[2]+1])
  197.  
  198.  
  199. # beatmap.append([[[i[0]+1, i[1], 0, 0, 0]], i[2]+1])
  200. # beatmap.append([[[i[0]-1, i[1], 0, 0, 0]], i[2]+1])
  201. # beatmap.append([[[i[0], i[1]+1, 0, 0, 0]], i[2]+1])
  202. # beatmap.append([[[i[0], i[1]-1, 0, 0, 0]], i[2]+1])
  203. for e in range(0,round(notemap[len(notemap)-1][2]), 16):
  204. """
  205. pulsing circle button colors
  206. """
  207. for k in range(0, 8):
  208. for f in range(0,9):
  209. beatmap.append([[k, 0, round(30/9*f), round(30/9*f), round(30/9*f)], e+8-f])
  210. #beatmap.append([[[k, 0, 0, 0, 0]], e + 8])
  211. #beatmap.append([[[8, k+1, 63, 63, 63]], e])
  212. beatmap.append([[8, k+1, round(30/9*f), round(30/9*f), round(30/9*f)], e + 8-f])
  213.  
  214. """
  215. uncomment these 2 lines to use sortkeys
  216. """
  217. beatmap.sort(key=sortKeyb)
  218. notetime.sort(key=sortKeyt)
  219.  
  220. #c = 0
  221. #stop = False
  222. # while stop is False:
  223. # a = beatmap[c][0]
  224. # if beatmap[c][1] == beatmap[c+1][1]:
  225. # beatmap[c][0] = beatmap[c][0].append(beatmap[c+1][0][0])
  226. # beatmap[c][0] = a
  227. # del beatmap[c+1]
  228. # if len(beatmap) >= c:
  229. # stop = True
  230. # c = c+1
  231. return beatmap, notetime
  232.  
  233.  
  234.  
  235. """
  236. ============Level playback Options=============
  237. """
  238.  
  239. def initPad():
  240. """
  241. Sets up launchpad using launchpad_py
  242. Currently supports LaunchpadMk2(), can be edited, but may require user input troubleshooting
  243. :Resources: https://github.com/FMMT666/launchpad.py
  244. :return:
  245. """
  246. lp = launchpad.LaunchpadLPX()
  247. lp.Open()
  248. lp.ButtonFlush()
  249. lp.LedCtrlString('OK', 63, 3, 63, direction=-1, waitms=0)
  250. return lp
  251.  
  252.  
  253. def checkClose(curnotes, curbeat, x, y):
  254. """
  255. Checks if user's input is close to actual note
  256. :param curnotes: notes to check
  257. :param curbeat: User's input time
  258. :param x: User's input x
  259. :param y: User's input y
  260. :return: string 'rating'
  261. """
  262. for note in curnotes:
  263. if note[0] == x and note[1] == y:
  264. if curbeat - 1 <= note[2] <= curbeat + 1:
  265. return 'perfect'
  266. elif curbeat - 3 <= note[2] <= curbeat + 3:
  267. return 'good'
  268. elif curbeat - 5 <= note[2] <= curbeat + 5:
  269. return 'ok'
  270. elif curbeat - 7 <= note[2] <= curbeat + 7:
  271. return 'bad'
  272. return ''
  273.  
  274.  
  275. def playSong(folder, DIFCHOICE, lp):
  276. """
  277. Main function to play the level
  278. Works using magic spaghetti code
  279. :param folder: 'songfolder'
  280. :param DIFCHOICE: int
  281. :param lp: launchpad
  282. :return:
  283. """
  284. restart = True
  285. while restart:
  286. notes = beatsaberConverter('Songs/{}'.format(FILE[0]), DIFCHOICE)
  287.  
  288. file = open('{}/info.dat'.format(folder))
  289. info = ast.literal_eval(file.read())
  290. songfile = '{}/{}'.format(folder, info['_songFilename'])
  291. SpB = 1/(info['_beatsPerMinute']/60)
  292. file.close()
  293.  
  294. lightmap, notemap = initBeatmap(notes)
  295.  
  296. restart = False
  297. mixer.music.load(songfile)
  298. mixer.music.play()
  299. time.sleep(info['_songTimeOffset']/1000)
  300.  
  301. startTime = time.perf_counter()
  302. going = True
  303. while going:
  304. button = lp.ButtonStateXY()
  305. print(button)
  306. curbeat = (time.perf_counter() - startTime)*16/SpB
  307. curlight = [x for x in lightmap if x[1] <= curbeat]
  308. curnotes = [x for x in notemap if x[2] <= curbeat+7]
  309.  
  310. for lights in curlight:
  311. lp.LedCtrlXY(lights[0][0], lights[0][1], lights[0][2], lights[0][3], lights[0][4])
  312. lightmap.remove(lights)
  313.  
  314. if button != []:
  315. """
  316. Checks if user presses a button
  317. """
  318. print("Tonk")
  319. x = button[0]
  320. y = button[1]
  321. r, g, b = 0, 0, 0
  322. if button[2] == 127:
  323. status = checkClose(curnotes, curbeat, x, y)
  324. lp.LedCtrlXY(x, y, 63, 63, 63)
  325. """
  326. chooses 'hit' color based on timing
  327. """
  328. if status == 'perfect':
  329. r,g,b = 10, 17, 31
  330. if status == 'good':
  331. r,g,b = 7, 31, 7
  332. elif status == 'ok':
  333. r, g, b = 31, 30, 5
  334. elif status == 'bad':
  335. r, g, b = 31, 5, 5
  336.  
  337. lp.LedCtrlXY(x + 1, y, r, g, b)
  338. lp.LedCtrlXY(x - 1, y, r, g, b)
  339. lp.LedCtrlXY(x, y + 1, r, g, b)
  340. lp.LedCtrlXY(x, y - 1, r, g, b)
  341.  
  342. if (x,y) == (8,8): #quick stop button
  343. mixer.music.unload()
  344. going = False
  345. lp.LedAllOn(3)
  346. time.sleep(.1)
  347. lp.LedAllOn(0)
  348.  
  349. if (x,y) == (8,7): #quick restart button
  350. mixer.music.unload()
  351. going = False
  352. restart = True
  353. lp.LedAllOn(6)
  354. time.sleep(.1)
  355. lp.LedAllOn(0)
  356.  
  357. else:
  358. lp.LedCtrlXYByCode(x, y, 0)
  359. lp.LedCtrlXYByCode(x + 1, y, 0)
  360. lp.LedCtrlXYByCode(x - 1, y, 0)
  361. lp.LedCtrlXYByCode(x, y + 1, 0)
  362. lp.LedCtrlXYByCode(x, y - 1, 0)
  363.  
  364. for n in curnotes:
  365. if n[2] <= curbeat - 17:
  366. notemap.remove(n)
  367. if len(notemap) == 0:
  368. time.sleep(1)
  369. going = False
  370. lp.LedCtrlString('l', 63, 63, 63, direction=-1, waitms=0)
  371. lp.LedCtrlString('l', 63, 63, 63, direction=1, waitms=0)
  372. lp.LedAllOn(0)
  373.  
  374.  
  375. # Press the green button in the gutter to run the script.
  376. if __name__ == '__main__':
  377. DIFCHOICE = [0]
  378. FILE = ['0cb1b38c96b71676db359a95353dca50ba54b183']
  379. COVERJPG = ['cover.jpg']
  380.  
  381. diffs = [('None', 0)]
  382. songlist, infolist = initmenu()
  383.  
  384. pygame.init()
  385. surface = pygame.display.set_mode((600, 600))
  386.  
  387. main_menu_theme = pygame_menu.themes.THEME_DARK.copy()
  388. main_menu_theme.set_background_color_opacity(0.4)
  389. main_menu_theme.widget_font=pygame_menu.font.FONT_HELVETICA
  390.  
  391. menu = pygame_menu.Menu(350, 450, 'Project LP', theme=main_menu_theme)
  392.  
  393. menu.add_selector('Song: ', songlist, onchange=setlevel)
  394. d1 = menu.add_selector('Difficulty: ', diffs, onchange=setdif)
  395. menu.add_button('START', startsong, shadow=True, shadow_color=(0, 0, 100))
  396. menu.add_button('Quit', pygame_menu.events.EXIT, align=pygame_menu.locals.ALIGN_RIGHT)
  397.  
  398. pygame.display.set_caption('Project LP')
  399.  
  400. lp = initPad()
  401. menu.mainloop(surface, main_background)
  402.  
  403.  
  404.  
  405.  
  406.  
  407.  
Advertisement
Add Comment
Please, Sign In to add comment