public static Int32 RoundUp(double x) { if (GlobalMembersUtil.FMod(x, 1.0) == 0) { return((Int32)x); } else { return((Int32)x + 1); } }
/// <summary> /// Get Settings from User Input or Config file /// </summary> /// <param name="bands">Specifies the desired height of the spectrogram (bands)</param> /// <param name="samplecount">Number of samples</param> /// <param name="samplerate">Sample rate</param> /// <param name="basefreq">Base frequency in Hertz</param> /// <param name="maxfreq">Maximum frequency in Hertz</param> /// <param name="pixpersec">Time resolution in Pixels Per Second</param> /// <param name="bandsperoctave">Frequency resolution in Bands Per Octave</param> /// <param name="Xsize">Specifies the desired width of the spectrogram</param> /// <param name="mode">0 = Analysis mode, 1 = Synthesis mode</param> public static void SettingsInput(ref int bands, ref int samplecount, ref int samplerate, ref double basefreq, ref double maxfreq, ref double pixpersec, ref double bandsperoctave, ref int Xsize, int mode) { /* mode : * 0 = Analysis mode * 1 = Synthesis mode */ int i = 0; double gf; double f; double trash; double ma; // maximum allowed frequency int unset = 0; // count of unset interdependant settings int set_min = 0; int set_max = 0; int set_bpo = 0; int set_y = 0; int set_pps = 0; int set_x = 0; #if DEBUG Console.Write("settingsinput...\n"); #endif // Path to the configuration file string configFileName = "arss.conf"; TextReader freqcfg = null; if (File.Exists(configFileName)) { freqcfg = new StreamReader(configFileName); } if (samplerate == 0) // if we're in synthesis mode and that no samplerate has been defined yet { if (GlobalMembersUtil.quiet) { Console.Error.WriteLine("Please provide a sample rate for your output sound.\nUse --sample-rate (-r).\nExiting with error.\n"); Environment.Exit(1); } //********Output settings querying******** Console.Write("Sample rate [44100] : "); // Query for a samplerate samplerate = (int)GlobalMembersUtil.GetFloat(); if (samplerate == 0 || samplerate < -2147483647) // The -2147483647 check is used for the sake of compatibility with C90 { samplerate = 44100; // Default value } //--------Output settings querying-------- } if (basefreq != 0) // count unset interdependant frequency-domain settings { set_min = 1; } if (maxfreq != 0) { set_max = 1; } if (bandsperoctave != 0) { set_bpo = 1; } if (bands != 0) { set_y = 1; } unset = set_min + set_max + set_bpo + set_y; if (unset == 4) // if too many settings are set { if (mode == 0) { Console.Error.WriteLine("You have set one parameter too many.\nUnset either --min-freq (-min), --max-freq (-max), --bpo (-b)\nExiting with error.\n"); } if (mode == 1) { Console.Error.WriteLine("You have set one parameter too many.\nUnset either --min-freq (-min), --max-freq (-max), --bpo (-b) or --height (-y)\nExiting with error.\n"); } Environment.Exit(1); } if (pixpersec != 0) { set_pps = 1; } if (Xsize != 0) { set_x = 1; } if (set_x + set_pps == 2 && mode == 0) { Console.Error.WriteLine("You cannot define both the image width and the horizontal resolution.\nUnset either --pps (-p) or --width (-x)\nExiting with error.\n"); Environment.Exit(1); } if (freqcfg != null) // load settings from file if it exists { if (basefreq == 0) // load values from it if they haven't been set yet { basefreq = double.Parse(freqcfg.ReadLine()); } else { trash = double.Parse(freqcfg.ReadLine()); } if (maxfreq == 0) { maxfreq = double.Parse(freqcfg.ReadLine()); } else { trash = double.Parse(freqcfg.ReadLine()); } if (bandsperoctave == 0) { bandsperoctave = double.Parse(freqcfg.ReadLine()); } else { trash = double.Parse(freqcfg.ReadLine()); } if (pixpersec == 0) { pixpersec = double.Parse(freqcfg.ReadLine()); } else { trash = double.Parse(freqcfg.ReadLine()); } } else { if (basefreq == 0) // otherwise load default values { basefreq = 27.5; } if (maxfreq == 0) { maxfreq = 20000; } if (bandsperoctave == 0) { bandsperoctave = 12; } if (pixpersec == 0) { pixpersec = 150; } } if (freqcfg != null) { freqcfg.Close(); } if (unset < 3 && set_min == 0) { if (GlobalMembersUtil.quiet) { Console.Error.WriteLine("Please define a minimum frequency.\nUse --min-freq (-min).\nExiting with error.\n"); Environment.Exit(1); } Console.Write("Min. frequency (Hz) [{0:f3}]: ", basefreq); gf = GlobalMembersUtil.GetFloat(); if (gf != 0) { basefreq = gf; } unset++; set_min = 1; } basefreq /= samplerate; // turn basefreq from Hz to fractions of samplerate if (unset < 3 && set_bpo == 0) { if (GlobalMembersUtil.quiet) { Console.Error.WriteLine("Please define a bands per octave setting.\nUse --bpo (-b).\nExiting with error.\n"); Environment.Exit(1); } Console.Write("Bands per octave [{0:f3}]: ", bandsperoctave); gf = GlobalMembersUtil.GetFloat(); if (gf != 0) { bandsperoctave = gf; } unset++; set_bpo = 1; } if (unset < 3 && set_max == 0) { i = 0; do { i++; f = basefreq * Math.Pow(GlobalMembersDsp.LOGBASE, (i / bandsperoctave)); }while (f < 0.5); ma = basefreq * Math.Pow(GlobalMembersDsp.LOGBASE, ((i - 2) / bandsperoctave)) * samplerate; // max allowed frequency if (maxfreq > ma) { if (GlobalMembersUtil.FMod(ma, 1.0) == 0.0) { maxfreq = ma; // replaces the "Upper frequency limit above Nyquist frequency" warning } else { maxfreq = ma - GlobalMembersUtil.FMod(ma, 1.0); } } if (mode == 0) // if we're in Analysis mode { if (GlobalMembersUtil.quiet) { Console.Error.WriteLine("Please define a maximum frequency.\nUse --max-freq (-max).\nExiting with error.\n"); Environment.Exit(1); } Console.Write("Max. frequency (Hz) (up to {0:f3}) [{1:f3}]: ", ma, maxfreq); gf = GlobalMembersUtil.GetFloat(); if (gf != 0) { maxfreq = gf; } if (maxfreq > ma) { if (GlobalMembersUtil.FMod(ma, 1.0) == 0.0) { maxfreq = ma; // replaces the "Upper frequency limit above Nyquist frequency" warning } else { maxfreq = ma - GlobalMembersUtil.FMod(ma, 1.0); } } } unset++; set_max = 1; } if (set_min == 0) { basefreq = Math.Pow(GlobalMembersDsp.LOGBASE, (bands - 1) / bandsperoctave) * maxfreq; // calculate the lower frequency in Hz Console.Write("Min. frequency : {0:f3} Hz\n", basefreq); basefreq /= samplerate; } if (set_max == 0) { maxfreq = Math.Pow(GlobalMembersDsp.LOGBASE, (bands - 1) / bandsperoctave) * (basefreq * samplerate); // calculate the upper frequency in Hz Console.Write("Max. frequency : {0:f3} Hz\n", maxfreq); } if (set_y == 0) { bands = 1 + (int)GlobalMembersUtil.RoundOff(bandsperoctave * (GlobalMembersUtil.Log(maxfreq) - GlobalMembersUtil.Log(basefreq * samplerate))); Console.Write("Bands : {0:D}\n", bands); } if (set_bpo == 0) { if (GlobalMembersDsp.LOGBASE == 1.0) { bandsperoctave = maxfreq / samplerate; } else { bandsperoctave = (bands - 1) / (GlobalMembersUtil.Log(maxfreq) - GlobalMembersUtil.Log(basefreq * samplerate)); } Console.Write("Bands per octave : {0:f3}\n", bandsperoctave); } if (set_x == 1 && mode == 0) // If we're in Analysis mode and that X is set (by the user) { pixpersec = (double)Xsize * (double)samplerate / (double)samplecount; // calculate pixpersec Console.Write("Pixels per second : {0:f3}\n", pixpersec); } if ((mode == 0 && set_x == 0 && set_pps == 0) || (mode == 1 && set_pps == 0)) // If in Analysis mode none are set or pixpersec isn't set in Synthesis mode { if (GlobalMembersUtil.quiet) { Console.Error.WriteLine("Please define a pixels per second setting.\nUse --pps (-p).\nExiting with error.\n"); Environment.Exit(1); } Console.Write("Pixels per second [{0:f3}]: ", pixpersec); gf = GlobalMembersUtil.GetFloat(); if (gf != 0) { pixpersec = gf; } } basefreq *= samplerate; // turn back to Hz just for the sake of writing to the file TextWriter freqcfgOut = new StreamWriter(configFileName); // saving settings to a file if (freqcfgOut == null) { Console.Error.WriteLine("Cannot write to configuration file"); Environment.Exit(1); } freqcfgOut.WriteLine(basefreq); freqcfgOut.WriteLine(maxfreq); freqcfgOut.WriteLine(bandsperoctave); freqcfgOut.WriteLine(pixpersec); freqcfgOut.Close(); basefreq /= samplerate; // basefreq is now in fraction of the sampling rate instead of Hz pixpersec /= samplerate; // pixpersec is now in fraction of the sampling rate instead of Hz }
/// <summary> /// Analyze the input /// </summary> /// <param name="s">Sound (original signal)</param> /// <param name="samplecount">Sample count (samplecount is the original signal's orginal length)</param> /// <param name="samplerate">Sample rate</param> /// <param name="Xsize">Specifies the desired width of the spectrogram</param> /// <param name="bands">Specifies the desired height of the spectrogram (bands is the total count of bands)</param> /// <param name="bpo">Frequency resolution in Bands Per Octave</param> /// <param name="pixpersec">Time resolution in Pixels Per Second</param> /// <param name="basefreq">Minimum frequency in Hertz</param> /// <returns>Image</returns> public static double[][] Analyze(ref double[] s, ref int samplecount, ref int samplerate, ref int Xsize, ref int bands, ref double bpo, ref double pixpersec, ref double basefreq) { int i = 0; // i is a general purpose iterator int ib = 0; // ib is the band iterator int Mb = 0; // Mb is the length of the original signal once zero-padded (always even) int Mc = 0; // Mc is the length of the filtered signal int Md = 0; // Md is the length of the envelopes once downsampled (constant) int Fa = 0; // Fa is the index of the band's start in the frequency domain int Fd = 0; // Fd is the index of the band's end in the frequency domain double[][] @out; // @out is the output image double[] h; double[] freq; // freq is the band's central frequency double[] t; // t is temporary pointer to a new version of the signal being worked on double coef; // coef is a temporary modulation coefficient double La; // La is the log2 of the frequency of Fa double Ld; // Ld is the log2 of the frequency of Fd double Li; // Li is the iterative frequency between La and Ld defined logarithmically double maxfreq; // maxfreq is the central frequency of the last band freq = GlobalMembersDsp.FrequencyArray(basefreq, bands, bpo); if (LOGBASE == 1.0) { maxfreq = bpo; // in linear mode we use bpo to store the maxfreq since we couldn't deduce maxfreq otherwise } else { maxfreq = basefreq * Math.Pow(LOGBASE, ((double)(bands - 1) / bpo)); } Xsize = (int)(samplecount * pixpersec); if (GlobalMembersUtil.FMod((double)samplecount * pixpersec, 1.0) != 0.0) { // round-up Xsize++; } Console.Write("Image size : {0:D}x{1:D}\n", Xsize, bands); @out = new double[bands][]; clockA = GlobalMembersUtil.GetTime(); //********ZEROPADDING******** Note : Don't do it in Circular mode // Mb is the length of the original signal once zero-padded (always even) if (LOGBASE == 1.0) { Mb = samplecount - 1 + (int)GlobalMembersUtil.RoundOff(5.0 / freq[1] - freq[0]); // linear mode } else { Mb = samplecount - 1 + (int)GlobalMembersUtil.RoundOff(2.0 * 5.0 / ((freq[0] * Math.Pow(LOGBASE, -1.0 / (bpo))) * (1.0 - Math.Pow(LOGBASE, -1.0 / bpo)))); } if (Mb % 2 == 1) // if Mb is odd { Mb++; // make it even (for the sake of simplicity) } Mc = 0; Md = 0; Mb = (int)GlobalMembersUtil.RoundOff((double)GlobalMembersUtil.NextPrime((int)GlobalMembersUtil.RoundOff(Mb * pixpersec)) / pixpersec); // Md is the length of the envelopes once downsampled (constant) Md = (int)GlobalMembersUtil.RoundOff(Mb * pixpersec); // realloc to the zeropadded size Array.Resize <double>(ref s, Mb); // zero-out the padded area. Equivalent of : for (i=samplecount; i<Mb; i++) s[i] = 0; for (i = samplecount; i < Mb; i++) { s[i] = 0; } //--------ZEROPADDING-------- //Export.exportCSV(String.Format("samples_before_fft.csv"), s, 256); GlobalMembersDsp.FFT(ref s, ref s, Mb, FFTMethod.DFT); // In-place FFT of the original zero-padded signal //Export.exportCSV(String.Format("samples_after_fft.csv"), s, 256); for (ib = 0; ib < bands; ib++) { //********Filtering******** Fa = (int)GlobalMembersUtil.RoundOff(GlobalMembersDsp.LogPositionToFrequency((double)(ib - 1) / (double)(bands - 1), basefreq, maxfreq) * Mb); Fd = (int)GlobalMembersUtil.RoundOff(GlobalMembersDsp.LogPositionToFrequency((double)(ib + 1) / (double)(bands - 1), basefreq, maxfreq) * Mb); La = GlobalMembersDsp.FrequencyToLogPosition((double)Fa / (double)Mb, basefreq, maxfreq); Ld = GlobalMembersDsp.FrequencyToLogPosition((double)Fd / (double)Mb, basefreq, maxfreq); if (Fd > Mb / 2) { Fd = Mb / 2; // stop reading if reaching the Nyquist frequency } if (Fa < 1) { Fa = 1; } // Mc is the length of the filtered signal Mc = (Fd - Fa) * 2 + 1; // '*2' because the filtering is on both real and imaginary parts, // '+1' for the DC. // No Nyquist component since the signal length is necessarily odd if (Md > Mc) // if the band is going to be too narrow { Mc = Md; } if (Md < Mc) // round the larger bands up to the next integer made of 2^n * 3^m { Mc = GlobalMembersUtil.NextPrime(Mc); } Console.Write("{0,4:D}/{1:D} (FFT size: {2,6:D}) {3:f2} Hz - {4:f2} Hz\r", ib + 1, bands, Mc, (double)Fa * samplerate / Mb, (double)Fd * samplerate / Mb); @out[bands - ib - 1] = new double[Mc + 1]; for (i = 0; i < Fd - Fa; i++) { Li = GlobalMembersDsp.FrequencyToLogPosition((double)(i + Fa) / (double)Mb, basefreq, maxfreq); // calculation of the logarithmic position Li = (Li - La) / (Ld - La); coef = 0.5 - 0.5 * Math.Cos(2.0 * PI * Li); // Hann function @out[bands - ib - 1][i + 1] = s[i + 1 + Fa] * coef; @out[bands - ib - 1][Mc - 1 - i] = s[Mb - Fa - 1 - i] * coef; } //--------Filtering-------- //Export.exportCSV(String.Format("test/band_{0}_filtered.csv", bands-ib-1), @out[bands-ib-1]); //********90° rotation******** h = new double[Mc + 1]; // Rotation : Re' = Im; Im' = -Re for (i = 0; i < Fd - Fa; i++) { h[i + 1] = @out[bands - ib - 1][Mc - 1 - i]; // Re' = Im h[Mc - 1 - i] = -@out[bands - ib - 1][i + 1]; // Im' = -Re } //--------90° rotation-------- //********Envelope detection******** //Export.exportCSV(String.Format("test/band_{0}_rotated.csv", bands-ib-1), @out[bands-ib-1]); GlobalMembersDsp.FFT(ref @out[bands - ib - 1], ref @out[bands - ib - 1], Mc, FFTMethod.IDFT); // In-place IFFT of the filtered band signal GlobalMembersDsp.FFT(ref h, ref h, Mc, FFTMethod.IDFT); // In-place IFFT of the filtered band signal rotated by 90° //Export.exportCSV(String.Format("test/band_{0}_before.csv", bands-ib-1), @out[bands-ib-1]); for (i = 0; i < Mc; i++) { // TODO: why does the above crash?! //for (i = 0; i < @out[bands-ib-1].Length; i++) { // Magnitude of the analytic signal double x = @out[bands - ib - 1][i]; double y = h[i]; double xSquared = x * x; double ySquared = y * y; double mag = Math.Sqrt(xSquared + ySquared); @out[bands - ib - 1][i] = mag; } Array.Clear(h, 0, h.Length); //--------Envelope detection-------- //********Downsampling******** if (Mc < Md) // if the band doesn't have to be resampled { Array.Resize <double>(ref @out[bands - ib - 1], Md); // simply ignore the end of it } if (Mc > Md) // If the band *has* to be downsampled { t = @out[bands - ib - 1]; @out[bands - ib - 1] = GlobalMembersDsp.BlackmanDownsampling(@out[bands - ib - 1], Mc, Md); // Blackman downsampling Array.Clear(t, 0, t.Length); } //--------Downsampling-------- Array.Resize <double>(ref @out[bands - ib - 1], Xsize); // Tail chopping //Export.exportCSV(String.Format("test/band_{0}_after.csv", bands-ib-1), @out[bands-ib-1]); } Console.Write("\n"); GlobalMembersDsp.Normalize(ref @out, ref Xsize, ref bands, 1.0); //Export.exportCSV(String.Format("out.csv"), @out); return(@out); }
/// <summary> /// Interpolation based on an estimate of the Blackman Square function, /// which is a Blackman function convolved with a square. /// It's like smoothing the result of a nearest neighbour interpolation /// with a Blackman FIR /// </summary> /// <param name="in">Signal in</param> /// <param name="out">Signal out</param> /// <param name="Mi">Mi is the original signal's length</param> /// <param name="Mo">Mo is the output signal's length</param> /// <param name="lut">Blackman Square Lookup Table</param> /// <param name="lut_size">Defines the number of elements in the Blackman Square look-up table. It's best to make it small enough to be entirely cached</param> public static void BlackmanSquareInterpolation(ref double[] @in, ref double[] @out, ref int Mi, ref int Mo, ref double[] lut, int lut_size) { int i = 0; // general purpose iterators int j = 0; double pos_in; // position in the original signal double x; // position of the iterator in the blackman_square(x) formula double ratio; // scaling ratio (> 1.0) double ratio_i; // ratio^-1 double coef; // Blackman square final coefficient double pos_lut; // Index on the look-up table int pos_luti = 0; // Integer index on the look-up table double mod_pos; // modulo of the position on the look-up table double y0; // values of the two closest values on the LUT double y1; double foo = (double)lut_size / 3.0; int j_start = 0; // boundary values for the j loop int j_stop = 0; /* * Mi is the original signal's length * Mo is the output signal's length */ ratio = (double)Mi / Mo; ratio_i = 1.0 / ratio; for (i = 0; i < Mo; i++) { pos_in = (double)i * ratio; j_stop = (int)(pos_in + 1.5); j_start = j_stop - 2; if (j_start < 0) { j_start = 0; } // The boundary check is done after j_start is calculated to avoid miscalculating it if (j_stop >= Mi) { j_stop = Mi - 1; } for (j = j_start; j <= j_stop; j++) { x = j - pos_in + 1.5; // calculate position within the Blackman square function in the [0.0 ; 3.0] range pos_lut = x * foo; pos_luti = (int)pos_lut; mod_pos = GlobalMembersUtil.FMod(pos_lut, 1.0); // modulo of the index if (pos_luti + 1 < lut.Length) { y0 = lut[pos_luti]; // interpolate linearly between the two closest values y1 = lut[pos_luti + 1]; coef = y0 + mod_pos * (y1 - y0); // linear interpolation @out[i] += @in[j] * coef; // convolve } } } }