From b9dc46c7013f0e0640974ae69366191ed39debdd Mon Sep 17 00:00:00 2001 From: cnlohr Date: Thu, 28 Mar 2019 06:29:48 -0400 Subject: [PATCH 01/14] First inroads to turbo8 --- colorchord2/Makefile | 2 +- colorchord2/default.conf | 5 +- colorchord2/notefinder.c | 4 + colorchord2/turbo8bit.conf | 103 +++++++++++++++ embeddedcommon/DFT32.c | 3 - embeddedcommon/DFT8Turbo.c | 264 +++++++++++++++++++++++++++++++++++++ embeddedcommon/DFT8Turbo.h | 9 ++ 7 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 colorchord2/turbo8bit.conf create mode 100644 embeddedcommon/DFT8Turbo.c create mode 100644 embeddedcommon/DFT8Turbo.h diff --git a/colorchord2/Makefile b/colorchord2/Makefile index f393fc3..8620946 100644 --- a/colorchord2/Makefile +++ b/colorchord2/Makefile @@ -17,7 +17,7 @@ LDLIBS:=-lpthread -lasound -lm -lpulse-simple -lpulse -ludev -lrt CFLAGS:=-g -O0 -flto -Wall -ffast-math -I../embeddedcommon -I. -DICACHE_FLASH_ATTR= EXTRALIBS:=-lusb-1.0 -colorchord : os_generic.o main.o dft.o decompose.o filter.o color.o notefinder.o util.o outdrivers.o $(RAWDRAW) $(SOUND) $(OUTS) parameters.o chash.o hook.o ../embeddedcommon/DFT32.o configs.o +colorchord : os_generic.o main.o dft.o decompose.o filter.o color.o notefinder.o util.o outdrivers.o $(RAWDRAW) $(SOUND) $(OUTS) parameters.o chash.o hook.o ../embeddedcommon/DFT32.o configs.o ../embeddedcommon/DFT8Turbo.o gcc -o $@ $^ $(CFLAGS) $(LDLIBS) $(EXTRALIBS) $(RAWDRAWLIBS) diff --git a/colorchord2/default.conf b/colorchord2/default.conf index c8d2a9d..297c676 100644 --- a/colorchord2/default.conf +++ b/colorchord2/default.conf @@ -58,8 +58,9 @@ octaves = 5 # 1 = DFT Progressive # 2 = DFT Progressive Integer # 3 = DFT Progressive Integer Skippy -# 4 = Integer, 32-Bit, Progressive, Skippy. -do_progressive_dft = 4 +# 4 = Integer, 32-Bit, Progressive, Skippy. (wow, this actually works) +# 5 = 8-bit turbo test. +do_progressive_dft = 5 filter_iter = 2 diff --git a/colorchord2/notefinder.c b/colorchord2/notefinder.c index f0e1178..1f0a01c 100644 --- a/colorchord2/notefinder.c +++ b/colorchord2/notefinder.c @@ -11,6 +11,7 @@ #include "filter.h" #include "decompose.h" #include "DFT32.h" +#include "DFT8Turbo.h" struct NoteFinder * CreateNoteFinder( int spsRec ) { @@ -199,6 +200,9 @@ void RunNoteFinder( struct NoteFinder * nf, const float * audio_stream, int head case 4: DoDFTProgressive32( dftbins, nf->frequencies, freqs, audio_stream, head, buffersize, nf->dft_q, nf->dft_speedup ); break; + case 5: + DoDFT8BitTurbo( dftbins, nf->frequencies, freqs, audio_stream, head, buffersize, nf->dft_q, nf->dft_speedup ); + break; default: fprintf( stderr, "Error: No DFT Seleced\n" ); } diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf new file mode 100644 index 0000000..9dd6b04 --- /dev/null +++ b/colorchord2/turbo8bit.conf @@ -0,0 +1,103 @@ +# This is the configuration file for colorchord. +# Most values are already defaulted in the software. +# This file is constantly checked for new versions. +# \r, and ; are used as terminators, so you can put +# multiple entries on the same line. + +#Whether to limit the control loop to ~60ish FPS. +cpu_autolimit = 1 + +#General GUI properties. +title = PA Test +set_screenx = 720 +set_screeny = 480 + +#Sound properties. +buffer = 384 +play = 0 +rec = 1 +channels = 2 +samplerate = 16000 +wininput = -1 + +#Compiled version will default this. +#sound_source = ALSA +#-1 indicates left and right, 0 left, 1 right. + +sample_channel = -1 +sourcename = default +#alsa_output.pci-0000_00_1f.3.analog-stereo.monitor +#default +# alsa_output.pci-0000_00_1b.0.analog-stereo.monitor +#alsa_output.pci-0000_00_1f.3.analog-stereo.monitor << New laptop +#use pactl list | grep pci- | grep monitor + +################################## +# General ColorChord properties. # +################################## + +# How much to amplify the incoming signal. +amplify = 2.0 + +# What is the base note? I.e. the lowest note. +# Note that it won't have very much impact until an octave up though! +base_hz = 110 + +# This is only used when dealing with the slow decompose (now defunct) +# decompose_iterations = 1000 +# default_sigma = 1.4000 + +# DFT properties for the DFT up top. +dft_iir = 0.6 +dft_q = 20.0000 +dft_speedup = 1000.0000 +octaves = 5 + +# Should we use a progressive DFT? +# 0 = DFT Quick +# 1 = DFT Progressive +# 2 = DFT Progressive Integer +# 3 = DFT Progressive Integer Skippy +# 4 = Integer, 32-Bit, Progressive, Skippy. (wow, this actually works) +# 5 = 8-bit turbo test. +do_progressive_dft = 5 + + +filter_iter = 2 +filter_strength = .5 + +# How many bins per octave to use? +freqbins = 24 + +# For the final note information... How much to slack everything? +note_attach_amp_iir = 0.3500 +note_attach_amp_iir2 = 0.250 +note_attach_freq_iir = 0.3000 + +#How many bins a note can jump from frame to frame to be considered a slide. +#this is used to prevent notes from popping in and out a lot. +note_combine_distance = 0.5000 +note_jumpability = 1.8000 +note_minimum_new_distribution_value = 0.0200 +note_out_chop = 0.05000 + +#compress_coefficient = 4.0 +#compress_exponent = .5 + + +#======================================================================= +#Outputs + + +This is a vornoi thing: +outdrivers = OutputVoronoi, DisplayArray +lightx = 64 +lighty = 32 +fromsides = 1 +shape_cutoff = 0.03 +satamp = 5.000 +amppow = 2.510 +distpow = 1.500 + + + diff --git a/embeddedcommon/DFT32.c b/embeddedcommon/DFT32.c index 21df4dd..b587c6c 100644 --- a/embeddedcommon/DFT32.c +++ b/embeddedcommon/DFT32.c @@ -353,6 +353,3 @@ void DoDFTProgressive32( float * outbins, float * frequencies, int bins, const f #endif - - - diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c new file mode 100644 index 0000000..5c91e34 --- /dev/null +++ b/embeddedcommon/DFT8Turbo.c @@ -0,0 +1,264 @@ +#include +#include +#include "DFT8Turbo.h" +#include + +#include + +#define MAX_FREQS (24) +#define OCTAVES (5) + + +/* + * The first thought was using an integration map and only operating when we need to, to pull the data out. + * Now we're doing the thing below this block comment + int16_t accumulated_total; //2 bytes + int16_t last_accumulated_total_at_bin[MAX_FREQS*2]; //24 * 2 * sizeof(int16_t) = 96 bytes. + uint8_t current_time; //1 byte + uint8_t placecode[MAX_FREQS]; +*/ +//OK... We don't have enough ram to sum everything... can we do something wacky with multiple ocatives to sum everything better? +//i.e. +// +// 4332322132212210 +// +// ++++++++++++++++----------------- +// ++++++++-------- +// ++++----++++---- +// ++--++--++--++-- +// +-+-+-+-+-+-+-+- +// +// Don't forget we need to do this for sin and cos. +// Can we instead of making this plusses, make it a multiplier? +// How can we handle sin+cos? +// +// Is it possible to do this for every frame? I.e. for each of the 24 notes, multiply with their current place in table? +// That's interesting. It's not like a sin table. +// There is no "multiply" in the attiny instruction set for attiny85. +// There is, however for attiny402 + +//Question: Can we do five octaves, or does this need to be balanced? +//Question2: Should we weight higher octaves? + + +//ATTiny402: 256x8 RAM, 4096x8 FLASH LPM: 3 cycles + FMUL: 2 cycles << Do stacked sin waves? +//ATtiny85: 512x8 RAM, 8192x8 FLASH LPM: 3 cycles + NO MULTIPLY << Do square waves? + + +/* Approaches: + + on ATtiny402: Stacked sin approach. + Say 16 MHz, though 12 MHz is interesting... + 16k SPS: 1k cycles per; say 24 bins per; 41 cycles per bin = hard. But is it too hard? + 20 cycles per s/c. + read place in stacked table (8? bits) 3 cycles + + //Inner loop = 17 cycles. + read stacked table (8 bits), 3 cycles + fractional multiply table with current value. 2 cycles + read current running for note 2 cycles (LDS = 3 cycles) + subtract a shifted version, to make it into an IIR. (4 cycles) + add in current values. (2 cycles) + store data back to ram (2 cycles) + advance place in stacked table (8?bits) 1 cycle + + store place in stacked table (8? bits) 3 cycles? + + //What if we chunk ADC updates into groups of 4 or 8? + //This is looking barely possible. + + on attiny85: scheduled adds/subtracts (like a stacked-square-wave-table) + //XXX TODO! + +*/ + +/* Ok... Let's think about the ATTiny402. 256x8 RAM + 4096x8 FLASH. + + * We can create a table which has all octaves overlaid. + * We would need to keep track of: + * 12 x 2 x 2 = 48 bytes = Current sin/cos values. + * 12 x 2 = 24 bytes = Current place in table. = 72 bytes + * We would need to store: + * The layered lookup table. If possible, keep @ 256 bytes to simplify math ops. + * The speed by which each note needs to advance. + * We would need to: + * Read current running place. X 8 cycles + * Use that place to look up into sin table. 3 cycles + * Read running val 4 cycles best case + * Multiply out the sin + IIR 5 cycles + * Store running val 4 cycles best case + * Cos-advance that place to look up into sin table. 4 cycles + * Read running val 4 cycles best case + * Multiply out the sin + IIR 5 cycles + * Store running val 4 cycles best case. + * Read how much to advance X by. 4 cycles + * (Cos^2+Sin^2) 8? + * Store it. 4 cycles best case. + * = 48 x 12 = 576 cycles. Assume 10 MHz @ 16k SPS. We're OK (625 samples) +*/ + +// Observation: The two tables are actually mirror images of each other, well diagonally mirrored. That's odd. But, would take CPU to exploit. + +#define SSTABLESIZE 256 +int8_t spikysin_interleved_cos[SSTABLESIZE*2]; +uint16_t advancespeed[MAX_FREQS]; + +static int CompTableWithPhase( int nelements, float phase, int scaling ) +{ + int highest = 0; + int i; + for( i = 0; i < nelements; i++ ) + { + float taued = i * 3.141592 * 2.0 / nelements; + int o; + float combsin = 0; + for( o = 0; o < OCTAVES; o++ ) + { + combsin += sin( taued * (1< highest ) highest = csadapt; + if( -csadapt > highest ) highest = -csadapt; + + if( csadapt > 127 ) csadapt = 127; + if( csadapt < -127 ) csadapt = -127; //tricky: Keep balanced. + spikysin_interleved_cos[i*2+0] = csadapt; + + float combcos = 0; + for( o = 0; o < OCTAVES; o++ ) + { + combcos += cos( taued * (1< highest ) highest = csadapt; + if( -csadapt > highest ) highest = -csadapt; + + if( csadapt > 127 ) csadapt = 127; + if( csadapt < -127 ) csadapt = -127; //tricky: Keep balanced. + spikysin_interleved_cos[i*2+1] = csadapt; + } + return highest; +} + + +static int Setup( float * frequencies, int bins ) +{ + int i; + + //Since start position/phase is arbitrary, we should try several to see which gives us the best dynamic range. + float tryphase = 0; + + float bestphase = 0; + int highest_val_at_best_phase = 1000000; + + for( tryphase = 0; tryphase < 3.14159; tryphase += 0.001 ) + { + int highest = CompTableWithPhase( SSTABLESIZE, tryphase, 65536 ); + if( highest < highest_val_at_best_phase ) + { + highest_val_at_best_phase = highest; + bestphase = tryphase; + } + } + printf( "Best comp: %f : %d\n", bestphase, highest_val_at_best_phase ); + + CompTableWithPhase( SSTABLESIZE, bestphase, (65536*128)/highest_val_at_best_phase ); + +// for( i = 0; i < SSTABLESIZE; i++ ) +// { +// printf( "%d %d\n", spikysin_interleved_cos[i*2+0], spikysin_interleved_cos[i*2+1] ); +// } + + for( i = 0; i < MAX_FREQS; i++ ) + { + //frequencies[i] = SPS / Freq + // Need to decide how quickly we sweep through the table. + advancespeed[i] = 256.0 /* fixed point */ * 256.0 /* size of table */ / frequencies[i]; + //printf( "%f\n", frequencies[i] ); + } + return 0; +} + + +/* +uint8_t spikysin_interleved_cos[256*2]; +uint16_t advancespeed[MAX_FREQS]; +*/ + +float toutbins[MAX_FREQS]; + +struct notedat +{ + uint16_t time; + int32_t sinm; + int32_t cosm; +}; + +static struct notedat nd[MAX_FREQS]; + +void Turbo8BitRun( int8_t adcval ) +{ + int i; + for( i = 0; i < MAX_FREQS; i++ ) + { + uint16_t ct = nd[i].time; + int32_t muxres; + int32_t running; + int32_t rdesc, rdess; + int8_t ss = spikysin_interleved_cos[(ct>>8) + 0]; + muxres = ((int16_t)adcval * ss) >> 8; + running = nd[i].cosm; + running += muxres; + rdesc = running >> 8; + running -= rdesc>>6; + nd[i].cosm = running; + + int8_t sc = spikysin_interleved_cos[(ct>>8) + 1]; + muxres = ((int16_t)adcval * sc) >> 8; + running = nd[i].sinm; + running += muxres; + rdess = running>>8; + running -= rdess>>6; + nd[i].sinm = running; + + nd[i].time = ct + advancespeed[i]; + toutbins[i] = rdess * rdess + rdesc * rdesc; + //printf( "%d %d = %f %p\n", rdess, rdesc, toutbins[i], &toutbins[i] ); + } +} + + +void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ) +{ + static int is_setup; + if( !is_setup ) { is_setup = 1; Setup( frequencies, bins ); } + static int last_place; + int i; + + for( i = last_place; i != place_in_data_buffer; i = (i+1)%size_of_data_buffer ) + { + int16_t ifr1 = (int16_t)( ((databuffer[i]) ) * 4095 ); + //ifr1 += 4095; + Turbo8BitRun( ifr1>>5 ); + } + + for( i = 0; i < bins; i++ ) + { + outbins[i] = 0; + } + for( i = 0; i < MAX_FREQS; i++ ) + { + int iss = nd[i].sinm; + int isc = nd[i].cosm; + int mux = iss * iss + isc * isc; + if( mux == 0 ) mux = 1; + outbins[i+MAX_FREQS] = sqrt(mux)/1000.0; + } + +} + + diff --git a/embeddedcommon/DFT8Turbo.h b/embeddedcommon/DFT8Turbo.h new file mode 100644 index 0000000..257cf89 --- /dev/null +++ b/embeddedcommon/DFT8Turbo.h @@ -0,0 +1,9 @@ +#ifndef _DFT8TURBO_H +#define _DFT8TURBO_H + +/* Note: Frequencies must be precompiled. */ + +void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ); + +#endif + From 0d230751256dc2c35db01405fa17002eb3dfae9c Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sun, 7 Apr 2019 03:47:58 -0700 Subject: [PATCH 02/14] progress on turbo8. Still not working - also new algebra problem found. --- colorchord2/Makefile | 2 +- colorchord2/colorchord.exe | Bin 181248 -> 0 bytes colorchord2/main.c | 95 +++++++++++++++++++++++++++---------- colorchord2/turbo8bit.conf | 18 +++---- embeddedcommon/DFT8Turbo.c | 77 +++++++++++++++++++++--------- 5 files changed, 134 insertions(+), 58 deletions(-) delete mode 100644 colorchord2/colorchord.exe diff --git a/colorchord2/Makefile b/colorchord2/Makefile index 8620946..e83c55c 100644 --- a/colorchord2/Makefile +++ b/colorchord2/Makefile @@ -26,4 +26,4 @@ colorchord.exe : os_generic.c main.c dft.c decompose.c filter.c color.c notefin clean : - rm -rf *.o *~ colorchord colorchord.exe embeddedcc + rm -rf *.o *~ ../embeddedcommon/*.o colorchord colorchord.exe embeddedcc diff --git a/colorchord2/colorchord.exe b/colorchord2/colorchord.exe deleted file mode 100644 index 7afcc430312579b5bd01608209b4082f9e95ab46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181248 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 diff --git a/colorchord2/main.c b/colorchord2/main.c index adc00c6..02f3a5c 100644 --- a/colorchord2/main.c +++ b/colorchord2/main.c @@ -54,6 +54,7 @@ float cpu_autolimit_interval = 0.016; REGISTER_PARAM( cpu_autolimit_interval, P int sample_channel = -1;REGISTER_PARAM( sample_channel, PAINT ); int showfps = 0; REGISTER_PARAM( showfps, PAINT ); float in_amplitude = 1; REGISTER_PARAM( in_amplitude, PAFLOAT ); +int shim_sinewave = 0; REGISTER_PARAM( shim_sinewave, PAINT ); struct NoteFinder * nf; @@ -96,6 +97,9 @@ void HandleMotion( int x, int y, int mask ) void SoundCB( float * out, float * in, int samplesr, int * samplesp, struct SoundDriver * sd ) { + static og_sema_t tss; + if( !tss ) tss = OGCreateSema(); + else OGLockSema( tss ); int channelin = sd->channelsRec; // int channelout = sd->channelsPlay; //*samplesp = 0; @@ -106,53 +110,90 @@ void SoundCB( float * out, float * in, int samplesr, int * samplesp, struct Soun int i; int j; - for( i = 0; i < samplesr; i++ ) + if( out ) { - if( out ) + for( i = 0; i < samplesr; i++ ) { for( j = 0; j < channelin; j++ ) { out[i*channelin+j] = 0; } } + } - if( sample_channel < 0 ) - { - float fo = 0; - for( j = 0; j < channelin; j++ ) - { - float f = in[i*channelin+j]; - if( f >= -1 && f <= 1 ) - { - fo += f; - } - else - { - fo += (f>0)?1:-1; -// printf( "Sound fault A %d/%d %d/%d %f\n", j, channelin, i, samplesr, f ); - } - } + if( shim_sinewave ) + { + static double sinplace; + static double sinfreq = 0; + static int msp; - fo /= channelin; - sound[soundhead] = fo*in_amplitude; - soundhead = (soundhead+1)%SOUNDCBSIZE; - } - else + for( i = 0; i < samplesr; i++ ) { - float f = in[i*channelin+sample_channel]; + sinfreq = 3.14159 * 2 * 110 * pow( 2, 5.0/12 ) / 16000; +// sinfreq += .000001; +// if( sinfreq > .2 ) sinfreq = 0; + sinplace += sinfreq; + if( sinplace > (3.14159*2) ) sinplace -= 3.14159 * 2; + + msp++; + float f = sin( sinplace ); + //if( msp % 20000 > 10000 ) f = 0; if( f > 1 || f < -1 ) { f = (f>0)?1:-1; } - //printf( "Sound fault B %d/%d\n", i, samplesr ); sound[soundhead] = f*in_amplitude; soundhead = (soundhead+1)%SOUNDCBSIZE; - } } + else + { + if( sample_channel < 0 ) + { + for( i = 0; i < samplesr; i++ ) + { + float fo = 0; + for( j = 0; j < channelin; j++ ) + { + float f = in[i*channelin+j]; + if( f >= -1 && f <= 1 ) + { + fo += f; + } + else + { + fo += (f>0)?1:-1; + // printf( "Sound fault A %d/%d %d/%d %f\n", j, channelin, i, samplesr, f ); + } + } + + fo /= channelin; + sound[soundhead] = fo*in_amplitude; + soundhead = (soundhead+1)%SOUNDCBSIZE; + } + } + else + { + for( i = 0; i < samplesr; i++ ) + { + float f = in[i*channelin+sample_channel]; + + if( f > 1 || f < -1 ) + { + f = (f>0)?1:-1; + } + + + //printf( "Sound fault B %d/%d\n", i, samplesr ); + sound[soundhead] = f*in_amplitude; + soundhead = (soundhead+1)%SOUNDCBSIZE; + } + } + } + SoundEventHappened( samplesr, in, 0, channelin ); if( out ) @@ -160,6 +201,8 @@ void SoundCB( float * out, float * in, int samplesr, int * samplesp, struct Soun SoundEventHappened( samplesr, out, 1, sd->channelsPlay ); } *samplesp = samplesr; + OGUnlockSema( tss ); + } int main(int argc, char ** argv) diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 9dd6b04..6420cee 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -89,15 +89,17 @@ note_out_chop = 0.05000 #Outputs +shim_sinewave = 0 + This is a vornoi thing: -outdrivers = OutputVoronoi, DisplayArray -lightx = 64 -lighty = 32 -fromsides = 1 -shape_cutoff = 0.03 -satamp = 5.000 -amppow = 2.510 -distpow = 1.500 +outdrivers = DisplayArray +#lightx = 64 +#lighty = 32 +#fromsides = 1 +#shape_cutoff = 0.03 +#satamp = 5.000 +#amppow = 2.510 +#distpow = 1.500 diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index 5c91e34..9c7b7b4 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -100,8 +100,8 @@ // Observation: The two tables are actually mirror images of each other, well diagonally mirrored. That's odd. But, would take CPU to exploit. #define SSTABLESIZE 256 -int8_t spikysin_interleved_cos[SSTABLESIZE*2]; -uint16_t advancespeed[MAX_FREQS]; +int8_t spikysin_interleved_cos[SSTABLESIZE][2]; +uint32_t advancespeed[MAX_FREQS]; static int CompTableWithPhase( int nelements, float phase, int scaling ) { @@ -117,14 +117,14 @@ static int CompTableWithPhase( int nelements, float phase, int scaling ) combsin += sin( taued * (1< highest ) highest = csadapt; if( -csadapt > highest ) highest = -csadapt; if( csadapt > 127 ) csadapt = 127; - if( csadapt < -127 ) csadapt = -127; //tricky: Keep balanced. - spikysin_interleved_cos[i*2+0] = csadapt; + if( csadapt < -128 ) csadapt = -128; //tricky: Keep balanced. + spikysin_interleved_cos[i][0] = csadapt; float combcos = 0; for( o = 0; o < OCTAVES; o++ ) @@ -132,14 +132,14 @@ static int CompTableWithPhase( int nelements, float phase, int scaling ) combcos += cos( taued * (1< highest ) highest = csadapt; if( -csadapt > highest ) highest = -csadapt; if( csadapt > 127 ) csadapt = 127; - if( csadapt < -127 ) csadapt = -127; //tricky: Keep balanced. - spikysin_interleved_cos[i*2+1] = csadapt; + if( csadapt < -128 ) csadapt = -128; //tricky: Keep balanced. + spikysin_interleved_cos[i][1] = csadapt; } return highest; } @@ -166,7 +166,11 @@ static int Setup( float * frequencies, int bins ) } printf( "Best comp: %f : %d\n", bestphase, highest_val_at_best_phase ); - CompTableWithPhase( SSTABLESIZE, bestphase, (65536*128)/highest_val_at_best_phase ); + //Set this because we would overflow the sinm and cosm regs if we don't. This is sort of like a master volume. + //use this as that input volume knob thing. + float further_reduce = 1.0; + + CompTableWithPhase( SSTABLESIZE, bestphase, (65536*128*further_reduce)/highest_val_at_best_phase ); // for( i = 0; i < SSTABLESIZE; i++ ) // { @@ -177,7 +181,7 @@ static int Setup( float * frequencies, int bins ) { //frequencies[i] = SPS / Freq // Need to decide how quickly we sweep through the table. - advancespeed[i] = 256.0 /* fixed point */ * 256.0 /* size of table */ / frequencies[i]; + advancespeed[i] = 65536 * 256.0 /* fixed point */ * 256.0 /* size of table */ / frequencies[i]; //printf( "%f\n", frequencies[i] ); } return 0; @@ -193,7 +197,7 @@ float toutbins[MAX_FREQS]; struct notedat { - uint16_t time; + uint32_t time; int32_t sinm; int32_t cosm; }; @@ -205,30 +209,53 @@ void Turbo8BitRun( int8_t adcval ) int i; for( i = 0; i < MAX_FREQS; i++ ) { - uint16_t ct = nd[i].time; + uint32_t ct = nd[i].time; int32_t muxres; int32_t running; int32_t rdesc, rdess; - int8_t ss = spikysin_interleved_cos[(ct>>8) + 0]; - muxres = ((int16_t)adcval * ss) >> 8; + uint8_t * spikysintable = &spikysin_interleved_cos[(ct>>24)][0]; + + int8_t ss = *(spikysintable++); + + #define DECIR 8 + + muxres = ((int16_t)adcval * ss + (1<<(DECIR-1)) ) >> (DECIR); running = nd[i].cosm; running += muxres; rdesc = running >> 8; - running -= rdesc>>6; - nd[i].cosm = running; + running -= rdesc >> 3; - int8_t sc = spikysin_interleved_cos[(ct>>8) + 1]; - muxres = ((int16_t)adcval * sc) >> 8; + nd[i].cosm = running; +if( i == 0) printf( "MRX %5d %9d %9d %9d %9d\n", muxres, adcval, ss, running, nd[i].sinm ); + int8_t sc = *(spikysintable++); + muxres = ((int16_t)adcval * sc + (1<<(DECIR-1)) ) >> (DECIR); running = nd[i].sinm; running += muxres; + rdess = running>>8; - running -= rdess>>6; + running -= rdess >> 3; + nd[i].sinm = running; nd[i].time = ct + advancespeed[i]; + toutbins[i] = rdess * rdess + rdesc * rdesc; //printf( "%d %d = %f %p\n", rdess, rdesc, toutbins[i], &toutbins[i] ); } + + static uint8_t stater; +/* stater++; + if( stater == 16 ) + { + stater = 0; + for( i = 0; i < MAX_FREQS; i++ ) + { + nd[i].sinm -= nd[i].sinm >> 12; + nd[i].cosm -= nd[i].cosm >> 12; + nd[i].sinm += 8; + nd[i].cosm += 8; + } + }*/ } @@ -243,8 +270,10 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float { int16_t ifr1 = (int16_t)( ((databuffer[i]) ) * 4095 ); //ifr1 += 4095; - Turbo8BitRun( ifr1>>5 ); + //ifr1 += 512; + Turbo8BitRun( ifr1>>5 ); //6 = Actually only feed algorithm numbers from -64 to 63. } + last_place = place_in_data_buffer; for( i = 0; i < bins; i++ ) { @@ -252,11 +281,13 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float } for( i = 0; i < MAX_FREQS; i++ ) { - int iss = nd[i].sinm; - int isc = nd[i].cosm; + int iss = nd[i].sinm>>8; + int isc = nd[i].cosm>>8; int mux = iss * iss + isc * isc; if( mux == 0 ) mux = 1; - outbins[i+MAX_FREQS] = sqrt(mux)/1000.0; + if( i == 0 ) + printf( "MUX: %d %d\n", isc, iss ); + outbins[i+MAX_FREQS] = sqrt(mux)/200.0; } } From 0a056db03dbc4241c9674434b92cc959be49a43c Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sat, 20 Apr 2019 01:05:05 -0700 Subject: [PATCH 03/14] try another method for turbo operations. --- embeddedcommon/DFT8Turbo.c | 286 +++++++++--------------------- embeddedcommon/DFT8Turbo.c.attic | 295 +++++++++++++++++++++++++++++++ embeddedcommon/DFT8Turbo.h.attic | 9 + 3 files changed, 384 insertions(+), 206 deletions(-) create mode 100644 embeddedcommon/DFT8Turbo.c.attic create mode 100644 embeddedcommon/DFT8Turbo.h.attic diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index 9c7b7b4..02791c4 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -17,245 +17,120 @@ uint8_t current_time; //1 byte uint8_t placecode[MAX_FREQS]; */ -//OK... We don't have enough ram to sum everything... can we do something wacky with multiple ocatives to sum everything better? -//i.e. -// -// 4332322132212210 -// -// ++++++++++++++++----------------- -// ++++++++-------- -// ++++----++++---- -// ++--++--++--++-- -// +-+-+-+-+-+-+-+- -// -// Don't forget we need to do this for sin and cos. -// Can we instead of making this plusses, make it a multiplier? -// How can we handle sin+cos? -// -// Is it possible to do this for every frame? I.e. for each of the 24 notes, multiply with their current place in table? -// That's interesting. It's not like a sin table. -// There is no "multiply" in the attiny instruction set for attiny85. -// There is, however for attiny402 +/* + So, the idea here is we would keep a running total of the current ADC value, kept away in a int16_t. + It is constantly summing, so we can take an integral of it. Or rather an integral range. -//Question: Can we do five octaves, or does this need to be balanced? -//Question2: Should we weight higher octaves? + Over time, we perform operations like adding or subtracting from a current place. -//ATTiny402: 256x8 RAM, 4096x8 FLASH LPM: 3 cycles + FMUL: 2 cycles << Do stacked sin waves? -//ATtiny85: 512x8 RAM, 8192x8 FLASH LPM: 3 cycles + NO MULTIPLY << Do square waves? - - -/* Approaches: - - on ATtiny402: Stacked sin approach. - Say 16 MHz, though 12 MHz is interesting... - 16k SPS: 1k cycles per; say 24 bins per; 41 cycles per bin = hard. But is it too hard? - 20 cycles per s/c. - read place in stacked table (8? bits) 3 cycles - - //Inner loop = 17 cycles. - read stacked table (8 bits), 3 cycles - fractional multiply table with current value. 2 cycles - read current running for note 2 cycles (LDS = 3 cycles) - subtract a shifted version, to make it into an IIR. (4 cycles) - add in current values. (2 cycles) - store data back to ram (2 cycles) - advance place in stacked table (8?bits) 1 cycle - - store place in stacked table (8? bits) 3 cycles? - - //What if we chunk ADC updates into groups of 4 or 8? - //This is looking barely possible. - - on attiny85: scheduled adds/subtracts (like a stacked-square-wave-table) - //XXX TODO! - +NOTE: + Optimizations: + Only use 16 bins, lets action table be 16-bits wide. */ -/* Ok... Let's think about the ATTiny402. 256x8 RAM + 4096x8 FLASH. +int16_t running_integral; +int16_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. - * We can create a table which has all octaves overlaid. - * We would need to keep track of: - * 12 x 2 x 2 = 48 bytes = Current sin/cos values. - * 12 x 2 = 24 bytes = Current place in table. = 72 bytes - * We would need to store: - * The layered lookup table. If possible, keep @ 256 bytes to simplify math ops. - * The speed by which each note needs to advance. - * We would need to: - * Read current running place. X 8 cycles - * Use that place to look up into sin table. 3 cycles - * Read running val 4 cycles best case - * Multiply out the sin + IIR 5 cycles - * Store running val 4 cycles best case - * Cos-advance that place to look up into sin table. 4 cycles - * Read running val 4 cycles best case - * Multiply out the sin + IIR 5 cycles - * Store running val 4 cycles best case. - * Read how much to advance X by. 4 cycles - * (Cos^2+Sin^2) 8? - * Store it. 4 cycles best case. - * = 48 x 12 = 576 cycles. Assume 10 MHz @ 16k SPS. We're OK (625 samples) -*/ -// Observation: The two tables are actually mirror images of each other, well diagonally mirrored. That's odd. But, would take CPU to exploit. -#define SSTABLESIZE 256 -int8_t spikysin_interleved_cos[SSTABLESIZE][2]; -uint32_t advancespeed[MAX_FREQS]; +uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. +uint8_t highbit_table[2< highest ) highest = csadapt; - if( -csadapt > highest ) highest = -csadapt; +#define ACTIONTABLESIZE 512 - if( csadapt > 127 ) csadapt = 127; - if( csadapt < -128 ) csadapt = -128; //tricky: Keep balanced. - spikysin_interleved_cos[i][0] = csadapt; - - float combcos = 0; - for( o = 0; o < OCTAVES; o++ ) - { - combcos += cos( taued * (1< highest ) highest = csadapt; - if( -csadapt > highest ) highest = -csadapt; - - if( csadapt > 127 ) csadapt = 127; - if( csadapt < -128 ) csadapt = -128; //tricky: Keep balanced. - spikysin_interleved_cos[i][1] = csadapt; - } - return highest; -} +uint16_t * placeintable; +//Put this in flash. +uint32_t actiontable[ACTIONTABLESIZE]; static int Setup( float * frequencies, int bins ) { int i; - - //Since start position/phase is arbitrary, we should try several to see which gives us the best dynamic range. - float tryphase = 0; - - float bestphase = 0; - int highest_val_at_best_phase = 1000000; - - for( tryphase = 0; tryphase < 3.14159; tryphase += 0.001 ) + printf( "BINS: %d\n", bins ); + for( i = bins-MAX_FREQS; i < bins; i++ ) { - int highest = CompTableWithPhase( SSTABLESIZE, tryphase, 65536 ); - if( highest < highest_val_at_best_phase ) + int topbin = i - (bins-MAX_FREQS); + float f = frequencies[i]/2.0; //2x the hits (sin/cos) + float hits_per_table = (float)ACTIONTABLESIZE/f; + int dhrpertable = (int)(hits_per_table+.5);//TRICKY: You might think you need to have even number of hits (sin/cos), but you don't! It can flip sin/cos each time through the table! + float err = (8000./((float)ACTIONTABLESIZE/dhrpertable) - 8000./f)/(8000./f); + //Perform an op every X samples. How well does this map into units of 1024? + printf( "%d %f -> hits per 1024: %f %d (%f error)\n", topbin, f, (float)ACTIONTABLESIZE/f, dhrpertable, err * 100.0 ); + + float advance_per_step = dhrpertable/(float)ACTIONTABLESIZE; + float fvadv = 0.0; + int j; + int actions = 0; + int countset = 0; + + //XXX TODO Tricky: We need to start fadv off at such a place that there won't be a hicchup when going back around to 0. + + for( j = 0; j < ACTIONTABLESIZE; j++ ) { - highest_val_at_best_phase = highest; - bestphase = tryphase; + if( fvadv >= 0.5 ) + { + actiontable[j] |= 1<> longestzeroes) & 1) == 0 ); longestzeroes++ ); + //longestzeroes goes: 255, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, ... + //This isn't great, because we need to also know whether we are attacking the SIN side or the COS side. + highbit_table[i] = longestzeroes; } - return 0; + //Repeat the highbit table in the second half. + //XXX PICK UP HERE + //Encode into highbit_table which cell is being operated on + //Also, do the * MAX_FREQS here. That will + + + + placeintable = actiontable; + // for( i = 0; i < ACTIONTABLESIZE; i++ ) printf( "%08x\n", actiontable[i] ); } -/* -uint8_t spikysin_interleved_cos[256*2]; -uint16_t advancespeed[MAX_FREQS]; -*/ -float toutbins[MAX_FREQS]; +int16_t running_integral; +int16_t cossindata[MAX_FREQS*OCTAVES*2]; +uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. +uint16_t * placeintable; -struct notedat -{ - uint32_t time; - int32_t sinm; - int32_t cosm; -}; +//Put this in flash. +uint32_t actiontable[ACTIONTABLESIZE]; -static struct notedat nd[MAX_FREQS]; void Turbo8BitRun( int8_t adcval ) { - int i; - for( i = 0; i < MAX_FREQS; i++ ) + uint32_t actions = *(placeintable++); + if( placeintable == &actiontable[ACTIONTABLESIZE] ) placeintable = actiontable; + int b; + for( b = 0; b < MAX_FREQS; b++ ) { - uint32_t ct = nd[i].time; - int32_t muxres; - int32_t running; - int32_t rdesc, rdess; - uint8_t * spikysintable = &spikysin_interleved_cos[(ct>>24)][0]; + if( ! ((1<>= 1; + int octavebit = op & ((1<> (DECIR); - running = nd[i].cosm; - running += muxres; - rdesc = running >> 8; - running -= rdesc >> 3; - - nd[i].cosm = running; -if( i == 0) printf( "MRX %5d %9d %9d %9d %9d\n", muxres, adcval, ss, running, nd[i].sinm ); - int8_t sc = *(spikysintable++); - muxres = ((int16_t)adcval * sc + (1<<(DECIR-1)) ) >> (DECIR); - running = nd[i].sinm; - running += muxres; - - rdess = running>>8; - running -= rdess >> 3; - - nd[i].sinm = running; - - nd[i].time = ct + advancespeed[i]; - - toutbins[i] = rdess * rdess + rdesc * rdesc; - //printf( "%d %d = %f %p\n", rdess, rdesc, toutbins[i], &toutbins[i] ); + //if( b == 0 ) printf( "%d\n", whichoctave ); + //XXX TODO Optimization: Use a table, since octavebit can only be 0...31. } - - static uint8_t stater; -/* stater++; - if( stater == 16 ) - { - stater = 0; - for( i = 0; i < MAX_FREQS; i++ ) - { - nd[i].sinm -= nd[i].sinm >> 12; - nd[i].cosm -= nd[i].cosm >> 12; - nd[i].sinm += 8; - nd[i].cosm += 8; - } - }*/ } @@ -269,12 +144,11 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float for( i = last_place; i != place_in_data_buffer; i = (i+1)%size_of_data_buffer ) { int16_t ifr1 = (int16_t)( ((databuffer[i]) ) * 4095 ); - //ifr1 += 4095; - //ifr1 += 512; Turbo8BitRun( ifr1>>5 ); //6 = Actually only feed algorithm numbers from -64 to 63. } last_place = place_in_data_buffer; +#if 0 for( i = 0; i < bins; i++ ) { outbins[i] = 0; @@ -289,7 +163,7 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float printf( "MUX: %d %d\n", isc, iss ); outbins[i+MAX_FREQS] = sqrt(mux)/200.0; } - +#endif } diff --git a/embeddedcommon/DFT8Turbo.c.attic b/embeddedcommon/DFT8Turbo.c.attic new file mode 100644 index 0000000..9c7b7b4 --- /dev/null +++ b/embeddedcommon/DFT8Turbo.c.attic @@ -0,0 +1,295 @@ +#include +#include +#include "DFT8Turbo.h" +#include + +#include + +#define MAX_FREQS (24) +#define OCTAVES (5) + + +/* + * The first thought was using an integration map and only operating when we need to, to pull the data out. + * Now we're doing the thing below this block comment + int16_t accumulated_total; //2 bytes + int16_t last_accumulated_total_at_bin[MAX_FREQS*2]; //24 * 2 * sizeof(int16_t) = 96 bytes. + uint8_t current_time; //1 byte + uint8_t placecode[MAX_FREQS]; +*/ +//OK... We don't have enough ram to sum everything... can we do something wacky with multiple ocatives to sum everything better? +//i.e. +// +// 4332322132212210 +// +// ++++++++++++++++----------------- +// ++++++++-------- +// ++++----++++---- +// ++--++--++--++-- +// +-+-+-+-+-+-+-+- +// +// Don't forget we need to do this for sin and cos. +// Can we instead of making this plusses, make it a multiplier? +// How can we handle sin+cos? +// +// Is it possible to do this for every frame? I.e. for each of the 24 notes, multiply with their current place in table? +// That's interesting. It's not like a sin table. +// There is no "multiply" in the attiny instruction set for attiny85. +// There is, however for attiny402 + +//Question: Can we do five octaves, or does this need to be balanced? +//Question2: Should we weight higher octaves? + + +//ATTiny402: 256x8 RAM, 4096x8 FLASH LPM: 3 cycles + FMUL: 2 cycles << Do stacked sin waves? +//ATtiny85: 512x8 RAM, 8192x8 FLASH LPM: 3 cycles + NO MULTIPLY << Do square waves? + + +/* Approaches: + + on ATtiny402: Stacked sin approach. + Say 16 MHz, though 12 MHz is interesting... + 16k SPS: 1k cycles per; say 24 bins per; 41 cycles per bin = hard. But is it too hard? + 20 cycles per s/c. + read place in stacked table (8? bits) 3 cycles + + //Inner loop = 17 cycles. + read stacked table (8 bits), 3 cycles + fractional multiply table with current value. 2 cycles + read current running for note 2 cycles (LDS = 3 cycles) + subtract a shifted version, to make it into an IIR. (4 cycles) + add in current values. (2 cycles) + store data back to ram (2 cycles) + advance place in stacked table (8?bits) 1 cycle + + store place in stacked table (8? bits) 3 cycles? + + //What if we chunk ADC updates into groups of 4 or 8? + //This is looking barely possible. + + on attiny85: scheduled adds/subtracts (like a stacked-square-wave-table) + //XXX TODO! + +*/ + +/* Ok... Let's think about the ATTiny402. 256x8 RAM + 4096x8 FLASH. + + * We can create a table which has all octaves overlaid. + * We would need to keep track of: + * 12 x 2 x 2 = 48 bytes = Current sin/cos values. + * 12 x 2 = 24 bytes = Current place in table. = 72 bytes + * We would need to store: + * The layered lookup table. If possible, keep @ 256 bytes to simplify math ops. + * The speed by which each note needs to advance. + * We would need to: + * Read current running place. X 8 cycles + * Use that place to look up into sin table. 3 cycles + * Read running val 4 cycles best case + * Multiply out the sin + IIR 5 cycles + * Store running val 4 cycles best case + * Cos-advance that place to look up into sin table. 4 cycles + * Read running val 4 cycles best case + * Multiply out the sin + IIR 5 cycles + * Store running val 4 cycles best case. + * Read how much to advance X by. 4 cycles + * (Cos^2+Sin^2) 8? + * Store it. 4 cycles best case. + * = 48 x 12 = 576 cycles. Assume 10 MHz @ 16k SPS. We're OK (625 samples) +*/ + +// Observation: The two tables are actually mirror images of each other, well diagonally mirrored. That's odd. But, would take CPU to exploit. + +#define SSTABLESIZE 256 +int8_t spikysin_interleved_cos[SSTABLESIZE][2]; +uint32_t advancespeed[MAX_FREQS]; + +static int CompTableWithPhase( int nelements, float phase, int scaling ) +{ + int highest = 0; + int i; + for( i = 0; i < nelements; i++ ) + { + float taued = i * 3.141592 * 2.0 / nelements; + int o; + float combsin = 0; + for( o = 0; o < OCTAVES; o++ ) + { + combsin += sin( taued * (1< highest ) highest = csadapt; + if( -csadapt > highest ) highest = -csadapt; + + if( csadapt > 127 ) csadapt = 127; + if( csadapt < -128 ) csadapt = -128; //tricky: Keep balanced. + spikysin_interleved_cos[i][0] = csadapt; + + float combcos = 0; + for( o = 0; o < OCTAVES; o++ ) + { + combcos += cos( taued * (1< highest ) highest = csadapt; + if( -csadapt > highest ) highest = -csadapt; + + if( csadapt > 127 ) csadapt = 127; + if( csadapt < -128 ) csadapt = -128; //tricky: Keep balanced. + spikysin_interleved_cos[i][1] = csadapt; + } + return highest; +} + + +static int Setup( float * frequencies, int bins ) +{ + int i; + + //Since start position/phase is arbitrary, we should try several to see which gives us the best dynamic range. + float tryphase = 0; + + float bestphase = 0; + int highest_val_at_best_phase = 1000000; + + for( tryphase = 0; tryphase < 3.14159; tryphase += 0.001 ) + { + int highest = CompTableWithPhase( SSTABLESIZE, tryphase, 65536 ); + if( highest < highest_val_at_best_phase ) + { + highest_val_at_best_phase = highest; + bestphase = tryphase; + } + } + printf( "Best comp: %f : %d\n", bestphase, highest_val_at_best_phase ); + + //Set this because we would overflow the sinm and cosm regs if we don't. This is sort of like a master volume. + //use this as that input volume knob thing. + float further_reduce = 1.0; + + CompTableWithPhase( SSTABLESIZE, bestphase, (65536*128*further_reduce)/highest_val_at_best_phase ); + +// for( i = 0; i < SSTABLESIZE; i++ ) +// { +// printf( "%d %d\n", spikysin_interleved_cos[i*2+0], spikysin_interleved_cos[i*2+1] ); +// } + + for( i = 0; i < MAX_FREQS; i++ ) + { + //frequencies[i] = SPS / Freq + // Need to decide how quickly we sweep through the table. + advancespeed[i] = 65536 * 256.0 /* fixed point */ * 256.0 /* size of table */ / frequencies[i]; + //printf( "%f\n", frequencies[i] ); + } + return 0; +} + + +/* +uint8_t spikysin_interleved_cos[256*2]; +uint16_t advancespeed[MAX_FREQS]; +*/ + +float toutbins[MAX_FREQS]; + +struct notedat +{ + uint32_t time; + int32_t sinm; + int32_t cosm; +}; + +static struct notedat nd[MAX_FREQS]; + +void Turbo8BitRun( int8_t adcval ) +{ + int i; + for( i = 0; i < MAX_FREQS; i++ ) + { + uint32_t ct = nd[i].time; + int32_t muxres; + int32_t running; + int32_t rdesc, rdess; + uint8_t * spikysintable = &spikysin_interleved_cos[(ct>>24)][0]; + + int8_t ss = *(spikysintable++); + + #define DECIR 8 + + muxres = ((int16_t)adcval * ss + (1<<(DECIR-1)) ) >> (DECIR); + running = nd[i].cosm; + running += muxres; + rdesc = running >> 8; + running -= rdesc >> 3; + + nd[i].cosm = running; +if( i == 0) printf( "MRX %5d %9d %9d %9d %9d\n", muxres, adcval, ss, running, nd[i].sinm ); + int8_t sc = *(spikysintable++); + muxres = ((int16_t)adcval * sc + (1<<(DECIR-1)) ) >> (DECIR); + running = nd[i].sinm; + running += muxres; + + rdess = running>>8; + running -= rdess >> 3; + + nd[i].sinm = running; + + nd[i].time = ct + advancespeed[i]; + + toutbins[i] = rdess * rdess + rdesc * rdesc; + //printf( "%d %d = %f %p\n", rdess, rdesc, toutbins[i], &toutbins[i] ); + } + + static uint8_t stater; +/* stater++; + if( stater == 16 ) + { + stater = 0; + for( i = 0; i < MAX_FREQS; i++ ) + { + nd[i].sinm -= nd[i].sinm >> 12; + nd[i].cosm -= nd[i].cosm >> 12; + nd[i].sinm += 8; + nd[i].cosm += 8; + } + }*/ +} + + +void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ) +{ + static int is_setup; + if( !is_setup ) { is_setup = 1; Setup( frequencies, bins ); } + static int last_place; + int i; + + for( i = last_place; i != place_in_data_buffer; i = (i+1)%size_of_data_buffer ) + { + int16_t ifr1 = (int16_t)( ((databuffer[i]) ) * 4095 ); + //ifr1 += 4095; + //ifr1 += 512; + Turbo8BitRun( ifr1>>5 ); //6 = Actually only feed algorithm numbers from -64 to 63. + } + last_place = place_in_data_buffer; + + for( i = 0; i < bins; i++ ) + { + outbins[i] = 0; + } + for( i = 0; i < MAX_FREQS; i++ ) + { + int iss = nd[i].sinm>>8; + int isc = nd[i].cosm>>8; + int mux = iss * iss + isc * isc; + if( mux == 0 ) mux = 1; + if( i == 0 ) + printf( "MUX: %d %d\n", isc, iss ); + outbins[i+MAX_FREQS] = sqrt(mux)/200.0; + } + +} + + diff --git a/embeddedcommon/DFT8Turbo.h.attic b/embeddedcommon/DFT8Turbo.h.attic new file mode 100644 index 0000000..257cf89 --- /dev/null +++ b/embeddedcommon/DFT8Turbo.h.attic @@ -0,0 +1,9 @@ +#ifndef _DFT8TURBO_H +#define _DFT8TURBO_H + +/* Note: Frequencies must be precompiled. */ + +void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ); + +#endif + From 21398bf6c61378e1642c394caf7f355219103d75 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sat, 27 Apr 2019 03:23:07 -0400 Subject: [PATCH 04/14] working on 8bit turbo --- embedded8266/esp82xx | 2 +- embeddedcommon/DFT8Turbo.c | 154 +++++++++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 40 deletions(-) diff --git a/embedded8266/esp82xx b/embedded8266/esp82xx index a08b471..113e0d1 160000 --- a/embedded8266/esp82xx +++ b/embedded8266/esp82xx @@ -1 +1 @@ -Subproject commit a08b47184b3fcf04172ecc0b6a1aee9c90e5d92d +Subproject commit 113e0d1a182cd138510f748abf2854c0e84cfa23 diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index 02791c4..f5f2e32 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -8,6 +8,7 @@ #define MAX_FREQS (24) #define OCTAVES (5) +#define TARGFREQ 8000.0 /* * The first thought was using an integration map and only operating when we need to, to pull the data out. @@ -29,21 +30,24 @@ NOTE: Only use 16 bins, lets action table be 16-bits wide. */ +//These live in RAM. int16_t running_integral; -int16_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. +int16_t integral_at[MAX_FREQS*OCTAVES*2]; +int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit) +uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. + +#define NR_OF_OPS (4< hits per 1024: %f %d (%f error)\n", topbin, f, (float)ACTIONTABLESIZE/f, dhrpertable, err * 100.0 ); + printf( "%d %f -> hits per %d: %f %d (%.2f%% error)\n", topbin, f, ACTIONTABLESIZE, (float)ACTIONTABLESIZE/f, dhrpertable, err * 100.0 ); float advance_per_step = dhrpertable/(float)ACTIONTABLESIZE; float fvadv = 0.0; int j; - int actions = 0; int countset = 0; //XXX TODO Tricky: We need to start fadv off at such a place that there won't be a hicchup when going back around to 0. @@ -80,38 +83,110 @@ static int Setup( float * frequencies, int bins ) printf( " countset: %d\n", countset ); } - for( i = 0; i < (1<> longestzeroes) & 1) == 0 ); longestzeroes++ ); + int val = i & ((1<> longestzeroes) & 1) == 0 ); longestzeroes++ ); //longestzeroes goes: 255, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, ... - //This isn't great, because we need to also know whether we are attacking the SIN side or the COS side. - highbit_table[i] = longestzeroes; + //This isn't great, because we need to also know whether we are attacking the SIN side or the COS side, and if it's + or -. + //We can actually decide that out. + + if( longestzeroes == 255 ) + { + //This is a nop. Emit a nop. + optable[i] = longestzeroes; + } + else + { + int iop = phaseinop[longestzeroes]++; + optable[i] = (longestzeroes<<1) | (iop & 1); + if( iop & 2 ) optable[i] |= 1<<4; + //printf( " %d %d\n", iop, val ); + } + //printf( "HBT: %d = %d\n", i, optable[i] ); } - //Repeat the highbit table in the second half. - //XXX PICK UP HERE - //Encode into highbit_table which cell is being operated on - //Also, do the * MAX_FREQS here. That will + - - - placeintable = actiontable; - // for( i = 0; i < ACTIONTABLESIZE; i++ ) printf( "%08x\n", actiontable[i] ); + return 0; } - +#if 0 int16_t running_integral; -int16_t cossindata[MAX_FREQS*OCTAVES*2]; -uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. -uint16_t * placeintable; +int16_t integral_at[MAX_FREQS*OCTAVES]; +int16_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. +uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. -//Put this in flash. -uint32_t actiontable[ACTIONTABLESIZE]; +#define NR_OF_OPS (4<= NR_OF_OPS ) ao = 0; + which_octave_for_op[n] = ao; + + if( op == 255 ) + { + dprintf( "*" ); //NOP + } + else + { + int octaveplace = op & 0xf; + int idx = (octaveplace>>1) * MAX_FREQS * 2 + n * (octaveplace&1)*2; + + int16_t diff; + + if( op & 0x10 ) //ADD + { + diff = integral_at[idx>>1] - running_integral; + dprintf( "%c", 'a' + octaveplace ); + } + else //SUBTRACT + { + diff = running_integral - integral_at[idx>>1]; + dprintf( "%c", 'A' + octaveplace ); + } + integral_at[idx>>1] = running_integral; + printf( "%d\n", diff ); + //dprintf( "%d\n", idx ); + cossindata[idx] += diff; + cossindata[idx] -= cossindata[idx] >> 8; + } + } + else + { + dprintf( " " ); + } + } + dprintf( "\n" ); + +#if 0 uint32_t actions = *(placeintable++); if( placeintable == &actiontable[ACTIONTABLESIZE] ) placeintable = actiontable; int b; @@ -131,6 +206,7 @@ void Turbo8BitRun( int8_t adcval ) //if( b == 0 ) printf( "%d\n", whichoctave ); //XXX TODO Optimization: Use a table, since octavebit can only be 0...31. } +#endif } @@ -148,20 +224,20 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float } last_place = place_in_data_buffer; -#if 0 +#if 1 for( i = 0; i < bins; i++ ) { outbins[i] = 0; } for( i = 0; i < MAX_FREQS; i++ ) { - int iss = nd[i].sinm>>8; - int isc = nd[i].cosm>>8; + int iss = 0;//cossindata[i*2+0]>>8; + int isc = 0;//cossindata[i*2+1]>>8; int mux = iss * iss + isc * isc; if( mux == 0 ) mux = 1; if( i == 0 ) - printf( "MUX: %d %d\n", isc, iss ); - outbins[i+MAX_FREQS] = sqrt(mux)/200.0; + //printf( "MUX: %d %d = %d\n", isc, iss, mux ); + outbins[i+MAX_FREQS] = sqrt(mux);///200.0; } #endif } From e8e96d7d010ffa4ca42a103621a82a23374ddb79 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sat, 27 Apr 2019 17:34:22 -0400 Subject: [PATCH 05/14] IT IS DOING A DFT --- colorchord2/turbo8bit.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 6420cee..5701d21 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -67,7 +67,7 @@ filter_iter = 2 filter_strength = .5 # How many bins per octave to use? -freqbins = 24 +freqbins = 12 # For the final note information... How much to slack everything? note_attach_amp_iir = 0.3500 From 56c0af05c166c4f52ee2440fce827ea773db3cc7 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sun, 28 Apr 2019 01:49:35 -0400 Subject: [PATCH 06/14] **Use this as reference point 1** Ok, this is actually pretty solid. --- colorchord2/turbo8bit.conf | 21 ++++++--- embeddedcommon/DFT8Turbo.c | 95 +++++++++++++++++++++++++++----------- 2 files changed, 82 insertions(+), 34 deletions(-) diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 5701d21..513d72d 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -17,7 +17,19 @@ buffer = 384 play = 0 rec = 1 channels = 2 -samplerate = 16000 + + + +# THis matters for CC Turbo8 +# What is the base note? I.e. the lowest note. +# Note that it won't have very much impact until an octave up though! +base_hz = 82.41 +samplerate = 10000 +freqbins = 8 + + + + wininput = -1 #Compiled version will default this. @@ -39,9 +51,6 @@ sourcename = default # How much to amplify the incoming signal. amplify = 2.0 -# What is the base note? I.e. the lowest note. -# Note that it won't have very much impact until an octave up though! -base_hz = 110 # This is only used when dealing with the slow decompose (now defunct) # decompose_iterations = 1000 @@ -51,7 +60,7 @@ base_hz = 110 dft_iir = 0.6 dft_q = 20.0000 dft_speedup = 1000.0000 -octaves = 5 +octaves = 4 # Should we use a progressive DFT? # 0 = DFT Quick @@ -66,8 +75,6 @@ do_progressive_dft = 5 filter_iter = 2 filter_strength = .5 -# How many bins per octave to use? -freqbins = 12 # For the final note information... How much to slack everything? note_attach_amp_iir = 0.3500 diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index f5f2e32..5f08daf 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -5,9 +5,16 @@ #include -#define MAX_FREQS (24) -#define OCTAVES (5) +#define MAX_FREQS (8) +#define OCTAVES (4) +//Right now, we need 8*freqs*octaves bytes. +//This is bad. +//What can we do to fix it? + +//4x the hits (sin/cos and we need to do it once for each edge) +//8x for selecting a higher octave. +#define FREQREBASE 8.0 #define TARGFREQ 8000.0 /* @@ -23,16 +30,11 @@ It is constantly summing, so we can take an integral of it. Or rather an integral range. Over time, we perform operations like adding or subtracting from a current place. - - -NOTE: - Optimizations: - Only use 16 bins, lets action table be 16-bits wide. */ //These live in RAM. int16_t running_integral; -int16_t integral_at[MAX_FREQS*OCTAVES*2]; +int16_t integral_at[MAX_FREQS*OCTAVES*2]; //THIS CAN BE COMPRESSED. int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit) uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. @@ -45,7 +47,7 @@ uint8_t optable[NR_OF_OPS]; //PUT IN FLASH #define ACTIONTABLESIZE 256 -uint32_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH +uint8_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH // If there are more than 8 freqbins, this must be a uint16_t, otherwise if more than 16, 32. uint8_t actiontableplace; //Format is @@ -56,12 +58,17 @@ static int Setup( float * frequencies, int bins ) for( i = bins-MAX_FREQS; i < bins; i++ ) { int topbin = i - (bins-MAX_FREQS); - float f = frequencies[i]/4.0; //4x the hits (sin/cos and we need to do it once for each edge) + float f = frequencies[i]/FREQREBASE; float hits_per_table = (float)ACTIONTABLESIZE/f; int dhrpertable = (int)(hits_per_table+.5);//TRICKY: You might think you need to have even number of hits (sin/cos), but you don't! It can flip sin/cos each time through the table! float err = (TARGFREQ/((float)ACTIONTABLESIZE/dhrpertable) - (float)TARGFREQ/f)/((float)TARGFREQ/f); //Perform an op every X samples. How well does this map into units of 1024? printf( "%d %f -> hits per %d: %f %d (%.2f%% error)\n", topbin, f, ACTIONTABLESIZE, (float)ACTIONTABLESIZE/f, dhrpertable, err * 100.0 ); + if( dhrpertable >= ACTIONTABLESIZE ) + { + fprintf( stderr, "Error: Too many hits.\n" ); + exit(0); + } float advance_per_step = dhrpertable/(float)ACTIONTABLESIZE; float fvadv = 0.0; @@ -100,14 +107,15 @@ static int Setup( float * frequencies, int bins ) } else { + longestzeroes = OCTAVES-1-longestzeroes; //Actually do octave 0 least often. int iop = phaseinop[longestzeroes]++; optable[i] = (longestzeroes<<1) | (iop & 1); if( iop & 2 ) optable[i] |= 1<<4; - //printf( " %d %d\n", iop, val ); + //printf( " %d %d %d\n", iop, val, longestzeroes ); } //printf( "HBT: %d = %d\n", i, optable[i] ); } - + //exit(1); return 0; } @@ -134,7 +142,7 @@ uint32_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH void Turbo8BitRun( int8_t adcval ) { - running_integral += adcval; + running_integral += adcval>>0; #define dprintf( ... ) @@ -158,25 +166,32 @@ void Turbo8BitRun( int8_t adcval ) else { int octaveplace = op & 0xf; - int idx = (octaveplace>>1) * MAX_FREQS * 2 + n * (octaveplace&1)*2; + int idx = (octaveplace>>1) * MAX_FREQS * 2 + n * 2 + (octaveplace&1); + //int invoct = OCTAVES-1-octaveplace; int16_t diff; if( op & 0x10 ) //ADD { - diff = integral_at[idx>>1] - running_integral; + diff = integral_at[idx] - running_integral; dprintf( "%c", 'a' + octaveplace ); } else //SUBTRACT { - diff = running_integral - integral_at[idx>>1]; + diff = running_integral - integral_at[idx]; dprintf( "%c", 'A' + octaveplace ); } - integral_at[idx>>1] = running_integral; - printf( "%d\n", diff ); + //diff = diff * (octaveplace+1); + if( diff > 256 || diff < -256 ) printf( "%d\n", diff ); + + integral_at[idx] = running_integral; + //if( n == 1 ) printf( "%d %d %d %d\n", n, idx, diff, op & 0x10 ); //dprintf( "%d\n", idx ); - cossindata[idx] += diff; - cossindata[idx] -= cossindata[idx] >> 8; + cossindata[idx] = cossindata[idx] + diff - (cossindata[idx]>>3); + // if( cossindata[idx] > 1 ) cossindata[idx]--; + // if( cossindata[idx] < -1 ) cossindata[idx]++; + // if( cossindata[idx] > 16 ) cossindata[idx]-=8; + // if( cossindata[idx] < -16 ) cossindata[idx]+=8; } } else @@ -224,21 +239,47 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float } last_place = place_in_data_buffer; + static int idiv; + idiv++; #if 1 for( i = 0; i < bins; i++ ) { outbins[i] = 0; } - for( i = 0; i < MAX_FREQS; i++ ) + for( i = 0; i < bins; i++ ) { - int iss = 0;//cossindata[i*2+0]>>8; - int isc = 0;//cossindata[i*2+1]>>8; + int iss = cossindata[i*2+0]>>8; + int isc = cossindata[i*2+1]>>8; + int issdiv = 0; + int iscdiv = 0; + int FWDOFFSET = 19;//MAX_FREQS*3/2; + if( i < bins-FWDOFFSET ) + { + issdiv = cossindata[(i+FWDOFFSET)*2+0]/256; + iscdiv = cossindata[(i+FWDOFFSET)*2+1]/256; + } int mux = iss * iss + isc * isc; - if( mux == 0 ) mux = 1; - if( i == 0 ) - //printf( "MUX: %d %d = %d\n", isc, iss, mux ); - outbins[i+MAX_FREQS] = sqrt(mux);///200.0; + int muxdiv = issdiv * issdiv + iscdiv * iscdiv; + + //if( (idiv % 100) > 50 ) { printf( "*" ); mux -= muxdiv; } + //mux -= muxdiv; + + if( mux <= 0 ) + { + outbins[i] = 0; + } + else + { + //if( i == 0 ) + //printf( "MUX: %d %d = %d\n", isc, iss, mux ); + outbins[i] = sqrt((float)mux/10.0)/50.0; + + if( abs( cossindata[i*2+0] ) > 1000 || abs( cossindata[i*2+1] ) > 1000 ) + printf( "%d/%d/%d/%f ", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); + //outbins[i] = (cossindata[i*2+0]/10000.0); + } } + printf( "\n" ); #endif } From 36c8d8a94f2bfedea839acf57094ef3c5dd8cbf5 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sun, 28 Apr 2019 18:11:35 -0400 Subject: [PATCH 07/14] First commit before we try to do a modified-square-wave kernel. --- embeddedcommon/DFT8Turbo.c | 70 +++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index 5f08daf..8137ebd 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -17,6 +17,18 @@ #define FREQREBASE 8.0 #define TARGFREQ 8000.0 +/* Tradeoff guide: + + * We will optimize for RAM size here. + + * If you weight the bins in advance, you can: + +) potentially use shallower bit depth but + -) have to compute the multiply every time you update the bin. + + *TODO: Investigate using all unsigned (to make multiply and/or 12-bit storage easier) + +*/ + /* * The first thought was using an integration map and only operating when we need to, to pull the data out. * Now we're doing the thing below this block comment @@ -35,7 +47,7 @@ //These live in RAM. int16_t running_integral; int16_t integral_at[MAX_FREQS*OCTAVES*2]; //THIS CAN BE COMPRESSED. -int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit) +int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. #define NR_OF_OPS (4<>0; + running_integral += adcval>>2; #define dprintf( ... ) @@ -181,13 +217,31 @@ void Turbo8BitRun( int8_t adcval ) diff = running_integral - integral_at[idx]; dprintf( "%c", 'A' + octaveplace ); } - //diff = diff * (octaveplace+1); + if( diff > 256 || diff < -256 ) printf( "%d\n", diff ); integral_at[idx] = running_integral; //if( n == 1 ) printf( "%d %d %d %d\n", n, idx, diff, op & 0x10 ); //dprintf( "%d\n", idx ); - cossindata[idx] = cossindata[idx] + diff - (cossindata[idx]>>3); + +#if 0 + //Apply IIR operation 1; This is rough because the Q changes and goes higher as a function of frequency. This is probably a bad move. + cossindata[idx] += diff>>4; + if( op & 0x20 ) + { + cossindata[idx] = cossindata[idx] + - (cossindata[idx]>>2); + } +#else + //Apply IIR. + //printf( "%d: %d + %d * %d >> 8 - %d\n", idx, cossindata[idx], diff, mulmux[idx/2], cossindata[idx]>>4 ); + cossindata[idx] = cossindata[idx] + + (((int32_t)diff * (int32_t)mulmux[idx/2])>>6) + - (cossindata[idx]>>4) + ; + // if( cossindata[idx] > 2047 ) cossindata[idx] = 2047; + // if( cossindata[idx] < -2048 ) cossindata[idx] = -2048; +#endif // if( cossindata[idx] > 1 ) cossindata[idx]--; // if( cossindata[idx] < -1 ) cossindata[idx]++; // if( cossindata[idx] > 16 ) cossindata[idx]-=8; @@ -272,7 +326,7 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float { //if( i == 0 ) //printf( "MUX: %d %d = %d\n", isc, iss, mux ); - outbins[i] = sqrt((float)mux/10.0)/50.0; + outbins[i] = sqrt((float)mux)/50.0; if( abs( cossindata[i*2+0] ) > 1000 || abs( cossindata[i*2+1] ) > 1000 ) printf( "%d/%d/%d/%f ", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); From 16fa4a9c4294d9dbe78fb04be0a63492eb27a5aa Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sun, 28 Apr 2019 18:53:45 -0400 Subject: [PATCH 08/14] Closing in. --- colorchord2/turbo8bit.conf | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 513d72d..88b3c72 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -25,7 +25,8 @@ channels = 2 # Note that it won't have very much impact until an octave up though! base_hz = 82.41 samplerate = 10000 -freqbins = 8 +freqbins = 12 +octaves = 4 @@ -56,25 +57,6 @@ amplify = 2.0 # decompose_iterations = 1000 # default_sigma = 1.4000 -# DFT properties for the DFT up top. -dft_iir = 0.6 -dft_q = 20.0000 -dft_speedup = 1000.0000 -octaves = 4 - -# Should we use a progressive DFT? -# 0 = DFT Quick -# 1 = DFT Progressive -# 2 = DFT Progressive Integer -# 3 = DFT Progressive Integer Skippy -# 4 = Integer, 32-Bit, Progressive, Skippy. (wow, this actually works) -# 5 = 8-bit turbo test. -do_progressive_dft = 5 - - -filter_iter = 2 -filter_strength = .5 - # For the final note information... How much to slack everything? note_attach_amp_iir = 0.3500 From 8677baebd3b34756585c57b69aac35ffddb3c607 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Sun, 28 Apr 2019 21:08:37 -0400 Subject: [PATCH 09/14] Further checkpoint - before optable rework --- colorchord2/turbo8bit.conf | 6 +++- embeddedcommon/DFT8Turbo.c | 68 +++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 88b3c72..53fb5e0 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -23,14 +23,18 @@ channels = 2 # THis matters for CC Turbo8 # What is the base note? I.e. the lowest note. # Note that it won't have very much impact until an octave up though! + +#These two are carefully selected. You should pick a base note such that it fully saturates the sample frequency. +#10000 / 2^4{octaves} / 8 base_hz = 82.41 samplerate = 10000 + freqbins = 12 octaves = 4 - +slope = 0 wininput = -1 #Compiled version will default this. diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index 8137ebd..db9101e 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -5,8 +5,9 @@ #include -#define MAX_FREQS (8) +#define MAX_FREQS (12) #define OCTAVES (4) +#define INITIAL_DECIMATE 1 //Right now, we need 8*freqs*octaves bytes. //This is bad. @@ -15,38 +16,43 @@ //4x the hits (sin/cos and we need to do it once for each edge) //8x for selecting a higher octave. #define FREQREBASE 8.0 -#define TARGFREQ 8000.0 +#define TARGFREQ 10000.0 /* Tradeoff guide: * We will optimize for RAM size here. - * If you weight the bins in advance, you can: + + * INITIAL_DECIMATE; A larger decimation: {NOTE 1} + +) Reduces the bit depth needed for the integral map. + If you use "1" and a fully saturted map (highest note is every sample), it will not overflow a signed 12-bit number. + -) Increases noise. + With full-scale: 0->1 minimal 1->2 minimal 2->3 significantly noticable, 3->4 major. + If sound is quieter, it matters more. I recommend no less than 1. + Also, other things, like frequency of hits can manipulate the maximum bit depth needed for integral map. + + * If you weight the bins in advance see "mulmux", you can: {NOTE 2} +) potentially use shallower bit depth but -) have to compute the multiply every time you update the bin. + * You can use a modified-square-wave which only integrates for 1/2 of the duty cycle. {NOTE 3} + +) uses 1/2 the integral memory. + -) Not as pretty of an output. See "integral_at" + *TODO: Investigate using all unsigned (to make multiply and/or 12-bit storage easier) -*/ -/* - * The first thought was using an integration map and only operating when we need to, to pull the data out. - * Now we're doing the thing below this block comment - int16_t accumulated_total; //2 bytes - int16_t last_accumulated_total_at_bin[MAX_FREQS*2]; //24 * 2 * sizeof(int16_t) = 96 bytes. - uint8_t current_time; //1 byte - uint8_t placecode[MAX_FREQS]; -*/ -/* + So, the idea here is we would keep a running total of the current ADC value, kept away in a int16_t. It is constantly summing, so we can take an integral of it. Or rather an integral range. - Over time, we perform operations like adding or subtracting from a current place. + Over time, we perform operations like adding or subtracting from a current place. It basically is + a DFT where the kernel is computed using square waves (or modified square waves) */ //These live in RAM. -int16_t running_integral; -int16_t integral_at[MAX_FREQS*OCTAVES*2]; //THIS CAN BE COMPRESSED. +int16_t running_integral; //Realistically treat as 12-bits on ramjet8 +int16_t integral_at[MAX_FREQS*OCTAVES]; //For ramjet8, make 12-bits int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. @@ -59,7 +65,7 @@ uint8_t optable[NR_OF_OPS]; //PUT IN FLASH #define ACTIONTABLESIZE 256 -uint8_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH // If there are more than 8 freqbins, this must be a uint16_t, otherwise if more than 16, 32. +uint16_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH // If there are more than 8 freqbins, this must be a uint16_t, otherwise if more than 16, 32. uint8_t actiontableplace; //Format is @@ -76,7 +82,6 @@ static int Setup( float * frequencies, int bins ) mulmux[i] = (uint8_t)( highestf / frequencies[i] * 255 + 0.5 ); printf( "MM: %d %f / %f\n", mulmux[i], frequencies[i], highestf ); } - //exit(0); for( i = bins-MAX_FREQS; i < bins; i++ ) { @@ -94,11 +99,12 @@ static int Setup( float * frequencies, int bins ) } float advance_per_step = dhrpertable/(float)ACTIONTABLESIZE; - float fvadv = 0.0; + float fvadv = 0.5; int j; int countset = 0; - //XXX TODO Tricky: We need to start fadv off at such a place that there won't be a hicchup when going back around to 0. + //Tricky: We need to start fadv off at such a place that there won't be a hicchup when going back around to 0. + // I believe this is done by setting fvadv to 0.5 initially. Unsure. for( j = 0; j < ACTIONTABLESIZE; j++ ) { @@ -112,6 +118,8 @@ static int Setup( float * frequencies, int bins ) } printf( " countset: %d\n", countset ); } + //exit(1); + int phaseinop[OCTAVES] = { 0 }; int already_hit_octaveplace[OCTAVES*2] = { 0 }; @@ -178,7 +186,7 @@ uint32_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH void Turbo8BitRun( int8_t adcval ) { - running_integral += adcval>>2; + running_integral += adcval>>INITIAL_DECIMATE; #define dprintf( ... ) @@ -202,25 +210,31 @@ void Turbo8BitRun( int8_t adcval ) else { int octaveplace = op & 0xf; - int idx = (octaveplace>>1) * MAX_FREQS * 2 + n * 2 + (octaveplace&1); + + //Tricky: We share the integral with SIN and COS. + //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 + int intindex = (octaveplace>>1) * MAX_FREQS + n; //int invoct = OCTAVES-1-octaveplace; int16_t diff; if( op & 0x10 ) //ADD { - diff = integral_at[idx] - running_integral; + diff = integral_at[intindex] - running_integral; dprintf( "%c", 'a' + octaveplace ); } else //SUBTRACT { - diff = running_integral - integral_at[idx]; + diff = running_integral - integral_at[intindex]; dprintf( "%c", 'A' + octaveplace ); } - if( diff > 256 || diff < -256 ) printf( "%d\n", diff ); + if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); + + integral_at[intindex] = running_integral; + + int idx = intindex * 2 + (octaveplace&1); - integral_at[idx] = running_integral; //if( n == 1 ) printf( "%d %d %d %d\n", n, idx, diff, op & 0x10 ); //dprintf( "%d\n", idx ); @@ -328,7 +342,7 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float //printf( "MUX: %d %d = %d\n", isc, iss, mux ); outbins[i] = sqrt((float)mux)/50.0; - if( abs( cossindata[i*2+0] ) > 1000 || abs( cossindata[i*2+1] ) > 1000 ) + if( abs( cossindata[i*2+0] ) > 2000 || abs( cossindata[i*2+1] ) > 2000 ) printf( "%d/%d/%d/%f ", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); //outbins[i] = (cossindata[i*2+0]/10000.0); } From 1432f22b775cd3aca32ccd3f14f528fd6cec907c Mon Sep 17 00:00:00 2001 From: cnlohr Date: Mon, 29 Apr 2019 00:04:28 -0400 Subject: [PATCH 10/14] Switch over to making 8Turbo *actually* turbo. --- colorchord2/turbo8bit.conf | 1 - embeddedcommon/DFT12Small.c | 359 ++++++++++++++++++++++++++++++++++++ embeddedcommon/DFT12Small.h | 9 + embeddedcommon/DFT8Turbo.c | 213 ++++++++++----------- 4 files changed, 477 insertions(+), 105 deletions(-) create mode 100644 embeddedcommon/DFT12Small.c create mode 100644 embeddedcommon/DFT12Small.h diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 53fb5e0..d9ce3d4 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -28,7 +28,6 @@ channels = 2 #10000 / 2^4{octaves} / 8 base_hz = 82.41 samplerate = 10000 - freqbins = 12 octaves = 4 diff --git a/embeddedcommon/DFT12Small.c b/embeddedcommon/DFT12Small.c new file mode 100644 index 0000000..41c9fdd --- /dev/null +++ b/embeddedcommon/DFT12Small.c @@ -0,0 +1,359 @@ +#include +#include +#include "DFT12Small.h" +#include + +#include + + +#define MAX_FREQS (12) +#define OCTAVES (4) + +/* + General procedure - use this code, with uint16_t or uint32_t buffers, and make sure none of the alarms go off. + All of the paths still require no more than an 8-bit multiply. + You should test with extreme cases, like square wave sweeps in, etc. +*/ + +//#define TWELVEBIT +#define EIGHTBIT + +#ifdef TWELVEBIT +//No larger than 12-bit signed values for integration or sincos +#define FRONTEND_AMPLITUDE (0) +#define INITIAL_DECIMATE (2) +#define INTEGRATOR_DECIMATE (8) +#define FINAL_DECIMATE (4) +#elif defined( EIGHTBIT ) +//No larger than 8-bit signed values for integration or sincos +#define FRONTEND_AMPLITUDE (2) +#define INITIAL_DECIMATE (5) //Yurgh... only 3 bits of ADC data. That's 8 unique levels :( +#define INTEGRATOR_DECIMATE (8) +#define FINAL_DECIMATE (1) +#endif + +//Right now, we need 8*freqs*octaves bytes. +//This is bad. +//What can we do to fix it? + +//4x the hits (sin/cos and we need to do it once for each edge) +//8x for selecting a higher octave. +#define FREQREBASE 8.0 +#define TARGFREQ 10000.0 + +/* Tradeoff guide: + + * We will optimize for RAM size here. + + * INITIAL_DECIMATE; A larger decimation: {NOTE 1} + +) Reduces the bit depth needed for the integral map. + If you use "1" and a fully saturted map (highest note is every sample), it will not overflow a signed 12-bit number. + -) Increases noise. + With full-scale: 0->1 minimal 1->2 minimal 2->3 significantly noticable, 3->4 major. + If sound is quieter, it matters more. Not sure with other changes in system. (2) seems ok. + -) If you make it (1) or (0) You can't do an 8-bit multiply and keep the output in a signed range. + Also, other things, like frequency of hits can manipulate the maximum bit depth needed for integral map. + + * If you weight the bins in advance see "mulmux", you can: {NOTE 2} + +) potentially use shallower bit depth but + -) have to compute the multiply every time you update the bin. + + * You can use a modified-square-wave which only integrates for 1/2 of the duty cycle. {NOTE 3} + +) uses 1/2 the integral memory. + -) Not as pretty of an output. See "integral_at" + + *TODO: Investigate using all unsigned (to make multiply and/or 12-bit storage easier) + *TODO: Consider a mode which has 16-bit integrals, but still 8-bit cossin data. + + So, the idea here is we would keep a running total of the current ADC value, kept away in a int16_t. + It is constantly summing, so we can take an integral of it. Or rather an integral range. + + Over time, we perform operations like adding or subtracting from a current place. It basically is + a DFT where the kernel is computed using square waves (or modified square waves) +*/ + +//These live in RAM. +int16_t running_integral; //Realistically treat as 12-bits on ramjet8 +int16_t integral_at[MAX_FREQS*OCTAVES]; //For ramjet8, make 12-bits +int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) +uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. +uint8_t actiontableplace; + +#define NR_OF_OPS (4< hits per %d: %f %d (%.2f%% error)\n", topbin, f, ACTIONTABLESIZE, (float)ACTIONTABLESIZE/f, dhrpertable, err * 100.0 ); + if( dhrpertable >= ACTIONTABLESIZE ) + { + fprintf( stderr, "Error: Too many hits.\n" ); + exit(0); + } + + float advance_per_step = dhrpertable/(float)ACTIONTABLESIZE; + float fvadv = 0.5; + int j; + int countset = 0; + + //Tricky: We need to start fadv off at such a place that there won't be a hicchup when going back around to 0. + // I believe this is done by setting fvadv to 0.5 initially. Unsure. + + for( j = 0; j < ACTIONTABLESIZE; j++ ) + { + if( fvadv >= 0.5 ) + { + actiontable[j] |= 1<> longestzeroes) & 1) == 0 ); longestzeroes++ ); + //longestzeroes goes: 255, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, ... + //This isn't great, because we need to also know whether we are attacking the SIN side or the COS side, and if it's + or -. + //We can actually decide that out. + + if( longestzeroes == 255 ) + { + //This is a nop. Emit a nop. + optable[i] = 255; + } + else + { + longestzeroes = OCTAVES-1-longestzeroes; //Actually do octave 0 least often. + int iop = phaseinop[longestzeroes]++; + int toop = longestzeroes; + int toopmon = (longestzeroes<<1) | (iop & 1); + + //if it's the first time an octave happened this set, flag it. This may be used later in the process. + if( !already_hit_octaveplace[toopmon] ) + { + already_hit_octaveplace[toopmon] = 1; + toop |= 1<<5; + } + if( iop & 1 ) + { + toop |= 1<<6; + } + + //Handle add/subtract bit. + if( iop & 2 ) toop |= 1<<4; + + optable[i] = toop; + + //printf( " %d %d %d\n", iop, val, longestzeroes ); + } + //printf( "HBT: %d = %d\n", i, optable[i] ); + } + //exit(1); + + return 0; +} + + +void Small12BitRun( int8_t adcval ) +{ + int16_t adcv = adcval; + adcv *= FRONTEND_AMPLITUDE; + if( adcv > 127 ) adcv = 127; + if( adcv < -128 ) adcv = -128; + running_integral += adcv>>INITIAL_DECIMATE; + +#define dprintf( ... ) + + uint32_t action = actiontable[actiontableplace++]; + int n; + dprintf( "%4d ", actiontableplace ); + for( n = 0; n < MAX_FREQS; n++ ) + { + if( action & (1<= NR_OF_OPS ) ao = 0; + which_octave_for_op[n] = ao; + + if( op == 255 ) + { + dprintf( "*" ); //NOP + } + else + { + //int octaveplace = op & 0xf; + + //Tricky: We share the integral with SIN and COS. + //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 + uint8_t octave = op & 0xf; + uint8_t intindex = octave * MAX_FREQS + n; + + //int invoct = OCTAVES-1-octaveplace; + int16_t diff; + + if( op & 0x10 ) //ADD + { + diff = integral_at[intindex] - running_integral; + dprintf( "%c", 'a' + (op & 0xf) ); + } + else //SUBTRACT + { + diff = running_integral - integral_at[intindex]; + dprintf( "%c", 'A' + (op & 0xf) ); + } + + integral_at[intindex] = running_integral; + +#ifdef TWELVEBIT + if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); +#elif defined( EIGHTBIT ) + if( diff > 124 || diff < -124 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); +#endif + + uint8_t idx = ( intindex << 1 ); + if( op&(1<<6) ) + { + idx |= 1; + } + + //printf( "%d: %d + %d * %d >> 8 - %d\n", idx, cossindata[idx], diff, mulmux[idx/2], cossindata[idx]>>4 ); + + uint8_t mulmuxval = mulmux[n]; + + + //Do you live on a super lame processor? {NOTE 4} + //If you do, you might not have good signed multiply operations. So, an alternative mechanism is found here. + // +) Able to more cleanly crush to an 8-bit multiply. + // +) Gets extra bit of precision back, i.e. the sign bit is now used as a data bit. + // -) More than 1 line of C code. Requires possible double invert. +#if 1 + //Terrible processor, i.e. PMS133 + if( 0 && diff < 0 ) + { + diff *= -1; + diff >>= (OCTAVES-1-octave); + + if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + + diff = (uint16_t)diff * (uint16_t)mulmuxval; + diff >>= INTEGRATOR_DECIMATE; + + diff *= -1; + } + else + { + diff >>= (OCTAVES-1-octave); + + if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + + diff = (uint16_t)diff * (uint16_t)mulmuxval; + diff >>= INTEGRATOR_DECIMATE; + } +#else + //Decent processor, i.e. ATTiny85. + diff = ((diff>>(OCTAVES-1-octave)) * mulmuxval ) >> 6; +#endif + cossindata[idx] = cossindata[idx] + + diff + - (cossindata[idx]>>4) + ; + +#ifdef EIGHTBIT + if( cossindata[idx] > 0 ) cossindata[idx]--; + if( cossindata[idx] < 0 ) cossindata[idx]++; +#endif + } + } + else + { + dprintf( " " ); + } + } + dprintf( "\n" ); + +} + + +void DoDFT12BitSmall( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ) +{ + static int is_setup; + if( !is_setup ) { is_setup = 1; Setup( frequencies, bins ); } + static int last_place; + int i; + + for( i = last_place; i != place_in_data_buffer; i = (i+1)%size_of_data_buffer ) + { + int16_t ifr1 = (int16_t)( ((databuffer[i]) ) * 4095 ); + Small12BitRun( ifr1>>5 ); //5 = Actually only feed algorithm numbers from -128 to 127. + } + last_place = place_in_data_buffer; + + static int idiv; + idiv++; +#if 1 + for( i = 0; i < bins; i++ ) + { + int iss = cossindata[i*2+0]>>FINAL_DECIMATE; + int isc = cossindata[i*2+1]>>FINAL_DECIMATE; + int mux = iss * iss + isc * isc; + + if( mux <= 0 ) + { + outbins[i] = 0; + } + else + { + outbins[i] = sqrt((float)mux)/50.0; + +#ifdef TWELVEBIT + if( abs( cossindata[i*2+0] ) > 1000 || abs( cossindata[i*2+1] ) > 1000 ) + printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); +#elif defined( EIGHTBIT ) + if( abs( cossindata[i*2+0] ) > 120 || abs( cossindata[i*2+1] ) > 120 ) + printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); +#endif + } + } +#endif +} + + diff --git a/embeddedcommon/DFT12Small.h b/embeddedcommon/DFT12Small.h new file mode 100644 index 0000000..13506e6 --- /dev/null +++ b/embeddedcommon/DFT12Small.h @@ -0,0 +1,9 @@ +#ifndef _DFT8TURBO_H +#define _DFT8TURBO_H + +/* Note: Frequencies must be precompiled. */ + +void DoDFT12BitSmall( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ); + +#endif + diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index db9101e..1471270 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -5,9 +5,32 @@ #include + #define MAX_FREQS (12) #define OCTAVES (4) -#define INITIAL_DECIMATE 1 + +/* + General procedure - use this code, with uint16_t or uint32_t buffers, and make sure none of the alarms go off. + All of the paths still require no more than an 8-bit multiply. + You should test with extreme cases, like square wave sweeps in, etc. +*/ + +//#define TWELVEBIT +#define EIGHTBIT + +#ifdef TWELVEBIT +//No larger than 12-bit signed values for integration or sincos +#define FRONTEND_AMPLITUDE (0) +#define INITIAL_DECIMATE (2) +#define INTEGRATOR_DECIMATE (8) +#define FINAL_DECIMATE (4) +#elif defined( EIGHTBIT ) +//No larger than 8-bit signed values for integration or sincos +#define FRONTEND_AMPLITUDE (2) +#define INITIAL_DECIMATE (5) //Yurgh... only 3 bits of ADC data. That's 8 unique levels :( +#define INTEGRATOR_DECIMATE (8) +#define FINAL_DECIMATE (1) +#endif //Right now, we need 8*freqs*octaves bytes. //This is bad. @@ -22,13 +45,13 @@ * We will optimize for RAM size here. - * INITIAL_DECIMATE; A larger decimation: {NOTE 1} +) Reduces the bit depth needed for the integral map. If you use "1" and a fully saturted map (highest note is every sample), it will not overflow a signed 12-bit number. -) Increases noise. With full-scale: 0->1 minimal 1->2 minimal 2->3 significantly noticable, 3->4 major. - If sound is quieter, it matters more. I recommend no less than 1. + If sound is quieter, it matters more. Not sure with other changes in system. (2) seems ok. + -) If you make it (1) or (0) You can't do an 8-bit multiply and keep the output in a signed range. Also, other things, like frequency of hits can manipulate the maximum bit depth needed for integral map. * If you weight the bins in advance see "mulmux", you can: {NOTE 2} @@ -40,8 +63,7 @@ -) Not as pretty of an output. See "integral_at" *TODO: Investigate using all unsigned (to make multiply and/or 12-bit storage easier) - - + *TODO: Consider a mode which has 16-bit integrals, but still 8-bit cossin data. So, the idea here is we would keep a running total of the current ADC value, kept away in a int16_t. It is constantly summing, so we can take an integral of it. Or rather an integral range. @@ -55,6 +77,7 @@ int16_t running_integral; //Realistically treat as 12-bits on ramjet8 int16_t integral_at[MAX_FREQS*OCTAVES]; //For ramjet8, make 12-bits int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. +uint8_t actiontableplace; #define NR_OF_OPS (4<>INITIAL_DECIMATE; + int16_t adcv = adcval; + adcv *= FRONTEND_AMPLITUDE; + if( adcv > 127 ) adcv = 127; + if( adcv < -128 ) adcv = -128; + running_integral += adcv>>INITIAL_DECIMATE; #define dprintf( ... ) @@ -209,11 +220,12 @@ void Turbo8BitRun( int8_t adcval ) } else { - int octaveplace = op & 0xf; + //int octaveplace = op & 0xf; //Tricky: We share the integral with SIN and COS. //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 - int intindex = (octaveplace>>1) * MAX_FREQS + n; + uint8_t octave = op & 0xf; + uint8_t intindex = octave * MAX_FREQS + n; //int invoct = OCTAVES-1-octaveplace; int16_t diff; @@ -221,45 +233,74 @@ void Turbo8BitRun( int8_t adcval ) if( op & 0x10 ) //ADD { diff = integral_at[intindex] - running_integral; - dprintf( "%c", 'a' + octaveplace ); + dprintf( "%c", 'a' + (op & 0xf) ); } else //SUBTRACT { diff = running_integral - integral_at[intindex]; - dprintf( "%c", 'A' + octaveplace ); + dprintf( "%c", 'A' + (op & 0xf) ); } - if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); - integral_at[intindex] = running_integral; - int idx = intindex * 2 + (octaveplace&1); +#ifdef TWELVEBIT + if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); +#elif defined( EIGHTBIT ) + if( diff > 124 || diff < -124 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); +#endif - //if( n == 1 ) printf( "%d %d %d %d\n", n, idx, diff, op & 0x10 ); - //dprintf( "%d\n", idx ); - -#if 0 - //Apply IIR operation 1; This is rough because the Q changes and goes higher as a function of frequency. This is probably a bad move. - cossindata[idx] += diff>>4; - if( op & 0x20 ) + uint8_t idx = ( intindex << 1 ); + if( op&(1<<6) ) { - cossindata[idx] = cossindata[idx] - - (cossindata[idx]>>2); + idx |= 1; } -#else - //Apply IIR. + //printf( "%d: %d + %d * %d >> 8 - %d\n", idx, cossindata[idx], diff, mulmux[idx/2], cossindata[idx]>>4 ); + + uint8_t mulmuxval = mulmux[n]; + + + //Do you live on a super lame processor? {NOTE 4} + //If you do, you might not have good signed multiply operations. So, an alternative mechanism is found here. + // +) Able to more cleanly crush to an 8-bit multiply. + // +) Gets extra bit of precision back, i.e. the sign bit is now used as a data bit. + // -) More than 1 line of C code. Requires possible double invert. +#if 1 + //Terrible processor, i.e. PMS133 + if( 0 && diff < 0 ) + { + diff *= -1; + diff >>= (OCTAVES-1-octave); + + if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + + diff = (uint16_t)diff * (uint16_t)mulmuxval; + diff >>= INTEGRATOR_DECIMATE; + + diff *= -1; + } + else + { + diff >>= (OCTAVES-1-octave); + + if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + + diff = (uint16_t)diff * (uint16_t)mulmuxval; + diff >>= INTEGRATOR_DECIMATE; + } +#else + //Decent processor, i.e. ATTiny85. + diff = ((diff>>(OCTAVES-1-octave)) * mulmuxval ) >> 6; +#endif cossindata[idx] = cossindata[idx] - + (((int32_t)diff * (int32_t)mulmux[idx/2])>>6) + + diff - (cossindata[idx]>>4) ; - // if( cossindata[idx] > 2047 ) cossindata[idx] = 2047; - // if( cossindata[idx] < -2048 ) cossindata[idx] = -2048; + +#ifdef EIGHTBIT + if( cossindata[idx] > 0 ) cossindata[idx]--; + if( cossindata[idx] < 0 ) cossindata[idx]++; #endif - // if( cossindata[idx] > 1 ) cossindata[idx]--; - // if( cossindata[idx] < -1 ) cossindata[idx]++; - // if( cossindata[idx] > 16 ) cossindata[idx]-=8; - // if( cossindata[idx] < -16 ) cossindata[idx]+=8; } } else @@ -269,27 +310,6 @@ void Turbo8BitRun( int8_t adcval ) } dprintf( "\n" ); -#if 0 - uint32_t actions = *(placeintable++); - if( placeintable == &actiontable[ACTIONTABLESIZE] ) placeintable = actiontable; - int b; - for( b = 0; b < MAX_FREQS; b++ ) - { - if( ! ((1<>= 1; - int octavebit = op & ((1<>5 ); //6 = Actually only feed algorithm numbers from -64 to 63. + Turbo8BitRun( ifr1>>5 ); //5 = Actually only feed algorithm numbers from -128 to 127. } last_place = place_in_data_buffer; @@ -312,25 +332,9 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float #if 1 for( i = 0; i < bins; i++ ) { - outbins[i] = 0; - } - for( i = 0; i < bins; i++ ) - { - int iss = cossindata[i*2+0]>>8; - int isc = cossindata[i*2+1]>>8; - int issdiv = 0; - int iscdiv = 0; - int FWDOFFSET = 19;//MAX_FREQS*3/2; - if( i < bins-FWDOFFSET ) - { - issdiv = cossindata[(i+FWDOFFSET)*2+0]/256; - iscdiv = cossindata[(i+FWDOFFSET)*2+1]/256; - } + int iss = cossindata[i*2+0]>>FINAL_DECIMATE; + int isc = cossindata[i*2+1]>>FINAL_DECIMATE; int mux = iss * iss + isc * isc; - int muxdiv = issdiv * issdiv + iscdiv * iscdiv; - - //if( (idiv % 100) > 50 ) { printf( "*" ); mux -= muxdiv; } - //mux -= muxdiv; if( mux <= 0 ) { @@ -338,16 +342,17 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float } else { - //if( i == 0 ) - //printf( "MUX: %d %d = %d\n", isc, iss, mux ); outbins[i] = sqrt((float)mux)/50.0; - if( abs( cossindata[i*2+0] ) > 2000 || abs( cossindata[i*2+1] ) > 2000 ) - printf( "%d/%d/%d/%f ", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); - //outbins[i] = (cossindata[i*2+0]/10000.0); +#ifdef TWELVEBIT + if( abs( cossindata[i*2+0] ) > 1000 || abs( cossindata[i*2+1] ) > 1000 ) + printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); +#elif defined( EIGHTBIT ) + if( abs( cossindata[i*2+0] ) > 120 || abs( cossindata[i*2+1] ) > 120 ) + printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); +#endif } } - printf( "\n" ); #endif } From cd56e249bc5f05e24aae5ee4bcdec5148ffec1c9 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Mon, 29 Apr 2019 01:28:52 -0400 Subject: [PATCH 11/14] 8turbo is really turning into a real 8turbo --- embeddedcommon/DFT12Small.c | 169 +++++++++++------------- embeddedcommon/DFT8Turbo.c | 255 +++++++++++++++--------------------- 2 files changed, 180 insertions(+), 244 deletions(-) diff --git a/embeddedcommon/DFT12Small.c b/embeddedcommon/DFT12Small.c index 41c9fdd..6d04241 100644 --- a/embeddedcommon/DFT12Small.c +++ b/embeddedcommon/DFT12Small.c @@ -1,3 +1,5 @@ +//NOTE DO NOT EDIT THIS FILE WITHOUT ALSO EDITING DFT8TURBO!!! + #include #include #include "DFT12Small.h" @@ -32,9 +34,6 @@ #define FINAL_DECIMATE (1) #endif -//Right now, we need 8*freqs*octaves bytes. -//This is bad. -//What can we do to fix it? //4x the hits (sin/cos and we need to do it once for each edge) //8x for selecting a higher octave. @@ -199,116 +198,104 @@ void Small12BitRun( int8_t adcval ) if( adcv < -128 ) adcv = -128; running_integral += adcv>>INITIAL_DECIMATE; -#define dprintf( ... ) - uint32_t action = actiontable[actiontableplace++]; int n; - dprintf( "%4d ", actiontableplace ); - for( n = 0; n < MAX_FREQS; n++ ) + for( n = 0; n < MAX_FREQS; n++, action>>=1 ) { - if( action & (1<= NR_OF_OPS ) ao = 0; + which_octave_for_op[n] = ao; + + int op = optable[ao]; + + if( op == 255 ) + continue; + + //int octaveplace = op & 0xf; + + //Tricky: We share the integral with SIN and COS. + //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 + uint8_t octave = op & 0xf; + uint8_t intindex = octave * MAX_FREQS + n; + + //int invoct = OCTAVES-1-octaveplace; + int16_t diff; + + if( op & 0x10 ) //ADD { - int ao = which_octave_for_op[n]; - int op = optable[ao]; - ao++; - if( ao >= NR_OF_OPS ) ao = 0; - which_octave_for_op[n] = ao; + diff = integral_at[intindex] - running_integral; + } + else //SUBTRACT + { + diff = running_integral - integral_at[intindex]; + } - if( op == 255 ) - { - dprintf( "*" ); //NOP - } - else - { - //int octaveplace = op & 0xf; - - //Tricky: We share the integral with SIN and COS. - //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 - uint8_t octave = op & 0xf; - uint8_t intindex = octave * MAX_FREQS + n; - - //int invoct = OCTAVES-1-octaveplace; - int16_t diff; - - if( op & 0x10 ) //ADD - { - diff = integral_at[intindex] - running_integral; - dprintf( "%c", 'a' + (op & 0xf) ); - } - else //SUBTRACT - { - diff = running_integral - integral_at[intindex]; - dprintf( "%c", 'A' + (op & 0xf) ); - } - - integral_at[intindex] = running_integral; + integral_at[intindex] = running_integral; #ifdef TWELVEBIT - if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); + if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); #elif defined( EIGHTBIT ) - if( diff > 124 || diff < -124 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); + if( diff > 124 || diff < -124 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); #endif - uint8_t idx = ( intindex << 1 ); - if( op&(1<<6) ) - { - idx |= 1; - } + //uint8_t idx = ( intindex << 1 ); + intindex<<=1; - //printf( "%d: %d + %d * %d >> 8 - %d\n", idx, cossindata[idx], diff, mulmux[idx/2], cossindata[idx]>>4 ); + if( op&(1<<6) ) + { + intindex |= 1; + } - uint8_t mulmuxval = mulmux[n]; + //printf( "%d: %d + %d * %d >> 8 - %d\n", intindex, cossindata[intindex], diff, mulmux[intindex/2], cossindata[intindex]>>4 ); + + uint8_t mulmuxval = mulmux[n]; - //Do you live on a super lame processor? {NOTE 4} - //If you do, you might not have good signed multiply operations. So, an alternative mechanism is found here. - // +) Able to more cleanly crush to an 8-bit multiply. - // +) Gets extra bit of precision back, i.e. the sign bit is now used as a data bit. - // -) More than 1 line of C code. Requires possible double invert. + //Do you live on a super lame processor? {NOTE 4} + //If you do, you might not have good signed multiply operations. So, an alternative mechanism is found here. + // +) Able to more cleanly crush to an 8-bit multiply. + // +) Gets extra bit of precision back, i.e. the sign bit is now used as a data bit. + // -) More than 1 line of C code. Requires possible double invert. #if 1 - //Terrible processor, i.e. PMS133 - if( 0 && diff < 0 ) - { - diff *= -1; - diff >>= (OCTAVES-1-octave); + //Terrible processor, i.e. PMS133 + if( 0 && diff < 0 ) + { + diff *= -1; + diff >>= (OCTAVES-1-octave); - if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); - diff = (uint16_t)diff * (uint16_t)mulmuxval; - diff >>= INTEGRATOR_DECIMATE; + diff = (uint16_t)diff * (uint16_t)mulmuxval; + diff >>= INTEGRATOR_DECIMATE; - diff *= -1; - } - else - { - diff >>= (OCTAVES-1-octave); - - if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); - - diff = (uint16_t)diff * (uint16_t)mulmuxval; - diff >>= INTEGRATOR_DECIMATE; - } -#else - //Decent processor, i.e. ATTiny85. - diff = ((diff>>(OCTAVES-1-octave)) * mulmuxval ) >> 6; -#endif - cossindata[idx] = cossindata[idx] - + diff - - (cossindata[idx]>>4) - ; - -#ifdef EIGHTBIT - if( cossindata[idx] > 0 ) cossindata[idx]--; - if( cossindata[idx] < 0 ) cossindata[idx]++; -#endif - } + diff *= -1; } else { - dprintf( " " ); - } + diff >>= (OCTAVES-1-octave); + + if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + + diff = (uint16_t)diff * (uint16_t)mulmuxval; + diff >>= INTEGRATOR_DECIMATE; + } +#else + //Decent processor, i.e. ATTiny85. + diff = ((diff>>(OCTAVES-1-octave)) * mulmuxval ) >> 6; +#endif + cossindata[intindex] = cossindata[intindex] + + diff + - (cossindata[intindex]>>4) + ; + +#ifdef EIGHTBIT + if( cossindata[intindex] > 0 ) cossindata[intindex]--; + if( cossindata[intindex] < 0 ) cossindata[intindex]++; +#endif } - dprintf( "\n" ); } diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index 1471270..fd07df4 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -1,3 +1,5 @@ +//NOTE DO NOT EDIT THIS FILE WITHOUT ALSO EDITING DFT12SMALL!!! + #include #include #include "DFT8Turbo.h" @@ -5,7 +7,6 @@ #include - #define MAX_FREQS (12) #define OCTAVES (4) @@ -15,67 +16,24 @@ You should test with extreme cases, like square wave sweeps in, etc. */ -//#define TWELVEBIT -#define EIGHTBIT - -#ifdef TWELVEBIT -//No larger than 12-bit signed values for integration or sincos -#define FRONTEND_AMPLITUDE (0) -#define INITIAL_DECIMATE (2) -#define INTEGRATOR_DECIMATE (8) -#define FINAL_DECIMATE (4) -#elif defined( EIGHTBIT ) //No larger than 8-bit signed values for integration or sincos #define FRONTEND_AMPLITUDE (2) #define INITIAL_DECIMATE (5) //Yurgh... only 3 bits of ADC data. That's 8 unique levels :( #define INTEGRATOR_DECIMATE (8) #define FINAL_DECIMATE (1) -#endif -//Right now, we need 8*freqs*octaves bytes. -//This is bad. -//What can we do to fix it? + +#define OPTABLETYPE uint16_t //Make uint8_t if on attiny. //4x the hits (sin/cos and we need to do it once for each edge) //8x for selecting a higher octave. #define FREQREBASE 8.0 #define TARGFREQ 10000.0 -/* Tradeoff guide: - - * We will optimize for RAM size here. - - * INITIAL_DECIMATE; A larger decimation: {NOTE 1} - +) Reduces the bit depth needed for the integral map. - If you use "1" and a fully saturted map (highest note is every sample), it will not overflow a signed 12-bit number. - -) Increases noise. - With full-scale: 0->1 minimal 1->2 minimal 2->3 significantly noticable, 3->4 major. - If sound is quieter, it matters more. Not sure with other changes in system. (2) seems ok. - -) If you make it (1) or (0) You can't do an 8-bit multiply and keep the output in a signed range. - Also, other things, like frequency of hits can manipulate the maximum bit depth needed for integral map. - - * If you weight the bins in advance see "mulmux", you can: {NOTE 2} - +) potentially use shallower bit depth but - -) have to compute the multiply every time you update the bin. - - * You can use a modified-square-wave which only integrates for 1/2 of the duty cycle. {NOTE 3} - +) uses 1/2 the integral memory. - -) Not as pretty of an output. See "integral_at" - - *TODO: Investigate using all unsigned (to make multiply and/or 12-bit storage easier) - *TODO: Consider a mode which has 16-bit integrals, but still 8-bit cossin data. - - So, the idea here is we would keep a running total of the current ADC value, kept away in a int16_t. - It is constantly summing, so we can take an integral of it. Or rather an integral range. - - Over time, we perform operations like adding or subtracting from a current place. It basically is - a DFT where the kernel is computed using square waves (or modified square waves) -*/ - //These live in RAM. -int16_t running_integral; //Realistically treat as 12-bits on ramjet8 -int16_t integral_at[MAX_FREQS*OCTAVES]; //For ramjet8, make 12-bits -int32_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) +int8_t running_integral; //Realistically treat as 12-bits on ramjet8 +int8_t integral_at[MAX_FREQS*OCTAVES]; //For ramjet8, make 12-bits +int8_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. uint8_t actiontableplace; @@ -84,13 +42,13 @@ uint8_t actiontableplace; // 255 = DO NOT OPERATE // bits 0..3 unfolded octave, i.e. sin/cos are offset by one. // bit 4 = add or subtract. -uint8_t optable[NR_OF_OPS]; //PUT IN FLASH +OPTABLETYPE optable[NR_OF_OPS]; //PUT IN FLASH #define ACTIONTABLESIZE 256 uint16_t actiontable[ACTIONTABLESIZE]; //PUT IN FLASH // If there are more than 8 freqbins, this must be a uint16_t, otherwise if more than 16, 32. //Format is -uint8_t mulmux[MAX_FREQS]; //PUT IN FLASH +OPTABLETYPE mulmux[MAX_FREQS]; //PUT IN FLASH static int Setup( float * frequencies, int bins ) { @@ -199,117 +157,112 @@ void Turbo8BitRun( int8_t adcval ) if( adcv < -128 ) adcv = -128; running_integral += adcv>>INITIAL_DECIMATE; -#define dprintf( ... ) + uint16_t action = actiontable[actiontableplace++]; + uint8_t n; - uint32_t action = actiontable[actiontableplace++]; - int n; - dprintf( "%4d ", actiontableplace ); - for( n = 0; n < MAX_FREQS; n++ ) + //Counts are approximate counts for PMS133 + + for( n = 0; //1CYC + n < MAX_FREQS; //2CYC + n++, //1CYC + action>>=1 //2CYC + ) { - if( action & (1<= NR_OF_OPS ) ao = 0; //2CYC + which_octave_for_op[n] = ao; //2CYC (idxm) + + uint8_t op = optable[ao]; //"theoretically" 3CYC (if you align things right) + //1CYC (Put A into specific RAM location) + + //If we are on the one thing we aren't supposed to operate within, cancel. + if( op == 255 ) continue; //2CYC (if op is in A) + + //Tricky: We share the integral with SIN and COS. + //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 + uint8_t octave = op & 0xf; //1CYC (if op is in A) + + + uint8_t intindex = octave * MAX_FREQS //Load mulop with 12 [2CYC]; mul [1CYC] + + n; //Add [1CYC] + //[1CYC] more cycle to write A into RAM[(intindex) + //int invoct = OCTAVES-1-octaveplace; + int8_t diff; + + if( op & 0x10 ) //ADD //2CYC { - int ao = which_octave_for_op[n]; - int op = optable[ao]; - ao++; - if( ao >= NR_OF_OPS ) ao = 0; - which_octave_for_op[n] = ao; + diff = integral_at[intindex] //Assume "IntIndex" is in A, add integral_at to A [1], move A to an index [1]. [2] to read into acc. [4CYC] + - running_integral; //1CYC to subtract. + //1CYC to write diff into a memory location. + } + else //SUBTRACT + { + diff = running_integral - integral_at[intindex]; + } - if( op == 255 ) - { - dprintf( "*" ); //NOP - } - else - { - //int octaveplace = op & 0xf; + //30 cycles so far. - //Tricky: We share the integral with SIN and COS. - //We don't need to. It would produce a slightly cleaner signal. See: NOTE 3 - uint8_t octave = op & 0xf; - uint8_t intindex = octave * MAX_FREQS + n; + integral_at[intindex] = running_integral; //[3CYC] - //int invoct = OCTAVES-1-octaveplace; - int16_t diff; + //if( diff > 124 || diff < -124 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); + + //uint8_t idx = ( intindex << 1 ); //Overwrite intindex. + intindex <<= 1; //1CYC - if( op & 0x10 ) //ADD - { - diff = integral_at[intindex] - running_integral; - dprintf( "%c", 'a' + (op & 0xf) ); - } - else //SUBTRACT - { - diff = running_integral - integral_at[intindex]; - dprintf( "%c", 'A' + (op & 0xf) ); - } + if( op&(1<<6) ) //2CYC + { + intindex |= 1; //1CYC + } - integral_at[intindex] = running_integral; - -#ifdef TWELVEBIT - if( diff > 2000 || diff < -2000 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); -#elif defined( EIGHTBIT ) - if( diff > 124 || diff < -124 ) printf( "!!!!!!!!!!!! %d !!!!!!!!!!!\n", diff ); -#endif - - uint8_t idx = ( intindex << 1 ); - if( op&(1<<6) ) - { - idx |= 1; - } - - //printf( "%d: %d + %d * %d >> 8 - %d\n", idx, cossindata[idx], diff, mulmux[idx/2], cossindata[idx]>>4 ); - - uint8_t mulmuxval = mulmux[n]; + uint8_t mulmuxval = mulmux[n]; //[4CYC] - //Do you live on a super lame processor? {NOTE 4} - //If you do, you might not have good signed multiply operations. So, an alternative mechanism is found here. - // +) Able to more cleanly crush to an 8-bit multiply. - // +) Gets extra bit of precision back, i.e. the sign bit is now used as a data bit. - // -) More than 1 line of C code. Requires possible double invert. + //Do you live on a super lame processor? {NOTE 4} + //If you do, you might not have good signed multiply operations. So, an alternative mechanism is found here. + // +) Able to more cleanly crush to an 8-bit multiply. + // +) Gets extra bit of precision back, i.e. the sign bit is now used as a data bit. + // -) More than 1 line of C code. Requires possible double invert. #if 1 - //Terrible processor, i.e. PMS133 - if( 0 && diff < 0 ) - { - diff *= -1; - diff >>= (OCTAVES-1-octave); + //rough processor, i.e. PMS133 + if( diff < 0 ) //[2CYC] + { + diff *= -1; //[1CYC] + diff >>= (OCTAVES-1-octave); // ???TRICKY??? + //if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); - if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); - - diff = (uint16_t)diff * (uint16_t)mulmuxval; - diff >>= INTEGRATOR_DECIMATE; - - diff *= -1; - } - else - { - diff >>= (OCTAVES-1-octave); - - if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); - - diff = (uint16_t)diff * (uint16_t)mulmuxval; - diff >>= INTEGRATOR_DECIMATE; - } -#else - //Decent processor, i.e. ATTiny85. - diff = ((diff>>(OCTAVES-1-octave)) * mulmuxval ) >> 6; -#endif - cossindata[idx] = cossindata[idx] - + diff - - (cossindata[idx]>>4) - ; - -#ifdef EIGHTBIT - if( cossindata[idx] > 0 ) cossindata[idx]--; - if( cossindata[idx] < 0 ) cossindata[idx]++; -#endif - } + diff = ((uint16_t)diff * (uint16_t)mulmuxval)>>INTEGRATOR_DECIMATE; //[3CYC] + diff *= -1; //[1CYC] } else { - dprintf( " " ); - } - } - dprintf( "\n" ); + diff >>= (OCTAVES-1-octave); + //if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + diff = ((uint16_t)diff * (uint16_t)mulmuxval)>>INTEGRATOR_DECIMATE; + } + //@48 cycles :( :( :( + +#else + //Decent processor, i.e. ATTiny85. + diff = ((diff>>(OCTAVES-1-octave)) * mulmuxval ) >> 6; +#endif + //printf( "%d\n", diff ); + + cossindata[intindex] = cossindata[intindex] + + diff + - (cossindata[intindex]>>4) + ; + + if( cossindata[intindex] > 0 ) cossindata[intindex]--; + if( cossindata[intindex] < 0 ) cossindata[intindex]++; + } } @@ -344,13 +297,9 @@ void DoDFT8BitTurbo( float * outbins, float * frequencies, int bins, const float { outbins[i] = sqrt((float)mux)/50.0; -#ifdef TWELVEBIT - if( abs( cossindata[i*2+0] ) > 1000 || abs( cossindata[i*2+1] ) > 1000 ) - printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); -#elif defined( EIGHTBIT ) - if( abs( cossindata[i*2+0] ) > 120 || abs( cossindata[i*2+1] ) > 120 ) - printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); -#endif + if( abs( cossindata[i*2+0] ) > 120 || abs( cossindata[i*2+1] ) > 120 ) + printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); + } } #endif From 15951d3128791186daeb27d9b38d17bef886d25c Mon Sep 17 00:00:00 2001 From: cnlohr Date: Mon, 29 Apr 2019 02:16:24 -0400 Subject: [PATCH 12/14] ok. 8turbo's base is good. --- embeddedcommon/DFT8Turbo.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index fd07df4..e4661a2 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -234,7 +234,8 @@ void Turbo8BitRun( int8_t adcval ) if( diff < 0 ) //[2CYC] { diff *= -1; //[1CYC] - diff >>= (OCTAVES-1-octave); // ???TRICKY??? + diff >>= (OCTAVES-1-octave); // ???TRICKY??? Should this be a multiply? + //if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); diff = ((uint16_t)diff * (uint16_t)mulmuxval)>>INTEGRATOR_DECIMATE; //[3CYC] @@ -255,13 +256,16 @@ void Turbo8BitRun( int8_t adcval ) #endif //printf( "%d\n", diff ); - cossindata[intindex] = cossindata[intindex] - + diff - - (cossindata[intindex]>>4) + int8_t tmp = + cossindata[intindex] //[3CYC] + + diff //[1CYC] + - (cossindata[intindex]>>4) //[2CYC] ; - if( cossindata[intindex] > 0 ) cossindata[intindex]--; - if( cossindata[intindex] < 0 ) cossindata[intindex]++; + if( tmp > 0 ) tmp--; //2CYC + if( tmp < 0 ) tmp++; //2CYC + cossindata[intindex] = tmp; //2CYC + //60ish cycles :( :( :( } } From fa186adaefe32ef804b91be417d7d31e0d4a8738 Mon Sep 17 00:00:00 2001 From: cnlohr Date: Mon, 29 Apr 2019 03:30:43 -0400 Subject: [PATCH 13/14] add the paduk thing currently broken. --- colorchord2/Makefile | 2 +- colorchord2/notefinder.c | 4 + colorchord2/turbo8bit.conf | 2 +- embeddedcommon/DFT8Padauk.c | 327 ++++++++++++++++++++++++++++++++++++ embeddedcommon/DFT8Padauk.h | 9 + embeddedcommon/DFT8Turbo.c | 4 +- 6 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 embeddedcommon/DFT8Padauk.c create mode 100644 embeddedcommon/DFT8Padauk.h diff --git a/colorchord2/Makefile b/colorchord2/Makefile index e83c55c..1299852 100644 --- a/colorchord2/Makefile +++ b/colorchord2/Makefile @@ -17,7 +17,7 @@ LDLIBS:=-lpthread -lasound -lm -lpulse-simple -lpulse -ludev -lrt CFLAGS:=-g -O0 -flto -Wall -ffast-math -I../embeddedcommon -I. -DICACHE_FLASH_ATTR= EXTRALIBS:=-lusb-1.0 -colorchord : os_generic.o main.o dft.o decompose.o filter.o color.o notefinder.o util.o outdrivers.o $(RAWDRAW) $(SOUND) $(OUTS) parameters.o chash.o hook.o ../embeddedcommon/DFT32.o configs.o ../embeddedcommon/DFT8Turbo.o +colorchord : os_generic.o main.o dft.o decompose.o filter.o color.o notefinder.o util.o outdrivers.o $(RAWDRAW) $(SOUND) $(OUTS) parameters.o chash.o hook.o ../embeddedcommon/DFT32.o configs.o ../embeddedcommon/DFT8Turbo.o ../embeddedcommon/DFT8Padauk.o gcc -o $@ $^ $(CFLAGS) $(LDLIBS) $(EXTRALIBS) $(RAWDRAWLIBS) diff --git a/colorchord2/notefinder.c b/colorchord2/notefinder.c index 1f0a01c..02bf0b7 100644 --- a/colorchord2/notefinder.c +++ b/colorchord2/notefinder.c @@ -12,6 +12,7 @@ #include "decompose.h" #include "DFT32.h" #include "DFT8Turbo.h" +#include "DFT8Padauk.h" struct NoteFinder * CreateNoteFinder( int spsRec ) { @@ -203,6 +204,9 @@ void RunNoteFinder( struct NoteFinder * nf, const float * audio_stream, int head case 5: DoDFT8BitTurbo( dftbins, nf->frequencies, freqs, audio_stream, head, buffersize, nf->dft_q, nf->dft_speedup ); break; + case 6: + DoDFT8BitPadauk( dftbins, nf->frequencies, freqs, audio_stream, head, buffersize, nf->dft_q, nf->dft_speedup ); + break; default: fprintf( stderr, "Error: No DFT Seleced\n" ); } diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index d9ce3d4..2d4bdcc 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -30,7 +30,7 @@ base_hz = 82.41 samplerate = 10000 freqbins = 12 octaves = 4 - +do_progressive_dft=6 slope = 0 diff --git a/embeddedcommon/DFT8Padauk.c b/embeddedcommon/DFT8Padauk.c new file mode 100644 index 0000000..bc1a976 --- /dev/null +++ b/embeddedcommon/DFT8Padauk.c @@ -0,0 +1,327 @@ +//NOTE DO NOT EDIT THIS FILE WITHOUT ALSO EDITING DFT12SMALL!!! + +#include +#include +#include "DFT8Turbo.h" +#include + +#include + +#define MAX_FREQS (12) +#define OCTAVES (4) + +/* + General procedure - use this code, with uint16_t or uint32_t buffers, and make sure none of the alarms go off. + All of the paths still require no more than an 8-bit multiply. + You should test with extreme cases, like square wave sweeps in, etc. +*/ + +//No larger than 8-bit signed values for integration or sincos +#define FRONTEND_AMPLITUDE (2) +#define INITIAL_DECIMATE (5) //Yurgh... only 3 bits of ADC data. That's 8 unique levels :( +#define INTEGRATOR_DECIMATE (8) +#define FINAL_DECIMATE (1) + + +#define OPTABLETYPE uint16_t //Make uint8_t if on attiny. + +//4x the hits (sin/cos and we need to do it once for each edge) +//8x for selecting a higher octave. +#define FREQREBASE 8.0 +#define TARGFREQ 10000.0 + +//These live in RAM. +int8_t running_integral; //Realistically treat as 12-bits on ramjet8 +int8_t integral_at[MAX_FREQS*OCTAVES]; //For ramjet8, make 12-bits +int8_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit for now, will be 16-bit, potentially even 8.) +uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. +uint8_t actiontableplace; + +#define NR_OF_OPS (4< hits per %d: %f %d (%.2f%% error)\n", topbin, f, ACTIONTABLESIZE, (float)ACTIONTABLESIZE/f, dhrpertable, err * 100.0 ); + if( dhrpertable >= ACTIONTABLESIZE ) + { + fprintf( stderr, "Error: Too many hits.\n" ); + exit(0); + } + + float advance_per_step = dhrpertable/(float)ACTIONTABLESIZE; + float fvadv = 0.5; + int j; + int countset = 0; + + //Tricky: We need to start fadv off at such a place that there won't be a hicchup when going back around to 0. + // I believe this is done by setting fvadv to 0.5 initially. Unsure. + + for( j = 0; j < ACTIONTABLESIZE; j++ ) + { + if( fvadv >= 0.5 ) + { + actiontable[j] |= 1<<(MAX_FREQS-1-topbin); //XXX-DEPARTURE (reversing the table symbols) + fvadv -= 1.0; + countset++; + } + fvadv += advance_per_step; + } + printf( " countset: %d\n", countset ); + } + //exit(1); + + + int phaseinop[OCTAVES] = { 0 }; + int already_hit_octaveplace[OCTAVES*2] = { 0 }; + for( i = 0; i < NR_OF_OPS; i++ ) + { + int longestzeroes = 0; + int val = i & ((1<> longestzeroes) & 1) == 0 ); longestzeroes++ ); + //longestzeroes goes: 255, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, ... + //This isn't great, because we need to also know whether we are attacking the SIN side or the COS side, and if it's + or -. + //We can actually decide that out. + + if( longestzeroes == 255 ) + { + //This is a nop. Emit a nop. + optable[i] = 255; + } + else + { + longestzeroes = OCTAVES-1-longestzeroes; //Actually do octave 0 least often. + int iop = phaseinop[longestzeroes]++; + int toop = longestzeroes; + int toopmon = (longestzeroes<<1) | (iop & 1); + + //if it's the first time an octave happened this set, flag it. This may be used later in the process. + if( !already_hit_octaveplace[toopmon] ) + { + already_hit_octaveplace[toopmon] = 1; + toop |= 1<<5; + } + if( iop & 1 ) + { + toop |= 1<<6; + } + + //Handle add/subtract bit. + if( iop & 2 ) toop |= 1<<4; + + optable[i] = toop; + + //printf( " %d %d %d\n", iop, val, longestzeroes ); + } + //printf( "HBT: %d = %d\n", i, optable[i] ); + } + //exit(1); + + return 0; +} + + +static uint16_t action; +static uint8_t note; +static uint8_t * memptr; +static uint16_t * romptr; +static uint8_t op; +static uint8_t octave; +static uint8_t intindex; +static int8_t diff; +static uint8_t tmp; + +void Padauk8BitRun( int8_t adcval ) +{ + int16_t adcv = adcval; + adcv *= FRONTEND_AMPLITUDE; + if( adcv > 127 ) adcv = 127; + if( adcv < -128 ) adcv = -128; + running_integral += adcv>>INITIAL_DECIMATE; + + uint8_t acc; + uint8_t * accM; + uint8_t mul2; + + action = actiontable[actiontableplace++]; + + //Counts are approximate counts for PMS133 + + for( note = MAX_FREQS; + note; //1CYC/PAIRED + note--, //1CYC/PAIRED (dzsn) + action>>=1 //2CYC (slc x2) + ) + { + //Everything inside this loop is executed ~3/4 * MAX_FREQS per audio sample. so.. ~9x. + //If op @ 4MHz, we get 44 cycles in here. + + //If no operation is scheduled, continue. + if( !( action & 1 ) ) continue; //1CYC + + accM = which_octave_for_op - 1; //1CYC + accM = accM + note; //1CYC + memptr = accM; //1CYC + acc = *memptr; //2CYC (idxm) + acc++; //1CYC + if( acc == NR_OF_OPS ) //2CYC + acc = 0; + *memptr = acc; //2CYC (idxm) + + accM = (uint8_t*)optable + acc*2; //1CYC + romptr = (uint16_t*)accM; //1CYC + acc = *romptr; //2CYC (ldtabl) + + //If we are on the one thing we aren't supposed to operate within, cancel. + if( acc == 255 ) continue; //2CYC + + op = acc; //1CYC + + //21 cycles so far. + + acc = MAX_FREQS; //1CYC + mul2 = acc; //1CYC + acc = op; //1CYC + acc = acc & 0xf; //1CYC + octave = acc; //1CYC + acc = acc * mul2; //2CYC + acc = acc + note; //1CYC + intindex = acc; //1CYC + accM = (uint8_t*)integral_at-1 + acc; //1CYC + memptr = accM; //1CYC + + if( op & 0x10 ) //ADD //2CYC + { + acc = *memptr; //2CYC + acc = acc - running_integral; //1CYC + diff = acc; //1CYC + } + else //SUBTRACT + { + acc = *memptr; //2CYC + tmp = acc; //1CYC + acc = running_integral; //1CYC + acc = acc - tmp; //1CYC + diff = acc; //1CYC + } + + acc = running_integral; //1CYC + *memptr = acc; //2CYC + + //42 AVERAGE cycles so far. + //??? Something below here is wrong??? Or near here??? XXX TODO PICK UP HERE!!! + intindex <<= 1; //1CYC + if( op&(1<<6) ) //2CYC + { + intindex |= 1; //1CYC + } + + accM = (uint8_t*)(mulmux - 1); //1CYC + accM = accM + note*2; //1CYC + romptr = accM; //1CYC + acc = *romptr; //2CYC + mul2 = acc; //1CYC + + if( diff < 0 ) //[2CYC] (t0sn on MSB) + { + diff *= -1; //[1CYC] (neg M) + diff >>= (OCTAVES-1-octave); // ???TRICKY??? Should this be a multiply? + + //if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + + diff = ((uint16_t)diff * (uint16_t)mul2)>>INTEGRATOR_DECIMATE; //[3CYC] + diff *= -1; //[1CYC] + } + else + { + diff >>= (OCTAVES-1-octave); + //if( diff > 250 ) printf( "!!!!!!!**** %d ****!!!!!!!\n", diff ); + diff = ((uint16_t)diff * (uint16_t)mul2)>>INTEGRATOR_DECIMATE; + } + + //@48 cycles :( :( :( + + //printf( "%d\n", diff ); + + int8_t tmp = + cossindata[intindex] //[3CYC] + + diff //[1CYC] + - (cossindata[intindex]>>4) //[2CYC] + ; + + if( tmp > 0 ) tmp--; //2CYC + if( tmp < 0 ) tmp++; //2CYC + cossindata[intindex] = tmp; //2CYC + //60ish cycles :( :( :( + } +} + + +void DoDFT8BitPadauk( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ) +{ + static int is_setup; + if( !is_setup ) { is_setup = 1; Setup( frequencies, bins ); } + static int last_place; + int i; + + for( i = last_place; i != place_in_data_buffer; i = (i+1)%size_of_data_buffer ) + { + int16_t ifr1 = (int16_t)( ((databuffer[i]) ) * 4095 ); + Padauk8BitRun( ifr1>>5 ); //5 = Actually only feed algorithm numbers from -128 to 127. + } + last_place = place_in_data_buffer; + + static int idiv; + idiv++; +#if 1 + for( i = 0; i < bins; i++ ) + { + int iss = cossindata[i*2+0]>>FINAL_DECIMATE; + int isc = cossindata[i*2+1]>>FINAL_DECIMATE; + int mux = iss * iss + isc * isc; + + if( mux <= 0 ) + { + outbins[i] = 0; + } + else + { + outbins[i] = sqrt((float)mux)/50.0; + + if( abs( cossindata[i*2+0] ) > 120 || abs( cossindata[i*2+1] ) > 120 ) + printf( "CS OVF %d/%d/%d/%f\n", i, cossindata[i*2+0], cossindata[i*2+1],outbins[i] ); + + } + } +#endif +} + + diff --git a/embeddedcommon/DFT8Padauk.h b/embeddedcommon/DFT8Padauk.h new file mode 100644 index 0000000..cb6387a --- /dev/null +++ b/embeddedcommon/DFT8Padauk.h @@ -0,0 +1,9 @@ +#ifndef _DFT8PADAUK_H +#define _DFT8PADAUK_H + +/* Note: Frequencies must be precompiled. */ + +void DoDFT8BitPadauk( float * outbins, float * frequencies, int bins, const float * databuffer, int place_in_data_buffer, int size_of_data_buffer, float q, float speedup ); + +#endif + diff --git a/embeddedcommon/DFT8Turbo.c b/embeddedcommon/DFT8Turbo.c index e4661a2..6645a41 100644 --- a/embeddedcommon/DFT8Turbo.c +++ b/embeddedcommon/DFT8Turbo.c @@ -37,7 +37,7 @@ int8_t cossindata[MAX_FREQS*OCTAVES*2]; //Contains COS and SIN data. (32-bit fo uint8_t which_octave_for_op[MAX_FREQS]; //counts up, tells you which ocative you are operating on. PUT IN RAM. uint8_t actiontableplace; -#define NR_OF_OPS (4<>=1 //2CYC ) { - //Everything inside this loop is executed ~3/4 * MAX_FREQS. so.. ~9x. + //Everything inside this loop is executed ~3/4 * MAX_FREQS per audio sample. so.. ~9x. //If op @ 4MHz, we get 44 cycles in here. //If no operation is scheduled, continue. From 1012c467d85aa1931b6228a27a026a0eb90c8add Mon Sep 17 00:00:00 2001 From: cnlohr Date: Tue, 18 Jun 2019 05:43:17 -0400 Subject: [PATCH 14/14] making progress. reworking as I go. --- colorchord2/turbo8bit.conf | 18 +++---- embeddedcommon/DFT8Padauk.c | 99 ++++++++++++++++++++++++------------- embeddedcommon/embeddednf.h | 2 +- 3 files changed, 75 insertions(+), 44 deletions(-) diff --git a/colorchord2/turbo8bit.conf b/colorchord2/turbo8bit.conf index 2d4bdcc..039af99 100644 --- a/colorchord2/turbo8bit.conf +++ b/colorchord2/turbo8bit.conf @@ -84,14 +84,12 @@ note_out_chop = 0.05000 shim_sinewave = 0 This is a vornoi thing: -outdrivers = DisplayArray -#lightx = 64 -#lighty = 32 -#fromsides = 1 -#shape_cutoff = 0.03 -#satamp = 5.000 -#amppow = 2.510 -#distpow = 1.500 - - +outdrivers = OutputVoronoi, DisplayArray +lightx = 64 +lighty = 32 +fromsides = 1 +shape_cutoff = 0.03 +satamp = 5.000 +amppow = 2.510 +distpow = 1.500 diff --git a/embeddedcommon/DFT8Padauk.c b/embeddedcommon/DFT8Padauk.c index bc1a976..0194799 100644 --- a/embeddedcommon/DFT8Padauk.c +++ b/embeddedcommon/DFT8Padauk.c @@ -1,4 +1,6 @@ //NOTE DO NOT EDIT THIS FILE WITHOUT ALSO EDITING DFT12SMALL!!! +//WARNING: DFT8Turbo, DFT12Small is currently the only one that's actually working. +//THIS FILE DOES NOT CURRENTLY WORK. #include #include @@ -9,6 +11,10 @@ #define MAX_FREQS (12) #define OCTAVES (4) +/* Backporting notes: + * Change loop to only check if the output table says it's complete. + * Pre-multiply octaves in optable. +*/ /* General procedure - use this code, with uint16_t or uint32_t buffers, and make sure none of the alarms go off. @@ -40,8 +46,11 @@ uint8_t actiontableplace; #define NR_OF_OPS (4< ROM dad is stored in word pairs. romptr = (uint16_t*)accM; //1CYC - acc = *romptr; //2CYC (ldtabl) + acc = *romptr; //2CYC (ldtabl) - //If we are on the one thing we aren't supposed to operate within, cancel. - if( acc == 255 ) continue; //2CYC + //If we are on the one operation we aren't supposed to operate within, we should cancel and loop around. + //XXX XXX XXX XXX XXX This is wrong. We should probably handle this logic above. + //XXX XXX XXX XXX XXX Logic handled above. XXX PICK UP HERE!!! + printf( "+ %d %d %d\n", note, acc, *memptr ); + //if( acc == 255 ) //2CYC + //{ + // //This way, when we loop back around, it will be at index 0, and everything should flow gracefully. + // *memptr = 255; + // continue; + //} + if( acc == 255 ) + { + //We dun goofed. + fprintf( stderr, "Goofed.\n" ); + exit( 0 ); + } - op = acc; //1CYC + //This actually reads the current octave specifier into "op" + //BIT7: add or subtract + //BIT6: reset + //BIT5: Even or odd? + //BITS 0..4 = Which octave. + op = acc; //1CYC - //21 cycles so far. - - acc = MAX_FREQS; //1CYC - mul2 = acc; //1CYC - acc = op; //1CYC - acc = acc & 0xf; //1CYC - octave = acc; //1CYC - acc = acc * mul2; //2CYC + acc = (*romptr)>>8; //2CYC (ldtabh) -> Contains memory offset of which note to use. + note_offset = acc; acc = acc + note; //1CYC - intindex = acc; //1CYC accM = (uint8_t*)integral_at-1 + acc; //1CYC memptr = accM; //1CYC + acc = *memptr; //2CYC idxm - if( op & 0x10 ) //ADD //2CYC + //acc now contains the running integral of the last time we were on this cell. + if( op & (1<<7) ) //ADD //2CYC { - acc = *memptr; //2CYC acc = acc - running_integral; //1CYC - diff = acc; //1CYC } else //SUBTRACT { - acc = *memptr; //2CYC tmp = acc; //1CYC acc = running_integral; //1CYC acc = acc - tmp; //1CYC - diff = acc; //1CYC } + diff = acc; //1CYC + + //Assume 2 extra cycles of overhead for if/else. //2 CYC + acc = running_integral; //1CYC + //Store the current running integral back into this note's running integral for next time. *memptr = acc; //2CYC - //42 AVERAGE cycles so far. - //??? Something below here is wrong??? Or near here??? XXX TODO PICK UP HERE!!! - intindex <<= 1; //1CYC - if( op&(1<<6) ) //2CYC - { - intindex |= 1; //1CYC - } + // op = info about what op we're on. WARNING: Bitfield. + // diff = how much to add to current value. + // note_offset = index of current operative note position. + octave = op & 0x1f; //XXX TODO + printf( "%d %d %d %d\n", op, diff, note_offset, octave ); accM = (uint8_t*)(mulmux - 1); //1CYC accM = accM + note*2; //1CYC romptr = accM; //1CYC diff --git a/embeddedcommon/embeddednf.h b/embeddedcommon/embeddednf.h index d360020..51d3672 100644 --- a/embeddedcommon/embeddednf.h +++ b/embeddedcommon/embeddednf.h @@ -32,7 +32,7 @@ //We take the raw signal off of the #ifndef FILTER_BLUR_PASSES -#define FILTER_BLUR_PASSES 2 +#define FILTER_BLUR_PASSES 1 #endif //Determines bit shifts for where notes lie. We represent notes with an