|  | @@ -34,6 +34,7 @@ laBaseNodeType LA_IDN_NOISE;
 | 
	
		
			
				|  |  |  laBaseNodeType LA_IDN_OUTPUT;
 | 
	
		
			
				|  |  |  laBaseNodeType LA_IDN_SCOPE;
 | 
	
		
			
				|  |  |  laBaseNodeType LA_IDN_ENVELOPE;
 | 
	
		
			
				|  |  | +laBaseNodeType LA_IDN_QUANTIZE;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_FM;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_VCA;
 | 
	
	
		
			
				|  | @@ -41,12 +42,18 @@ laPropContainer* LA_PC_IDN_NOISE;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_OUTPUT;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_SCOPE;
 | 
	
		
			
				|  |  |  laPropContainer* LA_PC_IDN_ENVELOPE;
 | 
	
		
			
				|  |  | +laPropContainer* LA_PC_IDN_QUANTIZE;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #define _2PI 6.283185307
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #define VAL2FREQ(val) \
 | 
	
		
			
				|  |  |      16.0f*pow(2,val) // 0-10, 16-16384Hz
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#define VAL13  -0.218640286
 | 
	
		
			
				|  |  | +#define VAL27  0.78135971352
 | 
	
		
			
				|  |  | +#define VAL440 4.78135971352 // 2^FREQC*16=440
 | 
	
		
			
				|  |  | +#define VALHALF (1.0f/12.0f)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  #define WRAPPHASE(p) \
 | 
	
		
			
				|  |  |      while(p>_2PI){ p-=_2PI; }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -397,10 +404,74 @@ void laui_EnvelopeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laCol
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |      b=laBeginRow(uil,c,0,0);
 | 
	
		
			
				|  |  |      laShowItem(uil,c,This,"in_trigger"); laShowItem(uil,c,This,"trigger")->Flags|=LA_UI_FLAGS_CYCLE;
 | 
	
		
			
				|  |  | -    laShowSeparator(uil,c); laShowItem(uil,c,This,"out");
 | 
	
		
			
				|  |  | +    laShowSeparator(uil,c)->Expand=1; laShowItem(uil,c,This,"out")->Flags|=LA_UI_SOCKET_LABEL_W;
 | 
	
		
			
				|  |  |      laEndRow(uil,b);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +void IDN_QuantizeInit(laSynthNodeQuantize* n, int NoCreate){
 | 
	
		
			
				|  |  | +    if(!NoCreate){ strSafeSet(&n->Base.Name,"Envelope");
 | 
	
		
			
				|  |  | +        n->In=laCreateInSocket("IN",0); n->Out=laCreateOutSocket(n, "OUT",0);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if(!n->OutSamples){ n->OutSamples=memAcquireSimple(sizeof(real)*LA_SYNTH_PLEN); }
 | 
	
		
			
				|  |  | +    n->Out->Data = n->OutSamples; n->Out->DataType = LA_PROP_FLOAT|LA_PROP_ARRAY; n->Out->ArrLen=LA_SYNTH_PLEN;
 | 
	
		
			
				|  |  | +    int Enabled[12]={1,0,1,0,1,1,0,1,0,1,0,1};
 | 
	
		
			
				|  |  | +    memcpy(n->EnabledBits,Enabled,sizeof(Enabled));
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void IDN_QuantizeDestroy(laSynthNodeQuantize* n){
 | 
	
		
			
				|  |  | +    laDestroyInSocket(n->In); laDestroyOutSocket(n->Out);
 | 
	
		
			
				|  |  | +    memFree(n->OutSamples);
 | 
	
		
			
				|  |  | +    strSafeDestroy(&n->Base.Name);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +int IDN_QuantizeVisit(laSynthNodeQuantize* n, laNodeVisitInfo* vi){
 | 
	
		
			
				|  |  | +    LA_GUARD_THIS_NODE(n,vi);
 | 
	
		
			
				|  |  | +    if(LA_SRC_AND_PARENT(n->In)){ laBaseNode*bn=n->In->Source->Parent; LA_VISIT_NODE(bn,vi); }
 | 
	
		
			
				|  |  | +    LA_ADD_THIS_NODE(n,vi);
 | 
	
		
			
				|  |  | +    return LA_DAG_FLAG_PERM;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +static const real QTABLE[12]={ VAL13, VAL13+VALHALF, VAL13+VALHALF*2, VAL13+VALHALF*3,
 | 
	
		
			
				|  |  | +    VAL13+VALHALF*4, VAL13+VALHALF*5, VAL13+VALHALF*6, VAL13+VALHALF*7,
 | 
	
		
			
				|  |  | +    VAL13+VALHALF*8, VAL13+VALHALF*9, VAL13+VALHALF*10, VAL13+VALHALF*11 };
 | 
	
		
			
				|  |  | +static void set_quantize_out_bit(laSynthNodeQuantize* n, int which){
 | 
	
		
			
				|  |  | +    for(int i=0;i<12;i++){
 | 
	
		
			
				|  |  | +        if(n->EnabledBits[i]){ n->EnabledBits[i]=1; }
 | 
	
		
			
				|  |  | +        if(i==which && n->EnabledBits){ n->EnabledBits[i]=3; }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +int IDN_QuantizeEval(laSynthNodeQuantize* n){ real cv0=VAL440; LA_GET_SRC_AS_VALUE(cv0, n->In);
 | 
	
		
			
				|  |  | +    real* cv; INPUTPACKET(cv,n->In); real dists[12];
 | 
	
		
			
				|  |  | +    for(int i=0;i<LA_SYNTH_PLEN;i++){
 | 
	
		
			
				|  |  | +        if(cv){ cv0=cv[i]; }
 | 
	
		
			
				|  |  | +        int octave = (int)(cv0-VAL13); int minkey=0;
 | 
	
		
			
				|  |  | +        real use_cv = cv0 - octave;
 | 
	
		
			
				|  |  | +        for(int j=0;j<12;j++){ dists[j] = fabs(use_cv - QTABLE[j]); }
 | 
	
		
			
				|  |  | +        for(int j=1;j<12;j++){ if(n->EnabledBits[j] && dists[j] < dists[minkey]){ minkey=j; } }
 | 
	
		
			
				|  |  | +        set_quantize_out_bit(n,minkey);
 | 
	
		
			
				|  |  | +        n->OutSamples[i] = QTABLE[minkey]+octave;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return 1;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void IDN_QuantizeCopy(laSynthNodeQuantize* new, laSynthNodeQuantize* old, int DoRematch){
 | 
	
		
			
				|  |  | +    if(DoRematch){
 | 
	
		
			
				|  |  | +        LA_IDN_NEW_LINK(In) return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    LA_IDN_OLD_DUPL(Out);
 | 
	
		
			
				|  |  | +    memcpy(new->EnabledBits,old->EnabledBits,sizeof(int)*12);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void laui_QuantizeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
 | 
	
		
			
				|  |  | +    laColumn* c=laFirstColumn(uil); laSynthNodeQuantize*n=This->EndInstance;
 | 
	
		
			
				|  |  | +    LA_BASE_NODE_HEADER(uil,c,This);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laUiItem* b;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    b=laBeginRow(uil,c,0,0);
 | 
	
		
			
				|  |  | +    laUiItem* ui=laShowItem(uil,c,This,"enabled_keys");
 | 
	
		
			
				|  |  | +    ui->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT|LA_UI_FLAGS_TRANSPOSE|LA_UI_FLAGS_ICON; ui->Expand=1;
 | 
	
		
			
				|  |  | +    laShowNodeSocket(uil,c,This,"in",0)->Flags|=LA_UI_SOCKET_LABEL_W;
 | 
	
		
			
				|  |  | +    laShowNodeSocket(uil,c,This,"out",0)->Flags|=LA_UI_SOCKET_LABEL_W;
 | 
	
		
			
				|  |  | +    laEndRow(uil,b);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void laRebuildSynthGraphs(){
 | 
	
		
			
				|  |  |      if(MAIN.Audio->CurrentSynth){
 | 
	
		
			
				|  |  |          laSpinLock(&MAIN.Audio->CurrentSynth->Lock);
 | 
	
	
		
			
				|  | @@ -638,6 +709,10 @@ void laset_CurrentSynth(laAudio* a,laSynth* s){
 | 
	
		
			
				|  |  |  void laset_CurrentAudioDevice(laAudio* a,laAudioDevice* ad){
 | 
	
		
			
				|  |  |      la_SelectAudioDevice(ad);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +void laset_QuantizeEnabledKeys(laSynthNodeQuantize* n, int index, int val_UNUSED){
 | 
	
		
			
				|  |  | +    if(n->EnabledBits[index] & 0x1){ n->EnabledBits[index]=0; }
 | 
	
		
			
				|  |  | +    else{ n->EnabledBits[index]=1; }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void la_AudioPreFrame(){
 | 
	
		
			
				|  |  |      if(MAIN.GraphNeedsRebuild){ laRebuildSynthGraphs(); }
 | 
	
	
		
			
				|  | @@ -748,17 +823,28 @@ void laInitAudio(){
 | 
	
		
			
				|  |  |      laAddEnumItemAs(p,"IDLE","Idle","Trigger idle",0,0);
 | 
	
		
			
				|  |  |      laAddEnumItemAs(p,"TRIG","Trigger","Trigger active",1,0);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    pc=laAddPropertyContainer("la_node_synth_quantize", "Quantize Node", "Quantize control voltages",0,laui_QuantizeNode,sizeof(laSynthNodeQuantize),lapost_Node,0,1);
 | 
	
		
			
				|  |  | +    LA_PC_IDN_QUANTIZE=pc; laPropContainerExtraFunctions(pc,0,0,0,0,laui_DefaultNodeOperationsPropUiDefine);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"in", "Input","CV input","la_in_socket",0,0,0,offsetof(laSynthNodeQuantize,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 | 
	
		
			
				|  |  | +    laAddSubGroup(pc,"out", "Output","CV output","la_out_socket",0,0,0,offsetof(laSynthNodeQuantize,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 | 
	
		
			
				|  |  | +    p=laAddEnumProperty(pc,"enabled_keys","Keys","Enabled keys",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laSynthNodeQuantize,EnabledBits),0,0,12,0,laset_QuantizeEnabledKeys,0,0,0,0,0);
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"DISABLED","Disabled","Key is disabled",0,' ');
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"ENABLED","Enabled","Key is enabled",1,' ');
 | 
	
		
			
				|  |  | +    laAddEnumItemAs(p,"OUTPUT","Outputting","Key is Outputting",3,U'🌑');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("VCO",'f',LA_IDN_FM, LA_PC_IDN_FM, IDN_FM, laSynthNodeFM);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("VCA",'a',LA_IDN_VCA, LA_PC_IDN_VCA, IDN_VCA, laSynthNodeVCA);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Noise",'~',LA_IDN_NOISE, LA_PC_IDN_NOISE, IDN_Noise, laSynthNodeNoise);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Scope",'s',LA_IDN_SCOPE, LA_PC_IDN_SCOPE, IDN_Scope, laSynthNodeScope);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Sound Output",U'🕪',LA_IDN_OUTPUT, LA_PC_IDN_OUTPUT, IDN_Output, laSynthNodeOutput);
 | 
	
		
			
				|  |  |      LA_IDN_REGISTER("Envelope",U'^',LA_IDN_ENVELOPE, LA_PC_IDN_ENVELOPE, IDN_Envelope, laSynthNodeEnvelope);
 | 
	
		
			
				|  |  | +    LA_IDN_REGISTER("Quantize",U'#',LA_IDN_QUANTIZE, LA_PC_IDN_QUANTIZE, IDN_Quantize, laSynthNodeQuantize);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      LA_NODE_CATEGORY_SYNTHESIZER=laAddNodeCategory("OSC",0,LA_RACK_TYPE_AUDIO);
 | 
	
		
			
				|  |  |      LA_NODE_CATEGORY_SYSTEM_SOUND=laAddNodeCategory("System",0,LA_RACK_TYPE_AUDIO);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_SYNTHESIZER, &LA_IDN_FM,&LA_IDN_NOISE,&LA_IDN_VCA,&LA_IDN_ENVELOPE,&LA_IDN_SCOPE,0);
 | 
	
		
			
				|  |  | +    laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_SYNTHESIZER, &LA_IDN_FM,&LA_IDN_NOISE,&LA_IDN_VCA,&LA_IDN_ENVELOPE,&LA_IDN_QUANTIZE,&LA_IDN_SCOPE,0);
 | 
	
		
			
				|  |  |      laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_SYSTEM_SOUND, &LA_IDN_OUTPUT,0);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 |