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
|
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "playit.h"
#include "sndfile.h"
#include "fmt.h"
#define BYTES_PER_FRAME 4 /* simply fixed at 16-bit+stereo: 4 bytes per frame. */
typedef struct playit_prerendered_t {
void *buf;
unsigned num_frames;
} playit_prerendered_t;
typedef struct playit_t {
song_t *song;
unsigned flags;
playit_prerendered_t prerendered;
unsigned current_frame;
} playit_t;
static song_t * song_open_file(const char *file)
{
slurp_t *s;
song_t *song;
assert(file);
s = slurp(file, NULL, 0);
if (!s) {
fprintf(stderr, "Failed to open \"%s\"\n", file);
goto _fail;
}
song = csf_allocate();
if (!song) {
fprintf(stderr, "Failed to allocate song_t\n");
goto _fail_slurp;
}
if (fmt_it_load_song(song, s, 0) != LOAD_SUCCESS)
goto _fail_song;
unslurp(s);
return song;
_fail_song:
csf_free(song);
_fail_slurp:
unslurp(s);
_fail:
return NULL;
}
/* prerender the opened song into playit->prerendered.buf */
static int playit_prerender(playit_t *playit)
{
unsigned alloc_size = 8192, rendered_size = 0;
void *buf = NULL, *new;
assert(playit);
assert(playit->song);
while (!(playit->song->flags & SONG_ENDREACHED)) {
unsigned ret;
alloc_size *= 2;
new = realloc(buf, alloc_size);
if (!new) {
fprintf(stderr, "Failed to realloc %u bytes\n", alloc_size);
goto _fail;
} else
buf = new;
ret = csf_read(playit->song, buf + rendered_size, alloc_size - rendered_size);
rendered_size += ret * BYTES_PER_FRAME;
}
playit->prerendered.buf = realloc(buf, rendered_size);
if (!playit->prerendered.buf) {
fprintf(stderr, "Failed to realloc to rendered %u bytes\n", rendered_size);
goto _fail;
}
playit->prerendered.num_frames = rendered_size / BYTES_PER_FRAME;
return 1;
_fail:
free(buf);
return 0;
}
playit_t * playit_open_file(const char *file, unsigned flags)
{
playit_t *playit;
song_t *song;
assert(file);
playit = calloc(1, sizeof(playit_t));
if (!playit) {
fprintf(stderr, "Failed to allocate playit_t\n");
goto _fail;
}
playit->song = song = song_open_file(file);
if (!playit->song)
goto _fail_playit;
/* TODO: it seems kind of silly that we're dicking directly with the song_t members,
* there should really be a schism api for doing this properly.
*/
csf_set_current_order(song, 0);
song->repeat_count = -1; /* -1 to stop vs. looping */
song->buffer_count = 0;
song->stop_at_order = -1;
song->stop_at_row = -1;
song->flags &= ~(SONG_PAUSED | SONG_PATTERNLOOP | SONG_ENDREACHED);
csf_reset_playmarks(song);
csf_set_resampling_mode(song, SRCMODE_LINEAR);
csf_set_wave_config(song, 44100, 16, 2);
/* If seekable render the whole song into an in-memory buffer for
* precise seekability. This is useful for demo development where
* jumping around the demo within a tool like GNU Rocket is desirable.
* Presumably you'd turn this off for the release and mix realtime,
* unless you need the CPU time and have RAM to spare.
*/
if ((flags & PLAYIT_FLAG_SEEKABLE) && !playit_prerender(playit))
goto _fail_playit;
playit->flags = flags;
return playit;
_fail_song:
csf_free(playit->song);
_fail_playit:
free(playit);
_fail:
return NULL;
}
/* set song frame to an arbitrary offset, returns new offset */
unsigned playit_seek(playit_t *playit, unsigned frame)
{
assert(playit);
assert((playit->flags & PLAYIT_FLAG_SEEKABLE));
if (frame > playit->prerendered.num_frames)
frame = playit->prerendered.num_frames;
return playit->current_frame = frame;
}
/* Populate buf with up to len bytes of rendered song data starting from the current frame offset. */
/* The number of frames written into buf is returned (not a byte count!). */
/* When the end of the song is reached, 0 is returned. */
int playit_update(playit_t *playit, void *buf, int len, unsigned *res_frame)
{
int ret = 0;
assert(playit);
assert(buf);
if (!(playit->flags & PLAYIT_FLAG_SEEKABLE)) {
if (!(playit->song->flags & SONG_ENDREACHED))
ret = csf_read(playit->song, buf, len);
} else {
unsigned frames_to_copy = len / BYTES_PER_FRAME;
playit_prerendered_t *pre = &playit->prerendered;
frames_to_copy = MIN(frames_to_copy, pre->num_frames - playit->current_frame);
if (frames_to_copy) {
void *src = pre->buf + playit->current_frame * BYTES_PER_FRAME;
memcpy(buf, src, frames_to_copy * BYTES_PER_FRAME);
}
ret = frames_to_copy;
}
playit->current_frame += ret;
if (res_frame)
*res_frame = playit->current_frame;
return ret;
}
void playit_destroy(playit_t *playit)
{
assert(playit);
assert(playit->song);
csf_free(playit->song);
free(playit);
}
|