blob: 45dd2a9046c26185cd7cd0662ee264a84171701f [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Data structures that hold audio samples are allocated on the heap
// because they are too big (for now) for .data/.bss. We use calloc
// for this and need to override the default quota (4K) to satisfy our
// large'ish requests.
//#define kSamples (5 * 16000)
#define kSamples (5000) // NB: reduced sample count for slow renode
#define kFilterSamples (256)
#define roundup(a, b) (((a) + (b) - 1) / (b)) * (b)
#define MALLOC_QUOTA \
roundup( \
(kSamples * sizeof(int32_t)) + 2 * (kFilterSamples * sizeof(int16_t)), \
4096)
#include "soundstream.h"
#include <fail-simulator-on-error.h>
#include <thread.h>
#include <debug.hh>
#include "hw/top_matcha/sw/autogen/top_matcha.h"
/// Expose debugging features unconditionally for this compartment.
using Debug = ConditionalDebug<true, "SOUNDSTREAM">;
#include "compat.h"
#define ABS(x) (x > 0 ? x : -x)
void __cheri_compartment("soundstream") entry(void) {
Debug::log("soundstream (Thread {})", thread_id_get());
i2s_init();
ml_top_init();
mailbox_init();
i2s_rxfifo_clear();
i2s_irq_acknowledge_all();
i2s_irq_set_enabled(kI2sIrqRxWatermark, /*enabled=*/true);
// NB: sample buffers are temporarily allocated from the heap because
// the toolchain does not support large'ish data structures. Support
// has been committed upstream but not yet brought in.
// XXX calloc returns 0xffffffff (v:0 0xfffffe00-0xfffffe00 l:0x0 o:0x0 p: -
// ------ -- ---) on failure so cannot check against NULL
// int16_t samples_left[kFilterSamples] = {0};
// int16_t samples_right[kFilterSamples] = {0};
int16_t* samples_left = (int16_t*)calloc(sizeof(int16_t), kFilterSamples);
Debug::Assert(__builtin_cheri_length_get(samples_left) > 0,
"samples_left allocation failed {}", samples_left);
int16_t* samples_right = (int16_t*)calloc(sizeof(int16_t), kFilterSamples);
Debug::Assert(__builtin_cheri_length_get(samples_right) > 0,
"samples_right allocation failed {}", samples_right);
size_t index_left = 0;
size_t index_right = 0;
int32_t total_left = 0;
int32_t total_right = 0;
// static int32_t samples[kSamples];
int32_t* samples = (int32_t*)calloc(sizeof(int32_t), kSamples);
Debug::Assert(__builtin_cheri_length_get(samples) > 0,
"Sample allocation failed {}", samples);
memset(samples, 0xa5, sizeof(int32_t) * kSamples);
Debug::log("Setup complete");
while (true) {
// TODO(sleffler): need custom security core code running and
// a way to toggle the gpio associated with the button; for now
// just force it to appear as though the button has been pressed.
mailbox_set_button_pressed(true);
// Wait until the record switch is pushed
Debug::log("Wait for button press...");
mailbox_wait_for_button_pressed();
mailbox_set_led(/*enabled=*/true);
Debug::log("Start recording (max {} samples)...", kSamples);
i2s_record_begin();
// Record until our buffer is full or the switch is released.
int sample = 0;
while (sample < kSamples && mailbox_button_pressed()) {
i2s_irq_set_enabled(kI2sIrqRxWatermark, /*enabled=*/true);
i2s_wait_for_rx_watermark();
while (!i2s_rxfifo_is_empty()) {
uint32_t reg_val = i2s_get_rdata();
// For each sample, split into left and right channel values,
// and update the moving average.
int16_t left = reg_val >> 16;
int16_t right = reg_val & 0xFFFF;
total_left -= samples_left[index_left];
total_right -= samples_right[index_right];
total_left += left;
samples_left[index_left] = left;
total_right += right;
samples_right[index_right] = right;
index_left = (index_left + 1) % kFilterSamples;
index_right = (index_right + 1) % kFilterSamples;
int16_t mean_left = total_left / kFilterSamples;
int16_t mean_right = total_right / kFilterSamples;
// Subtract the moving average from each channel, and repack into the
// sample buffer.
uint32_t offset_reg_val = (((left - mean_left) & 0xFFFF) << 16) |
((right - mean_right) & 0xFFFF);
samples[sample++] = offset_reg_val;
if (sample == kSamples || !mailbox_button_pressed()) {
break;
}
}
}
i2s_record_end();
mailbox_set_led(/*enabled=*/false);
Debug::log("Done recording {} samples", sample);
int samples_captured = sample;
// Calculate the min/max of the audio, after correcting DC offsets
int32_t max = INT16_MIN;
int32_t min = INT16_MAX;
for (int i = 1; i < (samples_captured * 2); i += 2) {
int16_t* samples_s16 = (int16_t*)samples;
int32_t sample = samples_s16[i];
if (sample < min) {
min = sample;
}
if (sample > max) {
max = sample;
}
}
// Calculate a scaling factor, and use this to scale the waveform
// to a peak of 75% amplitude.
int32_t scale_max = ((int32_t)max * 100) / ((int32_t)INT16_MAX);
int32_t scale_min = ABS(((int32_t)min * 100) / ((int32_t)INT16_MIN));
int32_t scale = scale_max > scale_min ? scale_max : scale_min;
for (int i = 1; i < (samples_captured * 2); i += 2) {
int16_t* samples_s16 = (int16_t*)samples;
int16_t sample = samples_s16[i];
int16_t scaled_sample = (int16_t)(((int32_t)sample * 100) / scale);
scaled_sample = (((int32_t)scaled_sample * 75) / 100);
samples_s16[i] = scaled_sample;
}
Debug::log("Processing recorded audio...");
// 320 x int16
int iterations_to_process = samples_captured / 320;
int16_t process_buffer[320];
int16_t result_buffer[64];
char result_buffer_encoded[ENCODE_OUT_SIZE(sizeof(result_buffer))];
struct output_header header;
memset(&header, 0, sizeof(header)); // NB: resume_pc = 0
for (int i = 0; i < iterations_to_process; ++i) {
Debug::log("Iteration {}", i);
int16_t* samples_s16 = (int16_t*)samples;
// Extract left channel audio
for (int j = 0; j < 320; ++j) {
process_buffer[j] = samples_s16[(i * 320 * 2) + (j * 2) + 1];
}
ml_top_set_input(process_buffer, sizeof(process_buffer));
// Start/resume kelvin
(void) ml_top_finish_done(); // NB: reset state
ml_top_resume_ctrl_en(header.resume_pc);
ml_top_wait_for_finish();
ml_top_get_output_header(&header);
CHECK(header.length == sizeof(result_buffer), "Unexpected ML result size");
ml_top_get_output_data(&header, result_buffer);
encode((const unsigned char*)result_buffer, sizeof(result_buffer),
result_buffer_encoded);
Debug::log("[sound]::ENCODER:{}",
std::string_view(result_buffer_encoded, sizeof(result_buffer_encoded)));
}
Debug::log("[sound]::ENCODER: done");
Debug::log("Done with processing.");
}
panic();
}