View difference between Paste ID: xFgbep3w and B5dxWyxg
SHOW: | | - or go back to the newest paste.
1
--18107's Jukebox
2-
--version 1.1.2
2+
--version 1.0
3-
--last updated 14 July 2015
3+
--last updated 01 July 2015
4
--requires 'project red' or 'computronics'
5-
--Noteblock player at http://pastebin.com/aX8quTFy
5+
6
local monitor = peripheral.find("monitor")
7
if monitor == nil then
8
  term.setTextColor(colors.red)
9
  print("Error: No monitor detected")
10
  return
11
end
12
local modem = peripheral.find("modem")
13
local musicList = {}
14
local monitorWidth, monitorHeight = monitor.getSize()
15
local song = nil
16
local playing = nil
17
local songLength, songName, songAuth, origSongAuth, songDesc
18
local tempo = 1000
19
local page = 1
20
local screen = 1
21
local noteblock = peripheral.find("iron_noteblock")
22
local instrum = {0, 0, 0, 0, 0}
23
local repeatMode = "off" -- off, single, all ordered, all random
24
local finished = false
25
local refreshCounter = 1
26
local ticksPlayed = 0
27
local updateNeeded = false
28
math.randomseed(os.time())
29-
local paused = false
29+
30
31
function newLine()
32
  local x, y = monitor.getCursorPos()
33
  monitor.setCursorPos(1, y + 1)
34
end --newline()
35
36
function readNumber(file, size) --reads a binary number
37
  local number = 0
38
  for siz = 1, size do
39
    number = number + bit.blshift(file.read(), 8*(siz-1))
40
  end --for size
41
  return number
42
end --readNumber()
43
44
function readString(file, length)
45
  if length == 0 then return end
46
  local text = string.char(file.read())
47
  for char = 2, length do
48
    text = text..string.char(file.read())
49
  end --for char
50
  return text
51
end --readString()
52
53
function refreshMusicList()
54
  local length = #musicList
55
  local thisSong
56
  if playing ~= 0 and playing ~= nil then
57
    thisSong = musicList[playing]
58
  end --if playing
59
  
60
  local list = fs.list("")
61
  local disks = {}
62
  musicList = {}
63
  local counter = 1
64
  local counter2 = 1
65
  for i = 1, #list do
66
    if string.sub(list[i],-4)==".nbs" then
67
      musicList[counter] = list[i]
68
      counter = counter + 1
69
    elseif string.sub(list[i],0,4) == "disk" and fs.isDir(list[i]) then
70
      disks[counter2] = list[i]
71
      counter2 = counter2 + 1
72
    end --if ".nbs"
73
  end --for #list
74
  
75
  for a = 1, #disks do
76
    list = fs.list(disks[a])
77
    for b = 1, #list do
78
      if string.sub(list[b],-4)==".nbs" then
79
        musicList[counter] = disks[a].."/"..list[b]
80
        counter = counter + 1
81
      end --if ".nbs"
82
    end --for #list
83
  end --for #disks
84
  
85
  if playing ~= 0 and playing ~= nil then
86
    if thisSong ~= musicList[playing] then
87
      playing = playing - length + #musicList --FIXME check if this worked
88
      updateNeeded = true
89
    end --if thisSong
90
  end --if playing
91
  
92
  if page > #musicList/(monitorHeight-3) and #musicList > 0 then
93
    page = math.ceil(#musicList/(monitorHeight-3))
94
  end --if page
95
end --refreshMusicList()
96
97
function nextSong()
98
  if playing == 0 or playing == nil then return end
99
100
  if repeatMode == "all random" then
101
    playing = math.random(#musicList)
102
  else
103
    if playing >= #musicList then
104
    playing = 0
105
  end --if playing == #musicList
106
  playing = playing + 1
107
  end --if repeatMode
108
  page = math.floor((playing-1)/(monitorHeight-3)) + 1
109
  updateNeeded = true
110
end --skip()
111
112
function displayMusicList(page, playing)
113
  refreshMusicList()
114
  monitor.clear()
115
  monitor.setCursorPos(monitorWidth-4-20, 1)
116
  monitor.setTextColor(colors.yellow)
117
  monitor.write("Repeat: "..repeatMode)
118
  monitor.setCursorPos(1, 1)
119
  monitor.setTextColor(colors.cyan)
120
  monitor.write("18107's Juke Box")
121
  monitor.setCursorPos(monitorWidth-3, 1)
122
  monitor.setTextColor(colors.red)
123
  monitor.write("Stop")
124
  monitor.setTextColor(colors.white)
125
  newLine()
126
  if #musicList > 0 then
127
    for i = (monitorHeight-3)*(page-1)+1, (monitorHeight-3)*(page-1)+monitorHeight-3 do
128
      if playing == i then monitor.setTextColor(colors.blue) end
129
      if i <= #musicList then
130
        if string.sub(musicList[i],0,4) == "disk" then
131
          if string.sub(musicList[i],5,5) == "/" then
132
            monitor.write("Disk : "..string.sub(musicList[i],6,-5))
133
          else
134
            monitor.write("Disk "..string.sub(musicList[i],5,5).." : "..string.sub(musicList[i],7,-5))
135
          end --if "/"
136
        else
137
          monitor.write(string.sub(musicList[i],0,-5))
138
        end --if disk/
139
      end --if #musicList
140
      if playing == i then monitor.setTextColor(colors.white) end
141
      newLine()
142
    end --for 1, 16
143
  end --if #musicList
144
  monitor.setCursorPos(1, monitorHeight-1)
145
  if playing == 0 or playing == nil then
146
    monitor.setTextColor(colors.lightGray)
147
  else
148
    monitor.setTextColor(colors.cyan)
149
  end --if playing
150
  monitor.write("Currently Playing")
151
  monitor.setCursorPos(monitorWidth-3, monitorHeight-1)
152
  if playing ~= 0 and playing ~= nil then
153-
  monitor.setCursorPos(monitorWidth-9, monitorHeight-1)
153+
154-
  if paused then
154+
155-
    monitor.write("Play  ")
155+
156
  newLine()
157-
    monitor.write("Pause ")
157+
158-
  end --if paused
158+
159
  monitor.setCursorPos(monitorWidth-3, monitorHeight)
160
  monitor.write("Next")
161
  monitor.setCursorPos(monitorWidth/2+1, monitorHeight)
162
  monitor.setTextColor(colors.yellow)
163
  monitor.write(tostring(page))
164
  monitor.setTextColor(colors.white)
165
end --displayMusicList()
166
167
function displayCurrentlyPlaying(playing, full)
168
  if full then
169
    monitor.clear()
170
    monitor.setCursorPos(monitorWidth-4-20, 1)
171
    monitor.setTextColor(colors.yellow)
172
    monitor.write("Repeat: "..repeatMode)
173
    monitor.setCursorPos(1, 1)
174
    monitor.setTextColor(colors.cyan)
175
    monitor.write("18107's Juke Box")
176
    monitor.setCursorPos(monitorWidth-3, 1)
177
    monitor.setTextColor(colors.red)
178
    monitor.write("Stop")
179
    monitor.setTextColor(colors.white)
180
    newLine()
181
    newLine()
182
    if string.sub(musicList[playing], 0, 4) == "disk" then
183
      if string.sub(musicList[playing], 5, 5) == "/" then
184
        monitor.write("Disk : "..string.sub(musicList[playing], 6, -5))
185
      else
186
        monitor.write("Disk "..string.sub(musicList[playing],5,5).." : "..string.sub(musicList[playing],7,-5))
187
      end --if "/"
188
    else
189
      monitor.write(string.sub(musicList[playing], 0, -5))
190
    end --if disk/
191
    newLine()
192
    newLine()
193
    if type(songAuth) == "string" then
194
      monitor.write("Author: "..songAuth)
195
    end --if string
196
    newLine()
197
    newLine()
198
    if type(origSongAuth) == "string" then
199
      monitor.write("Original author: "..origSongAuth)
200
    end --if string
201
    newLine()
202
    newLine()
203
    if type(songDesc) == "string" then
204
      for desc = 0, #songDesc/monitorWidth+1 do
205
        monitor.write(string.sub(songDesc, desc*monitorWidth+1, (desc+1)*monitorWidth))
206
        newLine()
207
      end --for #songDesc
208
    end --if string
209
    monitor.setCursorPos(1, monitorHeight-1)
210
    monitor.setTextColor(colors.cyan)
211
    monitor.write("Song list")
212
    monitor.setCursorPos(monitorWidth-3, monitorHeight-1)
213
    monitor.setTextColor(colors.blue)
214
    monitor.write("Skip")
215
  end --if full
216
  
217
  monitor.setCursorPos(1, monitorHeight)
218
  monitor.setTextColor(colors.lightGray)
219-
    monitor.setCursorPos(monitorWidth-9, monitorHeight-1)
219+
220-
    if paused then
220+
221-
      monitor.write("Play  ")
221+
222
  monitor.write(minutes..":")
223-
      monitor.write("Pause ")
223+
224-
    end --if paused
224+
225
  
226
  minutes = math.floor(songLength*100/tempo/60)
227
  seconds = math.floor(songLength*100/tempo) % 60
228
  monitor.setCursorPos(monitorWidth-4, monitorHeight)
229
  if minutes < 10 then monitor.write(" ") end
230
  monitor.write(minutes..":")
231
  if seconds < 10 then monitor.write("0") end
232
  monitor.write(seconds.."")
233
  
234
  monitor.setTextColor(colors.white)
235
end --displayCurrentlyPlaying()
236
237
function header(song)
238
  local strLen = 0
239
  songLength = readNumber(song, 2)
240
  readNumber(song, 2) --song height
241
  strLen = readNumber(song, 4)
242
  songName = readString(song, strLen)
243
  strLen = readNumber(song, 4)
244
  songAuth = readString(song, strLen)
245
  strLen = readNumber(song, 4)
246-
  monitor.setCursorPos(7, monitorHeight)
246+
247-
  monitor.write("<")
247+
248-
  for i = 8, monitorWidth-7 do
248+
249-
    monitor.write("=")
249+
250-
  end --for monitorWidth
250+
251-
  monitor.write(">")
251+
252
  readNumber(song, 1) --time signature
253
  readNumber(song, 4) --minutes spent
254
  readNumber(song, 4) --left clicks
255
  readNumber(song, 4) --right clicks
256
  readNumber(song, 4) --blocks added
257
  readNumber(song, 4) --blocks removed
258
  strLen = readNumber(song, 4)
259
  readString(song, strLen) --MIDI/schematic file name
260
  return tempo
261
end --header()
262
263
function playSong(number)
264
  while true do --coroutine loop
265
    if song ~= nil then song.close() end
266
    if number == 0 then number = nil end
267
    while number == nil do
268
      number = coroutine.yield()
269
      if number == 0 then number = nil end
270
    end -- while number == nil
271
    song = fs.open(musicList[number], "rb")
272
    if song == nil then
273
      print("File not found")
274
      break
275
    end --if nil
276
    
277
    tempo = header(song)
278
    
279
    ticksPlayed = -1
280
    local nextTick = 0
281
    local instrument = 0
282
    local key = 0
283
    local running = true
284
    while running do --song loop
285
      nextTick = readNumber(song, 2)
286
      -- if end of file
287
      if nextTick == 0 or nextTick == nil then
288
        finished = true
289
        break
290
      end --if nextTick
291
      for tick = 1, nextTick do --wait for next note
292
        ticksPlayed = ticksPlayed + 1
293
        number = coroutine.yield()
294
        if number ~= nil then running = false break end
295
      end --for tick
296
      
297
      while readNumber(song, 2) ~= 0 do --play chord
298
        instrument = readNumber(song, 1)
299
        key = readNumber(song, 1)
300
        instrum[instrument+1] = bit.bor(instrum[instrument+1], bit.blshift(1, key-33))
301
        playNote(instrument, key-33)
302
      end --while chord
303
      transmitNote()
304
      instrum = {0, 0, 0, 0, 0}
305
    end --while song loop
306
    song = nil
307-
        instrum = {0, 0, 0, 0, 0}
307+
308-
        transmitNote()
308+
309
310
function transmitNote()
311
  if modem == nil then return end
312
  for instrument = 1, 5 do
313
    modem.transmit(instrument, 0, instrum[instrument])
314
  end --for instrument
315
end --transmitNote()
316
317
function playNote(type, key)
318
  if noteblock == nil then return end
319-
      if number == nil then
319+
320-
        while readNumber(song, 2) ~= 0 do --play chord
320+
321-
          instrument = readNumber(song, 1)
321+
322-
          key = readNumber(song, 1)
322+
323-
          instrum[instrument+1] = bit.bor(instrum[instrument+1], bit.blshift(1, key-33))
323+
324-
          playNote(instrument, key-33)
324+
325-
        end --while chord
325+
326-
        transmitNote()
326+
327-
        instrum = {0, 0, 0, 0, 0}
327+
328-
      elseif number == 0 then --stop
328+
329-
        instrum = {0, 0, 0, 0, 0}
329+
330-
        transmitNote()
330+
331-
      end --if number
331+
332
  event, side, x, y = os.pullEventRaw()
333-
    --song = nil FIXME why was this here? - removed
333+
334
  if updateNeeded then
335
    updateNeeded = false
336
    if screen == 1 then
337
      displayMusicList(page, playing)
338
    elseif screen == 2 then
339
      displayCurrentlyPlaying(playing, true)
340
    end --if screen
341
  end --if update
342
  
343
  if event == "terminate" then
344
    monitor.clear()
345
    return
346
  end --if terminate
347
348
  if event == "timer" then
349
    os.startTimer(100/tempo)
350
    if finished then
351
      finished = false
352
      if repeatMode == "single" then
353
        arg = playing
354
      elseif repeatMode == "off" then
355
        screen = 1
356
        playing = nil
357
        tempo = 100 -- slow timer
358
        refreshCounter = 0
359
      else
360
        nextSong()
361
    arg = playing
362
      end -- if repeatMode
363
    if screen == 1 then
364
        displayMusicList(page, playing)
365
      elseif screen == 2 then
366
        displayCurrentlyPlaying(playing, true)
367
      end --if screen
368
    end -- if finished
369
370
    if refreshCounter <= 0 then
371
      refreshCounter = 100
372
      if screen == 1 then
373
        displayMusicList(page, playing)
374
      elseif screen == 2 then
375
        displayCurrentlyPlaying(playing, true)
376
      end -- if screen
377
    end -- if refreshCounter
378
    refreshCounter = refreshCounter - 1
379
    if screen == 2 then
380
      displayCurrentlyPlaying(playing, false)
381
    end --if screen
382
    coroutine.resume(play, arg)
383
  end
384
  arg = nil
385
386
  if event == "monitor_touch" then
387
    if screen == 1 then
388
      if y > 1 and y < monitorHeight-1 then
389
        -- select song
390
        arg = (y-1)+(page-1)*(monitorHeight-3)
391
        playing = arg
392
        displayMusicList(page, playing)
393
      elseif y == monitorHeight-1 then
394
      if x <= 17 then
395
          -- currently playing
396
          if playing ~= 0 and playing ~= nil then
397
            screen = 2
398
            displayCurrentlyPlaying(playing, true)
399
          end --if playing
400
    elseif x >= monitorWidth-3 then
401
      nextSong()
402
      arg = playing
403
      displayMusicList(page, playing)
404
      coroutine.resume(play, arg)
405
    end --if monitorHeight-1
406-
    if not paused then
406+
407-
      if screen == 2 then
407+
408-
        displayCurrentlyPlaying(playing, false)
408+
409
          page = page - 1
410
          if page <= 0 then page = 1 end
411-
    elseif arg ~= nil then
411+
412
        elseif x > monitorWidth-4 then
413-
    end --if not paused
413+
414
            page = page + 1
415
          end --if page
416
          displayMusicList(page, playing)
417
        end --if x
418
      else --if y
419
        -- top line
420
        if x > monitorWidth-4 then
421
          arg = 0
422
          playing = arg
423
          displayMusicList(page, playing)
424
        elseif x > 16 and x <= monitorWidth-6 then
425-
        if x >= monitorWidth-9 and x < monitorWidth-4 then
425+
426-
          paused = not paused
426+
427-
          updateNeeded = true
427+
428
            repeatMode = "all ordered"
429
          elseif repeatMode == "all ordered" then
430-
          arg = playing
430+
431
          else -- if repeatMode == "all random"
432-
          coroutine.resume(play, arg)
432+
433-
        elseif x <= 17 then
433+
434
          displayMusicList(page, playing)
435
        end --if x
436
      end --if y
437
    elseif screen == 2 then
438
      if y == monitorHeight-1 then
439-
        end --if monitorHeight-1
439+
        if x <= 9 then
440
          -- song list
441
          screen = 1
442
          displayMusicList(page, playing)
443
        elseif x >= monitorWidth-3 then
444
          nextSong()
445
      arg = playing
446
      displayCurrentlyPlaying(playing, true)
447
      coroutine.resume(play, arg)
448
        end --if x
449
      elseif y == 1 then
450
        if x > monitorWidth-4 then
451
        -- stop
452
        arg = 0
453
        playing = arg
454
        screen = 1
455-
          -- stop
455+
456
        elseif x > 16 and x <= monitorWidth-6 then
457
          if repeatMode == "off" then
458
            repeatMode = "single"
459
          elseif repeatMode == "single" then
460
            repeatMode = "all ordered"
461
          elseif repeatMode == "all ordered" then
462
            repeatMode = "all random"
463
          else -- if repeatMode == "all random"
464
            repeatMode = "off"
465
          end -- if repeatMode
466
          displayCurrentlyPlaying(playing, true)
467
        end
468
      end --if y
469
    end --if screen == 1
470
  end --monitor touch
471
472
  if event == "peripheral" or event == "peripheral_detach" or event == "monitor_resize" then
473
    modem = peripheral.find("modem")
474
    monitor = peripheral.find("monitor")
475-
        if x >= monitorWidth-3 then
475+
476
      term.setTextColor(colors.red)
477-
          arg = playing
477+
478
      return
479-
          coroutine.resume(play, arg)
479+
480-
        elseif x >= monitorWidth-9 and x < monitorWidth-4 then
480+
481-
          paused = not paused
481+
482-
          updateNeeded = true
482+
483-
        elseif x <= 9 then
483+
484
    elseif screen == 2 then
485
      displayCurrentlyPlaying(playing, true)
486
    end
487
  end --if peripheral
488
  
489
  if event == "disk" then
490
    refreshMusicList()
491
    updateNeeded = true
492
  end --if disk
493
  
494
  if event == "disk_eject" then
495
    if playing ~= 0 and playing ~= nil then
496
      local length = #musicList
497
      local thisSong = musicList[playing]
498
      if string.sub(thisSong, 0, 4) == "disk" then
499
        if string.sub(thisSong, 5, 5) == "/" then
500
          if fs.isDir("disk") == false then
501
            arg = 0
502
          end --if isDir
503
        else
504
          if fs.isDir("disk"..string.sub(thisSong, 5, 5)) == false then
505
            arg = 0
506
          end --if isDir
507
        end --if "/"
508
      end --if playing disk
509
      
510
      refreshMusicList()
511
      updateNeeded = true
512
      
513
      if arg == 0 then
514
        playing = arg
515
        if screen == 2 then
516
          screen = 1
517
        end
518
        coroutine.resume(play, arg)
519
      end --if arg
520
    else --if playing
521
      refreshMusicList()
522
      updateNeeded = true
523
    end --if playing
524
  end -- if disk_eject
525
end --while coroutine