!7

cmodule PerceptronVAD1 > DynImageSurface {
  switchable int w = 5;
  switchable int h = 10;
  switchable int nMaxExamples = 2000;
  switchable S teacherModule; // an accurate VAD that we learn from
  switchable bool learn;
  switchable bool train;
  switchable double c = 1;
  transient S status;
  new Perceptron perceptron;
  transient double[] parameters;
  transient ReliableSingleThread rstTrain = dm_rst(this, r train);
  transient double lastError;
  
  visualize {
    JComponent c = super.visualize();
    imageSurface_pixelated(imageSurface);
    setZoom(20);
    ret centerAndSouth(c, withMargin(vstackWithSpacing(
      centerAndEastWithMargin(dm_textFieldWithLabel teacherModule(), dm_checkBox learn()),
      centerAndEastWithMargin(dm_label status(), dm_checkBox train()))));
  }
    
  start {
    updateStatus();
    dm_requireLiveAudioFFT();
    dm_audioInput_enableSendOutClonedData();

    dm_vmBus_onMessage_q newAudioFrequencyImageFromData(voidfunc(virtual BWImage _img, short[] fromData) {
      BWImage img = cast quickImport(_img);
      BWIntegralImage ii = new(img);
      BWImage bw = scaleDownUsingIntegralImageBW(ii, w, h);
      setImage(bw);
      parameters = concatDoubleArrays(
        bwImage_averageBrightnessPerRow(bw),
        bwImage_standardDeviationPerRow(bw));
        
      if (teacherModule != null && learn) {
        Bool va = cast dm_call(teacherModule, 'hasVoiceActivity, fromData);
        // TODO: wait a bit while va == null
        print("va: " + va);
        if (va != null && l(perceptron.examples) < nMaxExamples) {
          perceptron.addExample(parameters, va);
          change();
          updateStatus();
          rstTrain.trigger();
        }
      }
    });
   
    dm_watchFieldAndNow c(r {perceptron.c = c });
    dm_watchFieldAndNow train(rstTrain);
  }
  
  void train enter {
    if (!train) ret;
    print("Training");
    long n = 0;
    for ping (Double error : perceptron.trainingIterator()) {
      if (deleted || !train) ret;
      ++n;
      if (error != null)
        lastError = error;
      if (error != null || (perceptron.trainingRound % 10000) == 0) {
        //perceptron.printWithWeights();
        updateStatus();
        change();
      }
    }
    print("Trained (" + nRounds(n) + ")");
  }
  
  void updateStatus {
    setField(status := "Error: " + firstNonNegativeDouble_orMinus1(perceptron.error, lastError) + " for " + nExamples(perceptron.examples) + ", " + nRounds(perceptron.trainingRound));
  }
}