Ok I already asked how to use timers for my needs and such and it does work, but it's not precise. The reason it needs to be exactly precise is I'm creating a metronome where you can change the beats per minute to anything. This is the main code I am using:
1 | void calculateMetronome() //This is called in the main loop |
2 | { |
3 | bps = ((tempo * 1000) / 60000); |
4 | pause = (1000 / bps); |
5 | } |
6 | |
7 | void handleMetronome() //This is called after calculate metronome |
8 | { |
9 | mettimer++; |
10 | if(mettimer >= pause) |
11 | { |
12 | play_sample(click, 255, 128, 1000, FALSE); |
13 | mettimer = 0; |
14 | } |
15 | } |
16 | |
17 | //This is the main loop |
18 | void mainLoop() |
19 | { |
20 | while(!key[KEY_ESC]) |
21 | { |
22 | int mx = mouse_x; |
23 | int my = mouse_y; |
24 | |
25 | calculateMetronome(); |
26 | |
27 | while(timer > 0) |
28 | { |
29 | --timer; |
30 | handleMetronome(); |
31 | } |
32 | if(mouse_b & 1) |
33 | handleMouse(mx, my); |
34 | } |
35 | } |
That is my code right now. It works awesome, it's just the fact that the beats aren't accurate and there isn't really a difference unless you change the beats by a large margin(maybe around 30). I was wondering if there was a much more accurate way to do this? Or is my method just not accurate itself and is there a better way? Thank you.
Allegro's timers aren't too precise..
Here's the timing code I use:
.hpp file
#include <allegro.h> void start_timer(void); void reset_timer(void); int check_timer(int frac_sec);
.cpp file
1 | #include <allegro.h> |
2 | #include "timer.hpp" |
3 | #ifdef ALLEGRO_WINDOWS |
4 | #include <winalleg.h> |
5 | |
6 | // High resolution timer code for Windows .Call start_timer first and then call |
7 | // check_timer with the required accuracy range. |
8 | // The timer is more accurate than the default Allegro timers.. |
9 | |
10 | struct timer |
11 | { |
12 | LARGE_INTEGER tstart, tticks, tnow; |
13 | int started; |
14 | int high_freq; |
15 | } timer; |
16 | |
17 | |
18 | |
19 | void start_timer(void) |
20 | { |
21 | timer.high_freq = QueryPerformanceFrequency(&timer.tticks); |
22 | |
23 | if (timer.high_freq) |
24 | { |
25 | QueryPerformanceCounter(&timer.tstart); |
26 | } |
27 | } |
28 | |
29 | void reset_timer(void) |
30 | { |
31 | if (timer.high_freq) |
32 | { |
33 | QueryPerformanceCounter(&timer.tnow); |
34 | timer.tstart = timer.tnow; |
35 | } |
36 | } |
37 | |
38 | int check_timer(int frac_sec) |
39 | { |
40 | if (timer.high_freq) |
41 | { |
42 | QueryPerformanceCounter(&timer.tnow); |
43 | return (int) (((timer.tnow.QuadPart - timer.tstart.QuadPart) * frac_sec) / timer.tticks.QuadPart); |
44 | } |
45 | return 0; |
46 | } |
47 | |
48 | #endif /* |
49 | */ |
50 | |
51 | #if defined(ALLEGRO_MACOSX) || defined(ALLEGRO_UNIX) |
52 | #include <sys/time.h> |
53 | |
54 | // Timer code for OSX.Call start_timer first and then call |
55 | // check_timer with the required accuracy range. |
56 | |
57 | static struct timeval tstart; |
58 | |
59 | |
60 | void start_timer(void) |
61 | { |
62 | gettimeofday(&tstart, NULL); |
63 | } |
64 | |
65 | void reset_timer(void) |
66 | { |
67 | gettimeofday(&tstart, NULL); |
68 | } |
69 | |
70 | int check_timer(int frac_sec) |
71 | { |
72 | struct timeval now; |
73 | double hi, lo; |
74 | |
75 | gettimeofday(&now, NULL); |
76 | |
77 | hi = (double) (now.tv_sec - tstart.tv_sec); |
78 | lo = ((double) (now.tv_usec - tstart.tv_usec)) / 1.0e6; |
79 | |
80 | return (int) ((hi + lo) * ((double) frac_sec)); |
81 | } |
82 | |
83 | #endif /* */ |
Once you've called start_timer, you can do say time = check_timer(100); Which will check with a precision of 100 ticks per second. You should be able to go up to at least (250) ticks per second precision. Just remember to call reset_timer before you do another check if you've found a new tick.. I.e.:
if (check_timer(100) > 0) { // do stuff.. reset_timer(); }
Hope that helps.
How am I supposed to implement this with my code? Do I call check_timer(); in the main while loop? Or do I replace the if(timer < 0) line in my main loop with a different variable?
Change:
while(timer > 0) { --timer; handleMetronome(); }
to:
timer = check_timer(100); while (timer > 0) { reset_timer(); handleMetronome(); }
You would have to add a start_timer() call before the main loop and use a timer global variable in your program that other functions can use (like you have now with an allegro timer. (or you could pass it to your functions as a variable).
Ok, I have this timer code of yours put in at the top of my source and it works great, but the problem is that it plays the sound very fast. What type of time measurement am I dealing with so I can change my algorithm to find the pause inbetween each sound?
I'm not sure what you mean.. I don't know music calculations. But the value you call check_timer() with is the accuracy. I.e. using check_timer(1); would mean that the value returned would be the number of seconds since the last reset_timer call.
10 would be 10'ths of a second, etc.. So if check timer(100) returns 100 then 1 second has elaped.
Is that helpful?
Yeah that makes sense. I'm simply trying to get the pause inbetween each beat by finding the beats per second (from beats per minute). So I can change that value to whatever I want to make it more accurate?
EDIT: I am also noticing that I cannot do anything when the program starts. I can't press any keys or click on anything with my mouse. I think it's because of the sound playing, but I don't think it should be doing that.
So I can change that value to whatever I want to make it more accurate?
Yes..
I am also noticing that I cannot do anything when the program starts. I can't press any keys or click on anything with my mouse. I think it's because of the sound playing, but I don't think it should be doing that.
No idea with this problem.
Ok everything is fine except the metronome code now. I'm not sure if I'm handling it correctly or not.
Main Loop:
Code to make sound occur every so often
void handleMetronome() { if(check_timer(100) >= pause) { play_sample(click, 255, 128, 1000, FALSE); reset_timer(); } }
Can I use the check_timer twice like that or am I doing it incorrectly?
Not sure about the sound issue.
Can I use the check_timer twice like that or am I doing it incorrectly?
No that's fine. However, wouldn't it be easier to do a fixed rate check, i.e. check_timer(200); And then have your metronome data take this into account with the song's tempo?
Is this what you mean?
void handleMetronome() { mettimer = check_timer(100); if(mettimer >= 100) { play_sample(click, 255, 128, 1000, FALSE); reset_timer(); } }
This is the main loop at the moment:
It's still not working either way though. Sorry to seem stupid.
this is the way I've done it for my sequencer, and the playback is pretty acurate. To change the tempo, just call set_playback_tempo() and get_playback_tempo(). In my sequencer, however, nothing is being drawn to the screen during playback. This is to ensure acurate timing.
1 | volatile int game_counter; |
2 | void my_timer_handler() { game_counter++; } |
3 | END_OF_FUNCTION(my_timer_handler) |
4 | |
5 | |
6 | void setup_sound() |
7 | { |
8 | |
9 | if (install_int_ex(my_timer_handler, BPS_TO_TIMER(1000)) != 0) |
10 | { |
11 | allegro_message("cannot setup playback timer"); |
12 | } |
13 | LOCK_FUNCTION(my_timer_handler); //locks the function so not to cause problems while interrupting |
14 | LOCK_VARIABLE(game_counter); //locks the variable so not to cause problems while interrupting |
15 | |
16 | } |
17 | |
18 | |
19 | //////////// |
20 | |
21 | |
22 | float playback_cursor = 0; |
23 | double playback_speed = 1.0; |
24 | double odd_multiplier = 1.0; |
25 | |
26 | void set_playback_tempo(double tempo, Duration duration=DURATION_quarter, int dots=0) |
27 | { |
28 | playback_speed = (tempo / 120.0); |
29 | } |
30 | |
31 | double get_playback_tempo() |
32 | { |
33 | return (playback_speed * 120.0f); |
34 | } |
35 | |
36 | |
37 | //////////// |
38 | |
39 | |
40 | void main_loop() |
41 | { |
42 | if (game_counter > 0) |
43 | { |
44 | game_counter = 0; |
45 | playback_cursor += (playback_speed * odd_multiplier); |
46 | } |
47 | |
48 | if (playback_cursor >= **the_note's_attack_time**) play_note(); |
49 | } |
the only strange thing is when I change MIDI drivers, I sometimes have to set the odd multiplier to 7.0 .
I'm trying to find a method more accurate than the allegro default timers actually. None of my code is working. ><
Check this thread for HighRes Timer.
Here is my own wrapper.
GullRaDriel: Did you read this thread? I already posted high res timer code..
I already posted high res timer code..
I know, but there are C/C++, RDTSC, and lots of various way of using GTOD & QPC.
I was not trying to say that your code is not right, I was trying to help.
GullRaDriel: Did you read this thread?
I am at work, and I did not took the time to read the whole thread.
Last, be sure I would have put your code into the thread if only I was able to edit it.
My excuse, dear RP.