local mod_order= -1
local mod_pat= orderlist[mod_order]
local mod_row= 64
local mod_tick= 255
local pat_loopstart= 0
local pat_loopcount= -1
local row_ticks= 0
local tick_sample= 0
local mod_channels={}   
local n_bufs = 8
local next_row= -1
local prev_row= n_bufs-1
local buf_size = 10
local channel_id = 15
local sfx_id = 63
local sfx_spd = 2
local base_addr = 0xef0000

local bpm= 125
local ticks_per_line= 6
local samples_per_sec = 44100
local samples_per_tick = flr(samples_per_sec * 30 / (bpm*ticks_per_line*2))
local framecnt= 0
local totalbuf= 0

local semitone= { 
 [0]= 1.000000,
      0.943874,
      0.890899,
      0.840896,
      0.793701,
      0.749154,
      0.707107,
      0.667420,
      0.629961,
      0.594604,
      0.561231,
      0.529732,
      0.500000,
      0.471937,
      0.445449,
      0.420448 }

function chn_mix(chn, dstbuffer, mixbufpos, total) 
  local smp=  chn.sample
  if (smp) then
    local pos,vol,incr=  chn.pos, chn.vol, chn.delta

    if (incr <= 0) return

--    if (vol>0) then
      local len=  smp[1]
      local loop= smp[4]
      local data= smp[5] -- sample data
      local start= 0
      local mask= 0x00ff00ff

      while (total>0) do
        -- maximal bis zum ende des samples...
        local part= flr( (len-pos) / incr + 1)  -- TODO: warum runden?
        if (part>total) part=total
  --      if (part<0) part= total

        local part2= (part+1)>>1

        -- fill index buffer to fetch samples
        idxbuffer:mutate("i32")
        idxbuffer[0]= pos*256.0
        pos+=incr*part
        idxbuffer[part]= pos*256.0
        idxbuffer:lerp(0,part)

  --[[      
        -- nearest neighbor (no filtering)
        idxbuffer:shr(8, true, 0,0, part)
        tempbuffer1:mutate("i32")
        tempbuffer1:copy(0, true, 0,0, part2) -- clear buffer
        tempbuffer1:mutate("u8")
        data:take(idxbuffer, tempbuffer1, 0,0, 1, 1,2, part) -- fetch 8bit samples into 16bit buffer (write every 2nd byte)
        tempbuffer1:mutate("i16")
  ]]


        
        -- linear filtering
        -- copy fractional part for linear filtering
        idxbuffer:mutate("u8")
        filterbuffer:mutate("u8")
        filterbuffer:copy(idxbuffer, 0,0, 1, 4,2, part)
        idxbuffer:mutate("i32")
        idxbuffer:shr(8, true, 0,0, part) -- remove fractional part from sample indizes

        -- get 8bit:8bit neighb. sample pairs
        tempbuffer1:mutate("u8")
        data:take(idxbuffer, tempbuffer1, 0,0, 2, 1,2, part)

        -- move each 2nd sample to separate buffer
        tempbuffer2:mutate("i32")
        tempbuffer1:mutate("i32")
        tempbuffer2:copy(tempbuffer1, true, 0,0, part2)
        tempbuffer2:shr(8, true, 0,0, part2)
        tempbuffer2:band(mask, true, 0,0, part2)

        -- clear 2nd sample from 1st buffer
  --      tempbuffer1:copy(0, true, 0,1, 1, 0,2, part)
  --      mask|= (mask<<32)
        tempbuffer1:band(mask, true, 0,0, part2)

        tempbuffer1:mutate("i16")
        tempbuffer2:mutate("i16")

        -- temp1+= (temp2-temp1)*filter>>8
        tempbuffer2:sub(tempbuffer1, true, 0,0, part)
        tempbuffer2:mul(filterbuffer, true, 0,0, part)
        tempbuffer2:shr(8, true, 0,0, part)
        tempbuffer1:add(tempbuffer2, true, 0,0, part)
        -- end of linear filtering
        

        -- adjust volume
        tempbuffer1:sub(128, true, 0,0, part)  -- make signed (expected by wavetable)
        tempbuffer1:mul(vol, true, 0,0, part)  -- * volume (0..63)
    --    tempbuffer1:shr(6, true, 0,0, part)

  --      mixbuffer:add(tempbuffer1, true, 0,mixbufpos, part)
        dstbuffer:copy(tempbuffer1, true, 0,mixbufpos, part)
        mixbufpos+=part


  --[[      
        for i=start,start+part-1 do
          local v1,v2= ord(data, pos, 2)
          local v= v1 + (v2-v1)*(pos & 0x0.ffff)
          mixbuffer[i] += (v-128)*vol
          pos+= incr
        end
  ]]

        total-=part
  --      start+=part

        if (pos>=len) then
          if (loop>0) then
            pos-=loop
          else
            pos,total= len-1,0   
            chn.delta= 0
          end
        end
      end

      chn.pos= pos
--    else
      -- volume was <=0
      -- but still update sample position
--      chn.pos += incr*total
--    end
  end
end

function hexstr(v, digits)
  v= v or 0
  digits= digits or 2

  local text=""
  local hex="0123456789ABCDEF "
    for l=(digits-1)*4,0,-4 do
      local x= v>>l&15
      text= text..sub(hex, x+1, true)
    end
  return text
end

function decstr(v, digits)
  v= v or 0
  digits= digits or 2

  local text=""
  local hex="0123456789 "
    for l=1,digits do
      local d= v
      v= v\10
      text= sub(hex, flr(d-v*10)+1, true)..text
    end
  return text
end

function update_row()
  local brk= -1
  local brk_pat= 0
  local rowidx= mod_row<<2
--  local text= decstr(mod_pat).."."..decstr(mod_row, 2)..": "

  row_ticks= ticks_per_line

  for c=0,3 do
    local chn= mod_channels[c]

--    local b1,b2,b3,b4= patterns:get(rowidx+c*4, mod_pat, 4)
--    local noteData= (b4<<24)|(b3<<16)|(b2<<8)|b1
    local noteData= patterns:get(rowidx+c, mod_pat, 1)
    local smpnr=    noteData >> 26 & 0x1f --  6
    local period=   noteData >> 16 & 1023 -- 10
    local fx=       noteData & 0xfff      -- 12

      if (smpnr>0) then
        local smp= samples[smpnr]
        chn.sample= smp
        chn.vol=  smp[2] -- 0..63
      end

    if (period > 0) then
     
      if (chn.sample) period -= chn.sample[3]  

      chn.pos= 0
      chn.period= period
      chn.base= period
      chn.delta= 3546895.0 / (samples_per_sec * period)
    end
--[[
    text= text..
          decstr(smpnr,2).." "..
          decstr(period,3).." "..
          hexstr(fx,3).."   "
]]
    chn.retrigger= 0
    chn.retrigger_count= 0
    chn.volslide= 0
    chn.porta= 0
    chn.notecut= 255  -- never
    chn.arpeggio= nil

    if (fx != 0) then
      local prev_period= chn.period

      local cmd= fx>>8&15

      if (cmd==0xc) then      -- volume (used the most: 1st here)
        chn.vol= (fx & 255)

      elseif (cmd==0xa) then  -- volume slide  Ax0 = up, A0y = down
        if ((fx&0x00f)==0) then -- Axy  y=0: slide up
          chn.volslide= (fx>>4&15)
        elseif ((fx&0x0f0)==0) then -- Axy  x=0: slide down
          chn.volslide= -(fx&15)
        end

      elseif (cmd==0x1) then  -- portamento up
        chn.porta= -(fx & 255)

      elseif (cmd==0x2) then  -- portamento down
        chn.porta= (fx & 255)
        
      elseif (cmd==0x9) then  -- offset
        chn.pos= (fx & 255)<<8 -- 16bit!

      elseif (cmd==0xd) then  -- pattern break
        brk= (fx>>4&15)*10 + (fx&15) 
        brk_pat= 1

      elseif (cmd==0x0) then -- arpeggio  0xy
        local p= chn.base
        chn.arpeggio= { [0]= p, flr(p * semitone[fx>>4&15]), flr(p * semitone[fx&15]) }

      elseif (cmd==0xe) then  -- extra commands
        local subcmd= fx>>4&15
        local param= fx&15
        if (subcmd==0xa) then -- volume slide up
          if (chn.vol < 64) chn.vol+=param
        elseif (subcmd==0xb) then -- volume slide down
          if (chn.vol > 0) chn.vol-=param
        elseif (subcmd==0x1) then -- portamento up
          if (chn.period > 113) chn.period-=param
        elseif (subcmd==0x2) then -- portamento down
          if (chn.period < 856) chn.period+=param
        elseif (subcmd==0x9) then -- retrigger
          chn.retrigger= (fx&15)
          chn.retrigger_count= 0
        elseif (subcmd==0xc) then -- note cut
          chn.notecut= (fx&15)
        elseif (subcmd==0xe) then -- pattern delay
          row_ticks= ticks_per_line*(param+1)
        elseif (subcmd==0x6) then -- loop pattern
          if (param==0) then
            pat_loopstart= mod_row
          else
            if (pat_loopcount < 0) pat_loopcount= param

            if (pat_loopcount>0) then
              brk= pat_loopstart
              brk_pat= 0
            end
            pat_loopcount-=1
          end
        end

      elseif (cmd==0xf) then  -- tempo
        local tempo= fx & 255
        if (tempo<32) then
          ticks_per_line= tempo
          row_ticks= ticks_per_line
        else 
          bpm= tempo
        end
        local rows_per_second= bpm*6*4/60
        samples_per_tick = flr(samples_per_sec / rows_per_second)
      end

      if (chn.period != prev_period) chn.delta= 3546895.0 / (samples_per_sec * period)
    end


  end


  if (brk>=0) then
    -- break to next pattern
    if (brk_pat>0) then
      mod_order+=brk_pat
      mod_pat= orderlist[mod_order]
    end
    -- ...or loop to row
	  mod_row= brk-1
  end

--  print(text)
    
end

function tick_update(count)
  tick_sample+= count
  if (tick_sample >= samples_per_tick) then
    tick_sample-=samples_per_tick
    mod_tick+=1

    if (mod_tick>=row_ticks) then
      mod_tick= 0
      mod_row+=1
      if (mod_row>=64) then
        mod_row=0
        mod_order+=1
        if (mod_order>=#orderlist) mod_order=#orderlist-1
        mod_pat=orderlist[mod_order]
      end
      update_row()
    end

    -- check channels for pending effects
    if (mod_tick>0) then
      for i=0,3 do
        local chn= mod_channels[i]

        if (chn.arpeggio != nil) then
          chn.period= chn.arpeggio[mod_tick%3]
          chn.delta= 3546895.0 / (samples_per_sec * chn.period)
        end

        if (chn.porta!=0) then
          chn.period+= chn.porta
          if (chn.period<113) chn.period=113
          if (chn.period>856) chn.period=856
          chn.delta= 3546895.0 / (samples_per_sec * chn.period)
        end

        if (chn.volslide!=0) then
          chn.vol+= chn.volslide
          if (chn.vol<0) chn.vol=0
          if (chn.vol>64) chn.vol=64
        end

        if (chn.retrigger>0) then
          chn.retrigger_count+=1
          if (chn.retrigger_count>=chn.retrigger) then
            chn.retrigger_count= 0
            chn.pos= 0
          end
        end

        if (mod_tick > chn.notecut) then
          chn.vol= 0
        end
      end
    end

  end
end

--local phase= 0
--local delta= 0.01

function update_mixer(total)
  -- clear mixbuffer
--  mixbuffer:mutate("i64")
--  mixbuffer:copy(0, true, 0,0, (total+3)>>2)
--  mixbuffer:mutate("i16")

    for j=0,3 do 
      chnbuffer[j]:copy(0,true, 0,0, total)
    end

  local pos= 0
  while (total > 0) do
    local rest= samples_per_tick - tick_sample
    if (rest > total) rest= total
    total-=rest

    for j=0,3 do 
--      chnbuffer[j]:copy(0,true, 0,0, total)
      chn_mix(mod_channels[j], chnbuffer[j], pos, rest) 
    end

    pos+= rest
    tick_update(rest)
  end

  mixbuffer:mutate("i16")
  mixbuffer:copy(chnbuffer[0], true, 0,0, pos)
  mixbuffer:add( chnbuffer[1], true, 0,0, pos)
  mixbuffer:add( chnbuffer[2], true, 0,0, pos)
  mixbuffer:add( chnbuffer[3], true, 0,0, pos)

  mixbuffer:shr(7, true, 0,0, pos)
  mixbuffer:min(127, true, 0,0, pos)
  mixbuffer:max(-127, true, 0,0, pos)
  mixbuffer:mutate("u8")
  mixbuffer:copy(mixbuffer, true, 0,0, 1, 2,1, pos)

  
end



-- TODO: this wav driver setup was copied luchak, must give proper credits.

function _pcm_setup_sfx(sfx_id, sfx_spd, first_instrument, n_bufs)
    local base = 0x50000 + 328*sfx_id
    poke2(base, 64)                              -- track length
    poke(base + 2, sfx_spd)                      -- spd
    poke(base + 3, 0)                            -- loop to 0
    poke(base + 4, n_bufs)                       -- loop len n_bufs
    poke(base + 5, 0)
    poke(base + 6, 0)
    for i=0,n_bufs-1 do
        poke(base + 8 + i,0)                      -- note
        poke(base + 8 + 64 + i, first_instrument + i)  -- instrument
        poke(base + 8 + 128 + i, 64)                 -- volume
    end
end

function _pcm_make_stream_instrument(instrument_id, wave_addr, wave_size)
    -- instr comments here mostly copied from sfx.p64/data.lua
    poke(0x40000 + (instrument_id * 512) + 0x1df, 2) -- wide

    -- node 0: root
    poke(0x40000 + (instrument_id * 512) + (0 * 32), -- instrument 63, node 0
        0,    -- parent (0x7)  op (0xf0)
        1,    -- kind (0x0f): 1 root  kind_p (0xf0): 0  -- wavetable_index
        0,    -- flags
        0,    -- unused extra

        -- MVALs:  kind/flags,  val0, val1, envelope_index

        0x0,0x40,0,0,  -- volume: mult. 0x40 is max (-0x40 to invert, 0x7f to overamp)
        0x1,0,0,0,     -- pan:   add. center
        0x1,0,0,0,     -- tune: +0 -- 0,48,0,0 absolute for middle c (c4) 261.6 Hz
        0x1,0,0,0,     -- bend: none
        -- following shouldn't be in root
        0x0,0,0,0,     -- wave: use wave 0
        0x0,0,0,0      -- phase
    )

    -- node 1: custom wt 
    poke(0x40000 + (instrument_id * 512) + (1 * 32), -- instrument 63, node 1
        0,    -- parent (0x7)  op (0xf0)
        0x20|2,    -- kind (0x0f): 2 osc  kind_p (0xf0): 2  -- wavetable_index
        0x0,    -- flags
        0,    -- unused extra

        -- MVALs:  kind/flags,  val0, val1, envelope_index

        0x0,0x40,0,0,  -- volume: mult. 0x40 is max (-0x40 to invert, 0x7f to overamp)
        0x0,0,0,0,     -- pan:   center
        0x0,77,0,0x20|0x80,    -- tune: +0 -- 0,5,0,0 absolute for f0
                       -- tune is quantized to semitones with 0x20
        0x0,50,0,0x20|0x80,   -- can fine-tune here but unsure of exact setting
        0x0,0,0,0,     -- wave: 0
        0x0,-128,0,0      -- phase
    )

    -- wavetable 2 (node 1)
    poke(0x40000 + 0x200*instrument_id + 0x1e0 + 4*2,
        (wave_addr >> 8) & 255, -- address (low)  in 256 byte increments
        (wave_addr >> 16) & 255, -- address (high) in 64k increments
        wave_size, -- samples (1 << n)
        0x1   -- wt_height 256(0); wave mval points at one of the entries
    )

    memset(wave_addr, 1 << wave_size, 0)
end


function msx_init()
  local bufsize= 2048*2
  chnbuffer= {}
  mixbuffer= userdata("i16", bufsize)
  idxbuffer= userdata("i32", bufsize)
  tempbuffer1= userdata("i16", bufsize)
  tempbuffer2= userdata("i16", bufsize)
  filterbuffer= userdata("i16", bufsize)

  for i=0,3 do
    chnbuffer[i]= userdata("i16", bufsize)

    mod_channels[i]= { 
      period=0,
      pos=0, 
      delta=0, 
      vol=0, 
      volslide= 0,-- volume slide
      porta= 0,   -- pitch slide
      retrigger=0, 
      retrigger_count= 0,
      notecut= 255,
      sample=nil 
    }
  end

  -- fetch(
	patterns = unpod( fetch(stat(151).."patterns.pod") )
  if (not patterns) then
    print("patterns.pod not found!")
  end
  patterns:mutate("i32") -- 4 bytes = one note entry in the pattern data

  for i=1,31 do
    local smp= samples[i]
-- samples[i]= { 14270, 64, 0, 14120 }  
-- [1]= length, [2]= volume, [3]= finetune, [4]= looplen
--    if (smp[3]!=0) smp[3]= finetune[smp[3]]
    local filename= "sample_"..i..".pod"
  	local temp= unpod( fetch(stat(151)..filename) )
  	smp[5]= temp
  end

  local buf_len = 1<<buf_size

  local first_instrument=64-n_bufs

  for i=0,n_bufs-1 do
    local buf_addr= base_addr + i*buf_len

    _pcm_make_stream_instrument(first_instrument+i, buf_addr, buf_size)
  end

  _pcm_setup_sfx(sfx_id, sfx_spd, first_instrument, n_bufs)
end

function msx_deinit()
  sfx(-1, channel_id)
end

function msx_fillblock()
end

function msx_update()
  local count= 0

  if (next_row<0) then
    sfx(sfx_id, channel_id)
    next_row= 0
  end

  local row = stat(400+channel_id, 9)
--  if (row<prev_row) totalbuf+=n_bufs
--  prev_row= row

--  local tick_len = stat(400+channel_id, 21)
  local chunk_size = samples_per_sec/60 --sfx_spd*tick_len

  memmap(base_addr+0x8000, mixbuffer)

  while next_row ~= row do
      update_mixer( chunk_size )

      local prev_row = (next_row + n_bufs - 1) & (n_bufs-1)

      local prev_base = base_addr + (prev_row<<buf_size)
      local base      = base_addr + (next_row<<buf_size)

      memcpy(prev_base + chunk_size, base_addr+0x8000, chunk_size)
      memcpy(base,                   base_addr+0x8000, chunk_size)
      next_row = (next_row + 1) & (n_bufs-1)
      count+=1
  end

  unmap(base_addr+0x8000)

  return count
end

