Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local noteblock = nil
- local notes = {
- { name = "F#", color = "black"};
- { name = "G", color = "white"}; { name = "G#", color = "black"};
- { name = "A", color = "white"}; { name = "Bb", color = "black"};
- { name = "B", color = "white"};
- { name = "C", color = "white"}; { name = "C#", color = "black"};
- { name = "D", color = "white"}; { name = "D#", color = "black"};
- { name = "E", color = "white"};
- { name = "F", color = "white"}; { name = "F#", color = "black"};
- { name = "G", color = "white"}; { name = "G#", color = "black"};
- { name = "A", color = "white"}; { name = "Bb", color = "black"};
- { name = "B", color = "white"};
- { name = "C", color = "white"}; { name = "C#", color = "black"};
- { name = "D", color = "white"}; { name = "D#", color = "black"};
- { name = "E", color = "white"};
- { name = "F", color = "white"}; { name = "F#", color = "black"};
- }
- local scales = {
- major = {2,2,1,2,2,2,1}, minor = {2,1,2,2,1,3,1}
- }
- local chordscale = { 2, 2, 3 }
- local voice = 0
- --Sets up the program
- for _,side in pairs(rs.getSides()) do
- if peripheral.getType(side) == "iron_note" then
- noteblock = peripheral.wrap(side)
- break
- end
- end
- if noteblock == nil then
- print("No noteblock block found")
- return
- end
- --Half debug half for fun, this shows which notes have just been played
- --on a virtual keyboard
- function drawKeyboard(pressed)
- local xoff = term.getSize()/2 - #notes/2
- local yoff = 5;
- --Drawing each key on the keyboard. This really only needs to be done
- --once, but lazy...
- term.setBackgroundColour(colours.black)
- term.clear()
- for i=1,#notes do
- term.setBackgroundColour(colours[notes[i].color])
- for y=1,2 do
- term.setCursorPos(xoff+i,yoff+y)
- term.write(" ")
- end
- term.setBackgroundColour(colours.white)
- for y=3,4 do
- term.setCursorPos(xoff+i,yoff+y)
- term.write(" ")
- end
- end
- term.setBackgroundColour(colours.black)
- term.setTextColour(colours.white)
- for i=1,#pressed do
- local ypos = yoff + 5
- if notes[pressed[i]+1].color == "black" then ypos = yoff end
- term.setCursorPos(xoff+pressed[i]+1,ypos)
- term.write(notes[pressed[i]+1].name)
- end
- end
- --This takes a tune, a sequence of notes, chords and intervals in a chosen
- --voice and plays them sequentially.
- function playTune(tune)
- drawKeyboard({})
- for i=1,#tune.seq do
- sleep(tune.int[i])
- if type(tune.seq[i]) == "number" then
- noteblock.playNote(tune.voice,tune.seq[i])
- drawKeyboard( {tune.seq[i]} )
- else for _,note in pairs(tune.seq[i]) do
- noteblock.playNote(tune.voice,note)
- drawKeyboard( tune.seq[i] )
- end end
- end
- end
- --This takes a list of tunse and plays them each simultaneously.
- function playTuneList(tuneList)
- drawKeyboard({})
- --We keep two lists; a list of progress in each track, and the length of
- --the next interval, or delay, before a note is played.
- local delayList,progList = {
- min = function(self)
- local minval = math.huge
- for _,v in ipairs(self) do
- if type(v) ~= "string" then
- minval = math.min(minval, v)
- end
- end
- if minval == math.huge then minval = 0 end
- return minval
- end
- }, {
- sum = function(self)
- local sumval = 0
- for _,v in ipairs(self) do
- sumval = sumval + v
- end
- return sumval
- end
- }
- local tuneSum = 0
- for i=1,#tuneList do
- progList[i] = 1
- delayList[i] = tuneList[i].int[progList[i]]
- tuneSum = tuneSum + #tuneList[i].seq
- end
- --So long we still have notes in any sequence to play, we keep going
- while progList:sum() <= tuneSum do
- local playedNotes = {}
- --First we see if there are any notes we can play. With the exception
- --of the first call, there should always be at least one. Remember
- --only 5 notes can play at once; we catch this and cut off the extra
- --in the order they're put in (so least important track last if you
- --anticipate overlap!)
- for i,v in ipairs(delayList) do
- if type(v) == "number" and v <= 0 then
- local tune = tuneList[i]
- if type(tune.seq[i]) == "number" then
- if #playedNotes <= 5 then
- noteblock.playNote(tune.voice, tune.seq[progList[i]])
- end
- table.insert(playedNotes, tune.seq[progList[i]])
- else for _,note in pairs(tune.seq[progList[i]]) do
- if #playedNotes <= 5 then
- noteblock.playNote(tune.voice,note)
- end
- table.insert(playedNotes, note)
- end end
- progList[i] = progList[i] + 1
- if progList[i] > #tuneList[i].seq then
- --Completed songs are marked as such
- delayList[i] = "complete"
- else
- --Incomplete songs have their delays updated
- delayList[i] = tuneList[i].int[progList[i]]
- end
- end
- end
- --We then draw our keyboard (cos it's fun)
- drawKeyboard(playedNotes)
- --And then find the smallest next delay, subtracting this from all
- --next delays.
- local minint = delayList:min()
- for i = 1,#delayList do
- if type(delayList[i]) == "number" then
- delayList[i] = delayList[i] - minint
- --Correcting for precision errors, half a tick is enough
- if delayList[i] < 1/40 then delayList[i] = 0 end
- end
- end
- --And capturing similar errors to stop less than tick updates...
- if minint <= 1/20 then minint = 1/20 + 1/40 end
- os.sleep(minint)
- end
- end
- --Constructs a chord, or three notes in a given scale of a pitch. The pitch
- --of the leading note is dropped by a perfect 3rd on the 1st inversion and
- --a 5th on the 2nd inversion (inversion is 0, 1 or 2). The scale will wrap
- --around the keyboard if there isn't enough space.
- function makeChord(pitch, scale, inversion, length)
- if not length then length = 3 end
- if not inversion then inversion = 0 end
- local chordprog,noteprog = 1, 1
- --We firstly invert the chord, by going back the number of steps we
- --need to to get to our starting pitch. As each scale step is different,
- --we have to do this incrementally for each pitch progression in our chord
- for i=1,inversion do
- chordprog = chordprog - 1
- if chordprog < 1 then chordprog = chordprog + 3 end
- for j=1,chordscale[chordprog] do
- noteprog = noteprog - 1
- if noteprog < 1 then noteprog = noteprog + 7 end
- pitch = pitch - scales[scale][noteprog]
- if pitch < 0 then pitch = pitch + 24 end
- end
- end
- local chord = { pitch }
- --We then increment the pitch by each chord step and add them to our tune
- for i=1,length-1 do
- for j=1,chordscale[chordprog] do
- pitch = pitch + scales[scale][noteprog]
- if pitch > 24 then pitch = pitch - 24 end
- noteprog = noteprog + 1
- if noteprog > 7 then noteprog = noteprog - 7 end
- end
- chordprog = chordprog + 1
- if chordprog > 3 then chordprog = chordprog - 3 end
- table.insert(chord, pitch)
- end
- --The final result is the notes form a complete melodic chord
- return{ voice = voice, seq = { chord }, int = { 0 } }
- end
- --Plays a simple arpeggio, or broken chord of user-defined length forward and
- --back. The arpeggio will wrap around the keyboard if there isn't enough space.
- function makeArpeggio(pitch,scale,length)
- if not length then length = 4 end
- local seq, int = { pitch }, { 0 }
- local chordprog,noteprog = 1, 1
- --Identical to forming a chord, we start by going up our chord scale...
- for i=1,length-1 do
- for j=1,chordscale[chordprog] do
- pitch = pitch + scales[scale][noteprog]
- if pitch > 24 then pitch = pitch - 24 end
- noteprog = noteprog + 1
- if noteprog > 7 then noteprog = noteprog - 7 end
- end
- chordprog = chordprog + 1
- if chordprog > 3 then chordprog = chordprog - 3 end
- table.insert(seq, pitch)
- table.insert(int, 0.3)
- end
- --...and then back down it again.
- for i=1,length-1 do
- chordprog = chordprog - 1
- if chordprog < 1 then chordprog = chordprog + 3 end
- for j=1,chordscale[chordprog] do
- noteprog = noteprog - 1
- if noteprog < 1 then noteprog = noteprog + 7 end
- pitch = pitch - scales[scale][noteprog]
- if pitch < 0 then pitch = pitch + 24 end
- end
- table.insert(seq, pitch)
- table.insert(int, 0.3)
- end
- return { voice = voice, seq = seq, int = int }
- end
- --This plays a musical scale in a given key, either major or minor harmonic.
- --It plays the scale forwards and backwards, and wrap if it runs out of room.
- function makeScale(pitch,scale)
- if not scales[scale] then return end
- local seq,int = { pitch }, { 0 }
- --We simply increase our pitch according to the scale progression one
- --step at a time to get each note in the scale
- for i=1,#scales[scale] do
- pitch = pitch + scales[scale][i]
- if pitch < 0 then pitch = pitch + 24 end
- table.insert(seq, pitch)
- table.insert(int, 0.3)
- end
- --And then decrease to go back down it again.
- for i=#scales[scale], 1, -1 do
- pitch = pitch - scales[scale][i]
- if pitch > 24 then pitch = pitch - 24 end
- table.insert(seq, pitch)
- table.insert(int, 0.3)
- end
- return { voice = voice, seq = seq, int = int }
- end
- --This function plays a chromatic scale, that is every key on the keyboard
- --very quickly. It is the simplest scale to program.
- function makeChromatic()
- local seq, int = { }, { }
- --Start at the bottom, and play each note until we reach the top.
- for pitch=0,24 do
- table.insert(seq, pitch)
- table.insert(int, 0.1)
- end
- return { voice = voice, seq = seq, int = int }
- end
- --This just makes a simple drum beat with a delay for the timer.
- function makeSimpleBeat(pitch, delay)
- local hpitch = pitch + 12
- seq = { pitch, hpitch, pitch, pitch, hpitch }
- int = { delay, delay, delay, delay/2, delay/2 }
- return { voice = voice, seq = seq, int = int }
- end
- --This constructs a tune made from a purely random selection of notes
- --and plays them back at a fixed interval. Works surprisingly great with
- --the snare drums
- function makePureRandom(count,delay)
- local seq, int = { }, { }
- for i=1,count do
- table.insert(seq, math.random(0,24))
- table.insert(int, delay)
- end
- return { voice = voice, seq = seq, int = int }
- end
- --This constructs a slightly more structured but still totally random tune.
- --It only plays notes on the scale of the key specified and works by randomly
- --incrementing or decrementing the pitch by that scale, with larger 'jumps'
- --being exponentially less likely to occur. It produces something more
- --consistent; perhaps a track I'd play to represent losing one's mind.
- function makeScaledRandom(pitch,scale,count,delay)
- if not delay then delay = 0.2 end
- local seq, int = { pitch }, { delay }
- local prog = 1
- for i=1,count do
- --This makes each additional jump exponentially less likely
- local jump = math.ceil(math.log(16/math.random(1,16))/math.log(2))
- if 1 == math.random(0,1) then
- for j=1,jump do
- prog = prog - 1
- if prog < 1 then prog = prog + 7 end
- pitch = pitch - scales[scale][prog]
- end
- else
- for j=1,jump do
- pitch = pitch + scales[scale][prog]
- prog = prog + 1
- if prog > 7 then prog = prog - 7 end
- end
- end
- if pitch < 0 then pitch = pitch + 24 end
- if pitch > 24 then pitch = pitch - 24 end
- table.insert(seq, pitch)
- table.insert(int, delay)
- end
- return { voice = voice, seq = seq, int = int }
- end
- voice = 1
- drums = makeSimpleBeat(6,0.9)
- voice = 2
- percussion = makePureRandom(50,0.15)
- voice = 4
- bass = makeScaledRandom(6,"major",25,0.3)
- voice = 0
- melody = makeScaledRandom(12,"major",50,0.15)
- playTune(makeChromatic())
- sleep(0.5)
- playTune(makeArpeggio(0,"major",7))
- sleep(0.5)
- playTune( makeScale(12, "minor") )
- sleep(0.5)
- playTuneList( { makeScale( 0, "major" ), makeScale (12, "major") } )
- sleep(0.5)
- playTune(drums)
- sleep(0.5)
- playTune(percussion)
- sleep(0.5)
- playTune(bass)
- sleep(0.5)
- playTune(melody)
- sleep(0.5)
- --The horrible noise machine!!!!!
- playTuneList( {melody, percussion, drums, bass} )
Advertisement
Add Comment
Please, Sign In to add comment