diff --git a/engine.lua b/engine.lua deleted file mode 100644 index 1a91c80..0000000 --- a/engine.lua +++ /dev/null @@ -1,191 +0,0 @@ -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 index fa960bb..b5a69fe 100644 --- a/pulsar.p8 +++ b/pulsar.p8 @@ -1,8 +1,12 @@ pico-8 cartridge // http://www.pico-8.com version 42 __lua__ -#include engine.lua -#include song.lua +#include shared/_meta.lua +#include shared/nflags.lua +#include shared/pattern.lua +#include shared/pflags.lua +#include shared/track.lua +#include unique.lua __gfx__ 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 diff --git a/shared/_meta.lua b/shared/_meta.lua new file mode 100644 index 0000000..f8e3881 --- /dev/null +++ b/shared/_meta.lua @@ -0,0 +1,15 @@ +function klass() + local k={} + k.__index=k + function k:new(...) + local n={} + setmetatable(n,k) + n:init(...) + return n + end + return k +end + +function assert_range(i,mn,mx,name) + assert(i >= mn and i < mx, name.." must be ["..mn..","..mx..")") +end \ No newline at end of file diff --git a/shared/nflags.lua b/shared/nflags.lua new file mode 100644 index 0000000..588cd7b --- /dev/null +++ b/shared/nflags.lua @@ -0,0 +1,41 @@ +nflags=klass() +function nflags:init(o) + self.c = o.c or 0 -- custom + self.e = o.e or 0 -- effect + self.v = o.v or 0 -- volume + self.w = o.w or 0 -- waveform + self.p = o.p or 0 -- pitch + + self:validate() +end + +function nflags:validate() + local c,e,v,w,p + =self.c,self.e,self.v,self.w,self.p + + assert_range(c,0,2,"custom") + assert_range(e,0,8,"effect") + assert_range(v,0,8,"volume") + assert_range(w,0,8,"waveform") + assert_range(p,0,64,"pitch") +end + +function nflags:encode() + self:validate() + local c,e,v,w,p + =self.c,self.e,self.v,self.w,self.p + + c &= 0xffff + e &= 0xffff + v &= 0xffff + w &= 0xffff + p &= 0xffff + + return ( + (c << 15) | + (e << 12) | + (v << 9) | + (w << 6) | + (p) + ) +end \ No newline at end of file diff --git a/shared/pattern.lua b/shared/pattern.lua new file mode 100644 index 0000000..fd11841 --- /dev/null +++ b/shared/pattern.lua @@ -0,0 +1,45 @@ + +pattern=klass() +function pattern:init(p) + self.pflags=pflags:new(p) + + self.nflags={} + for i=0,31 do -- because the user could change the length + self.nflags[i]=nflags:new{} + end +end +function pattern:plot(ix, n) + assert_range(ix,0,self.pflags.len,"ix") + self.nflags[ix]=nflags:new(n) +end +function pattern:silent() + for i=0,31 do + if (self.nflags[i].v!=0) return + end + return true +end +function pattern:key() + local key=0 + for i=0,self.pflags.len-1 do + key ^= self.nflags[i]:encode()>>(i%16) + end + return key +end +function pattern:eq(other) + if (self.pflags:encode() != other.pflags:encode()) return + + for i=0,self.pflags.len-1 do + if (self.nflags[i]:encode()!= other.nflags[i]:encode()) return + end + return true +end +function pattern:map_to(ix) + self.map_ix=ix + + local at=0x3200+ix*68 + poke4(at+64,self.pflags:encode()) + + for i=0,31 do + poke2(at+i*2,self.nflags[i]:encode()) + end +end \ No newline at end of file diff --git a/shared/pflags.lua b/shared/pflags.lua new file mode 100644 index 0000000..147cd1c --- /dev/null +++ b/shared/pflags.lua @@ -0,0 +1,53 @@ +pflags=klass() +function pflags:init(o) + self.speed = o.speed or 15 + self.len = o.len or 32 + self.noiz = o.noiz or 0 + self.buzz = o.buzz or 0 + self.detune = o.detune or 0 + self.reverb = o.reverb or 0 + self.dampen = o.dampen or 0 + self.editormode = o.editormode or 1 +end + +function pflags:validate() + local speed,len,noiz,buzz,detune,reverb,dampen,editormode + =self.speed,self.len,self.noiz,self.buzz, + self.detune,self.reverb,self.dampen,self.editormode + + assert_range(speed,1,255,"speed") + assert_range(len,1,33,"len") + assert_range(noiz,0,2,"noiz") + assert_range(buzz,0,2,"buzz") + assert_range(detune,0,3,"detune") + assert_range(reverb,0,3,"reverb") + assert_range(dampen,0,3,"dampen") + assert_range(editormode,0,2,"editormode") +end + +function pflags:encode() + local speed,len,noiz,buzz,detune,reverb,dampen,editormode + =self.speed,self.len,self.noiz,self.buzz, + self.detune,self.reverb,self.dampen,self.editormode + + speed &= 0xffff + len &= 0xffff + noiz &= 0xffff + buzz &= 0xffff + detune &= 0xffff + reverb &= 0xffff + dampen &= 0xffff + editormode &= 0xffff + + self:validate() + local byte0 = ( + dampen*72 + + reverb*24 + + detune*8 + + buzz*4 + + noiz*2 + + editormode -- "in editor mode" flag + ) + return len | speed>>8 | byte0 >>16 +end + diff --git a/shared/track.lua b/shared/track.lua new file mode 100644 index 0000000..a7a0407 --- /dev/null +++ b/shared/track.lua @@ -0,0 +1,78 @@ +track=klass() +function track:init() + self.frames={} + self.ix_to_frame={} + self.next_frame_start=0 +end +function track:add(len) + for i=0,len-1 do + self.ix_to_frame[self.next_frame_start+i]={#self.frames,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 track:pattern(channel,offset) + offset = offset or -1 + channel &= 0xffff + offset &= 0xffff + + assert_range(channel,0,4,"channel") + local n_frames_long=#self.frames + assert_range(offset,-n_frames_long,n_frames_long,"offset") + if offset<0 then + offset+=n_frames_long + end + return self.frames[offset+1][channel+1] +end + +function track:plot(channel,offset,inst) + assert_range(channel,0,4,"channel") + assert_range(offset,0,self.next_frame_start,"offset") + local tup=self.ix_to_frame[offset] + assert(tup) -- should be unable to fail + local frame,offset=unpack(tup) + self.frames[frame+1][channel+1]:plot(offset,inst) +end + +function track: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 + local mapped_patterns={} + local function map_to_real_pattern(pat) + if (pat:silent()) return 0 | (1<<6) + + local key = pat:key() + mapped_patterns[key] = mapped_patterns[key] or {} + local t = mapped_patterns[key] + for other in all(t) 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(t,pat) + return pat.map_ix + end + + local fmaddr=0x3100+(frame_a)*4 + for frame=1,n_frames_long do + for i=0,3 do + poke(fmaddr+i,map_to_real_pattern(self.frames[frame][i+1])) + end + fmaddr+=1 + end +end \ No newline at end of file diff --git a/song.lua b/unique.lua similarity index 95% rename from song.lua rename to unique.lua index cf5df70..34b55a7 100644 --- a/song.lua +++ b/unique.lua @@ -16,7 +16,7 @@ function _draw() end function build_bgm() - local bgm=song:new() + local bgm=track:new() bgm:add(32)