1 module armos.audio.spectrumanalyzer;
2 
3 import std.typecons:Tuple;
4 import armos.audio.spectrum;
5 
6 /++
7 +/
8 enum BpmDetectionSource {
9     Beats,
10     // Tones
11     Volumes, 
12 }//enum BpmDetectionSource
13 
14 ///
15 Tuple!(F, F) analyzeBpmAndPhase(F = double, T)(in T[] sample, in size_t samplingRate, in size_t bufferSize, in BpmDetectionSource sourceType)
16 in{
17     assert(0<sample.length);
18 }
19 body{
20     //Calc difference between current frame and previous one.
21     F[] differedSample = new F[](sample.length);
22     differedSample[0] = 0;
23     for (int i = 1; i < sample.length; i++) {
24         import std.algorithm:max;
25         differedSample[i] = max(0.0, sample[i] - sample[i-1]);
26     }
27     
28     //Calculate each frame spectrums.
29     Spectrum!F[] spectrums = new Spectrum!F[](sample.length/bufferSize);
30     for (int i = 0; i < sample.length/bufferSize; i++) {
31         import std.stdio;
32         import std.conv:to;
33         writeln("analyzing... ", (i*bufferSize).to!F/sample.length.to!F*100f, "%");
34         
35         F[] bufferSizedsample = new F[](bufferSize*2);
36 
37         for (size_t j = 0; j < bufferSize*2; j++) {
38             size_t k = 0;
39             if(j + i*bufferSize < bufferSize*2){
40                 bufferSizedsample[j] = 0;
41             }else{
42                 k = j + i*bufferSize - bufferSize*2;
43                 bufferSizedsample[j] = differedSample[k];
44             }
45         }
46         
47         spectrums[i] = analyzeSpectrum!F(bufferSizedsample, samplingRate);
48     }
49     
50     //Select tone range by sourceType.
51     auto filteredSample = new F[](sample.length/bufferSize); 
52     switch (sourceType) {
53         case BpmDetectionSource.Beats:
54             foreach (size_t i, ref s; spectrums) {
55                 import std.algorithm:sum;
56                 filteredSample[i] = s.powers[0..$/108].sum;
57                 import std.math;
58                 assert(!isNaN(filteredSample[i]));
59             }
60             break;
61             
62         case BpmDetectionSource.Volumes:
63             foreach (size_t i, ref s; spectrums) {
64                 import std.algorithm:sum;
65                 filteredSample[i] = s.powers[0..$].sum;
66                 import std.math;
67                 assert(!isNaN(filteredSample[i]));
68             }
69             break;
70         default:
71             assert(false);
72     }
73     
74     auto bpmSpectrum = analyzeSpectrum!F(filteredSample, samplingRate/bufferSize);
75     
76     //filter by common bpm range.
77     auto filteredSpectrum = Spectrum!F();
78     {
79         auto r = bpmSpectrum.frequencyRange.arrayRange!("60<=a*60", "a*60<=150");
80         immutable first = r[0];
81         immutable last= r[1]+1;
82         filteredSpectrum.samplingRate = bpmSpectrum.samplingRate;
83         filteredSpectrum.frequencyRange = bpmSpectrum.frequencyRange[first..last];
84         filteredSpectrum.powers = bpmSpectrum.powers[first..last];
85         filteredSpectrum.phases = bpmSpectrum.phases[first..last];
86     }
87     
88     //Sort by power.
89     import std.range:zip;
90     import std.algorithm:sort;
91     zip(filteredSpectrum.frequencyRange, filteredSpectrum.powers, filteredSpectrum.phases).sort!"a[1]>b[1]";
92     
93     //Set bpm.
94     immutable resultBpm = filteredSpectrum.frequencyRange[0]*60.0;
95     
96     //Set phase.
97     immutable theta = filteredSpectrum.phases[0];
98     import std.math:PI;
99     immutable resultPhase = ((theta<0.0)?theta+2.0*PI:theta)/(2.0*PI*filteredSpectrum.frequencyRange[0]);
100     
101     return Tuple!(F, F)(resultBpm, resultPhase);
102 }
103 
104 ///
105 Spectrum!R analyzeSpectrum(R, T)(in T[] sample, in size_t samplingRate)
106 in{
107     static if(__traits(isFloating, T)){
108         import std.math:isNaN;
109         import std.conv;
110         foreach (size_t i, ref e; sample) {
111             assert(!isNaN(e), i.to!string);
112         }
113     }
114 }
115 out(spectrum){
116     import std.math:isNaN;
117     foreach (e; spectrum.powers) {
118         assert(!isNaN(e));
119     }
120     foreach (e; spectrum.phases) {
121         assert(!isNaN(e));
122     }
123     foreach (e; spectrum.frequencyRange) {
124         assert(!isNaN(e));
125     }
126 }
127 body{
128     import std.numeric:fft;
129     import std.range:iota;
130     import std.algorithm:map, fill;
131     import std.array:array;
132     import std.conv:to;
133     version(DigitalMars){
134         import std.math:nextPow2;
135     }
136     
137     auto fixedLengthSample = new T[](sample.length.nextPow2);
138     if((sample.length-1).nextPow2 != sample.length){
139         auto zeroSlice = new T[](sample.length.nextPow2 - sample.length);
140         zeroSlice.fill(T(0));
141         fixedLengthSample = zeroSlice ~ sample;
142     }else{
143         fixedLengthSample = sample.dup;
144     }
145     
146     
147     immutable windowLength = fixedLengthSample.length;
148     immutable transformLength = windowLength;//TODO
149     
150     auto frequencyRange = transformLength.iota.map!(x=>x.to!float*(samplingRate.to!float/transformLength.to!float).to!R)[0..$/2].array;
151     
152     const fx = fft(fixedLengthSample)[0..$/2];
153 
154     Spectrum!R powerSpectrum;
155 
156     powerSpectrum.samplingRate = samplingRate;
157     powerSpectrum.powers = fx.map!(x=>(x*typeof(x)(x.re, -x.im)).re/transformLength).array;
158     import std.math:atan2;
159     powerSpectrum.phases = fx.map!(x=>atan2(x.im, x.re)).array;
160     powerSpectrum.frequencyRange = frequencyRange;
161     return powerSpectrum;
162 }
163 
164 private{
165     version(LDC){
166         size_t nextPow2(size_t val) pure nothrow @nogc @safe
167         {
168             size_t res = 1;
169             while (res < val)
170                 res <<= 1;
171             return res;
172         }
173     }
174     
175     import std.typecons:Tuple;
176     Tuple!(size_t, size_t) arrayRange(string Cl, string Cu, T)(in T[] r){
177         import std.algorithm:find, filter;
178         import std.array:array;
179         immutable first = r.length - r.find!(Cl).length;
180         immutable last = r.filter!(Cu).array.length-1;
181         return Tuple!(size_t, size_t)(first, last);
182     }
183 }