File: test_sfont_unloading.c

package info (click to toggle)
fluidsynth 2.4.4%2Bdfsg-1%2Bdeb13u1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,328 kB
  • sloc: ansic: 43,529; cpp: 1,434; xml: 1,020; makefile: 71; sh: 46
file content (231 lines) | stat: -rw-r--r-- 8,868 bytes parent folder | download | duplicates (3)
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

#include "test.h"
#include "fluidsynth.h"
#include "synth/fluid_synth.h"
#include "utils/fluid_sys.h"

void wait_and_free(fluid_synth_t* synth, int id, const char* calling_func)
{
    int i;
    fluid_list_t *list, *list_orig;
    list_orig = list = synth->fonts_to_be_unloaded;
    synth->fonts_to_be_unloaded = NULL;
    delete_fluid_synth(synth);
    
    for(; list; list = fluid_list_next(list))
    {
        fluid_timer_t* timer = fluid_list_get(list);
        FLUID_LOG(FLUID_INFO, "%s(): Start waiting for soundfont %d to unload", calling_func, id);
        for(i = 0; fluid_timer_is_running(timer) && i < 5; i++)
        {
            /* timer still running, wait a bit */
            fluid_msleep(fluid_timer_get_interval(timer));
        }
        // In worst case we've waited 5*timer_interval, i.e. soundfont should be really unloaded by now.
        TEST_ASSERT(!fluid_timer_is_running(timer));
        delete_fluid_timer(timer);
        FLUID_LOG(FLUID_INFO, "%s(): End waiting for soundfont %d to unload", calling_func, id);
    }
    delete_fluid_list(list_orig);
}

static void test_without_rendering(fluid_settings_t* settings)
{
    int id;
    fluid_synth_t *synth = new_fluid_synth(settings);
    TEST_ASSERT(synth != NULL);

    TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE);

    // load a sfont to synth
    TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1));
    // one sfont loaded
    TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
    
    TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127));
    
    TEST_SUCCESS(fluid_synth_sfunload(synth, id, 1));
    
    TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 60));
    
    // there must be one font scheduled for lazy unloading
    TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
    
    wait_and_free(synth, id, __func__);
}

// this should work fine after applying JJCs fix a4ac56502fec5f0c20a60187d965c94ba1dc81c2
static void test_after_polyphony_exceeded(fluid_settings_t* settings)
{
    int id;
    fluid_synth_t *synth = new_fluid_synth(settings);
    TEST_ASSERT(synth != NULL);

    TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE);

    // load a sfont to synth
    TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1));
    // one sfont loaded
    TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
    
    TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127));
    FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): note on C4, voice count=%d",
                           fluid_synth_get_active_voice_count(synth));
    
    // need to render a bit to make synth->ticks_since_start advance, to make the previous voice "killable"
    TEST_SUCCESS(fluid_synth_process(synth, 2048, 0, NULL, 0, NULL));
    
    // polyphony exceeded - killing the killable voice from above
    TEST_SUCCESS(fluid_synth_noteon(synth, 0, 61, 127));
    
    // need to render again, to make the synth thread assign rvoice->dsp.sample, so that sample_unref() later really unrefs
    TEST_SUCCESS(fluid_synth_process(synth, 2048, 0, NULL, 0, NULL));
    FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): note on C#4, voice count=%d",
                           fluid_synth_get_active_voice_count(synth));

    FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): unload sounfont");
    TEST_SUCCESS(fluid_synth_sfunload(synth, id, 1));
    
    TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 61));
    
    // need to render yet again, to make the synth thread release the rvoice so it can be reclaimed by
    // fluid_synth_check_finished_voices()
    // need to render may more samples this time, so the voice makes it pass the release phase...
    TEST_SUCCESS(fluid_synth_process(synth, 204800, 0, NULL, 0, NULL));
    
    // make any API call to execute fluid_synth_check_finished_voices()
    FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): note off C#4, voice count=%d",
                           fluid_synth_get_active_voice_count(synth));
    
    // there must be one font scheduled for lazy unloading
    TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
    
    wait_and_free(synth, id, __func__);
}

static void test_default_polyphony(fluid_settings_t* settings, int with_rendering)
{
    enum { BUFSIZE = 128 };
    fluid_voice_t* buf[BUFSIZE];
    
    int id;
    fluid_synth_t *synth = new_fluid_synth(settings);
    TEST_ASSERT(synth != NULL);

    TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE);

    // load a sfont to synth
    TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1));
    // one sfont loaded
    TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
    
    TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127));
    
    if(with_rendering)
    {
        TEST_SUCCESS(fluid_synth_process(synth, fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
    }
    
    TEST_SUCCESS(fluid_synth_noteon(synth, 0, 61, 127));
    
    fluid_synth_get_voicelist(synth, buf, BUFSIZE, -1);
    
    TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
    
    if(with_rendering)
    {
        // make the synth thread assign rvoice->dsp.sample
        TEST_SUCCESS(fluid_synth_process(synth, 2 * fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
        
        TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
    }
    
    TEST_ASSERT(synth->fonts_to_be_unloaded == NULL);
    
    TEST_SUCCESS(fluid_synth_sfunload(synth, id, 1));
    
    // now, there must be one font scheduled for lazy unloading
    TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
    TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
    
    // noteoff the second note and render something
    TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 61));
    if(with_rendering)
    {
        TEST_SUCCESS(fluid_synth_process(synth, fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
    }
    
    // still 4 because key 61 is playing in release phase now
    TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
    // must be still running
    TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
    TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
    
    // noteoff the first note and render something
    TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 60));
    if(with_rendering)
    {
        TEST_SUCCESS(fluid_synth_process(synth, fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
    }
    
    // still 4 because keys 60 + 61 are playing in release phase now
    TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
    // must be still running
    TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
    TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
    
    if(with_rendering)
    {
        // render enough, to make the synth thread release the rvoice so it can be reclaimed by
        // fluid_synth_check_finished_voices()
        TEST_SUCCESS(fluid_synth_process(synth, 2048000, 0, NULL, 0, NULL));
        
        // this API call should reclaim the rvoices and call fluid_voice_stop()
        TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 0);
    }

    TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
    if(with_rendering)
    {
        // We want to see that the timer thread unloads the soundfont before we call delete_fluid_synth().
        // Wait to give the timer thread a chance to unload and finish.
        fluid_msleep(10 * fluid_timer_get_interval(fluid_list_get(synth->fonts_to_be_unloaded)));
        TEST_ASSERT(!fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
    }
    else
    {
        TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
    }
    
    wait_and_free(synth, id, __func__);
}

// this tests the soundfont loading API of the synth.
// might be expanded to test the soundfont loader as well...
int main(void)
{
    fluid_settings_t *settings = new_fluid_settings();
    TEST_ASSERT(settings != NULL);
    
    FLUID_LOG(FLUID_INFO, "Begin test_default_polyphony() with rendering");
    test_default_polyphony(settings, TRUE);
    FLUID_LOG(FLUID_INFO, "End test_default_polyphony()\n");
    
    FLUID_LOG(FLUID_INFO, "Begin test_default_polyphony() without rendering");
    test_default_polyphony(settings, FALSE);
    FLUID_LOG(FLUID_INFO, "End test_default_polyphony()\n");
    
    fluid_settings_setint(settings, "synth.polyphony", 2);
    
    FLUID_LOG(FLUID_INFO, "Begin test_after_polyphony_exceeded()");
    test_after_polyphony_exceeded(settings);
    FLUID_LOG(FLUID_INFO, "End test_after_polyphony_exceeded()\n");

    FLUID_LOG(FLUID_INFO, "Begin test_without_rendering()");
    test_without_rendering(settings);
    FLUID_LOG(FLUID_INFO, "End test_without_rendering()");
    
    delete_fluid_settings(settings);

    return EXIT_SUCCESS;
}