diff --git a/engine.lua b/engine.lua new file mode 100644 index 0000000..1a91c80 --- /dev/null +++ b/engine.lua @@ -0,0 +1,191 @@ +function klass() + local k={} + k.__index=k + function k:new(...) + local n={} + setmetatable(n,k) + n:init(...) + return n + end + return k +end + +song=klass() +function song:init() + self.frames={} + self.ix_to_frame={} + self.next_frame_start=0 +end +function song:add(len) + for i=0,len-1 do + self.ix_to_frame[self.next_frame_start+i]={#self.frames+1,i} + end + add(self.frames,{ + pattern:new({len=len}), + pattern:new({len=len}), + pattern:new({len=len}), + pattern:new({len=len}), + }) + self.next_frame_start+=len +end +function song:pattern(channel,offset) + offset = offset or -1 + channel &= 0xffff + offset &= 0xffff + assert(channel>=0 and channel<4, "channel must be [0,4)") + local n_frames_long=#self.frames + if offset<0 then + assert(offset>=-n_frames_long, "offset must not exceed -"..n_frames_long) + offset+=n_frames_long+1 + else + assert(offset>=0 and offset=0 and channel<4, "channel must be [0,4)") + local tup=self.ix_to_frame[offset] + assert(tup, "invalid offset for current length: "..offset) + local f,offset=unpack(tup) + self.frames[f][channel+1]:plot(offset, instant) +end + +function song:build( + free_patterns, + frame_a, + frame_z +) + local n_frames_long = #self.frames + if (not frame_z) frame_z = frame_a + n_frames_long + assert(frame_z-frame_a == n_frames_long, "wrong number of frames (must be "..frame_a.." to "..frame_a+n_frames_long..")") + + -- dump patterns and frames + mapped_patterns={} + function map_to_real_pattern(pat) + if (pat:silent()) return 0 | (1<<6) + + local key = pat:key() + mapped_patterns[key] = mapped_patterns[key] or {} + for other in all(mapped_patterns[key]) do + if (pat:eq(other)) return other.map_ix + end + assert(#free_patterns>0, "out of free patterns") + pat:map_to(deli(free_patterns,1)) + add(mapped_patterns[key],pat) + return pat.map_ix + end + + local fmaddr=0x3100+(frame_a)*4 + for frame=1,n_frames_long do + for i=1,4 do + poke(fmaddr+i-1,map_to_real_pattern(self.frames[frame][i])) + end + fmaddr+=1 + end +end + +pattern=klass() +function pattern:init(o) + speed = o.speed or 15 + len = o.len or 32 + noiz = o.noiz or 0 + buzz = o.buzz or 0 + detune = o.detune or 0 + reverb = o.reverb or 0 + dampen = o.dampen or 0 + editormode = true + + assert(speed >= 1 and speed <255, "speed must be [1,255)") + assert(len >= 1 and len < 33, "len must be [1,33)") + assert(noiz >= 0 and noiz < 2, "noiz must be [0,2)") + assert(buzz >= 0 and buzz < 2, "buzz must be [0,2)") + assert(detune >= 0 and detune < 3, "detune must be [0,3)") + assert(reverb >= 0 and reverb < 3, "reverb must be [0,3)") + assert(dampen >= 0 and dampen < 3, "dampen must be [0,3)") + + self.instants={} + self.len=len + -- https://pico-8.fandom.com/wiki/Memory#Music + self.speed=speed + self.pattern_flags=( + ( + tonum(editormode) | + noiz<<1 | + buzz<<2 + ) + + detune*8 + + reverb*24 + + dampen*72 + ) + + for i=0,self.len-1 do + self.instants[i]=0 + end +end +function pattern:plot(ix, iat) + assert(ix>=0 and ix= 0 and effect < 8, "effect must be [0,8)") + assert(volume >= 0 and volume < 8, "volume must be [0,8)") + assert(waveform >= 0 and waveform < 8, "waveform must be [0,8)") + assert(pitch >= 0 and pitch < 64, "pitch must be [0,64)") + + custom = custom + effect = effect & 0xffff + volume = volume & 0xffff + waveform = waveform & 0xffff + pitch = pitch & 0xffff + + -- not a method: handle the nil instant + -- https://pico-8.fandom.com/wiki/Memory#Music + return ( + (tonum(custom) << 15) | + (effect << 12) | + (volume << 9) | + (waveform << 6) | + (pitch) + ) +end \ No newline at end of file diff --git a/pulsar.p8 b/pulsar.p8 new file mode 100644 index 0000000..fa960bb --- /dev/null +++ b/pulsar.p8 @@ -0,0 +1,20 @@ +pico-8 cartridge // http://www.pico-8.com +version 42 +__lua__ +#include engine.lua +#include song.lua +__gfx__ +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +010f2000000500c05007055030421c03028030230351f022000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +010f20000000007040130400e0450a032230302f0302a035260220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +010f200000000000000e0401a04015045110322a03036030310352d02200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +010f200000000000000000015040210401c04518032310203d0203802534012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__music__ +00 00010203 + diff --git a/song.lua b/song.lua new file mode 100644 index 0000000..cf5df70 --- /dev/null +++ b/song.lua @@ -0,0 +1,37 @@ +function _init() + local bgm=build_bgm() + local free_patterns={} + for i=0,63 do add(free_patterns,i) end + + bgm:build(free_patterns,0) + + cstore(0x3100,0x3100,0x1200) + music(0) +end +function _update() +end +function _draw() + cls(0) + print("enjoy the music!") +end + +function build_bgm() + local bgm=song:new() + + bgm:add(32) + + local root=0 + for chan=0,7 do + local p=root+chan*7 + local start=0+chan*1 + local v=5-chan/3 + local c=chan%4 + bgm:plot(c,start+0,{v=v,p=p}) + bgm:plot(c,start+1,{v=v,p=p+12}) + bgm:plot(c,start+2,{v=v,p=p+7,e=5}) + bgm:plot(c,start+3,{v=v-1,p=p+3,e=2}) + -- bgm:plot(c,start+4,{v=v-1,p=p}) + end + + return bgm +end \ No newline at end of file