poly_voice.c 2.7 KB
Newer Older
1
#include "poly_voice.h"
Jacob Vosmaer's avatar
Jacob Vosmaer committed
2
#include "list.h"
3
#include "voice.h"
Jacob Vosmaer's avatar
Jacob Vosmaer committed
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
// The voice assigner moves voices between the 'avail' (available) queue
// and the 'playing' list. The 'avail' data structure is a queue to get a
// pleasing 'rotating' voice assignment effect. The 'playing' list is a
// linked list to be able to free up arbitrary voices when they get
// released, and to be able to find the longest playing voices in case we
// need to steal a voice.

// -- initial condition
// avail: 0,1,2,3      playing: empty
// -- play voice 0
// avail: 1,2,3        playing: 0
// -- play voice 1. Note that we are drawing the 'playing' structure backwards here.
// avail: 2, 3         playing: 0, 1 <- playing_head
// -- release voice 0
// avail: 2, 3, 0      playing: 1
// -- play voice 2
// avail: 3, 0         playing: 1, 2
// -- play voice 3
// avail: 0            playing: 1, 2, 3
// -- play voice 0
// avail: empty        playing: 1, 2, 3, 0
// -- new voice is requested: we must steal one first. voice 1 is the oldest now; we release it.
// avail: 1            playing: 2, 3, 0
// -- now use voice 1 and add it to the back (playing_head) of the playing list
// avail: empty        playing: 2, 3, 0, 1

// -- example of 'rotating' effect
// avail: 0, 1, 2, 3   playing: empty
// -- play 0
// avail: 1, 2, 3      playing: 0
// -- release 0
// avail: 1, 2, 3, 0   playing: empty
// -- now we play 1
// avail: 2, 3, 0, 1   playing: 1

40 41 42
// avail is a fixed-size queue implemented with an array and two
// pointers. It is used to track which voices are available (i.e. not
// playing). It is a queue in order to get 'rotating' voice assignment.
Jacob Vosmaer's avatar
Jacob Vosmaer committed
43

44
INIT_LIST(avail, NUM_VOICES);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
45 46 47 48

void
avail_enqueue(uint8_t v)
{
49
	l_push(avail, v);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
50 51 52 53 54
}

uint8_t
avail_is_empty(void)
{
55
	return l_empty(avail);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
56 57 58 59 60
}

uint8_t
avail_dequeue(void)
{
61 62
	uint8_t v = l_last(avail);
	l_delete(avail, v);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
63
	return v;
Jacob Vosmaer's avatar
Jacob Vosmaer committed
64 65
}

66
INIT_LIST(playing, NUM_VOICES);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
67 68 69 70

void
playing_play(uint8_t v)
{
71
	l_push(playing, v);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
72 73 74 75 76
}

void
playing_release(uint8_t v)
{
77
	l_delete(playing, v);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
78 79 80 81 82
}

uint8_t
playing_oldest(void)
{
83
	return l_last(playing);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
84 85 86 87 88
}

void
voice_init(void)
{
89
	l_flush(avail);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
90
	for_each_voice(i) {
Jacob Vosmaer's avatar
Jacob Vosmaer committed
91 92
		avail_enqueue(i);
	}
93

94
	l_flush(playing);
Jacob Vosmaer's avatar
Jacob Vosmaer committed
95 96
}

97 98 99 100 101
// Release the oldest playing voice, regardless of what note it is playing.
void
release_oldest(void)
{
	uint8_t v = playing_oldest();
102
	if (v == playing->sup) {
103 104 105 106 107 108 109
		return;
	}

	playing_release(v);
	avail_enqueue(v);
}

Jacob Vosmaer's avatar
Jacob Vosmaer committed
110
uint8_t
111
voice_acquire()
Jacob Vosmaer's avatar
Jacob Vosmaer committed
112
{
113 114 115 116 117
	if (avail_is_empty()) {
		release_oldest();
	}

	uint8_t v = avail_dequeue();
118
	if (v == avail->sup) {
119
		// This is impossible unless there is a mistake in our program.
120
		return NUM_VOICES;
Jacob Vosmaer's avatar
Jacob Vosmaer committed
121 122 123 124 125 126
	}

	playing_play(v);
	return v;
}

127 128
void
voice_release(uint8_t v)
Jacob Vosmaer's avatar
Jacob Vosmaer committed
129
{
130
	if (l_contains(playing, v)) {
131 132
		playing_release(v);
		avail_enqueue(v);
133
	}
Jacob Vosmaer's avatar
Jacob Vosmaer committed
134
}