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