Files
klubhaus-doorbell/libraries/audio-tools/src/AudioTools/AudioLibs/Desktop/JupyterAudio.h
2026-02-12 21:00:02 -08:00

221 lines
5.5 KiB
C++

#pragma once
#include "AudioTools/AudioLibs/Desktop/NoArduino.h"
#include "AudioTools/CoreAudio/AudioStreams.h"
#include "AudioTools/CoreAudio/AudioOutput.h"
#include "AudioTools/AudioCodecs/CodecWAV.h"
#include <string.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <stdio.h>
#include "nlohmann/json.hpp"
#include "xtl/xbase64.hpp"
namespace audio_tools {
/**
* @brief Simple layer for Print object to write to a c++ file
*/
class FileOutput : public Print {
public:
FileOutput(std::fstream &stream){
p_audio_stream = &stream;
}
size_t write(const uint8_t *data, size_t len) override {
p_audio_stream->write((const char*)data,len);
return len;
}
int availableForWrite() override {
return 1024;
}
protected:
std::fstream *p_audio_stream=nullptr;
};
/**
* @brief Displays audio in a Jupyter as chart
* Just wrapps a stream to provide the chart data
*/
template <typename T>
class ChartT {
public:
void setup(std::string fName, int channelCount, int channelNo) {
this->fname = fName;
this->channels = channelCount;
if (this->channels==0){
LOGE("Setting channels to 0");
}
this->channel = channelNo;
}
int getChannels() {
return this->channels;
}
int getChannel() {
return this->channel;
}
/// Provides data as svg polyline
const std::string chartData() {
str.clear();
str.str("");
// reset buffer;
if (channel<channels){
ifstream is;
is.open(fname, is.binary);
is.seekg(wav_header_size, is.beg);
std::list<int16_t> audioList;
T buffer[channels];
size_t rec_size = channels*sizeof(T);
while(is.read((char *)buffer, rec_size)){
audioList.push_back(transform(buffer[channel]));
}
string str_size = "102400"; //std::to_string(audioList.size());
str << "<style>div.x-svg {width: "<< str_size <<"px; }</style>";
str << "<div class='x-svg'><svg viewBox='0 0 "<< str_size << " 100'> <polyline fill='none' stroke='blue' stroke-width='1' points ='";
// copy data from input stream
size_t idx = 0;
for(int16_t sample: audioList){
str << idx++ << "," << sample << " ";
}
str << "'/></svg></div>";
} else {
str << "<p>Channel " << channel << " of " << channels << " does not exist!</p>";
}
return str.str();
}
protected:
std::stringstream str;
std::string fname;
const int wav_header_size = 44;
int channels=0;
int channel=0;
int transform(int x){
int result = x / 1000; // scale -32 to 32
result += 60; // shift down
return result;
}
};
/// @brief Default chart type for Jupyter integration
/// @ingroup io
using Chart = ChartT<int16_t>;
/**
* @brief Output to Jupyter. We write the data just to a file from where we can
* load the data again for different representations.
*/
template <typename T>
class JupyterAudioT : public AudioStream {
public:
JupyterAudioT(const char* fileName, AudioStream &stream, int bufferCount=20, int bufferSize=1024) {
buffer_count = bufferCount;
p_audio_stream = &stream;
cfg = stream.audioInfo();
copier.resize(bufferSize);
fname = fileName;
if (fileExists()){
remove(fileName);
}
}
ChartT<T> &chart(int channel=0) {
createWAVFile();
assert(cfg.channels>0);
chrt.setup(fname, cfg.channels, channel);
return chrt;
}
// provide the file name
const std::string &name() const {
return fname;
}
// provides the absolute file path as string
const std::string path() const {
std::filesystem::path p = fname;
std::string result = std::filesystem::absolute(p);
return result;
}
// fills a wav file with data once, the first time it was requested
void createWAVFile(){
try{
if (!fileExists()){
std::fstream fstream(fname, fstream.binary | fstream.trunc | fstream.out);
FileOutput fp(fstream);
wave_encoder.setAudioInfo(audioInfo());
out.setOutput(&fp);
out.setEncoder(&wave_encoder);
out.begin(); // output to decoder
copier.begin(out, *p_audio_stream);
copier.copyN(buffer_count);
fstream.close();
}
} catch(const std::exception& ex){
std::cerr << ex.what();
}
}
bool fileExists() {
ifstream f(fname.c_str());
return f.good();
}
int bufferCount(){
return buffer_count;
}
// provides the wav data as bas64 encded string
std::string audio() {
std::ifstream fin(fname, std::ios::binary);
std::stringstream m_buffer;
m_buffer << fin.rdbuf();
return xtl::base64encode(m_buffer.str());
}
// Provides the audion information
AudioInfo audioInfo() {
return cfg;
}
protected:
AudioStream *p_audio_stream=nullptr;
ChartT<T> chrt;
WAVEncoder wave_encoder;
EncodedAudioOutput out;
StreamCopyT<T> copier;
AudioInfo cfg;
string fname;
size_t buffer_count=0;
};
/// @brief Default Jupyter audio output with 16-bit samples
/// @ingroup io
using JupyterAudio = JupyterAudioT<int16_t>;
} // namespace audio_tools
/// Disply Chart in Jupyterlab xeus
nl::json mime_bundle_repr(Chart &in) {
auto bundle = nl::json::object();
bundle["text/html"] = in.chartData();
return bundle;
}
/// Disply Audio player in Jupyterlab xeus
nl::json mime_bundle_repr(JupyterAudio &in) {
auto bundle = nl::json::object();
in.createWAVFile();
bundle["text/html"] = "<audio controls "
"src='data:audio/wav;base64," +
in.audio() + "'/>";
return bundle;
}