#include #include #include #include #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include // This example is using gl3w to access OpenGL functions (because it is small). You may use glew/glad/glLoadGen/etc. whatever already works for you. #include #ifdef _WIN32 #include #else #include #endif #include #include "pffft.h" #include #include "../Core/Src/config.h" #ifdef _WIN32 #pragma comment(lib,"winmm.lib") // midi #pragma comment(lib,"shcore.lib") // dpi #pragma comment(lib,"opengl32.lib") // for wglGetProcAddress #ifdef _WIN64 #pragma comment(lib,"portaudio/lib/portaudio_x64.lib") #pragma comment(lib,"imgui/glfw/lib-vc2010-64/glfw3.lib") // for imgui rendering #else #pragma comment(lib,"portaudio/lib/portaudio_x86.lib") #pragma comment(lib,"imgui/glfw/lib-vc2010-32/glfw3.lib") // for imgui rendering #endif #endif // _WIN32 //#define STRETCH_PROTO bool enable_emu_audio = true; #define USER_DEFAULT_SCREEN_DPI 96 #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image.h" #include "stb_image_write.h" #define FS 32000 #ifdef STRETCH_PROTO #define BLOCK_SAMPLES 512 #else #define BLOCK_SAMPLES 64 #endif typedef unsigned char u8; typedef unsigned int u32; typedef short s16; typedef signed char s8; extern "C" { extern unsigned short expander_out[4]; uint32_t emupixels[128 * 32]; int16_t accel_raw[3]; float accel_lpf[2]; float accel_smooth[2]; void resetspistate(void); void spi_update_dac(int chan) { resetspistate(); } void emu_setadc(float araw, float braw, float pitchcv, float gatecv, float xcv, float ycv, float acv, float bcv, int gateforce, int pitchsense, int gatesense); void plinky_frame(); void plinky_init(); void uitick(u32* dst, const u32* src, int half); extern float gainhistoryrms[512]; extern int ghi; extern float knobhistory[512]; extern int khi; typedef unsigned short u16; extern volatile int encval; extern volatile u8 encbtn; extern float arpdebug[1024]; extern int arpdebugi; extern int emucvouthist; extern float emucvout[6][256]; extern float emupitchloopback; extern int emupitchsense, emugatesense; void ApplyUF2File(const char* fname); extern int samplelen; typedef struct FingerRecord { u8 pos[4]; u8 pressure[8]; } FingerRecord; typedef struct Preset { s16 params[96][8]; u8 arpon; s8 loopstart_step; s8 looplen_step; u8 paddy[16 - 3]; } Preset; typedef struct PatternQuarter { FingerRecord steps[16][8]; s8 autoknob[16 * 8][2]; } PatternQuarter; extern Preset rampreset; extern PatternQuarter rampattern[4]; extern s8 cur_step; #include "../Core/Src/adc.h" #include "../Core/Src/wtenum.h" extern const short wavetable[WT_LAST][WAVETABLE_SIZE ]; } static GLFWwindow* window; static PaStream *stream; static PaError err; void paerror(const char *e) { printf( "PortAudio error: %s-> %s\n", e, Pa_GetErrorText( err ) ); exit(1); } FILE* WriteWAVHeader(const char* fname) { FILE* f = fopen(fname, "wb"); if (!f) { printf("Failed to open file %s %d (%s)\n", fname, errno, strerror(errno)); return f; } fseek(f, 44, SEEK_SET); return f; } void UpdateWAVHeader(FILE* f, u32 fs, u32 chans) { fflush(f); // make sure we've written all the data u32 filelen = ftell(f); fseek(f, 0, SEEK_SET); u32 wavhdr[11] = { 0x46464952,filelen - 8,0x45564157,0x20746d66, 0x00000010,0x00000001 + (chans << 16), (u32)fs, (u32)fs * 4, 0x00100000 + (chans * 2),0x61746164,filelen - 44 }; fwrite(wavhdr, 1, 44, f); fseek(f, filelen, SEEK_SET); } void FinishWAVHeader(FILE* f, u32 fs, u32 chans) { UpdateWAVHeader(f, fs, chans); fclose(f); } FILE* wavfile; extern "C" int getheadphonevol(void); #ifdef STRETCH_PROTO #define WINDOWSIZE 16384 float inputtape[512 * 1024]; int inputpos = 0; int inputjitter = 4096; float pitches[4] = {0.f,0.f,12.f,-12.f}; float unison = 0.1f; float attack = 0.6f, decay = 0.6f; int inputdelay = 0; PFFFT_Setup* fftsetup32768; float outputtape[2][WINDOWSIZE]; #define PI 3.1415926535897932384626433832795f float fftwindow(int i) { return 1.f - cosf(i * PI * 2.f / WINDOWSIZE); } void SplatFFT(float* dst, int dstpos) { static __declspec(align(32)) float tmp[WINDOWSIZE]; static __declspec(align(32 )) float fft[WINDOWSIZE]; static __declspec(align(32)) float mag[WINDOWSIZE/2]; memset(tmp, 0, sizeof(tmp)); float kattack = 1.f-attack; float kdecay = 1.f-decay; kattack *= kattack; kdecay *= kdecay; kattack *= kattack; kdecay *= kdecay; for (int chan = 0; chan < 4; ++chan) { float ptch = (chan - 1.5f) * unison + pitches[chan]; ptch = exp2f(ptch * 1.f / 12.f); int basepos = int(inputpos - ptch * WINDOWSIZE - inputdelay - 1 - (rand() % inputjitter)) & (512 * 1024 - 1); for (int i = 0; i < WINDOWSIZE; ++i) { float fpos = (i*ptch); int pos = int(floor(fpos) + basepos)& (512 * 1024 - 1); fpos -= floor(fpos); float a = inputtape[pos]; pos = (pos + 1) & (512 * 1024 - 1); float b = inputtape[pos]; a += (b - a) * fpos; tmp[i] += fftwindow(i) * a * (1.f/(1.f+chan)); } } pffft_transform_ordered(fftsetup32768, tmp, fft, nullptr, PFFFT_FORWARD); for (int i = 2; i < WINDOWSIZE; i+=2) { float phase = rand() * (PI * 2.f / RAND_MAX); float ss = sinf(phase) / float(WINDOWSIZE), cc=cosf(phase) / float(WINDOWSIZE); float re = fft[i], im = fft[i + 1]; float mg = sqrtf(re * re + im * im); float& mgsmooth = mag[i / 2]; mgsmooth += (mg - mgsmooth) * ((mg > mgsmooth) ? kattack : kdecay); fft[i] = mgsmooth * cc; // re* cc - im * ss; fft[i + 1] = mgsmooth * ss; // im* cc + re * ss; } fft[0] *= 0.f / WINDOWSIZE; fft[1] *= 0.f / WINDOWSIZE; pffft_transform_ordered(fftsetup32768, fft, tmp, nullptr, PFFFT_BACKWARD); for (int i = 0; i < WINDOWSIZE; ++i) dst[(i+dstpos)&(WINDOWSIZE-1)] += fftwindow(i) * tmp[i] * (float(BLOCK_SAMPLES)/WINDOWSIZE); } bool recording = false; #endif int pacb( const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) { #ifdef STRETCH_PROTO if (recording) { for (int i = 0; i < BLOCK_SAMPLES; ++i) { inputtape[inputpos] = ((const float*)input)[i*2]; inputpos = (inputpos + 1) & (1024 * 512 - 1); } } static int side = 0; static int dstpos = 0; SplatFFT(outputtape[side], dstpos); for (int i = 0; i < BLOCK_SAMPLES; ++i) { ((float*)output)[i * 2 + 0] = outputtape[0][(dstpos + i) & (WINDOWSIZE-1)]; outputtape[0][(dstpos + i) & (WINDOWSIZE - 1)] = 0.f; ((float*)output)[i * 2 + 1] = outputtape[1][(dstpos + i) & (WINDOWSIZE - 1)]; outputtape[1][(dstpos + i) & (WINDOWSIZE - 1)] = 0.f; } dstpos += BLOCK_SAMPLES; side = 1 - side; #else static u32 audioin[BLOCK_SAMPLES]; u32 temp[BLOCK_SAMPLES]; static int half; half = 1 - half; float hpvol = getheadphonevol() * (1.f/32768.f/63.f); #define PI 3.1415926535897932384626433832795f { float* src = (float*)input; short* dst = (short*)audioin; static float theta = 0.f; for (int i = 0; i < BLOCK_SAMPLES; ++i) { float l = src ? *src++:0; float r = src ? *src++:0; if (0) { r = l = sinf(theta) * sinf(theta * 0.001f) * 0.125f; theta += 440.f * PI * 2.f / 32000.f; if (theta >= PI * 2.f * 1000.f) theta -= PI * 2.f * 1000.f; } *dst++ = (short)(l * (32767.f)); *dst++ = (short)(r * (32767.f)); } } uitick(temp, audioin, half); float *dst=(float*)output; short* src = (short*)temp; for (int i=0;i 100) { counter = 0; UpdateWAVHeader(wavfile, FS, 2); } } #endif return 0; } static void glfw_error_callback(int error, const char* description){ fprintf(stderr, "Error %d: %s\n", error, description);} #undef min #undef max int clampi(int x, int mn, int mx) { return (xmx)?mx:x; } extern "C" u8 emuleds[9][8]; u8 emuleds[9][8]; int buttonsw, buttonsh, buttonscomp; GLuint oledtex,buttonstex; //#define ROTATE_OLED #ifdef ROTATE_OLED const static int oledw = 32, oledh = 128; #else const static int oledw=128,oledh=32; #endif int Knob(const char *label, float &curval, float *randamount, float minval, float maxval, unsigned int encodercol, float size); extern "C" void EmuStartSound(void) { if (enable_emu_audio) { err = Pa_StartStream(stream); if (err != paNoError) paerror("start"); } } float GRandom() { return rand()/float(RAND_MAX); } extern "C" short _flashram[8 * 2 * 1024 * 1024]; extern "C" int emutouch[9][2]; int emutouch[9][2]; typedef struct Sample { // fmt u16 formattag, nchannels; u32 samplerate, bytespersec; u16 blockalign, bitspersample; int datastart; int dataend; int numsamples; int firstsample; short *samples; } Sample; Sample s; static inline short read_sample(Sample* s, int pos, int chan) { // resample to 32khz crudely if (chan >= s->nchannels) chan = 0; int64_t p64 = (int64_t(pos) * s->samplerate * 65536) / 32000; int si = int(p64 >> 16); if (si<0 || si >= s->numsamples - 1) return 0; int f = (int)(p64 & 65535); return (s->samples[si*s->nchannels] * (65536 - f) + s->samples[si * s->nchannels + s->nchannels] * f) >> 16; } const char* ParseWAV(Sample* s, const char* fname) { memset(s, 0, sizeof(*s)); FILE* f = fopen(fname, "rb"); if (!f) return "cant open"; u32 wavhdr[3] = { 0 }; fread(wavhdr, 4 , 3, f); if (wavhdr[0] != 0x46464952) return "bad header 1"; if (wavhdr[2] != 0x45564157) return "bad header 2"; while (!feof(f)) { if (fread(wavhdr, 1, 8,f)<8) break; int nextchunk = ftell(f) + wavhdr[1] + (wavhdr[1] & 1); if (wavhdr[0] == 0x61746164) { // 'data' s->datastart = ftell(f); s->dataend = s->datastart + wavhdr[1]; int bytespersample = s->bitspersample / 8 * s->nchannels; s->numsamples = wavhdr[1] / (bytespersample); s->firstsample = s->datastart / bytespersample; printf("%d channels, %d samplerate, %d bits, %d samples\r\n", s->nchannels, s->samplerate, s->bitspersample, s->numsamples); s->samples = new short[(s->numsamples * s->nchannels * s->bitspersample / 8+3)/2]; s->numsamples =(int) fread(s->samples, s->nchannels * s->bitspersample/8, s->numsamples, f); if (s->bitspersample == 24) { for (int i = 0; i < s->numsamples * s->nchannels; ++i) { short sh = *(short*) (((char*)(s->samples)) + (i * 3+1)); s->samples[i] = sh; } s->bitspersample = 16; } fclose(f); return 0; } else if (wavhdr[0] == 0x20746d66) { // 'fmt ' fread(s, 1,16, f); if (s->formattag != 1) return fclose(f), "bad formattag"; if (s->nchannels < 1 || s->nchannels>2) return fclose(f), "bad channel count"; if (s->bitspersample < 16) return fclose(f),"bad bits per sample"; // if (s->samplerate < 8000 || s->samplerate>48000) // return fclose(f),"bad samplerate"; } fseek(f, nextchunk, SEEK_SET); } fclose(f); return "error"; } void EmuFrame(); extern "C" float life_damping; extern "C" float life_force; extern "C" float p_grainpos ; extern "C" float p_grainsize ; extern "C" float p_timestretch; extern "C" float p_pitchy; extern "C" int64_t p_playhead; extern "C" float m_compressor, m_dry, m_audioin, m_dry2wet, m_delaysend, m_delayreturn, m_reverbin, m_reverbout, m_fxout, m_output; extern "C" float lfo_eval(u32 ti, float warp, unsigned int shape); float dflux[4096]; static float keys[64][128]; int slicepos[64]; u8 slicenote[64]; float slicepeak[64]; float sliceflux[64]; bool snappos=true; int maxstep; float maxdflux; inline float maxf(float a, float b) { return (a > b) ? a : b; } inline int maxi(int a, int b) { return (a > b) ? a : b; } inline int mini(int a, int b) { return (a < b) ? a : b; } inline float clampf(float a, float mn, float mx) { return (a < mn) ? mn : (a > mx) ? mx : a; } #ifdef _WIN32 HMIDIIN hmidiin; HMIDIOUT hmidiout; u32 midiq[256]; u32 midiqw, midiqr; void CALLBACK midicb( HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) { // midi in looks like dwParam1 is what we want - 00643090 00403080 // printf("%08x %08x %08x midi\n", wMsg, dwParam1, dwParam2); if (midiqw-midiqr<256) midiq[(midiqw++) & 255] = dwParam1; } extern "C" bool midi_receive(unsigned char packet[4]) { if (midiqr == midiqw) return false; u32 mm = midiq[(midiqr++) & 255]; packet[0] = (mm >> 4)&15; packet[1] = mm >> 0; packet[2] = mm >> 8; packet[3] = mm >> 16; return true; } extern "C" bool send_midimsg(u8 status, u8 data1, u8 data2) { //if (status == 0x90) printf("------------- DOWN %d (%d)\n", data1, data2); //else if (status == 0x80) printf("------------- UP %d\n", data1); //else printf("%02x %02x %02x\n", status, data1, data2); if (hmidiout) if (MIDIERR_NOTREADY == midiOutShortMsg(hmidiout, status + (data1 << 8) + (data2 << 16))) return false; return true; } #else // _WIN32 // TODO: Add MIDI support on non-Windows platforms. extern "C" bool midi_receive(unsigned char packet[4]) { return false; } extern "C" bool send_midimsg(u8 status, u8 data1, u8 data2) { return false; } #endif typedef struct CalibResult { u16 pressure[8]; s16 pos[8]; } CalibResult; extern "C" CalibResult calibresults[18]; extern "C" int flash_readcalib(void); bool plinky_inited = false; static float fwavetable[WT_LAST][WAVETABLE_SIZE]; float softtri(float x) { x *= PI*0.5f; float y=sinf(x) - sinf(x * 3.f) / 9.f + sinf(x * 5.f) / 25.f - sinf(x * 7.f) / 49.f + sinf(x * 9.f) / 81.f - sinf(x * 11.f) / 121.f + sinf(x * 13.f) / 169.f - sinf(x * 15.f) / 225.f; return y * (8.f / PI / PI); } uint32_t wang_hash(uint32_t seed) { seed = (seed ^ 61) ^ (seed >> 16); seed *= 9; seed = seed ^ (seed >> 4); seed *= 0x27d4eb2d; seed = seed ^ (seed >> 15); return seed; } float noisey(int i) { return (wang_hash(i) & 65535) / 32768.f - 1.f; } Sample cycles[16]; const char* cyclenames[16] = { // 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"c:/temp/ss.wav" 0,0, "c:/temp/waves/saw_1024.wav", "c:/temp/waves/saw_2048.wav", "c:/temp/waves/saw2_1024.wav", "c:/temp/waves/saw2_2048.wav", "c:/temp/waves/wave1_1024.wav", "c:/temp/waves/wave1_2048.wav", "c:/temp/waves/wave2_1024.wav", "c:/temp/waves/wave2_2048.wav", "c:/temp/waves/wave3_1024.wav", "c:/temp/waves/wave3_2048.wav", "c:/temp/waves/wave4_1024.wav", "c:/temp/waves/wave4_2048.wav", "c:/temp/waves/wave5_1024.wav", "c:/temp/waves/wave5_2048.wav", }; float cyclegain[16]; bool cycledone[16]; float eval_wave(int shape, int i) { i &= 65535; if (cyclenames[shape]) { if (!cycledone[shape]) { if (const char* err = ParseWAV(&cycles[shape], cyclenames[shape])) printf("ERROR READING WAV %d %s %s \n", shape, cyclenames[shape], err); cycledone[shape] = true; short max = 0; for (int i = 0; i < cycles[shape].numsamples; ++i) { short s = cycles[shape].samples[i * cycles[shape].nchannels]; if (s < 0) s = -s; if (s > max) max = s; } cyclegain[shape] = 1.f / max; } if (cycles[shape].numsamples > 100) { int i0 = (i * cycles[shape].numsamples) / 65536; int i1 = (i0 + 1); if (i1 >= cycles[shape].numsamples) i1 = 0; float s0 = cycles[shape].samples[i0 * cycles[shape].nchannels] *cyclegain[shape]; float s1 = cycles[shape].samples[i1 * cycles[shape].nchannels] * cyclegain[shape]; float t = ((i * cycles[shape].numsamples) & 65535) * (1.f / 65536.f); return s0 + (s1 - s0) * t; } } float ph = i * (PI*2.f / 65536.f); float ns = 0.f; int seed = 0; float f = 1.f; if (shape>4) for (int oct = 15; oct >= 2; --oct) { ns += noisey((i >> oct) + seed) * f; f *= 0.5f; seed += 232532; } switch (shape) { case WT_SAW: return (i - 32768) * (1.f/32768.f); // saw case WT_SQUARE: return (i < 32768) ? -1.f : 1.f; // square case WT_SIN: return cosf(ph); // sin case WT_SIN_2: return cosf(ph)*0.75f+cosf(ph*3.f)*0.5f; // sin case WT_FM: return cosf(ph + sinf(ph * 7.f + sinf(ph * 11.f) * 0.1f) * 0.7f); // fm thing case WT_SIN_FOLD1: return softtri(sinf(ph) * 2.f); // folded sin case WT_SIN_FOLD2: return softtri(sinf(ph) * 6.f); // folded sin case WT_SIN_FOLD3: return softtri(sinf(ph) * 12.f); // folded sin case WT_NOISE_FOLD1: return softtri(sinf(ph) + 2.f * ns); // folded noise case WT_NOISE_FOLD2: return softtri(sinf(ph) + 8.f * ns); // folded noise case WT_NOISE_FOLD3: return softtri(sinf(ph) + 32.f * ns); // folded noise case WT_WHITE_NOISE: return ((wang_hash(i)&65535)-32768)*(1.f/32768.f); default: return 0.f; } // return ns; // noise } int main(int argc, char** argv) { const char* midiin_name_to_match = ""; const char* midiout_name_to_match = ""; for (int i = 1; i < argc; ++i) if (strcmp(argv[i], "-q") == 0) enable_emu_audio = false; else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) midiin_name_to_match = argv[++i]; else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) midiout_name_to_match = argv[++i]; if (0) { float kernel[256]; float totk = 0.f; for (int i = 0; i < 256; ++i) { float x = i * PI / 28.f; // 32 is good. 24 may be a bit more open kernel[i] = i ? (0.5f + 0.5f * cosf(i * PI / 256.f)) * sinf(x) / x : 1.f; totk += kernel[i] * (i ? 2.f : 1.f); } for (int i = 0; i < 256; ++i) { kernel[i] /= totk; } // memoize the shapes :) static float wshape[WT_LAST][65536]; for (int shape = 0; shape < WT_LAST; ++shape) for (int i = 0; i < 65536; ++i) wshape[shape][i] = eval_wave(shape, i); FILE* fh = fopen("../Core/Src/wavetable.h", "w"); if (fh) { fprintf(fh, "// generated for WT_LAST %d WAVETABLE_SIZE %d \nconst short wavetable[%d][%d]={\n", WT_LAST,WAVETABLE_SIZE, WT_LAST, WAVETABLE_SIZE); } for (int shape = 0; shape < WT_LAST; ++shape) { //s16* dst = wavetable + shape * WAVETABLE_SIZE; float* fdst = fwavetable[shape]; if (fh) fprintf(fh, "\t// %s\n\t{", wavetablenames[shape]); for (int octave = 0; octave < 9; ++octave) { int n = (512 >> octave); for (int i = 0; i <= n; ++i) { float x = 0.f; for (int j = -255; j < 256; ++j) { x += kernel[abs(j)] * wshape[shape][65535 & ( (i << (octave + 7)) + (j << (octave+2)))]; } *fdst++ = x; short s = /**dst++ = */ x * (16384.f); if (fh) fprintf(fh, "%d,", s); } if (fh) fprintf(fh, "\n\t"); } if (fh) fprintf(fh, "},"); } if (fh) { fprintf(fh, "\n};\n"); fclose(fh); } } // lpzw text converter if (0) { int w, h, comp; u8* bmp = stbi_load("c:/temp/lpzwtext_bw.png", &w, &h, &comp, 1); int xpos[128] = { 0 }; int x = 0, y = 0; int ox = 0; uint8_t obmp[24][1024] = {}; printf("static const uint16_t font24_xpos[] PROGMEM ={\n"); for (int c = 33; c<0x60; ++c) { while (1) { for (y = 0; y < h; ++y) if (bmp[x + y * w] < 128) break; if (y < h) break; ++x; } // skip white int x1 = x; while (1) { for (y = 0; y < h; ++y) if (bmp[x + y * w] < 128) break; if (y == h) break;++x; } // skip black int x2 = x; if (c == '"') { while (1) { for (y = 0; y < h; ++y) if (bmp[x + y * w] < 128) break; if (y < h) break; ++x; } // skip white while (1) { for (y = 0; y < h; ++y) if (bmp[x + y * w] < 128) break; if (y == h) break; ++x; } // skip black x2 = x; } printf("%d,", ox); for (int xx = x1; xx < x2; ++xx) for (int yy = 0; yy < 24; ++yy) obmp[yy][ox + (x2-x1-1) - (xx - x1)] = bmp[xx + yy * w] ^ 255; // flipped in x ox += (x2 - x1); } printf("%d};\n", ox); ox += 7; ox &= ~7; int nb = ox / 8; printf("static const uint8_t font24[24][%d] PROGMEM = {\n", nb); for (int y = 0; y < 24; ++y) { for (int x = 0; x < nb; ++x) { u8 b = 0; for (int xx = 0; xx < 8; ++xx) { if (obmp[y][xx + x * 8] > 128) b |= 1 << xx; } printf("0x%02x,", b); } printf("\n"); } printf("};\n"); } /* dart throwing logo generator :) * int w, h, comp; u8* bmp = stbi_load("plinkydots4k.png", &w, &h, &comp, 1); float* sdf = new float[w * h]; for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) sdf[y * w + x] = (bmp[y * w + x] < 128) ? 0.f : w*2; int iters = 0; while (iters<8) { printf("%d\n", iters++); bool more = false; int x1 = (iters & 1) ? 0 : w-1; int x2 = (iters & 1) ? w : -1; int y1 = (iters & 2) ? 0 : h-1; int y2 = (iters & 2) ? h : -1; for (int y = y1; y != y2; y+=(y1 0 && x > 0 && (od = sdf[(y - 1) * w + (x - 1)] + 1.414f) < d) sdf[y * w + x] = d = od; if (y > 0 && (od = sdf[(y - 1) * w + (x)] + 1.000f) < d) sdf[y * w + x] = d = od; if (y > 0 && x < w - 1 && (od = sdf[(y - 1) * w + (x + 1)] + 1.414f) < d) sdf[y * w + x] = d = od; if (x > 0 && (od = sdf[(y + 0) * w + (x - 1)] + 1.f) < d) sdf[y * w + x] = d = od; if (x < w - 1 && (od = sdf[(y + 0) * w + (x + 1)] + 1.f) < d) sdf[y * w + x] = d = od; if (y < h - 1 && x > 0 && (od = sdf[(y + 1) * w + (x - 1)] + 1.414f) < d) sdf[y * w + x] = d = od; if (y < h - 1 && (od = sdf[(y + 1) * w + (x)] + 1.000f) < d) sdf[y * w + x] = d = od; if (y < h - 1 && x < w - 1 && (od = sdf[(y + 1) * w + (x + 1)] + 1.414f) < d) sdf[y * w + x] = d = od; if (od < w * 2) more = true; } if (!more) break; } //for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) bmp[y * w + x] = sdf[y * w + x]*(255.f/w); //stbi_write_png("plinkysdf.png", w, h, 1, bmp, w); memset(bmp, 255, w * h); for (int j = 0;j < 2000000; ++j) { if ((j%100000)==0) printf("%d\n", j); int x = rand() % w; int y = rand() % h; float rmax = sdf[y * w + x] ; rmax *= 0.125f * 0.75f; rmax += 5.f; if (rmax <= 5.5f) continue; int i = int(rmax + 1.f); bool ohno = false; for (int y2 = -i; y2 <= i; ++y2) { int xr = 1.f + sqrtf(rmax * rmax - y2 * y2); int yy = y + y2; if (yy < 0 || yy >= h) continue; for (int x2 = maxi(0, x - xr); x2 <= x + xr && x2 < w; ++x2) if (bmp[yy * w + x2]<128 && (x2 - x) * (x2 - x) + y2 * y2 < rmax * rmax) { ohno = true; break; } if (ohno) break; } if (ohno) continue; float rr = 4.f + rmax * 0.05f; if (rr > 6.f) rr = 6.f; for (int x2 = -6; x2 <= 6; ++x2) for (int y2 = -6; y2 <= 6; ++y2) { int xx = x + x2; int yy = y + y2; int dd = x2 * x2 + y2 * y2; if (dd=0 && yy>=0 && xx= 128) ? (i >= 192) ? 0.875f : 0.125f : 0.5f; for (int j=0;j<10;++j) printf("%0.2f ", lfo_eval(i * 65536 / 64, warp, j)); printf("\n"); }*/ // PFFFT_Setup* fftsetup256 = pffft_new_setup(256, PFFFT_REAL); // PFFFT_Setup* fftsetup2048 = pffft_new_setup(2048, PFFFT_REAL); #ifdef STRETCH_PROTO fftsetup32768 = pffft_new_setup(WINDOWSIZE, PFFFT_REAL); #endif const char* fname = // "C:\\Users\\mmalex\\Dropbox\\think.wav"; //"C:\\Users\\mmalex\\Dropbox\\pv\\samples\\amen.wav"; // "C:\\Users\\mmalex\\Dropbox\\pianohome.wav"; // "C:\\Users\\mmalex\\Dropbox\\pianohome2.wav"; "C:\\Users\\mmalex\\Dropbox\\pv\\samples\\164718__bradovic__piano.wav"; /* if (const char* err = ParseWAV(&s, fname)) printf("Can't load wav file %s\n", err); else { short* dst = (short*)_flashram; for (int i = 0; i < 8 * 2 * 1024 * 1024; ++i) { if (1) {// sample *dst++ = (read_sample(&s, i, 0) + read_sample(&s, i, 1)) >> 1; } else // test tone *dst++ = (short)(30000.f * sinf(i / 32000.f * (440.f) * PI * 2.f)); } s.numsamples = (int64_t(s.numsamples) * 32000) / s.samplerate; } samplelen = s.numsamples; */ /* static float inp[2048], fft2[2][2048]; maxstep = s.numsamples / 256; maxdflux = 0.f; for (int i = 0; i < 1024 * 1024; i += 256) { for (int j = 0; j < 256; ++j) inp[j] = _flashram[i + j] * 1.f/32768.f; int step = i / 256; float* fft = fft2[step & 1]; float* prevfft = fft2[(step & 1) ^ 1]; pffft_transform_ordered(fftsetup256, inp, fft, nullptr, PFFFT_FORWARD); float df = 0.f; for (int bucket = 4; bucket < 128; bucket++) { int j = bucket * 2; float re = fft[j], im = fft[j + 1]; float pwr = re * re + im * im; fft[j] = pwr; float prevpwr = prevfft[j]; if (pwr > prevpwr) df += pwr - prevpwr; } dflux[step] = df; if (df > maxdflux) maxdflux = df; } // slice into 64 bits for (int i = 0; i < 64; ++i) { int pos = (maxstep * i) / 64; int minpos = maxi(0, (maxstep * (i - 0.5f)) / 64); int maxpos = mini(maxstep, (maxstep * (i + 0.5f)) / 64); float maxf = maxdflux*1.f/20.f; for (int j = minpos; j <= maxpos; ++j) if (dflux[j] > maxf) { maxf = dflux[j]; pos = j; } slicepos[i] = maxi(0,pos * 256-128); sliceflux[i] = maxf; } // analyse pitch of each slice float peak = 0.f; int peakkey = 0; for (int si = 0; si < 64; ++si) { int firstsamp = (slicepos[si])-1024; int lastsamp = (si==63)?s.numsamples : (slicepos[si+1])-1024; if (firstsamp < 0) firstsamp = 0; memset(fft2, 0, sizeof(fft2)); int step = 0; peak *= 0.5f; while (firstsamp+1024 <= lastsamp) { // zero pad top half, parabola window float* fft = fft2[step & 1]; float* prevfft = fft2[(step & 1) ^ 1]; for (int j = 0; j < 1024; ++j) inp[j] = _flashram[firstsamp + j] * (1.f / 322768.f) * (j * (1024 - j)) * (4.f / (1024.f * 1024.f)); pffft_transform_ordered(fftsetup2048, inp, fft, nullptr, PFFFT_FORWARD); static float keys[64]; memset(keys, 0, sizeof(keys)); for (int bucket = 4; bucket < 1024; ++bucket) { int j = bucket * 2; float re = fft[j], im = fft[j + 1]; float pwr = re * re + im * im; // convert to polar float phase = atan2f(re, im) * (1.f / PI); // measured in half-cycles, not radians fft[j] = pwr; fft[j + 1] = phase; if (step > 0) { float cycle_delta = (prevfft[j + 1] - phase); cycle_delta -= floorf(cycle_delta); if (cycle_delta > 0.5f) cycle_delta -= 1.f; // for debugging float bucket_freq = 32000.f/2048.f * bucket; float expected_half_cycles = bucket; // we do this in half cycles, because we overlap the ffts by half float actual_freq = (32000.f / 2048.f) * (expected_half_cycles + cycle_delta); pwr = sqrtf(pwr); //p /= f * 0.01f + 2.f; // 1/f, but dont boost the bass too much! for (int overtone = 1; overtone < 10; overtone++) { float p = pwr * ((overtone > 1) ? 0.875f : 1.f); float f = actual_freq / overtone; float key = log2f(f * (1.f / 261.63f)) * 12.f + 24.f ; //key = fmodf(key, 12.f); if (key >= 0 && key < 63) { int ik = (int)key; float fk = key - ik; keys[(ik)] += p * (1.f - fk); keys[(ik + 1)] += p * fk; } } // overtone } // step>0 } // bucket // find strongest note for (int i = 0; i < 128; ++i) if (keys[i] > peak) { peak = keys[i]; peakkey = i; } step++; firstsamp += 1024; } // steps slicepeak[si] = peak; slicenote[si] = peakkey; } // slice */ if (enable_emu_audio) { if (Pa_Initialize() != paNoError) paerror("init"); // wavfile = fopen("out.wav", "wb"); err = Pa_OpenDefaultStream(&stream, 2, 2, paFloat32, FS, BLOCK_SAMPLES, pacb, nullptr); if (err != paNoError) { // second attempt.. now without input! err = Pa_OpenDefaultStream(&stream, 0, 2, paFloat32, FS, BLOCK_SAMPLES, pacb, nullptr); if (err != paNoError) { paerror("open"); } } #ifdef STRETCH_PROTO static bool first = true; if (first) { first = false; Sample s = {}; //ParseWAV(&s, "C:\\Users\\blues\\Dropbox\\pv\\samples\\164718__bradovic__piano.wav"); //if (!s.samples) ParseWAV(&s, "piano.wav"); int l = s.numsamples; if (l > 512 * 1024) l = 512 * 1024; for (int i = 0; i < l; ++i) inputtape[i] = read_sample(&s, i, 0) / 32768.f; } #endif } #ifdef _WIN32 int numin = midiInGetNumDevs(); int numout = midiOutGetNumDevs(); printf("%d midi in devices, %d midi out devices\n", numin, numout); int midiindev = -1; for (int i = 0; i < numin; ++i) { MIDIINCAPSA caps = {}; midiInGetDevCapsA(i, &caps, sizeof(caps)); if (midiin_name_to_match && midiindev < 0 && strstr(caps.szPname, midiin_name_to_match)) midiindev = i; printf("%d: %s %c\n", i, caps.szPname, (midiindev==i)?'*':' '); } if (midiindev < 0) midiindev = 0; // default to first one if (numin >= 0) { midiInOpen(&hmidiin, midiindev, (DWORD_PTR)midicb, 0, CALLBACK_FUNCTION); midiInStart(hmidiin); } int midioutdev = -1; for (int i = 0; i < numout; ++i) { MIDIOUTCAPSA caps = {}; midiOutGetDevCapsA(i, &caps, sizeof(caps)); if (midiout_name_to_match && midioutdev < 0 && strstr(caps.szPname, midiout_name_to_match)) midioutdev = i; printf("%d: %s %c\n", i, caps.szPname, (midioutdev == i) ? '*' : ' '); } if (midioutdev < 0) midioutdev = 0; // default to first one if (numout >= 0) { midiOutOpen(&hmidiout, midioutdev, (DWORD_PTR)0, 0, CALLBACK_NULL); } #endif // _WIN32 glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) return 1; // Decide GL+GLSL versions #if __APPLE__ // GL 3.2 + GLSL 150 const char* glsl_version = "#version 150"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac #else // GL 3.0 + GLSL 130 const char* glsl_version = "#version 130"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only #endif #define WINDOW_WIDTH 1170 #define WINDOW_HEIGHT 820 window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "plinky", NULL, NULL); glfwSetWindowSizeLimits(window, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT); glfwMakeContextCurrent(window); glfwSwapInterval(1); // Enable vsync gl3wInit(); // Setup ImGui binding ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls //io.DisplayFramebufferScale = ImVec2(2.f, 2.f); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); ImGui::StyleColorsDark(); //ImGui::StyleColorsClassic(); struct stat src = {}, dst = {}; stat("../core/src/icons.h", &dst); stat("plinkyicons.png", &src); if (dst.st_mtime < src.st_mtime) { // regenerate icons table from square png sprite sheet const char* names[128] = { "KNOB", "SEND", "TOUCH", "DISTORT", "ADSR_A", "ADSR_D", "ADSR_S", "ADSR_R", "SLIDERS", "FORK", "PIANO", "NOTES", "DELAY", "REVERB", "SEQ", "RANDOM", "AB", "A", "B", "ALFO", "BLFO", "XY", "X", "Y", "XLFO", "YLFO", "REWIND", "PLAY", "RECORD", "LEFT", "RIGHT", "PREV", "NEXT", "CROSS", "PRESET", "ORDER", "WAVE", "MICRO", "LENGTH", "TIME", "FEEDBACK", "TIMES", "OFFSET", "INTERVAL", "PERIOD", "AMPLITUDE", "WARP", "SHAPE", "TILT", "GLIDE", "COLOR", "FM", "OCTAVE", "HPF", "DIVIDE", "PERCENT", "TEMPO", "PHONES", "JACK", "ENV", }; FILE* f = fopen("../core/src/icons.h", "w"); if (f) { int w, h, comp; u8* iconpixels = stbi_load("plinkyicons.png", &w, &h, &comp, 4); int nw = w / 15, nh = h / 15; fprintf(f, "const static u8 numicons=%d;\nconst static u16 icons[%d][16]={\n", nw * nh, nw * nh); for (int yi = 0; yi < nh; yi++) { for (int xi = 0; xi < nw; xi++) { fprintf(f, "\t{"); for (int xx = 0; xx < 15; ++xx) { u16 bits = 0; for (int yy = 0; yy < 15; ++yy) { if (iconpixels[(xx + xi * 15) * 4 + (yy + yi * 15) * (w * 4) + 3] > 128) bits |= 1 << yy; } fprintf(f, "0x%04x,", bits); } fprintf( f, "0},\n"); } } fprintf(f, "};\n"); for (int i = 0; i < 128; ++i) { if (!names[i]) break; fprintf(f, "#define I_%s \"\\x%02x\"\n", names[i], i + 0x80); } fclose(f); } } glGenTextures(1, &oledtex); glGenTextures(1, &buttonstex); u8*buttonspixels=stbi_load("buttons_v2.jpg", &buttonsw, &buttonsh, &buttonscomp, 3); glBindTexture(GL_TEXTURE_2D, buttonstex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, buttonsw, buttonsh, 0, GL_RGB, GL_UNSIGNED_BYTE, buttonspixels); glBindTexture(GL_TEXTURE_2D, oledtex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, oledw, oledh, 0, GL_RGBA, GL_UNSIGNED_BYTE, emupixels); for (int i=1;iAddLine(ImVec2(p.x+x, p.y), ImVec2(p.x+x, p.y + 80.f), (si==slice)?0xff4080ff:0x80ffffff); } ImGui::PlotLines("flux", [](void* data, int idx) {return maxf(maxf(dflux[idx],dflux[idx+1]),dflux[idx+2]); }, nullptr, maxstep, 0, "flux", 0.f, maxdflux, ImVec2(1024.f, 80.f)); ImGui::SliderInt("slice", &slice, 0, 64); ImGui::Text("slice %c%c%d peak %0.2f pitch, %0.2f tot", "CCDDEFFGGAAB"[slicenote[slice]%12], " # # # # # "[slicenote[slice] % 12], (slicenote[slice] /12)+1,slicepeak[slice], slicepeak[slice]*sliceflux[slice]); ImGui::PlotLines("wave", [](void* data, int idx) { int slice = *(int*)data; int addr = (snappos ? slicepos[slice] : (slice * maxstep*256) / 64) ; addr=addr-512 * 8 + idx * 8; if (idx == 512) return 32768.f; if (idx == 511) return -32768.f; if (addr < 0 || addr >= 1024 * 1024) return 0.f; return (float)_flashram[addr]; }, &slice, 1024, 0, "waveform", -32768,32768,ImVec2(1024.f,80.f)); */ // ImGui::PlotLines("knob", knobhistory, 512, khi, "knob", -1.f, 1.f, ImVec2(1024.f, 40.f)); if (ImGui::CollapsingHeader("cv inputs")) { ImGui::SetNextItemWidth(200); ImGui::SliderFloat("A cv", &acv, -5.f, 5.f); ImGui::SetNextItemWidth(200); ImGui::SliderFloat("B cv", &bcv, -5.f, 5.f); ImGui::SetNextItemWidth(200); ImGui::SliderFloat("X cv", &xcv, -5.f, 5.f); ImGui::SetNextItemWidth(200); ImGui::SliderFloat("Y cv", &ycv, -5.f, 5.f); ImGui::SetNextItemWidth(200); ImGui::SliderFloat("Gate cv", &gatecv, 0.f, 5.f); //ImGui::SameLine(); ImGui::Checkbox("plug##gate", (bool*)&emugatesense); ImGui::SameLine(); ImGui::Button("5v!"); gateforce = ImGui::IsItemActive(); ImGui::RadioButton("no pitch plug", &emupitchsense, 0); ImGui::RadioButton("pitch loopback (in only)", &emupitchsense, 1); ImGui::RadioButton("pitch loopback (cabled!)", &emupitchsense, 2); ImGui::RadioButton("pitch control", &emupitchsense, 3); if (emupitchsense == 2) pitchcv = emupitchloopback * 12.f; // pitchcv is in semitones, loopback is in volts aka octaves ImGui::SliderFloat("Pitch cv", &pitchcv, -1.5f * 12.f, 6.f * 12.f); } //ImVec2 debcont = ImGui::GetCursorScreenPos(); ImGui::Spacing(); float volhisto[9 * 8]; float pitchhisto[5 * 8]; float knobhisto[(65 * 2)]; int step = cur_step; int q = (step / 16) & 3; for (int i = 0; i < 8; ++i) { FingerRecord* fr = &rampattern[q].steps[(step & 15)][i]; for (int j = 0; j < 8; ++j) { volhisto[i * 9 + j] = (float)fr->pressure[j] + 10; } volhisto[i * 9 + 8] = 0; for (int j = 0; j < 4; ++j) { pitchhisto[i * 5 + j] = (float)fr->pos[j] + 10; } pitchhisto[i * 5 + 4] = 0; } for (int k = 0; k < 2; ++k) { for (int j = 0; j < 64; ++j) { knobhisto[k * 65 + j] = (float)rampattern[q].autoknob[(cur_step & 8) * 8 + j][k] + 128; } } ImGui::SetNextItemWidth(200); ImGui::PlotHistogram("velocity", volhisto, 9 * 8, 0, nullptr, 0.f, 265, ImVec2(0, 50)); ImGui::SetNextItemWidth(200); ImGui::PlotHistogram("pitch", pitchhisto, 5 * 8, 0, nullptr, 0.f, 265, ImVec2(0, 50)); ImGui::SetNextItemWidth(200); ImGui::PlotHistogram("knobs", knobhisto, 65 * 2, 0, nullptr, 0.f, 265, ImVec2(0, 50)); ///////////////////////////////////////////////////////////////////////////////////////// ImGui::Spacing(); //ImGui::SetCursorScreenPos(ImVec2(400.f, 0.f)); ImGui::NextColumn(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 p = ImGui::GetCursorScreenPos(); p.y += 2.f; draw_list->AddImage((void*)size_t(buttonstex), ImVec2(p.x, p.y), ImVec2(p.x + buttonsw, p.y + buttonsh)); // circles are 81x81, spacing is 96x91 float pw = 80.f;//ImGui::GetWindowContentRegionWidth(); float xgutter=95.f-pw-6.8f, ygutter=90.2f-pw; // static int tt = GetTickCount(); printf("%dms\n", GetTickCount() - tt); tt = GetTickCount(); // Draw + handle each column of the grid (except the shift row), then the final shift row separately (f==8). for (int f = 0; f < 9; ++f) { bool horiz = (f == 8); float ph = horiz ? (pw+xgutter+7.f)*8.f : (pw + ygutter) * 8.f; float jh = ph * 0.125f; const ImVec2 p = ImGui::GetCursorScreenPos(); for (int j = 0; j < 8; ++j) { ImVec2 cen; if (horiz) cen = ImVec2(p.x + (j + 0.5f) * jh, p.y + pw*0.5f); else cen = ImVec2(p.x + (0.5f) * pw, p.y + jh * (0.5f + j)); draw_list->AddCircle(cen, pw * 0.5f, 0xffcccccc, 32); // Draw LED. ImVec2 led_center(cen.x - pw * (horiz ? 0.0f : 0.4f), cen.y - jh * 0.4f); int led = clampi((int)(sqrtf(emuleds[f][j]) * 16.f), 0, 255)/2; draw_list->AddCircleFilled(led_center, pw * 0.1f, 0xff000000 + led * (horiz ? 0x20100 : 0x20202)); draw_list->AddCircle(led_center, pw * 0.1f, 0xffcccccc, 16); } ImVec2 m = ImGui::GetIO().MousePos; int mb = ImGui::GetIO().MouseDown[0]; m.x -= p.x; m.y -= p.y; if (horiz) { float t = m.x; m.x = pw - m.y; m.y = t; } if (mb && m.x >= 0 && m.y >= 0 && m.x < pw && m.y < ph) { emutouch[f][1] = clampi(int(((m.y) / ph) * 2047.f ), 0, 2047); int target = (clampi(int( (1.f-(fabsf(m.x-pw/2) / (pw/2) )) * 1024.f) + 1024, 1024, 2047)); emutouch[f][0] += (target- emutouch[f][0])/4; //int j=emutouch[f][1]/256; //emuleds[f][j]=emutouch[f][0]/8; } else { if (!ImGui::GetIO().KeyShift && f<8) emutouch[f][0] = 0; if (!ImGui::GetIO().KeyCtrl && f == 8) emutouch[f][0] = 0; } ImGui::Dummy(horiz ? ImVec2(ph,pw) : ImVec2(pw+xgutter, ph+2.f)); if (f < 7) ImGui::SameLine(); else { ImVec2 nl = ImGui::GetCursorScreenPos(); // nl.x += 390; // ImGui::SetCursorScreenPos(nl); } } ImGui::Spacing(); //ImGui::SetCursorScreenPos(debcont); /*ImGui::Button("hello"); for (int f = 0; f < 9; ++f) { ImGui::Text("%d-%d ", emutouch[f][0], emutouch[f][1]); ImGui::SameLine(); }*/ #endif ImGui::End(); // Rendering int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); if (!enable_emu_audio && plinky_inited) { for (int i = 0; i < 32000 / 33; i += 64) { static int half = 0; u32 temp[256]; u32 audioin[256] = {}; uitick(temp, audioin, half); half = 1 - half; } } }