Log In  

Well, I'm trying to make a rhythm game and I want the enemies to change sprite (To look as they were dancing) and attack following the tempo of the current song. It's my first game and I don't know too much about code or how pico-8 works. I tried to make a T variable that add one to its current value each frame and to use a the "%" operator to sync the frame counter to the beat but it's hard to do it that way and I feel like it doesn't sync too well. Can anyone help me to code it?

P#76669 2020-05-15 21:49 ( Edited 2020-05-15 21:50)

2

2021 December edit

See User Manual: Stat

Relevant quote from there:

"46..49 Index of currently playing SFX on channels 0..3 50..53 Note number (0..31) on channel 0..3 54 Currently playing pattern index 55 Total patterns played 56 Ticks played on current pattern 80..85 UTC time: year, month, day, hour, minute, second 90..95 Local time 100 Current breadcrumb label, or nil 110 Returns true when in frame-by-frame mode

Audio values 16..26 are the legacy version of audio state queries 46..56. It only operates on the current state of the audio mixer, which changes only ~20 times a second (depending on the host sound driver and other factors). 46..56 instead stores a history of mixer state at each tick to give a higher resolution estimate of the currently audible state."

Using the newly added stats 46..56 it should be possible to easily get accurate enough information to achieve this.


I have left my original answer (for now) but with the increases in accuracy the new stats 46..56 bring, I suspect my original answer is unnecessarily complicated for the accuracy it tries to achieve, and is therefore no longer a good answer.


Cart #frogjumpaudiosync_remcode-1 | 2020-05-17 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

Here's a cart which shows one way of doing this. I have stuck to syncing the sprites with the tempo of the current song, using the stat command to get information on this. You have said you are new to coding and to pico-8 so I didn't want to introduce much more than you were already looking at, but I can think of other ways to achieve this (perhaps someone else already has a suitable example sitting around that they can share).

I have extensively commented my code, to help you or anyone else new to coding.

Here's the code with the comments removed for anyone who finds that easier to read (I don't think I deleted anything other than comments, but if this doesn't work, see the cart instead).

function _init()
 debug=true

 delay=60

 spritestate={standing=1,jumping=2}

 frog={}
 for i=1,3 do
  local y=96
  local x=i*32 --32,64,96 across the screen
  add(frog,{
   x=x,
   y=y,
   yacc=0,
   state=spritestate.standing
  })
 end
end

function _update()
 delay=max(0,delay-1) 

 if (delay==0 and stat(24)==-1) then
  music(0)

  delay=512
 end

 for i=1,3 do
  if (frog[i].state==spritestate.standing and
      stat(26)%(32*8)>=0 and
      stat(26)%(32*8)<=7) then
      --jump
      frog[i].state=spritestate.jumping
      frog[i].yacc=-8
  end
  -----------------------------
  if (frog[i].state==spritestate.jumping) then
   frog[i].y=min(frog[i].y+frog[i].yacc,96)   
   frog[i].yacc=min(frog[i].yacc+1,8)    

   if (frog[i].y==96) then
    frog[i].state=spritestate.standing
    frog[i].yacc=0
   end
  end
 end
end

function _draw()
 cls()

 rectfill(0,103,127,105,3) 

 for i=1,3 do
  local g
  if (frog[i].state==spritestate.standing) then
   g=1
  else
   g=2
  end
  spr(g,frog[i].x,frog[i].y)
 end

 if (debug) then
  print(stat(26),2,2,2)  
  print(stat(26),1,1,9)
 end 
end

Notes

Ticks and frames


The tickrate is going so fast it is possible to miss certain beats; I compensate for this by testing for a short range of values, consequently sometimes it misses the beat by a quarter of the length of a note before the range catches it, but in play this is unlikely to be noticed.

(I expect there are situations, particularly when under heavy load, when even this range will not catch the beat. But if you have slowdown causing sync problems, you have bigger issues to fix.)

I've said "the tickrate is going so fast". I believe in fact that frames per second and ticks per second are related to hardware/standards, so they end up being slightly out of sync, with tps slightly faster than fps. This means that tracking where we are in the ticks is not going to be as simple as incrementing once per frame. I don't have complete information on this; perhaps someone with more technical knowledge about the implementation of such things can comment.

Offset attacks


My code has all the frogs act at the same time. It would be possible to offset their attacks by offsetting the range that is being tested, say by an offset of 8 (a quarter of a note). One way of doing this would be to give each frog/enemy an offset variable when you create them. Then you can use this in the condition test something like:

 for i=1,3 do
  local a=stat(26)+frog[i].offset

  if (frog[i].state==spritestate.standing and
      a%(32*8)>=0 and
      a%(32*8)<=8) then

Presuming offset is given in steps of 8, with 0 as the low boundary, this would have the frogs jumping at specified quarter note intervals. (Code untested and incomplete.)

Direct control of SFX


Another way of getting sync is to take more direct control of the audio, rather than letting pico-8 control the music. One way of going about this is to put all the notes you are going to use into various SFXs, in tonal order (for example) then keeping your own timer in-game; based on the timer, you then use the longer version of the SFX command to specify which note you want to play.

So you might use SFX(0,1,0,1) to play from SFX 0 on channel 1 from note 0 for 1 note. To go up an octave on the same SFX bank of notes SFX(0,1,12,1) as octaves are 12 notes apart when you include the sharps/flats. (Presuming of course that you've entered every note in tonal order.)

You would then need to write your own tracks in code, and your own controller for them. That's a bit more complex than what I've done in my example.

Other posts to look at


Other people have found sync with the sfx difficult in the past. Don't let that put you off, but I thought it should probably be mentioned; see for example:

sfx states sometimes don't update in sync with one another https://www.lexaloffle.com/bbs/?tid=32379

3 channel music out of sync https://www.lexaloffle.com/bbs/?tid=29169 (this post mentions using sfx with different length notes, which can be tricky)

And these which use more advanced techniques (and are probably not something someone new to code should look to imitate):

music sync experiment https://www.lexaloffle.com/bbs/?tid=29010

4-beat https://www.lexaloffle.com/bbs/?tid=3710

Edits
Correction of numeral by 1, & change line to rectfill

P#76720 2020-05-16 20:43 ( Edited 2021-12-24 08:35)

I recently implemented beat sync in a cart I'm working on. I made it a generic system where you can register callbacks to occur on even subdivisions: full note, half note, quarter, eighth, sixteenth, 32nd. After including the code, just do this:

-- perform once per quarter note
sync.on(4, function()
  do_thing()
  do_other_thing()
end)

-- or if you're only doing one thing
sync.on(16, do_thing) -- every 16th-note

Note that in this version, I set the expected speed to 16 in the library. That's because my main rhythm track uses a speed of 16, and other tracks are all multiples of that. You could modify this library to require explicit initialization with a speed of your choice, or just hardcode it to something else.

https://gist.github.com/amacdougall/a1c977e2036d529bbdb76109cffdb3ea

P#97549 2021-09-19 17:23 ( Edited 2021-09-19 17:25)

[Please log in to post a comment]