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 }