1 /* test.c - Test implementation
2 *
3 * Copyright 2010 Petteri Hintsanen <petterih@iki.fi>
4 *
5 * This file is part of abx.
6 *
7 * abx is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * abx is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with abx. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "test.h"
22 #include <glib.h>
23 #include <math.h>
24
25 /*
26 * Ongoing test structure.
27 */
28 typedef struct {
29 char isvalid;
30 unsigned int ntrials;
31 answer *answers; /* correct answers array with ntrials elements */
32 answer *guesses; /* guesses array with ntrials elements */
33 } Test;
34
35 static Test current_test = { 0, -1, NULL, NULL };
36
37 /*
38 * Initialize a new test with ntrials trials. Close current test
39 * first, if it is valid.
40 *
41 * Use the specified PortAudio device for audio output, or scan for
42 * default audio device if outdev is -1.
43 *
44 * Return
45 * 0 on success,
46 * 1 if playback could not be initialized for sample a,
47 * 2 if playback could not be initialized for sample b,
48 * 3 if the samples have differed duration, or
49 * 4 if ntrials is zero.
50 */
51 int
init_test(const char * a,const char * b,unsigned int ntrials,PaDeviceIndex outdev)52 init_test(const char *a, const char *b, unsigned int ntrials,
53 PaDeviceIndex outdev)
54 {
55 Test test;
56 int i, res;
57
58 /* g_debug("creating new test, a: '%s', b: '%s', ntrials: %d", */
59 /* a, b, ntrials); */
60
61 close_test();
62
63 res = init_playback(a, b, outdev);
64 if (res == 2 || res == 3 || res == 4) {
65 g_warning("can't initialize playback");
66 test.isvalid = 0;
67 return res - 1;
68 }
69
70 if (ntrials == 0) {
71 g_warning("number of trials is zero");
72 test.isvalid = 0;
73 return 4;
74 }
75
76 test.guesses = g_malloc(ntrials * sizeof(answer));
77 test.answers = g_malloc(ntrials * sizeof(answer));
78 for (i = 0; i < ntrials; i++) {
79 if (g_random_boolean() == TRUE) test.answers[i] = SAMPLE_A;
80 else test.answers[i] = SAMPLE_B;
81 test.guesses[i] = SAMPLE_NA;
82 }
83
84 test.ntrials = ntrials;
85 test.isvalid = 1;
86 current_test = test;
87 return 0;
88 }
89
90 /*
91 * Close the current test. Close playback and release all
92 * test-related resources. Mark the current test invalid. Return
93 * immediately if there is no test in progress.
94 */
95 void
close_test(void)96 close_test(void)
97 {
98 if (!current_test.isvalid) return;
99 close_playback();
100 g_free(current_test.guesses);
101 g_free(current_test.answers);
102 current_test.isvalid = 0;
103 }
104
105 /*
106 * Return non-zero if the current test is valid.
107 */
108 int
is_test_valid(void)109 is_test_valid(void)
110 {
111 return current_test.isvalid;
112 }
113
114 /*
115 * Get the listener's guess for trial t.
116 *
117 * Return
118 * SAMPLE_A if the listener has guessed sample A,
119 * SAMPLE_B if the listener has guessed sample B,
120 * SAMPLE_NA if the listener has not yet guessed, or
121 * -1 on error.
122 */
123 answer
get_guess(unsigned int t)124 get_guess(unsigned int t)
125 {
126 if (!is_test_valid()
127 || t < 0 || t > current_test.ntrials) return -1;
128 else return current_test.guesses[t];
129 }
130
131 /*
132 * Get the correct answer for trial t.
133 *
134 * Return
135 * SAMPLE_A if sample A is the correct answer,
136 * SAMPLE_B if sample B is the correct answer, or
137 * -1 on error.
138 */
139 answer
get_answer(unsigned int t)140 get_answer(unsigned int t)
141 {
142 if (!is_test_valid()
143 || t < 0 || t > current_test.ntrials) return -1;
144 else return current_test.answers[t];
145 }
146
147 /*
148 * Return the number of test trials, or -1 if no test is in progress.
149 */
150 int
num_test_trials(void)151 num_test_trials(void)
152 {
153 if (!is_test_valid()) return -1;
154 else return current_test.ntrials;
155 }
156
157 /*
158 * Set the guessed value to ans for trial t. Return 0 on success, or
159 * -1 on error.
160 */
161 int
set_guess(unsigned int t,answer ans)162 set_guess(unsigned int t, answer ans)
163 {
164 if (!is_test_valid()
165 || t < 0 || t > current_test.ntrials) {
166 return -1;
167 } else {
168 current_test.guesses[t] = ans;
169 return 0;
170 }
171 }
172
173 /*
174 * Calculate cumulative distribution function value F(k) for binomial
175 * distribution Bin(n, p).
176 *
177 * The algorithm is from Råde, Westergren: Mathematics Handbook, page
178 * 426, 4th ed., 1998. Studentlitteratur, Lund.
179 */
180 static double
cdf_binomial(unsigned int k,unsigned int n,double p)181 cdf_binomial(unsigned int k, unsigned int n, double p)
182 {
183 double f, F;
184 int x;
185
186 if (k >= n) return 1;
187
188 f = pow((1 - p), n); /* f is the pf, init to f(0) */
189 F = f; /* F is the cdf, init to F(k) */
190
191 for (x = 1; x <= k; x++) {
192 f = (p / (1 - p)) * ((1.0 * n - x + 1) / x) * f;
193 F += f;
194 }
195
196 return F;
197 }
198
199
200 /*
201 * Return the p-value for the test statistic under the null
202 * hypothesis.
203 */
204 double
calculate_p_value(void)205 calculate_p_value(void)
206 {
207 int i;
208 int ncorr = 0;
209 for (i = 0; i < num_test_trials(); i++) {
210 if (get_answer(i) == get_guess(i)) ncorr++;
211 }
212
213 if (ncorr == 0) {
214 return 1.0;
215 } else {
216 return 1.0 - cdf_binomial(ncorr - 1, num_test_trials(), 0.5);
217 }
218 }
219