andrewm@55: /* andrewm@55: * render.cpp andrewm@55: * andrewm@55: * Created on: Oct 24, 2014 andrewm@55: * Author: parallels andrewm@55: */ andrewm@55: andrewm@55: andrewm@56: #include andrewm@55: #include andrewm@55: andrewm@55: #define ANALOG_LOW (2048.0 / 65536.0) andrewm@55: #define ANALOG_HIGH (50000.0 / 65536.0) andrewm@55: andrewm@55: const int gDACPinOrder[] = {6, 4, 2, 0, 1, 3, 5, 7}; andrewm@55: andrewm@268: enum { andrewm@268: kStateTestingAudioLeft = 0, andrewm@268: kStateTestingAudioRight, andrewm@268: kStateTestingAudioDone andrewm@268: }; andrewm@268: andrewm@55: uint64_t gLastErrorFrame = 0; andrewm@55: uint32_t gEnvelopeSampleCount = 0; andrewm@268: float gEnvelopeValueL = 0.5, gEnvelopeValueR = 0.5; andrewm@55: float gEnvelopeDecayRate = 0.9995; andrewm@268: int gEnvelopeLastChannel = 0; andrewm@268: andrewm@268: float gPositivePeakLevels[2] = {0, 0}; andrewm@268: float gNegativePeakLevels[2] = {0, 0}; andrewm@268: float gPeakLevelDecayRate = 0.999; andrewm@268: const float gPeakLevelLowThreshold = 0.02; andrewm@268: const float gPeakLevelHighThreshold = 0.2; andrewm@268: const float gDCOffsetThreshold = 0.1; andrewm@268: int gAudioTestState = kStateTestingAudioLeft; andrewm@268: int gAudioTestStateSampleCount = 0; andrewm@268: int gAudioTestSuccessCounter = 0; andrewm@268: const int gAudioTestSuccessCounterThreshold = 64; andrewm@268: const int gAudioTestStateSampleThreshold = 16384; andrewm@55: andrewm@56: // setup() is called once before the audio rendering starts. andrewm@55: // Use it to perform any initialisation and allocation which is dependent andrewm@55: // on the period size or sample rate. andrewm@55: // andrewm@55: // userData holds an opaque pointer to a data structure that was passed andrewm@55: // in from the call to initAudio(). andrewm@55: // andrewm@55: // Return true on success; returning false halts the program. andrewm@55: andrewm@56: bool setup(BeagleRTContext *context, void *userData) andrewm@55: { andrewm@55: return true; andrewm@55: } andrewm@55: andrewm@55: // render() is called regularly at the highest priority by the audio engine. andrewm@55: // Input and output are given from the audio hardware and the other andrewm@55: // ADCs and DACs (if available). If only audio is available, numMatrixFrames andrewm@55: // will be 0. andrewm@55: andrewm@55: void render(BeagleRTContext *context, void *userData) andrewm@55: { andrewm@55: static float phase = 0.0; andrewm@55: static int sampleCounter = 0; andrewm@55: static int invertChannel = 0; andrewm@55: float frequency = 0; andrewm@55: andrewm@55: // Play a sine wave on the audio output andrewm@55: for(unsigned int n = 0; n < context->audioFrames; n++) { andrewm@268: andrewm@268: // Peak detection on the audio inputs, with offset to catch andrewm@268: // DC errors andrewm@268: for(int ch = 0; ch < 2; ch++) { andrewm@268: if(context->audioIn[2*n + ch] > gPositivePeakLevels[ch]) andrewm@268: gPositivePeakLevels[ch] = context->audioIn[2*n + ch]; andrewm@268: gPositivePeakLevels[ch] += 0.1; andrewm@268: gPositivePeakLevels[ch] *= gPeakLevelDecayRate; andrewm@268: gPositivePeakLevels[ch] -= 0.1; andrewm@268: if(context->audioIn[2*n + ch] < gNegativePeakLevels[ch]) andrewm@268: gNegativePeakLevels[ch] = context->audioIn[2*n + ch]; andrewm@268: gNegativePeakLevels[ch] -= 0.1; andrewm@268: gNegativePeakLevels[ch] *= gPeakLevelDecayRate; andrewm@268: gNegativePeakLevels[ch] += 0.1; andrewm@268: } andrewm@268: andrewm@268: if(gAudioTestState == kStateTestingAudioLeft) { andrewm@268: context->audioOut[2*n] = 0.2 * sinf(phase); andrewm@268: context->audioOut[2*n + 1] = 0; andrewm@268: andrewm@268: frequency = 3000.0; andrewm@268: phase += 2.0 * M_PI * frequency / 44100.0; andrewm@268: if(phase >= 2.0 * M_PI) andrewm@268: phase -= 2.0 * M_PI; andrewm@268: andrewm@268: gAudioTestStateSampleCount++; andrewm@268: if(gAudioTestStateSampleCount >= gAudioTestStateSampleThreshold) { andrewm@268: // Check if we have the expected input: signal on the left but not andrewm@268: // on the right. Also check that there is not too much DC offset on the andrewm@268: // inactive signal andrewm@268: if((gPositivePeakLevels[0] - gNegativePeakLevels[0]) >= gPeakLevelHighThreshold andrewm@268: && (gPositivePeakLevels[1] - gNegativePeakLevels[1]) <= gPeakLevelLowThreshold && andrewm@268: fabsf(gPositivePeakLevels[1]) < gDCOffsetThreshold && andrewm@268: fabsf(gNegativePeakLevels[1]) < gDCOffsetThreshold) { andrewm@268: // Successful test: increment counter andrewm@268: gAudioTestSuccessCounter++; andrewm@268: if(gAudioTestSuccessCounter >= gAudioTestSuccessCounterThreshold) { andrewm@268: gAudioTestState = kStateTestingAudioRight; andrewm@268: gAudioTestStateSampleCount = 0; andrewm@268: gAudioTestSuccessCounter = 0; andrewm@268: } andrewm@55: andrewm@268: } andrewm@268: else { andrewm@268: if(!((context->audioSampleCount + n) % 22050)) { andrewm@268: // Debugging print messages andrewm@268: if((gPositivePeakLevels[0] - gNegativePeakLevels[0]) < gPeakLevelHighThreshold) andrewm@268: rt_printf("Left Audio In FAIL: insufficient signal: %f\n", andrewm@268: gPositivePeakLevels[0] - gNegativePeakLevels[0]); andrewm@268: else if(gPositivePeakLevels[1] - gNegativePeakLevels[1] > gPeakLevelLowThreshold) andrewm@268: rt_printf("Right Audio In FAIL: signal present when it should not be: %f\n", andrewm@268: gPositivePeakLevels[1] - gNegativePeakLevels[1]); andrewm@268: else if(fabsf(gPositivePeakLevels[1]) >= gDCOffsetThreshold || andrewm@268: fabsf(gNegativePeakLevels[1]) >= gDCOffsetThreshold) andrewm@268: rt_printf("Right Audio In FAIL: DC offset: (%f, %f)\n", andrewm@268: gPositivePeakLevels[1], gNegativePeakLevels[1]); andrewm@268: } andrewm@268: gAudioTestSuccessCounter--; andrewm@268: if(gAudioTestSuccessCounter <= 0) andrewm@268: gAudioTestSuccessCounter = 0; andrewm@268: } andrewm@55: } andrewm@268: } andrewm@268: else if(gAudioTestState == kStateTestingAudioRight) { andrewm@268: context->audioOut[2*n] = 0; andrewm@268: context->audioOut[2*n + 1] = 0.2 * sinf(phase); andrewm@268: andrewm@268: frequency = 3000.0; andrewm@268: phase += 2.0 * M_PI * frequency / 44100.0; andrewm@268: if(phase >= 2.0 * M_PI) andrewm@268: phase -= 2.0 * M_PI; andrewm@268: andrewm@268: gAudioTestStateSampleCount++; andrewm@268: if(gAudioTestStateSampleCount >= gAudioTestStateSampleThreshold) { andrewm@268: // Check if we have the expected input: signal on the left but not andrewm@268: // on the right andrewm@268: if((gPositivePeakLevels[1] - gNegativePeakLevels[1]) >= gPeakLevelHighThreshold andrewm@268: && (gPositivePeakLevels[0] - gNegativePeakLevels[0]) <= gPeakLevelLowThreshold && andrewm@268: fabsf(gPositivePeakLevels[0]) < gDCOffsetThreshold && andrewm@268: fabsf(gNegativePeakLevels[0]) < gDCOffsetThreshold) { andrewm@268: // Successful test: increment counter andrewm@268: gAudioTestSuccessCounter++; andrewm@268: if(gAudioTestSuccessCounter >= gAudioTestSuccessCounterThreshold) { andrewm@268: gAudioTestSuccessCounter = 0; andrewm@268: gAudioTestStateSampleCount = 0; andrewm@268: gAudioTestState = kStateTestingAudioDone; andrewm@268: } andrewm@268: } andrewm@268: else { andrewm@268: if(!((context->audioSampleCount + n) % 22050)) { andrewm@268: // Debugging print messages andrewm@268: if((gPositivePeakLevels[1] - gNegativePeakLevels[1]) < gPeakLevelHighThreshold) andrewm@268: rt_printf("Right Audio In FAIL: insufficient signal: %f\n", andrewm@268: gPositivePeakLevels[1] - gNegativePeakLevels[1]); andrewm@268: else if(gPositivePeakLevels[0] - gNegativePeakLevels[0] > gPeakLevelLowThreshold) andrewm@268: rt_printf("Left Audio In FAIL: signal present when it should not be: %f\n", andrewm@268: gPositivePeakLevels[0] - gNegativePeakLevels[0]); andrewm@268: else if(fabsf(gPositivePeakLevels[0]) >= gDCOffsetThreshold || andrewm@268: fabsf(gNegativePeakLevels[0]) >= gDCOffsetThreshold) andrewm@268: rt_printf("Left Audio In FAIL: DC offset: (%f, %f)\n", andrewm@268: gPositivePeakLevels[0], gNegativePeakLevels[0]); andrewm@268: } andrewm@268: gAudioTestSuccessCounter--; andrewm@268: if(gAudioTestSuccessCounter <= 0) andrewm@268: gAudioTestSuccessCounter = 0; andrewm@268: } andrewm@268: } andrewm@55: } andrewm@55: else { andrewm@268: // Audio input testing finished. Play tones depending on status of andrewm@268: // analog testing andrewm@268: context->audioOut[2*n] = gEnvelopeValueL * sinf(phase); andrewm@268: context->audioOut[2*n + 1] = gEnvelopeValueR * sinf(phase); andrewm@268: andrewm@268: // If one second has gone by with no error, play one sound, else andrewm@268: // play another andrewm@268: if(context->audioSampleCount + n - gLastErrorFrame > 44100) { andrewm@268: gEnvelopeValueL *= gEnvelopeDecayRate; andrewm@268: gEnvelopeValueR *= gEnvelopeDecayRate; andrewm@268: gEnvelopeSampleCount++; andrewm@268: if(gEnvelopeSampleCount > 22050) { andrewm@268: if(gEnvelopeLastChannel == 0) andrewm@268: gEnvelopeValueR = 0.5; andrewm@268: else andrewm@268: gEnvelopeValueL = 0.5; andrewm@268: gEnvelopeLastChannel = !gEnvelopeLastChannel; andrewm@268: gEnvelopeSampleCount = 0; andrewm@268: } andrewm@268: frequency = 880.0; andrewm@268: } andrewm@268: else { andrewm@268: gEnvelopeValueL = gEnvelopeValueR = 0.5; andrewm@268: gEnvelopeLastChannel = 0; andrewm@268: frequency = 220.0; andrewm@268: } andrewm@268: andrewm@268: phase += 2.0 * M_PI * frequency / 44100.0; andrewm@268: if(phase >= 2.0 * M_PI) andrewm@268: phase -= 2.0 * M_PI; andrewm@55: } andrewm@55: } andrewm@55: andrewm@55: for(unsigned int n = 0; n < context->analogFrames; n++) { andrewm@55: // Change outputs every 512 samples andrewm@55: if(sampleCounter < 512) { andrewm@55: for(int k = 0; k < 8; k++) { andrewm@55: if(k == invertChannel) andrewm@55: context->analogOut[n*8 + gDACPinOrder[k]] = ANALOG_HIGH; andrewm@55: else andrewm@55: context->analogOut[n*8 + gDACPinOrder[k]] = 0; andrewm@55: } andrewm@55: } andrewm@55: else { andrewm@55: for(int k = 0; k < 8; k++) { andrewm@55: if(k == invertChannel) andrewm@55: context->analogOut[n*8 + gDACPinOrder[k]] = 0; andrewm@55: else andrewm@55: context->analogOut[n*8 + gDACPinOrder[k]] = ANALOG_HIGH; andrewm@55: } andrewm@55: } andrewm@55: andrewm@55: // Read after 256 samples: input should be low andrewm@55: if(sampleCounter == 256) { andrewm@55: for(int k = 0; k < 8; k++) { andrewm@55: if(k == invertChannel) { andrewm@55: if(context->analogIn[n*8 + k] < ANALOG_HIGH) { andrewm@55: rt_printf("FAIL [output %d, input %d] -- output HIGH input %f (inverted)\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]); andrewm@55: gLastErrorFrame = context->audioSampleCount + n; andrewm@55: } andrewm@55: } andrewm@55: else { andrewm@55: if(context->analogIn[n*8 + k] > ANALOG_LOW) { andrewm@55: rt_printf("FAIL [output %d, input %d] -- output LOW --> input %f\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]); andrewm@55: gLastErrorFrame = context->audioSampleCount + n; andrewm@55: } andrewm@55: } andrewm@55: } andrewm@55: } andrewm@55: else if(sampleCounter == 768) { andrewm@55: for(int k = 0; k < 8; k++) { andrewm@55: if(k == invertChannel) { andrewm@55: if(context->analogIn[n*8 + k] > ANALOG_LOW) { andrewm@55: rt_printf("FAIL [output %d, input %d] -- output LOW input %f (inverted)\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]); andrewm@55: gLastErrorFrame = context->audioSampleCount + n; andrewm@55: } andrewm@55: } andrewm@55: else { andrewm@55: if(context->analogIn[n*8 + k] < ANALOG_HIGH) { andrewm@55: rt_printf("FAIL [output %d, input %d] -- output HIGH input %f\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]); andrewm@55: gLastErrorFrame = context->audioSampleCount + n; andrewm@55: } andrewm@55: } andrewm@55: } andrewm@55: } andrewm@55: andrewm@55: if(++sampleCounter >= 1024) { andrewm@55: sampleCounter = 0; andrewm@55: invertChannel++; andrewm@55: if(invertChannel >= 8) andrewm@55: invertChannel = 0; andrewm@55: } andrewm@55: } andrewm@55: } andrewm@55: andrewm@56: // cleanup() is called once at the end, after the audio has stopped. andrewm@56: // Release any resources that were allocated in setup(). andrewm@55: andrewm@56: void cleanup(BeagleRTContext *context, void *userData) andrewm@55: { andrewm@55: andrewm@55: }