From e5ead7c8f3a5bfd9e0245156c5f6a2fff6e01282 Mon Sep 17 00:00:00 2001 From: g012 Date: Wed, 11 Oct 2017 02:30:23 +0200 Subject: [PATCH] Fixed ttt loader. Added vcs_music.l65 sample. --- README.md | 2 +- l65.lua | 12 +- samples/Ishtar.ttt | 6 +- samples/vcs_music.l65 | 310 ++++++++++++++++++++++++++++++++++++++++++ vcs.l65 | 45 +++--- 5 files changed, 345 insertions(+), 30 deletions(-) create mode 100644 samples/vcs_music.l65 diff --git a/README.md b/README.md index f9d20bd..7e4071c 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Have a look at these files in the `samples` folder to get started with l65: * `vcs_banks.l65`: demonstrates 2 methods to call functions across banks. * `vcs_flush.l65`: a port of flewww's famous flush logo from the demo [.bin](http://www.pouet.net/prod.php?which=69666) * `vcs_spr48.l65`: a port of [.bin](http://www.pouet.net/prod.php?which=69666) title 48 pixels sprite animation. + * `vcs_music.l65`: imports and plays back a TIATracker .ttt file directly. There's also `vcspal.act`, a palette file for authoring software for VCS. Use this palette or a similar one to create 8b PNG for `l65.image` and helper loaders depending on it. You can generate such a palette, or a GPL one for GIMP using [vcsconv](https://github.com/g012/vcsconv) `authpalette` command. @@ -617,7 +618,6 @@ Note that the syntax file includes some highlighting for features only activated * [k65](http://devkk.net/wiki/index.php?title=K65) style syntax * helpers to inter-operate with cc/ca65 and dasm - * import TIATracker \*.ttt files directly ## Credits diff --git a/l65.lua b/l65.lua index 841c268..6b55f3a 100644 --- a/l65.lua +++ b/l65.lua @@ -128,7 +128,7 @@ local opcode_absolute_x = lookupify{ } local opcode_absolute_y = lookupify{ 'adc', 'and', 'cmp', 'eor', 'lda', 'ldx', 'ora', 'sbc', - 'sta', 'stx', + 'sta', -- illegal opcodes 'dcp', 'isb', 'jam', 'las', 'lax', 'rla', 'rra', 'sax', 'sha', 'shx', 'slo', 'shs', 'sre', @@ -1618,6 +1618,7 @@ local function ParseLua(src, src_name) else tok:Commit() if not tok:ConsumeSymbol(',', tokenList) then + if not opcode_absolute[op] and not opcode_zeropage[op] then return false, expr end suffix = suffix=='b' and "zpg" or suffix=='w' and "abs" or "zab" if suffix == 'zab' then if not opcode_zeropage[op] then suffix='abs' @@ -1628,6 +1629,7 @@ local function ParseLua(src, src_name) stat = emit_call{name=op .. suffix, args={expr}, inverse_encapsulate=inverse_encapsulate} break end if tok:Peek().Data == 'x' then + if not opcode_absolute_x[op] and not opcode_zeropage_x[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end @@ -1642,7 +1644,7 @@ local function ParseLua(src, src_name) stat = emit_call{name=op .. suffix, args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end if tok:Peek().Data == 'y' then - if not opcode_absolute_y[op] then return false, expr end + if not opcode_absolute_y[op] and not opcode_zeropage_y[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end @@ -1660,6 +1662,7 @@ local function ParseLua(src, src_name) local mod_st, mod_expr = ParseExpr(scope) if not mod_st then return false, mod_expr end if not tok:ConsumeSymbol(',', tokenList) then + if not opcode_absolute[op] and not opcode_zeropage[op] then return false, expr end suffix = suffix=='b' and "zpg" or suffix=='w' and "abs" or "zab" if suffix == 'zab' then if not opcode_zeropage[op] then suffix='abs' @@ -1670,6 +1673,7 @@ local function ParseLua(src, src_name) stat = emit_call{name=op .. suffix, args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate} break end if tok:Peek().Data == 'x' then + if not opcode_absolute_x[op] and not opcode_zeropage_x[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end @@ -1684,7 +1688,7 @@ local function ParseLua(src, src_name) stat = emit_call{name=op .. suffix, args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break end if tok:Peek().Data == 'y' then - if not opcode_absolute_y[op] then return false, expr end + if not opcode_absolute_y[op] and not opcode_zeropage_y[op] then return false, expr end tok:Get(tokenList) local paren_close_whites = {} for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end @@ -2546,7 +2550,7 @@ l65_def = { if not l65 then l65 = l65_def else for k,v in pairs(l65_def) do l65[k]=v end end l65.report = function(success, ...) if success then return success,... end - local message=... io.stderr:write(message..'\n') + local message=... io.stderr:write(tostring(message)..'\n') os.exit(-1) end l65.msghandler = function(msg) diff --git a/samples/Ishtar.ttt b/samples/Ishtar.ttt index bdfc8de..fa8c99c 100644 --- a/samples/Ishtar.ttt +++ b/samples/Ishtar.ttt @@ -449,9 +449,9 @@ "waveform": 16 } ], - "metaAuthor": "glafouk", - "metaComment": "", - "metaName": "ishtar", + "metaAuthor": "Glafouk", + "metaComment": "Crunchy tune taken from\n\nFlush VCS Music Cart Vol.1\n\nhttps://www.pouet.net/prod.php?which=71561\n\nBig up to Exocet,Flewww,g012 & p0ke for making this crazy project alive...\nAnd special danke to Kylearan for his marvelous TIAtracker !", + "metaName": "Ishtar", "oddspeed": 6, "patterns": [ { diff --git a/samples/vcs_music.l65 b/samples/vcs_music.l65 new file mode 100644 index 0000000..58be50e --- /dev/null +++ b/samples/vcs_music.l65 @@ -0,0 +1,310 @@ +require'vcs' +mappers['2K']() + +local AUDC0s = 0x90 +local AUDC1s, AUDF0s, AUDF1s, AUDV0s, AUDV1s = AUDC0s+1, AUDC0s+2, AUDC0s+3, AUDC0s+4, AUDC0s+5 +local vubars = 0xA0 +local tmp = 0xF0 + +local zic_filename = 'Ishtar.ttt' +local zic = ttt(zic_filename) +print(string.format('Using file %s (ttt version %d)\n Name: %s\n Author: %s\n%s', zic_filename, zic.version, zic.name, zic.author, zic.comment)) + +-- debug printing of zic data +--[[ +local printbytetable = function(v) + local s = ' ' + for kk,vv in ipairs(v) do + s = s .. string.format('%02x, ', vv) + if #s >= 34 then print(s) s = ' ' end + end + if #s > 0 then print(s) end +end +for k,v in pairs(zic) do + if type(v) ~= 'table' then + print(k,v) + elseif type(v[1]) == 'number' then + print(k) printbytetable(v) + elseif type(v[1]) == 'table' then + for ek, e in ipairs(v) do + print(k, ek, e.name) printbytetable(e) + end + end +end +]] + +@@tt_InsCtrlTable byte(zic.insctrltable) +@@tt_InsADIndexes byte(zic.insadindexes) +@@tt_InsSustainIndexes byte(zic.inssustainindexes) +@@tt_InsReleaseIndexes byte(zic.insreleaseindexes) +@@tt_InsFreqVolTable byte(zic.insfreqvoltable) +@@tt_PercIndexes byte(zic.percindexes) +@@tt_PercFreqTable byte(zic.percfreqtable) +@@tt_PercCtrlVolTable byte(zic.percctrlvoltable) +local patterns = {} +for i,pattern in ipairs(zic.patterns) do + table.insert(patterns, section("pattern"..i)) byte(pattern) +end +@@tt_PatternSpeeds byte(zic.patternspeeds) +@@tt_PatternPtrLo byte_lo(patterns) +@@tt_PatternPtrHi byte_hi(patterns) +@@tt_SequenceTable byte(zic.sequence[1]) byte(zic.sequence[2]) + +local tt = { +-- ===================================================================== +-- Permanent variables. These are states needed by the player. +-- ===================================================================== + 'timer', -- current music timer value + 'cur_pat_index_c0', -- current pattern index into tt_SequenceTable + 'cur_pat_index_c1', + 'cur_note_index_c0', -- note index into current pattern + 'cur_note_index_c1', + 'envelope_index_c0', -- index into ADSR envelope + 'envelope_index_c1', + 'cur_ins_c0', -- current instrument + 'cur_ins_c1', +-- ===================================================================== +-- Temporary variables. These will be overwritten during a call to the +-- player routine, but can be used between calls for other things. +-- ===================================================================== + 'ptr', -- 2 bytes +} +for k,v in ipairs(tt) do tt[v] = k + 0x7F end + +tt.FREQ_MASK = 0b00011111 +tt.INS_HOLD = 8 +tt.INS_PAUSE = 16 +tt.FIRST_PERC = 17 + +tt.fetchCurrentNoteImpl = function() +@_constructPatPtr + ldy tt.cur_pat_index_c0,x lda tt_SequenceTable,y + if zic.usegoto then + bpl _noPatternGoto + ;and #0b01111111 sta tt.cur_pat_index_c0,x bpl _constructPatPtr +@_noPatternGoto + end + tay lda tt_PatternPtrLo,y sta tt.ptr lda tt_PatternPtrHi,y sta tt.ptr+1 + if zic.overlay then + clv + lda tt.cur_note_index_c0,x bpl _notPrefetched + ;and #0b01111111 sta tt.cur_note_index_c0,x + bit tt_Bit6Set +@_notPrefetched + tay + else + ldy tt.cur_note_index_c0,x + end + lda (tt.ptr),y bne _noEndOfPattern + sta tt.cur_note_index_c0,x + inc tt.cur_pat_index_c0,x + bne _constructPatPtr +@_noEndOfPattern +end +if zic.useoverlay then + @@tt_fetchCurrentNoteSection tt.fetchCurrentNoteImpl() rts + tt.fetchCurrentNote = function() jsr tt_fetchCurrentNoteSection end +else + tt.fetchCurrentNote = tt.fetchCurrentNoteImpl +end + +@@tt_CalcInsIndex + lsr lsr lsr lsr lsr + tay +@tt_Bit6Set + rts + +local function zic_tick() + dec tt.timer bpl _noNewNote + ldx #1 +@_advanceLoop + tt.fetchCurrentNote() + cmp #tt.INS_PAUSE + if zic.useslide then + beq _pause + bcs _newNote + adc tt.cur_ins_c0,x sec sbc #8 sta tt.cur_ins_c0,x + bcs _finishedNewNote + else + bcc _finishedNewNote + bne _newNote + end +@_pause + lda tt.cur_ins_c0,x jsr tt_CalcInsIndex + lda tt_InsReleaseIndexes-1,y + clc adc #1 bcc _storeADIndex +@_newNote + sta tt.cur_ins_c0,x + cmp #tt.FREQ_MASK+1 bcs _startInstrument + tay + lda tt_PercIndexes-tt.FIRST_PERC,y + bne _storeADIndex +@_startInstrument + if zic.useoverlay then + bvs _finishedNewNote + end + jsr tt_CalcInsIndex + lda tt_InsADIndexes-1,y +@_storeADIndex + sta tt.envelope_index_c0,x +@_finishedNewNote + inc tt.cur_note_index_c0,x +@_sequencerNextChannel + dex + bpl _advanceLoop + if zic.globalspeed then + ldx #zic.speed-1 + if zic.usefunktempo then + lda tt.cur_note_index_c0 lsr bcc _noOddFrame ldx #zic.oddspeed-1 @_noOddFrame + end + stx tt.timer + else + ldx tt.cur_pat_index_c0 ldy tt_SequenceTable,x + if zic.usefunktempo then + lda tt.cur_note_index_c0 lsr + lda tt_PatternSpeeds,y + bcc _evenFrame + ;and #0x0f + bcs _storeFunkTempo +@_evenFrame + lsr lsr lsr lsr +@_storeFunkTempo + sta tt.timer + else + lda tt_PatternSpeeds,y sta tt.timer + end + end +@_noNewNote + ldx #1 +@_updateLoop + lda tt.cur_ins_c0,x + if not zic.startswithnotes then + beq _afterAudioUpdate + end + cmp #tt.FREQ_MASK+1 bcs _instrument + ldy tt.envelope_index_c0,x + lda tt_PercCtrlVolTable-1,y beq _endOfPercussion inc tt.envelope_index_c0,x @_endOfPercussion + sta AUDV0,x lsr lsr lsr lsr sta AUDC0,x + lda tt_PercFreqTable-1,y + sta AUDF0,x + sta AUDF0s,x -- EXTRA for vumeter rendering + if zic.useoverlay then + bpl _afterAudioUpdate + tt.fetchCurrentNote() + cmp #tt.FREQ_MASK+1 + bcc _afterAudioUpdate + sta tt.cur_ins_c0,x + jsr tt_CalcInsIndex + lda tt_InsSustainIndexes-1,y sta tt.envelope_index_c0,x + asl tt.cur_note_index_c0,x sec ror tt.cur_note_index_c0,x + bmi _afterAudioUpdate + else + jmp _afterAudioUpdate + end +@_instrument + jsr tt_CalcInsIndex + lda tt_InsCtrlTable-1,y sta AUDC0,x + sta AUDC0s,x -- EXTRA for vumeter rendering + lda tt.envelope_index_c0,x cmp tt_InsReleaseIndexes-1,y bne _noEndOfSustain lda tt_InsSustainIndexes-1,y @_noEndOfSustain + tay + lda tt_InsFreqVolTable,y beq _endOfEnvelope iny @_endOfEnvelope + sty tt.envelope_index_c0,x + sta AUDV0,x + sta AUDV0s,x -- EXTRA for vumeter rendering + lsr lsr lsr lsr clc adc tt.cur_ins_c0,x sec sbc #8 + sta AUDF0,x + sta AUDF0s,x -- EXTRA for vumeter rendering +@_afterAudioUpdate + dex + bpl _updateLoop +end + +local function zic_init() + if zic.c0init ~= 0 then lda#zic.c0init sta tt.cur_pat_index_c0 end + if zic.c1init ~= 0 then lda#zic.c1init sta tt.cur_pat_index_c1 end +end + + +---------------------------------------------------------------------- +-- equalizer anim +---------------------------------------------------------------------- + +-- convert to freq: http://atariage.com/forums/topic/257526-musicsound-programming-question-atari-2600/ +-- maps AUDCi to 0-7 3 bits (<<5) value for wave length of: 1, 2, 6, 15, 31, 93, 465, 511 +@@wavlen samepage +dc.b 0x00,0x60,0xC0,0xC0,0x20,0x20,0x80,0x80, + 0xF0,0x80,0x80,0x00,0x40,0x40,0xA0,0xA0 +end +local wavelen_map = { 1, 2, 6, 15, 31, 93, 465, 511 } +local freq_ranges = { 30, 60, 120, 240, 480, 960, 1920, 9999999 } +local t = {} +for i=0,255 do + local wavelen = wavelen_map[(i>>5)+1] + local note = (i&31)+1 + local freq = 3546894 / 114 / wavelen / note + for x=1,9 do + if freq <= freq_ranges[x] then + t[#t+1] = x-1 + break + end + end +end +section{'regfreq', align=256} byte(t) +@@vm_pf2 samepage +dc.b 0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF +end +@@vm_pf1 samepage +dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F +end +@@vm_col samepage +dc.b 0x3C,0x7C,0x9C,0xDC,0x2C,0x4C,0x6C,0xAC +end +local vumeter_tick = function() + lda #0 ldy #15 @_vudec ldx vubars,y dex bmi _vudecskip stx vubars,y @_vudecskip dey bpl _vudec + lda AUDF0s ldy AUDC0s ora wavlen,y tax ldy regfreq,x + lda AUDV0s cmp vubars,y bmi _vuless0 sta vubars,y @_vuless0 + lda AUDF1s ldy AUDC1s ora wavlen,y tax ldy regfreq,x + lda AUDV1s cmp vubars+8,y bmi _vuless1 sta vubars+8,y @_vuless1 +end +local vumeter_draw = function() + lda #ctrlpf.PF_MIRRORED sta CTRLPF + ldy #7 +@_vudraw + samepage + lda #21 sta tmp +@_vudraw1 + sta WSYNC + lda vm_col,y sta COLUPF sleep(4) + ldx vubars,y + lda vm_pf1,x sta PF1 + lda vm_pf2,x sta PF2 + sleep(8) + ldx vubars+8,y + lda vm_pf2,x sta PF2 + lda vm_pf1,x sta PF1 + dec tmp bpl _vudraw1 + sta WSYNC lda #0 sta PF2 sta PF1 + sta WSYNC + dey bpl _vudraw + end + lda #0 sta CTRLPF + sta WSYNC sta WSYNC +end + +local tick = function() + zic_tick() + vumeter_tick() +end + +@@main + init() + zic_init() +@_frame + overscan() vblank(tick) screen(vumeter_draw) jmp _frame + +; -- needed if last instruction is implied +writebin(filename..'.bin') +writesym(filename..'.sym') +print(stats) diff --git a/vcs.l65 b/vcs.l65 index 293a7f7..0525aa9 100644 --- a/vcs.l65 +++ b/vcs.l65 @@ -468,19 +468,17 @@ ttt = function(filename) t.author = s.metaAuthor t.name = s.metaName t.comment = s.metaComment - t.globalspeed = (s.globalspeed ~= nil and s.globalspeed or true) and 1 or 0 - t.speed = s.evenspeed - 1 - t.oddspeed = s.oddspeed - 1 - local usefunktempo - if t.globalspeed == 1 then usefunktempo = s.evenspeed ~= s.oddspeed + t.globalspeed = s.globalspeed == nil or s.globalspeed + t.speed = s.evenspeed + t.oddspeed = s.oddspeed + if t.globalspeed then t.usefunktempo = s.evenspeed ~= s.oddspeed else for k,v in ipairs(s.channels[1].sequence) do local pattern = s.patterns[v.patternindex+1] - if pattern.evenspeed ~= pattern.oddspeed then usefunktempo=true break end + if pattern.evenspeed ~= pattern.oddspeed then t.usefunktempo=true break end end end - t.usefunktempo = usefunktempo and 1 or 0 - t.usegoto,t.useslide,t.useoverlay,t.startswithnotes = 0,0,0,1 + t.usegoto,t.useslide,t.useoverlay,t.startswithnotes = false,false,false,true local patternmap = {} local instrumentmap,instrumentcount = {},0 @@ -499,20 +497,22 @@ ttt = function(filename) local patternindex = sequence.patternindex + 1 if not patternmap[patternindex] then patternmap[patternindex] = #t.patterns - local pattern = { name = s.patterns[patternindex].name } + local spattern = s.patterns[patternindex] + local pattern = { name = spattern.name } ins(t.patterns, pattern) - for noteindex,note in ipairs(s.patterns[patternindex].notes) do - if note.type == 0 then -- Instrument + for noteindex,note in ipairs(spattern.notes) do + if note.type == 1 then -- Instrument if not instrumentmap[note.number] then instrumentmap[note.number] = instrumentcount local instrument = s.instruments[note.number+1] instrumentcount = instrumentcount + (instrument.waveform == 16 and 2 or 1) local sz = instrument.envelopeLength while sz > instrument.releaseStart+1 and instrument.frequencies[sz] == 0 and instrument.volumes[sz] == 0 do sz = sz-1 end + local oldsz = #t.insfreqvoltable for i=1,sz do ins(t.insfreqvoltable, instrument.frequencies[i]+8 << 4 | instrument.volumes[i]) end - ins(t.insfreqvoltable, instrument.releaseStart+1, 0) + ins(t.insfreqvoltable, instrument.releaseStart + 1 + oldsz, 0) ins(t.insfreqvoltable, 0) for i = 1, instrument.waveform == 16 and 2 or 1 do ins(t.insadindexes, insEnvelopeIndex) @@ -530,7 +530,7 @@ ttt = function(filename) local valueIns = instrumentmap[note.number] + 1 if s.instruments[note.number+1].waveform == 16 and note.value > 31 then valueIns=valueIns+1 end ins(pattern, valueIns<<5 | note.value&0x1f) - elseif note.type == 1 then -- Percussion + elseif note.type == 3 then -- Percussion if not percussionmap[note.number] then percussionmap[note.number] = percussioncount percussioncount = percussioncount + 1 @@ -547,30 +547,30 @@ ttt = function(filename) ins(t.percctrlvoltable, 0) ins(t.percindexes, percEnvelopeIndex+1) percEnvelopeIndex = percEnvelopeIndex + sz + 1 - if percussion.overlay then t.useoverlay = 1 end + if percussion.overlay then t.useoverlay = true end end ins(pattern, percussionmap[note.number] + 17) -- NoteFirstPerc - elseif note.type == 2 then -- Hold + elseif note.type == 0 then -- Hold ins(pattern, 8) -- NoteHold - if sequenceindex == 1 and noteindex == 1 then t.startswithnotes = 0 end - elseif note.type == 3 then -- Pause + if sequenceindex == 1 and noteindex == 1 then t.startswithnotes = false end + elseif note.type == 2 then -- Pause ins(pattern, 16) -- NotePause elseif note.type == 4 then -- Slide ins(pattern, 8 + note.value) -- NoteHold + note.value - t.useslide = 1 + t.useslide = true end end ins(pattern, 0) - if t.globalspeed == 0 then - ins(t.patternspeeds, usefunktempo and (t.speed << 4 | t.oddspeed) or t.speed) + if not t.globalspeed then + ins(t.patternspeeds, t.usefunktempo and (spattern.evenspeed-1 << 4 | spattern.oddspeed-1) or spattern.evenspeed-1) end end ins(t.sequence[channelindex], patternmap[patternindex]) if sequence.gototarget ~= -1 then - t.usegoto = 1 + t.usegoto = true local value = 128 + sequence.gototarget + gotooffset gotooffset = gotooffset + 1 - if channelindex == 1 then value = value + #channel.sequence[1] end + if channelindex == 2 then value = value + #t.sequence[1] end assert(value < 256, "goto target in channel " .. (channelindex-1) .. " is out of range: " .. value) ins(t.sequence[channelindex], value) end @@ -588,6 +588,7 @@ ttt = function(filename) if t.sequence[2][i+1] > 127 then t.c1init = t.c1init + 1 end i = i+1 end + t.c1init = t.c1init + #t.sequence[1] return t end