diff --git a/.gitmodules b/.gitmodules index ed48a58..35e4876 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "embedded8266/esp82xx"] path = embedded8266/esp82xx url = https://github.com/cnlohr/esp82xx.git +[submodule "colorchord2/rawdraw"] + path = colorchord2/rawdraw + url = https://github.com/cntools/rawdraw diff --git a/colorchord2/DisplayNetwork.c b/colorchord2/DisplayNetwork.c index b2bb3e3..a2eb6d4 100644 --- a/colorchord2/DisplayNetwork.c +++ b/colorchord2/DisplayNetwork.c @@ -5,6 +5,7 @@ #include #include "parameters.h" #include +#include #include #include #include "color.h" diff --git a/colorchord2/Makefile b/colorchord2/Makefile index f393fc3..3d992a5 100644 --- a/colorchord2/Makefile +++ b/colorchord2/Makefile @@ -14,10 +14,12 @@ RAWDRAWLIBS:=-lX11 -lm -lpthread -lXinerama -lXext LDLIBS:=-lpthread -lasound -lm -lpulse-simple -lpulse -ludev -lrt -CFLAGS:=-g -O0 -flto -Wall -ffast-math -I../embeddedcommon -I. -DICACHE_FLASH_ATTR= +CFLAGS:=-g -O1 -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 +OBJS:=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 : $(OBJS) gcc -o $@ $^ $(CFLAGS) $(LDLIBS) $(EXTRALIBS) $(RAWDRAWLIBS) @@ -26,4 +28,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 *~ colorchord colorchord.exe embeddedcc $(OBJS) diff --git a/colorchord2/configs.c b/colorchord2/configs.c index 61c0acf..db3b1c2 100644 --- a/colorchord2/configs.c +++ b/colorchord2/configs.c @@ -62,7 +62,50 @@ void SetEnvValues( int force ) if( !hits && !force ) return; //Otherwise, something changed. +#ifdef ANDROID + SetParametersFromString( "cpu_autolimit=1" ); + SetParametersFromString( "set_screenx=720" ); + SetParametersFromString( "set_screeny=480" ); + SetParametersFromString( "buffer=384" ); + SetParametersFromString( "play=0" ); + SetParametersFromString( "rec=1" ); + SetParametersFromString( "channels=2" ); + SetParametersFromString( "samplerate=44100" ); + SetParametersFromString( "sourcename=default" ); + SetParametersFromString( "amplify=2.0" ); + SetParametersFromString( "base_hz=55" ); + SetParametersFromString( "dft_iir=0.6" ); + SetParametersFromString( "dft_q=20.0000" ); + SetParametersFromString( "dft_speedup=1000.0000" ); + SetParametersFromString( "octaves=5" ); + + SetParametersFromString( "do_progressive_dft=4" ); + + SetParametersFromString( "filter_iter=2" ); + SetParametersFromString( "filter_strength=.5" ); + SetParametersFromString( "freqbins = 24" ); + SetParametersFromString( "do_progressive_dft=4" ); + SetParametersFromString( "note_attach_amp_iir=0.3500" ); + SetParametersFromString( "note_attach_amp_iir2=0.250" ); + + SetParametersFromString( "note_combine_distance=0.5000" ); + SetParametersFromString( "note_jumpability=1.8000" ); + SetParametersFromString( "note_minimum_new_distribution_value=0.0200" ); + SetParametersFromString( "note_out_chop=0.05000" ); + SetParametersFromString( "outdrivers=OutputVoronoi,DisplayArray" ); + SetParametersFromString( "note_attach_amp_iir2=0.250" ); + + SetParametersFromString( "lightx=64" ); + SetParametersFromString( "lighty=32" ); + SetParametersFromString( "fromsides=1" ); + SetParametersFromString( "shape_cutoff=0.03" ); + + SetParametersFromString( "satamp=5.000" ); + SetParametersFromString( "amppow=2.510" ); + SetParametersFromString( "distpow=1.500" ); + +#else LoadFile( InitialFile[0] ); for( i = 1; i < gargc; i++ ) @@ -78,6 +121,7 @@ void SetEnvValues( int force ) LoadFile( gargv[i] ); } } +#endif } void ProcessArgs() diff --git a/colorchord2/esp32shirt.conf b/colorchord2/esp32shirt.conf new file mode 100644 index 0000000..dd6001c --- /dev/null +++ b/colorchord2/esp32shirt.conf @@ -0,0 +1,36 @@ +outdrivers = DisplayArray,DisplayNetwork,OutputCells +#OutputVoronoi +leds = 199 +light_siding = 1.0 #Turn this to ~1.9 for more uniformity, ~1.0 for less. +satamp = 1.600 +is_loop=0 +led_floor = .1 #Turn to .25 for more uniformity, .1 for less. +#note_attach_amp_iir = .3 #.3000 +#note_attach_amp_iir2 = .15 #.1500 +#note_attach_freq_iir = .3 #0.3000 +steady_bright = 0 +#dft_iir = 0.0 +#dft_q = 20.0000 +#dft_speedup = 1000.0000 + +sourcename = default +# alsa_output.pci-0000_01_00.1.hdmi-stereo.monitor +# alsa_output.pci-0000_01_00.1.hdmi-stereo-extra1.monitor +#alsa_output.pci-0000_00_1f.3.analog-stereo.monitor +skipfirst = 0 +firstval = 0 +port = 7000 +address = 192.168.43.179 +#address = 192.168.0.245 + +slope=.10 +amplify=.9 + +headless = 00 +zigzag = 0 +lightx = 13 +lighty = 19 +qtyamp = 50 +timebased = 1 +snakey = 0 + diff --git a/colorchord2/main.c b/colorchord2/main.c index adc00c6..c8a7ba3 100644 --- a/colorchord2/main.c +++ b/colorchord2/main.c @@ -18,6 +18,36 @@ #include "hook.h" #include "configs.h" +#ifdef ANDROID +#include +#include +#include +#include +#include +#include + +static int pfd[2]; +static pthread_t loggingThread; +static const char *LOG_TAG = "colorchord"; +static void *loggingFunction(void*v) { + ssize_t readSize; + char buf[128]; + + while((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) { + if(buf[readSize - 1] == '\n') { + --readSize; + } + + buf[readSize] = 0; // add null-terminator + + __android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, buf); // Set any log level you want + } + + return 0; +} + +#endif + struct SoundDriver * sd; #if defined(WIN32) || defined(USE_WINDOWS) @@ -162,9 +192,40 @@ void SoundCB( float * out, float * in, int samplesr, int * samplesp, struct Soun *samplesp = samplesr; } +#ifdef ANDROID +void HandleSuspend() +{ + //Unused. +} + +void HandleResume() +{ + //Unused. +} +#endif + int main(int argc, char ** argv) { int i; + +#ifdef ANDROID + setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered + setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered + + /* create the pipe and redirect stdout and stderr */ + pipe(pfd); + dup2(pfd[1], 1); + dup2(pfd[1], 2); + + /* spawn the logging thread */ + if(pthread_create(&loggingThread, 0, loggingFunction, 0) == -1) { + return -1; + } + + pthread_detach(loggingThread); + +#endif + #ifdef TCC void ManuallyRegisterDevices(); ManuallyRegisterDevices(); @@ -181,6 +242,8 @@ int main(int argc, char ** argv) WSAStartup(0x202, &wsaData); strcpy( sound_source, "WIN" ); +#elif defined( ANDROID ) + strcpy( sound_source, "ANDROID" ); #else strcpy( sound_source, "PULSE" ); #endif @@ -263,6 +326,7 @@ int main(int argc, char ** argv) double Last = Now; while(1) { + printf( ".\n" ); char stt[1024]; //Handle Rawdraw frame swappign diff --git a/colorchord2/os_generic.c b/colorchord2/os_generic.c index da281e3..2b4321e 100644 --- a/colorchord2/os_generic.c +++ b/colorchord2/os_generic.c @@ -228,7 +228,12 @@ void OGCancelThread( og_thread_t ot ) { return; } +#ifdef ANDROID + void * fakeret; + pthread_join( *(pthread_t*)ot, &fakeret ); +#else pthread_cancel( *(pthread_t*)ot ); +#endif free( ot ); } diff --git a/colorchord2/quickwash.conf b/colorchord2/quickwash.conf index ab598e8..e83d38f 100644 --- a/colorchord2/quickwash.conf +++ b/colorchord2/quickwash.conf @@ -1,10 +1,10 @@ This is a vornoi thing: -outdrivers = DisplayArray, OutputProminent -lightx = 2 -lighty = 2 -leds = 4 -fromsides = 1 +outdrivers = DisplayArray, OutputCells +lightx = 3 +lighty = 3 +leds = 9 +fromsides = 0 shape_cutoff = 0.03 satamp = 5.000 amppow = 2.510 @@ -13,7 +13,8 @@ distpow = 1.500 samplerate = 11025 buffer = 64 -sourcename = default +sourcename = alsa_output.pci-0000_00_1f.3.analog-stereo.monitor + #default amplify = 2.5 note_attach_amp_iir = 0.9000 diff --git a/colorchord2/rawdraw b/colorchord2/rawdraw new file mode 160000 index 0000000..e5acb75 --- /dev/null +++ b/colorchord2/rawdraw @@ -0,0 +1 @@ +Subproject commit e5acb751f337f3ae9e558e98907be8e3d6f8381f diff --git a/colorchord2/sound_alsa.c b/colorchord2/sound_alsa.c index e3c986d..3771752 100644 --- a/colorchord2/sound_alsa.c +++ b/colorchord2/sound_alsa.c @@ -4,6 +4,7 @@ #include "os_generic.h" #include "parameters.h" #include +#include #define BUFFERSETS 4 diff --git a/colorchord2/sound_android.c b/colorchord2/sound_android.c new file mode 100644 index 0000000..6975802 --- /dev/null +++ b/colorchord2/sound_android.c @@ -0,0 +1,244 @@ +//Copyright 2019-2020 <>< Charles Lohr under the ColorChord License. +// This should be used with rawdrawandroid + +#include "sound.h" +#include "os_generic.h" +#include //Using android threads not os_generic threads. +#include +#include +#include +#include + +#ifndef NO_SOUND_PARAMETERS +#include "parameters.h" +#else +#define GetParameterI( x, y ) (y) +#define GetParameterS( x, y ) (y) +#endif + + +//based on https://github.com/android/ndk-samples/blob/master/native-audio/app/src/main/cpp/native-audio-jni.c + +// for native audio +#include +#include + +#include +#include +#include +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, APPNAME, __VA_ARGS__)) +#define printf( x...) LOGI( x ) + +#define RECORDER_FRAMES 1024 + +#define BUFFERSETS 4 + +#define BLOCKING + +//Across all sound systems. +static pthread_mutex_t audioEngineLock = PTHREAD_MUTEX_INITIALIZER; + +struct SoundDriverAndroid +{ + void (*CloseFn)( struct SoundDriverAndroid * object ); + int (*SoundStateFn)( struct SoundDriverAndroid * object ); + SoundCBType callback; + SLObjectItf engineObject; + SLEngineItf engineEngine; + SLRecordItf recorderRecord; + SLObjectItf recorderObject; + SLAndroidSimpleBufferQueueItf recorderBufferQueue; + unsigned recorderSize; + + short recorderBuffer[RECORDER_FRAMES]; +}; + + +void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct SoundDriverAndroid * r = (struct SoundDriverAndroid*)context; + int samplesp = 0; + float buffout[RECORDER_FRAMES]; + int i; + short * rb = r->recorderBuffer; + for( i = 0; i < RECORDER_FRAMES; i++ ) buffout[i] = (rb[i]+0.5)/32767.5; + r->callback( 0, buffout, RECORDER_FRAMES, &samplesp, r ); + (*r->recorderBufferQueue)->Enqueue(r->recorderBufferQueue, r->recorderBuffer, sizeof(r->recorderBuffer)); +} + +static struct SoundDriverAndroid* InitAndroidSound( struct SoundDriverAndroid * r ) +{ + SLresult result; + LOGI( "Starting InitAndroidSound\n" ); + // create engine + result = slCreateEngine(&r->engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the engine + result = (*r->engineObject)->Realize(r->engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the engine interface, which is needed in order to create other objects + result = (*r->engineObject)->GetInterface(r->engineObject, SL_IID_ENGINE, &r->engineEngine); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + + // configure audio source + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + // configure audio sink + SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + + SLDataFormat_PCM format_pcm ={ + SL_DATAFORMAT_PCM, + 1, + SL_SAMPLINGRATE_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN, + }; + + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + // create audio recorder + // (requires the RECORD_AUDIO permission) + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + result = (*r->engineEngine)->CreateAudioRecorder(r->engineEngine, &r->recorderObject, &audioSrc, + &audioSnk, 1, id, req); + if (SL_RESULT_SUCCESS != result) { + LOGI( "CreateAudioRecorder failed\n" ); + return JNI_FALSE; + } + + // realize the audio recorder + result = (*r->recorderObject)->Realize(r->recorderObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGI( "AudioRecorder Realize failed: %d\n", result ); + return JNI_FALSE; + } + + // get the record interface + result = (*r->recorderObject)->GetInterface(r->recorderObject, SL_IID_RECORD, &r->recorderRecord); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the buffer queue interface + result = (*r->recorderObject)->GetInterface(r->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &r->recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // register callback on the buffer queue + result = (*r->recorderBufferQueue)->RegisterCallback(r->recorderBufferQueue, bqRecorderCallback, r); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + + assert( !pthread_mutex_trylock(&audioEngineLock)); + // in case already recording, stop recording and clear buffer queue + result = (*r->recorderRecord)->SetRecordState(r->recorderRecord, SL_RECORDSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + result = (*r->recorderBufferQueue)->Clear(r->recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // the buffer is not valid for playback yet + r->recorderSize = 0; + + // enqueue an empty buffer to be filled by the recorder + // (for streaming recording, we would enqueue at least 2 empty buffers to start things off) + result = (*r->recorderBufferQueue)->Enqueue(r->recorderBufferQueue, r->recorderBuffer, sizeof(r->recorderBuffer)); + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // start recording + result = (*r->recorderRecord)->SetRecordState(r->recorderRecord, SL_RECORDSTATE_RECORDING); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + LOGI( "Complete Init Sound Android\n" ); + return r; +} + +void CloseSoundAndroid( struct SoundDriverAndroid * r ); + +int SoundStateAndroid( struct SoundDriverAndroid * soundobject ) +{ + return ((soundobject->recorderObject)?1:0) | ((soundobject->recorderObject)?2:0); +} + +void CloseSoundAndroid( struct SoundDriverAndroid * r ) +{ + // destroy audio recorder object, and invalidate all associated interfaces + if (r->recorderObject != NULL) { + (*r->recorderObject)->Destroy(r->recorderObject); + r->recorderObject = NULL; + r->recorderRecord = NULL; + r->recorderBufferQueue = NULL; + } + + + // destroy engine object, and invalidate all associated interfaces + if (r->engineObject != NULL) { + (*r->engineObject)->Destroy(r->engineObject); + r->engineObject = NULL; + r->engineEngine = NULL; + } + +} + + +int AndroidHasPermissions(const char* perm_name); +void AndroidRequestAppPermissions(const char * perm); + + +void * InitSoundAndroid( SoundCBType cb ) +{ + int hasperm = AndroidHasPermissions( "RECORD_AUDIO" ); + if( !hasperm ) + { + AndroidRequestAppPermissions( "RECORD_AUDIO" ); + } + + struct SoundDriverAndroid * r = (struct SoundDriverAndroid *)malloc( sizeof( struct SoundDriverAndroid ) ); + memset( r, 0, sizeof( *r) ); + r->CloseFn = CloseSoundAndroid; + r->SoundStateFn = SoundStateAndroid; + r->callback = cb; + r->engineObject = 0; + r->engineEngine = 0; +/* + r->spsPlay = GetParameterI( "samplerate", 44100 ); + r->channelsPlay = GetParameterI( "channels", 2 ); + r->spsRec = r->spsPlay; + r->channelsRec = r->channelsPlay; + + r->playback_handle = 0; + r->record_handle = 0; + r->buffer = GetParameterI( "buffer", 1024 ); + + r->Android_fmt_s16le = 0; + +*/ + return InitAndroidSound(r); +} + +//Tricky: On Android, this can't actually run before main. Have to manually execute it. + +REGISTER_SOUND( AndroidSound, 10, "ANDROID", InitSoundAndroid ); +