% newamp_700c.m % % Copyright David Rowe 2017 % This program is distributed under the terms of the GNU General Public License % Version 2 % % Library of Octave functions for rate K, mel spaced % vector quantisation of spectral magnitudes used in Codec 2 700C mode. 1; melvq; % mbest VQ functions % -------------------------------------------------------------------------------- % Functions used by rate K mel work % -------------------------------------------------------------------------------- % General 2nd order parabolic interpolator. Used splines originally, % but this is much simpler and we don't need much accuracy. Given two % vectors of points xp and yp, find interpolated values y at points x function y = interp_para(xp, yp, x) assert( (length(xp) >=3) && (length(yp) >= 3) ); y = zeros(1,length(x)); k = 1; for i=1:length(x) xi = x(i); % k is index into xp of where we start 3 points used to form parabola while ((xp(k+1) < xi) && (k < (length(xp)-2))) k++; end x1 = xp(k); y1 = yp(k); x2 = xp(k+1); y2 = yp(k+1); x3 = xp(k+2); y3 = yp(k+2); %printf("k: %d i: %d xi: %f x1: %f y1: %f\n", k, i, xi, x1, y1); a = ((y3-y2)/(x3-x2)-(y2-y1)/(x2-x1))/(x3-x1); b = ((y3-y2)/(x3-x2)*(x2-x1)+(y2-y1)/(x2-x1)*(x3-x2))/(x3-x1); y(i) = a*(xi-x2)^2 + b*(xi-x2) + y2; end endfunction % simple linear interpolator function y = interp_linear(xp, yp, x) assert( (length(xp) == 2) && (length(yp) == 2) ); m = (yp(2) - yp(1))/(xp(2) - xp(1)); c = yp(1) - m*xp(1); y = zeros(1,length(x)); for i=1:length(x) y(i) = m*x(i) + c; end endfunction % quantise input sample to nearest value in table, optionally return binary code function [quant_out best_i bits] = quantise(levels, quant_in) % find closest quantiser level best_se = 1E32; for i=1:length(levels) se = (levels(i) - quant_in)^2; if se < best_se quant_out = levels(i); best_se = se; best_i = i; end end % convert index to binary bits numbits = ceil(log2(length(levels))); bits = zeros(1, numbits); for b=1:numbits bits(b) = bitand(best_i-1,2^(numbits-b)) != 0; end endfunction % Quantisation functions for Wo in log freq domain function index = encode_log_Wo(Wo, bits) Wo_levels = 2.^bits; Wo_min = 2*pi/160; Wo_max = 2*pi/20; norm = (log10(Wo) - log10(Wo_min))/(log10(Wo_max) - log10(Wo_min)); index = floor(Wo_levels * norm + 0.5); index = max(index, 0); index = min(index, Wo_levels-1); endfunction function Wo = decode_log_Wo(index, bits) Wo_levels = 2.^bits; Wo_min = 2*pi/160; Wo_max = 2*pi/20; step = (log10(Wo_max) - log10(Wo_min))/Wo_levels; Wo = log10(Wo_min) + step*index; Wo = 10 .^ Wo; endfunction % convert index to binary bits function bits = index_to_bits(value, numbits) levels = 2.^numbits; bits = zeros(1, numbits); for b=1:numbits bits(b) = bitand(value,2^(numbits-b)) != 0; end end function value = bits_to_index(bits, numbits) value = 2.^(numbits-1:-1:0) * bits; endfunction % Determine a phase spectra from a magnitude spectra % from http://www.dsprelated.com/showcode/20.php % Haven't _quite_ figured out how this works but have to start somewhere .... % % TODO: we may be able to sample at a lower rate, like mWo % but start with something that works function [phase Gdbfk s Aw] = determine_phase(model, f, Nfft=512, ak) Fs = 8000; max_amp = 80; L = min([model(f,2) max_amp-1]); Wo = model(f,1); sample_freqs_kHz = (Fs/1000)*[0:Nfft/2]/Nfft; % fft frequency grid (nonneg freqs) Am = model(f,3:(L+2)); AmdB = 20*log10(Am); rate_L_sample_freqs_kHz = (1:L)*Wo*4/pi; Gdbfk = interp_para(rate_L_sample_freqs_kHz, AmdB, sample_freqs_kHz); % optional input of aks for testing if nargin == 4 Aw = 1 ./ fft(ak,Nfft); Gdbfk = 20*log10(abs(Aw(1:Nfft/2+1))); end [phase s] = mag_to_phase(Gdbfk, Nfft); endfunction % Non linear sampling of frequency axis, reducing the "rate" is a % first step before VQ function mel = ftomel(fHz) mel = floor(2595*log10(1+fHz/700)+0.5); endfunction function rate_K_sample_freqs_kHz = mel_sample_freqs_kHz(K) mel_start = ftomel(200); mel_end = ftomel(3700); step = (mel_end-mel_start)/(K-1); mel = mel_start:step:mel_end; rate_K_sample_freqs_Hz = 700*((10 .^ (mel/2595)) - 1); rate_K_sample_freqs_kHz = rate_K_sample_freqs_Hz/1000; endfunction function [rate_K_surface rate_K_sample_freqs_kHz] = resample_const_rate_f_mel(model, K) rate_K_sample_freqs_kHz = mel_sample_freqs_kHz(K); rate_K_surface = resample_const_rate_f(model, rate_K_sample_freqs_kHz); endfunction % Resample Am from time-varying rate L=floor(pi/Wo) to fixed rate K. This can be viewed % as a 3D surface with time, freq, and ampitude axis. function [rate_K_surface rate_K_sample_freqs_kHz] = resample_const_rate_f(model, rate_K_sample_freqs_kHz) % convert rate L=pi/Wo amplitude samples to fixed rate K max_amp = 80; [frames col] = size(model); K = length(rate_K_sample_freqs_kHz); rate_K_surface = zeros(frames, K); for f=1:frames Wo = model(f,1); L = min([model(f,2) max_amp-1]); Am = model(f,3:(L+2)); AmdB = 20*log10(Am); %pre = 10*log10((1:L)*Wo*4/(pi*0.3)); %AmdB += pre; % clip between peak and peak -50dB, to reduce dynamic range AmdB_peak = max(AmdB); AmdB(find(AmdB < (AmdB_peak-50))) = AmdB_peak-50; rate_L_sample_freqs_kHz = (1:L)*Wo*4/pi; %rate_K_surface(f,:) = interp1(rate_L_sample_freqs_kHz, AmdB, rate_K_sample_freqs_kHz, "spline", "extrap"); rate_K_surface(f,:) = interp_para(rate_L_sample_freqs_kHz, AmdB, rate_K_sample_freqs_kHz); %printf("\r%d/%d", f, frames); end %printf("\n"); endfunction % Take a rate K surface and convert back to time varying rate L function [model_ AmdB_] = resample_rate_L(model, rate_K_surface, rate_K_sample_freqs_kHz) max_amp = 80; [frames col] = size(model); model_ = zeros(frames, max_amp+2); for f=1:frames Wo = model(f,1); L = model(f,2); rate_L_sample_freqs_kHz = (1:L)*Wo*4/pi; % back down to rate L % AmdB_ = interp1(rate_K_sample_freqs_kHz, rate_K_surface(f,:), rate_L_sample_freqs_kHz, "spline", 0); AmdB_ = interp_para([ 0 rate_K_sample_freqs_kHz 4], [0 rate_K_surface(f,:) 0], rate_L_sample_freqs_kHz); model_(f,1) = Wo; model_(f,2) = L; model_(f,3:(L+2)) = 10 .^ (AmdB_(1:L)/20); end endfunction % Post Filter, has a big impact on speech quality after VQ. When used % on a mean removed rate K vector, it raises formants, and suppresses % anti-formants. As it manipulates amplitudes, we normalise energy to % prevent clipping or large level variations. pf_gain of 1.2 to 1.5 % (dB) seems to work OK. Good area for further investigations and % improvements in speech quality. function vec = post_filter(vec, sample_freq_kHz, pf_gain = 1.5, voicing) % vec is rate K vector describing spectrum of current frame % lets pre-emp before applying PF. 20dB/dec over 300Hz pre = 20*log10(sample_freq_kHz/0.3); vec += pre; levels_before_linear = 10 .^ (vec/20); e_before = sum(levels_before_linear .^2); vec *= pf_gain; levels_after_linear = 10 .^ (vec/20); e_after = sum(levels_after_linear .^2); gain = e_after/e_before; gaindB = 10*log10(gain); vec -= gaindB; vec -= pre; endfunction % construct energy quantiser table, and save to text file to include in C function energy_q = create_energy_q energy_q = 10 + 40/16*(0:15); endfunction function save_energy_q(fn) energy_q = create_energy_q; f = fopen(fn, "wt"); fprintf(f, "1 %d\n", length(energy_q)); for n=1:length(energy_q) fprintf(f, "%f\n", energy_q(n)); end fclose(f); endfunction % save's VQ in format that can be compiled by Codec 2 build system function save_vq(vqset, filenameprefix) [Nvec order stages] = size(vqset); for s=1:stages fn = sprintf("%s_%d.txt", filenameprefix, s); f = fopen(fn, "wt"); fprintf(f, "%d %d\n", order, Nvec); for n=1:Nvec for k=1:order fprintf(f, "% 8.4f ", vqset(n,k,s)); end fprintf(f, "\n"); end fclose(f); end endfunction % Decoder side interpolation of Wo and voicing, to go from 25 Hz % sample rate used over channel to 100Hz internal sample rate of Codec % 2. function [Wo_ voicing_] = interp_Wo_v(Wo1, Wo2, voicing1, voicing2) M = 4; max_amp = 80; Wo_ = zeros(1,M); voicing_ = zeros(1,M); if !voicing1 && !voicing2 Wo_(1:M) = 2*pi/100; end if voicing1 && !voicing2 Wo_(1:M/2) = Wo1; Wo_(M/2+1:M) = 2*pi/100; voicing_(1:M/2) = 1; end if !voicing1 && voicing2 Wo_(1:M/2) = 2*pi/100; Wo_(M/2+1:M) = Wo2; voicing_(M/2+1:M) = 1; end if voicing1 && voicing2 Wo_samples = [Wo1 Wo2]; Wo_(1:M) = interp_linear([1 M+1], Wo_samples, 1:M); voicing_(1:M) = 1; end #{ printf("f: %d f+M/2: %d Wo: %f %f (%f %%) v: %d %d \n", f, f+M/2, model(f,1), model(f+M/2,1), 100*abs(model(f,1) - model(f+M/2,1))/model(f,1), voicing(f), voicing(f+M/2)); for i=f:f+M/2-1 printf(" f: %d v: %d v_: %d Wo: %f Wo_: %f\n", i, voicing(i), voicing_(i), model(i,1), model_(i,1)); end #} endfunction % Equaliser in front of EQ, see vq_700c_eq.m for development version function [rate_K_vec eq] = front_eq(rate_K_vec, eq) [tmp K] = size(rate_K_vec); ideal = [ 8 10 12 14 14*ones(1,K-1-4) -20]; gain = 0.02; update = rate_K_vec - ideal; eq = (1-gain)*eq + gain*update; eq(find(eq < 0)) = 0; endfunction