-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathayc_common.bai
373 lines (343 loc) · 13.8 KB
/
ayc_common.bai
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
' This file contains things that are common between the AYC and JJAY compressed AY
' format players - eventually, we'll rename some things and stop using the AYC naming, but
' for now, we just want it to work...
dim ayc_last_frame_tick
dim ayc_last_timer
'---------------------------
' GLOBALS - these must be global
' for your music to play!
' TODO: possibly refactor this to be a struct of an ayc_data type, so it can be passed
' instead of global
'---------------------------
'
' if this is 1, we call Sound, if this is 0, we call FillBuffer, and
' play via the codesprite
buffer_mode = 1
' should we use IRQ based timing, or Poke based timing?
irq_mode = 0
' do we wait for the next frame to be "due" before we continue?
buffer_mode_preserve_refresh = 0
ayc_start_time = 0
' drop this if you're only doing 2 channels, leaving one for sound effects
max_regs = 14
include "ayc_settings.bai"
if buffer_mode = 1
' This is almost certainly wrong/destructive!
' but is.... probably enough?
dualport_return = 1
dualport_status = 2
ayc_buffer_overflow = false
'--------------------------------------------------------------------'
' This is only required if buffer_mode is set to 1 - you can save a few byres of ram by excluding it if you want, otherwise
' number of buffers.
'
' Currently, we non-optionally consume 70 bytes of dpram per buffer - so 4 buffers would be 280 bytes of dpram.
lframe = 0
buffer_count = 6
' rate of playback - 50hz by default...
player_rate = 50
' you'll need to allow for max_regs*buffer_count worth of iram at this location
' if this is the only weird thing you're using, c882 should be fine. c880 is better, but doens't work
' on all v32 firmware revisions right now....
buffer_location = $c882
buffer_base = $c884
' buffers are 14*2 + 1 bytes long
buffer_end = buffer_base + (buffer_count - 1) * 29
' add an extra buffer_end for this, because of that way it's calculated...
if irq_mode = 1
flag_loc = buffer_end + 29
else
flag_loc = dualport_flag
endif
ayc_ticked = 0
player_code_loc = buffer_end + 31
player_jmp = player_code_loc + 10
print "player_jmp: "+player_jmp+" player_code_loc: "+player_code_loc
game_frame_count = 0
' below here is not, for the most part, user servicable :)
via_rate = 1500000 / player_rate
tick_rate = 960 / player_rate
print "AYC: VIA Rate is "+via_rate+" cycles"
print "AYC: Tick Rate is "+tick_rate+" cycles"
current_buffer = 0
' we use this being negative to represent first frame
ayc_buffer_played = -1
ayc_dp_sequence = 254
dim ayc_pokedata[max_regs*5*buffer_count]
dim ay_output_data[buffer_count, max_regs*2]
'--------------------------------------------------------------------'
' should be in hex format, but for now this is what we get!
' the listing for this is an other file within the github, but I built it with
' asm80.com :)
'--------------------------------------------------------------------'
' there are two bits of code here:
' a) the irq hander - this just sets a flag, to avoid messing up line drawing
' b) the actual player code itself - this is called outside, so that we know we're not drawing at the time
'
' lines 1-3 are the irq handler
' rest is plaer
'
' first line: check if we play ;)
' then next overflow check
' thjen call sound_bytes_x, increment dualport return
' then incremener buffer pointer
' then finally an RTS ;)
'
' orig code is in buffer.a09 with comments
'
' we acutally shove the main playcode into vectrex ram to try and save ourselves some dpram....
' see the .lst file for disas
ayc_playcode = { $bd, player_jmp / 256, player_jmp mod 256 }
internal_ayc_playcode = { _
$cc, via_rate mod 256, via_rate / 256, $fd, $d0, $08, _
$7c, flag_loc / 256, flag_loc mod 256, _
$3b, _
$b6, flag_loc / 256, flag_loc mod 256, _
$81, $00, _
$27, $2a, _
$4a, _
$7c, (player_jmp+4) / 256, (player_jmp+4) mod 256, _
$b6, dualport_return / 256, dualport_return mod 256, _
$81, buffer_count, _
$2c, $1f, _
$FE, buffer_location / 256, buffer_location mod 256, _
$BD, $F2, $7D, _
$b6, dualport_return/256, dualport_return mod 256, _
$4c, _
$b7, dualport_return/256, dualport_return mod 256, _
$fc, buffer_location / 256, buffer_location mod 256, _
$c3, $00, $1d, _
$10, $83, buffer_end / 256, buffer_end mod 256, _
$2f, $03, _
$cc, buffer_base / 256, buffer_base mod 256, _
$fd, buffer_location / 256, buffer_location mod 256, _
$39 }
'--------------------------------------------------------------------'
' this sets up the timer we need to keep time on the VX side...
' this will be modified by the player to set it to what's remaining for the first
' vblank that we need. it should be called as early as posisble during your dualport config
'
' It also resets the dualport_return register to 0 - that register ends up containing how many frames were acutally played!
'--------------------------------------------------------------------'
' 1) clear dualport_return
' 2) clear play flag
' 3)
' 3) set timer b to the remaining time untilk music
if irq_mode = 1
ayc_init = { $86, $00, $b7, dualport_return / 256, dualport_return mod 256, _
$1c, $ef, _
$86, $a0, $b7, $d0, $0e, _
$cc, $0, $2, $fd, $d0, $08 }
else
' in non-irq mode, we ignore the code that actually sets upo the IRQ - we're going
' to push in data a different way...
'ayc_init = { $7c, dualport_status/256, dualport_status mod 256, _
' $86, $00, $b7, dualport_return / 256, dualport_return mod 256 }
'ayc_init = { $86, $00, $b7, dualport_return / 256, dualport_return mod 256 }
ayc_init = { $86, $01, $b7, dualport_status/256, dualport_status mod 256, _
$86, $00, $b7, dualport_return / 256, dualport_return mod 256 }
endif
'ayc_init = { $cc, $30, $75, $fd, $d0, $08, $86, $00, $b7, dualport_return / 256, dualport_return mod 256 }
'--------------------------------------------------------------------
' this resets the VIA at the end, so that wait_recal doens't wait - this should be the last thing you call.
' note that, in VIA buffered mode, all it does is update dualport_status to the current sequence semaphore
' without that, playback is.... weird due to locking...
'--------------------------------------------------------------------
' first line is: lda #sequence, sta $dualport_status
' in betten turn of finterrupts again
' second line is: ldd #$100, std $d008 (remember: endian is reversed)
' which should set timer b to "almost nothing"
if irq_mode = 1
ayc_exit = { $86, ayc_dp_sequence, $b7, dualport_status / 256, dualport_status mod 256, _
$1a, $10, _
$86, $80, $b7, $d0, $0e, _
$cc, $1, $0, $fd, $d0, $08}
else
ayc_exit = { $86, ayc_dp_sequence, $b7, dualport_status / 256, dualport_status mod 256 }
endif
endif
sub ayc_update_timer
if ayc_start_time == 0
ayc_start_time = GetTickCount()
ayc_last_timer = 0
endif
current_tick = GetTickCount() - ayc_start_time
music_target = int((current_tick / 960.0) * player_rate + 1)
if ayc_last_timer != music_target
call Poke(flag_loc, int(music_target mod 256))
ayc_last_timer = music_target
endif
endsub
' this function is only used in buffer mode :)
' the algorythm is:
' 0) update ayc_buffer_played from what actually got played last frame... this needs to be done first so we
' know where we are!
' 1) get where we SHOULD be from the vectrex32 tick counter, and convert that into frames (in music_target)
' 2) compare that to where we are (in ayc_buffer_played), which is already in frames
' 3) set wait_ time to the difference between these, which since it's constantly playing, SHOULD be only the
' fractional part
' 4) shove that into the VIA countdown register 2 that's normally used for vx refresh
sub update_music_vbi
if ayc_start_time == 0
ayc_start_time = GetTickCount()
ayc_last_frame_tick = GetTickCount()
endif
ayc_tick = GetTickCount()
ayc_played_this_frame = 0
if ayc_buffer_played >= 0
' have a 1 second timeout on this - we've simplified the term since the '&1' could never ever be matched
' anyhow - the sequence either _is_, or _is not_. If it _is not_, we wait at least 10 ticks for
' the first code to be executed. We finally add a 1 second timeout - this should never ever get hit, but it'll cause
' us to break out...
while ((Peek(dualport_status) != ayc_dp_sequence) or (GetTickCount()-ayc_tick)<10 and ayc_dp_sequence!=1) _
and (GetTickCount()-ayc_last_frame_tick)<96
if irq_mode = 0
call ayc_update_timer
endif
endwhile
if Peek(dualport_status) != ayc_dp_sequence
print "ohai, we didn't actually update in "+(GetTickCount()-ayc_last_frame_tick)+"... weird - dualport_status=" +Peek(dualport_status)+" expected "+ayc_dp_sequence
endif
'print "endframe"
' reset benchmark counter once we've synced ;)
ayc_tick = GetTickCount()
ayc_dp_sequence = (ayc_dp_sequence + 4) mod 256
ayc_exit[2] = ayc_dp_sequence
' fill any used buffers with new sound data
ayc_played_this_frame = Peek(dualport_return)
if ayc_played_this_frame >= buffer_count
print "WARN: AYC buffer limit of "+ayc_played_this_frame+" hit in "+(((GetTickCount()-lframe)/960.0)*1000.0)+" ms - consider increasing buffer size..."
ayc_buffer_overflow = true
endif
lframe = GetTickCount()
for i = 1 to ayc_played_this_frame
call play_that_music
next
ayc_buffer_played = ayc_buffer_played + ayc_played_this_frame
'print "Played "+ayc_played_this_frame+" full "+ayc_buffer_played
else
ayc_buffer_played = 0
endif
ayc_last_frame_tick = GetTickCount()
if buffer_mode_preserve_refresh = 1
target_tick = (game_frame_count * 960) / GetFrameRate()
while (GetTickCount() - ayc_start_time) < target_tick
endwhile
game_frame_count = game_frame_count + 1
endif
' fix the IRQ timing
if irq_mode = 1
' This is all terrible and absolutely should
' NOT be fpmath, which is almost certainly slow as hell. But it also might not be
' worht optimizing....
current_tick = GetTickCount() - ayc_start_time
' where should be for the _next_ frame
music_target = (current_tick / 960.0) * player_rate + 1
' ... vs where are we right now...
played_to = ayc_buffer_played
' music_target _SHOULD_ be ahead... as a general rule - the player is _triggered_ on the x.00 tick,
' and so music target should, as a general rule, be above that - and we're waiting for the next whole
' number to tick over....
wait_time = played_to - music_target
' wait_time gets multiplied by via_wait - it should be fractional, so this should work out....
wait_time = wait_time * via_rate
wait_time = Int(wait_time)
if wait_time < 2
wait_time = 2
endif
if wait_time > 65535
wait_time = 65535
endif
print "AYC: (last: "+ayc_played_this_frame+") music target is "+music_target+" for tick "+current_tick, " vs " + played_to + " wait_time: "+wait_time
' shove that wait_time in the codesptie for the VIA, so we wait for that
ayc_init[14] = wait_time mod 256
ayc_init[15] = wait_time / 256
endif
' benchmark
w_tick = GetTickCount() - ayc_tick
endsub
' generate a codesprite with lda #imm, sta buffer_base+offset
sub generate_ayc_pokedata_codesprite()
addr = buffer_base
offset = 1
for b = 1 to buffer_count
for r = 1 to max_regs
' incr addr to skip channel set
addr = addr + 1
' lda_imm
ayc_pokedata[offset] = $86
offset = offset + 1
' this will be filled in later!
ayc_pokedata[offset] = (r-1)
offset = offset + 1
' sta_abs, hi, lo
ayc_pokedata[offset] = $b7
offset = offset + 1
ayc_pokedata[offset] = (addr / 256) mod 256
offset = offset + 1
ayc_pokedata[offset] = addr mod 256
offset = offset + 1
' incr addr
addr = addr + 1
next
' skip the $ff
addr = addr + 1
next
endsub
sub fill_buffer(outregs)
for r = 1 to 14
' 5 bytes per "reg"
' our write is at +6
' + 29*5*current_buffer -> buf ptr (the other part is the $ff) (2 x per reg + $ff)
' +1 for gsbasic
'print "r:"+r+" addr:"+((((r-1)*5)+1)+(14*5*current_buffer)+1)
ayc_pokedata[(((r-1)*5)+1)+(14*5*current_buffer)+1] = outregs[r,2]
next
current_buffer = (current_buffer + 1) mod buffer_count
endsub
' Stole this function from Malban's lightpen test - thanks!
sub pokeRAM(where, what)
if what<0 then
what = 256 +what
endif
poke_RAM = {$86, what, $b7, (where/256) MOD 256, where MOD 256}
call CodeSprite(poke_RAM)
endsub
sub setup_music_codesprites
if buffer_mode = 1
dim pd[2]
call clearscreen()
call TextSprite("AYC Loader")
call pokeRAM(buffer_location, (buffer_base / 256) mod 256)
call pokeRAM(buffer_location+1, buffer_base mod 256)
' set up IRQ jmp
call pokeRAM($cbf8, $7e)
call pokeRAM($cbf9, (player_code_loc / 256) mod 256)
call pokeRAM($cbfa, player_code_loc mod 256)
call pokeRAM(flag_loc, 0)
for j = 1 to Ubound(internal_ayc_playcode)
call pokeRAM(player_code_loc+(j-1), internal_ayc_playcode[j])
next
addr = buffer_base
for b = 1 to buffer_count
for reg = 1 to 14
for v = 1 to 2
call pokeRAM(addr, reg-1)
addr = addr + 1
next
next
call pokeRAM(addr, $ff)
addr = addr + 1
next
' why do i need two of these? If I have one, it doens't seem to work at all.....
' I have no idea what i'm doing wrong :)
controls = WaitForFrame(JoystickNone, Controller2, JoystickNone)
'call Peek(buffer_location, 2, pd)
controls = WaitForFrame(JoystickNone, Controller2, JoystickNone)
call clearscreen()
'while pd[1] = 0
'endwhile
'data = pd[2]
'print data
endif
endsub