diff options
Diffstat (limited to 'src/lib/netlist/prg/nlwav.cpp')
-rw-r--r-- | src/lib/netlist/prg/nlwav.cpp | 664 |
1 files changed, 478 insertions, 186 deletions
diff --git a/src/lib/netlist/prg/nlwav.cpp b/src/lib/netlist/prg/nlwav.cpp index 3974dde1f3a..7c47b2a5ddc 100644 --- a/src/lib/netlist/prg/nlwav.cpp +++ b/src/lib/netlist/prg/nlwav.cpp @@ -1,34 +1,57 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud #include "plib/pstring.h" -#include "netlist/nl_setup.h" -#include "plib/plists.h" #include "plib/pmain.h" #include "plib/ppmf.h" #include "plib/pstream.h" +#include "plib/pstrutil.h" #include <cstdio> -/* From: https://ffmpeg.org/pipermail/ffmpeg-devel/2007-October/038122.html - * The most compatible way to make a wav header for unknown length is to put - * 0xffffffff in the header. 0 as the RIFF length and 0 as the data chunk length - * is a common agreement in serious recording applications while - * still recording the file. So a playback application can determine that the - * given file is still being recorded. As soon as the recording application - * finishes the ongoing recording, it writes the correct values for RIFF lenth - * and data chunk length to the file. - */ -/* http://de.wikipedia.org/wiki/RIFF_WAVE */ +// FIXME: These are either defined in other parts or have more complex consequences +// spell-checker: words nlconst, rpad, pfmt, pexception, pbinary, dynlib, pstring, psplit, putf, pmfp +// +// Specific technical terms +// spell-checker: words vcda, vcdd + +// FIXME: see below - this belongs somewhere else! +#ifdef _WIN32 +#include <stdio.h> +#include <fcntl.h> +#include <io.h> +#endif + +// From: https://ffmpeg.org/pipermail/ffmpeg-devel/2007-October/038122.html +// The most compatible way to make a wav header for unknown length is to put +// 0xffffffff in the header. 0 as the RIFF length and 0 as the data chunk length +// is a common agreement in serious recording applications while +// still recording the file. So a playback application can determine that the +// given file is still being recorded. As soon as the recording application +// finishes the ongoing recording, it writes the correct values for RIFF length +// and data chunk length to the file. +// +// http://de.wikipedia.org/wiki/RIFF_WAVE +// + +using arena = plib::aligned_arena<>; class wav_t { public: - // XXNOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) - wav_t(std::ostream &strm, bool is_seekable, std::size_t sr, std::size_t channels) + + enum format + { + s16, + s32, + f32 + }; + + wav_t(std::ostream &strm, bool is_seekable, format fmt, std::size_t sr, std::size_t channels) : m_f(strm) , m_stream_is_seekable(is_seekable) - /* force "play" to play and warn about eof instead of being silent */ - , m_fmt(static_cast<std::uint16_t>(channels), static_cast<std::uint32_t>(sr)) + , m_format(fmt) + // force "play" to play and warn about eof instead of being silent + , m_fmt(static_cast<std::uint16_t>(channels), static_cast<std::uint32_t>(sr), fmt) , m_data(is_seekable ? 0 : 0xffffffff) { @@ -37,13 +60,13 @@ public: write(m_data); } - COPYASSIGNMOVE(wav_t, delete) + PCOPYASSIGNMOVE(wav_t, delete) ~wav_t() { if (m_stream_is_seekable) { - m_fh.filelen = m_data.len + sizeof(m_data) + sizeof(m_fh) + sizeof(m_fmt) - 8; + m_fh.file_len = m_data.len + sizeof(m_data) + sizeof(m_fh) + sizeof(m_fmt) - 8; m_f.seekp(0); write(m_fh); write(m_fmt); @@ -53,62 +76,103 @@ public: } } - std::size_t channels() { return m_fmt.channels; } - std::size_t sample_rate() { return m_fmt.sample_rate; } + std::size_t channels() const { return m_fmt.channels; } + std::size_t sample_rate() const { return m_fmt.sample_rate; } template <typename T> void write(const T &val) { - m_f.write(reinterpret_cast<const std::ostream::char_type *>(&val), sizeof(T)); + plib::ostream_write(m_f, &val, 1); + } + + template <typename T> + void write_sample_int(double sample) + { + constexpr auto type_max(static_cast<double>(plib::numeric_limits<T>::max())); + constexpr auto type_min(static_cast<double>(plib::numeric_limits<T>::min())); + + sample *= type_max; + sample = std::max(type_min, sample); + sample = std::min(type_max, sample); + const T dest(static_cast<T>(sample)); + write(dest); } - void write_sample(int *sample) + // expects normalized samples between -1.0 to 1.0 for s16 and s32 + void write_samples(double *sample) { m_data.len += m_fmt.block_align; for (std::size_t i = 0; i < channels(); i++) { - auto ps = static_cast<int16_t>(sample[i]); /* 16 bit sample, FIXME: Endianess? */ - write(ps); + switch (m_format) + { + case s16: + write_sample_int<int16_t>(sample[i]); + break; + case s32: + write_sample_int<int32_t>(sample[i]); + break; + case f32: + const auto df32(static_cast<float>(sample[i])); + write(df32); + break; + } } } private: struct riff_chunk_t { - uint8_t group_id[4] = {'R','I','F','F'}; - uint32_t filelen = 0; - uint8_t rifftype[4] = {'W','A','V','E'}; + std::array<uint8_t, 4> group_id = {{'R','I','F','F'}}; + uint32_t file_len = 0; + std::array<uint8_t, 4> riff_type = {{'W','A','V','E'}}; }; struct riff_format_t { - riff_format_t(uint16_t achannels, uint32_t asample_rate) + riff_format_t(uint16_t num_channels, uint32_t a_sample_rate, format fm) { - channels = achannels; - sample_rate = asample_rate; + switch (fm) + { + case s16: + format_tag = 0x0001; // PCM + bits_sample = 16; + break; + case s32: + format_tag = 0x0001; // PCM + bits_sample = 32; + break; + case f32: + format_tag = 0x0003; // FLOAT + bits_sample = 32; + break; + } + channels = num_channels; + sample_rate = a_sample_rate; block_align = channels * ((bits_sample + 7) / 8); bytes_per_second = sample_rate * block_align; } - uint8_t signature[4] = {'f','m','t',' '}; + std::array<uint8_t, 4> signature = {{'f','m','t',' '}}; uint32_t fmt_length = 16; - uint16_t format_tag = 0x0001; // PCM + uint16_t format_tag; uint16_t channels; uint32_t sample_rate; uint32_t bytes_per_second; uint16_t block_align; - uint16_t bits_sample = 16; + uint16_t bits_sample; }; struct riff_data_t { - riff_data_t(uint32_t alen) : len(alen) {} - uint8_t signature[4] = {'d','a','t','a'}; + explicit riff_data_t(uint32_t alen) : len(alen) {} + std::array<uint8_t, 4> signature = {{'d','a','t','a'}}; uint32_t len; // data follows }; std::ostream &m_f; bool m_stream_is_seekable; + format m_format; riff_chunk_t m_fh; riff_format_t m_fmt; @@ -119,7 +183,7 @@ private: class log_processor { public: - using callback_type = plib::pmfp<void, std::size_t, double, double>; + using callback_type = plib::pmfp<void (std::size_t, double, double)>; struct elem { @@ -135,20 +199,21 @@ public: , m_e(channels) { } - bool readmore(std::vector<plib::putf8_reader> &r) + bool read_more(std::vector<plib::putf8_reader> &r) { bool success = false; for (std::size_t i = 0; i< r.size(); i++) { if (m_e[i].need_more) { - pstring line; - m_e[i].eof = !r[i].readline(line); + putf8string line; + m_e[i].eof = !r[i].read_line(line); if (!m_e[i].eof) { // sscanf is very fast ... // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - std::sscanf(line.c_str(), "%lf %lf", &m_e[i].t, &m_e[i].v); + if (2 != std::sscanf(line.c_str(), "%lf %lf", &m_e[i].t, &m_e[i].v)) + fprintf(stderr, "arg: <%s>\n", line.c_str()); m_e[i].need_more = false; } } @@ -157,7 +222,7 @@ public: return success; } - void process(std::vector<plib::unique_ptr<std::istream>> &is) + void process(std::vector<std::unique_ptr<std::istream>> &is) { std::vector<plib::putf8_reader> readers; for (auto &i : is) @@ -167,7 +232,7 @@ public: } pstring line; - bool more = readmore(readers); + bool more = read_more(readers); while (more) { @@ -185,10 +250,11 @@ public: m_e[mini].need_more = true; m_cb(mini, mint, m_e[mini].v); - more = readmore(readers); + more = read_more(readers); } } + private: callback_type m_cb; std::vector<elem> m_e; @@ -196,7 +262,7 @@ private: struct aggregator { - using callback_type = plib::pmfp<void, std::size_t, double, double>; + using callback_type = plib::pmfp<void (std::size_t, double, double)>; aggregator(std::size_t channels, double quantum, callback_type cb) : m_channels(channels) @@ -204,27 +270,27 @@ struct aggregator , m_cb(cb) , ct(0.0) , lt(0.0) - , outsam(channels, 0.0) - , cursam(channels, 0.0) + , output_samples(channels, 0.0) + , current_samples(channels, 0.0) { } void process(std::size_t chan, double time, double val) { while (time >= ct + m_quantum) { + ct += m_quantum; for (std::size_t i=0; i< m_channels; i++) { - outsam[i] += (ct - lt) * cursam[i]; - outsam[i] = outsam[i] / m_quantum; - m_cb(i, ct, outsam[i]); - outsam[i] = 0.0; + output_samples[i] += (ct - lt) * current_samples[i]; + output_samples[i] = output_samples[i] / m_quantum; + m_cb(i, ct, output_samples[i]); + output_samples[i] = 0.0; } lt = ct; - ct += m_quantum; } for (std::size_t i=0; i< m_channels; i++) - outsam[i] += (time-lt)*cursam[i]; + output_samples[i] += (time-lt)*current_samples[i]; lt = time; - cursam[chan] = val; + current_samples[chan] = val; } private: @@ -233,60 +299,134 @@ private: callback_type m_cb; double ct; double lt; - std::vector<double> outsam; - std::vector<double> cursam; + std::vector<double> output_samples; + std::vector<double> current_samples; +}; + +struct filter_hp +{ + using callback_type = plib::pmfp<void (std::size_t, double, double)>; + + filter_hp(double freq, bool boost, std::size_t channels, callback_type cb) + : m_cb(cb) + , m_hp_omega(plib::constants<double>::two() * plib::constants<double>::pi() * freq) + , m_boost(boost) + , m_lt(channels, 0.0) + , m_in(channels, 0.0) + , m_cap(channels, 0.0) + { } + void process(std::size_t chan, double time, double val) + { + // based on CR filter + auto dt(time - m_lt[chan]); + + double omega = ((m_boost && (time < 1.0/m_hp_omega)) ? 1e12 : m_hp_omega); + auto m(1.0 - plib::exp(-dt * omega)); + m_cap[chan] += m * (m_in[chan] - m_cap[chan]); + // out = in - vcap + m_cb(chan, time, m_in[chan] - m_cap[chan]); + + m_in[chan] = val; + m_lt[chan] = time; + } + +private: + callback_type m_cb; + double m_hp_omega; + bool m_boost; + std::vector<double> m_lt; + std::vector<double> m_in; + std::vector<double> m_cap; +}; + +struct filter_lp +{ + using callback_type = plib::pmfp<void (std::size_t, double, double)>; + + filter_lp(double freq, std::size_t channels, callback_type cb) + : m_cb(cb) + , m_lp_omega(plib::constants<double>::two() * plib::constants<double>::pi() * freq) + , m_lt(channels, 0.0) + , m_in(channels, 0.0) // lp filter + , m_cap(channels, 0.0) // hp filter + { } + void process(std::size_t chan, double time, double val) + { + // based on RC filter + auto dt(time - m_lt[chan]); + + auto m(1.0 - plib::exp(-dt * m_lp_omega)); + + m_cap[chan] += m * (m_in[chan] - m_cap[chan]); + // out = vcap + m_cb(chan, time, m_cap[chan]); + + m_in[chan] = val; + m_lt[chan] = time; + } + +private: + callback_type m_cb; + double m_lp_omega; + std::vector<double> m_lt; + std::vector<double> m_in; + std::vector<double> m_cap; }; -class wavwriter +class wav_writer { public: - wavwriter(std::ostream &fo, bool is_seekable, std::size_t channels, std::size_t sample_rate, double ampa) - : mean(channels, 0.0) - , means(channels, 0.0) - , maxsam(channels, -1e9) - , minsam(channels, 1e9) + wav_writer(std::ostream &fo, bool is_seekable, wav_t::format fmt, + std::size_t channels, std::size_t sample_rate, double amplification) + : max_samples(channels, -1e9) + , min_samples(channels, 1e9) , m_n(channels, 0) , m_samples(channels, 0) , m_last_time(0) , m_fo(fo) - , m_amp(ampa) - , m_wo(m_fo, is_seekable, sample_rate, channels) + , m_amp(amplification <= 0.0 ? 1.0e6 : amplification) + , m_auto(amplification <= 0.0) + , m_wo(m_fo, is_seekable, fmt, sample_rate, channels) { } - void process(std::size_t chan, double time, double outsam) + void process(std::size_t chan, double time, double sample) { if (time > m_last_time) - m_wo.write_sample(m_samples.data()); + m_wo.write_samples(m_samples.data()); m_last_time = time; - means[chan] += outsam; - maxsam[chan] = std::max(maxsam[chan], outsam); - minsam[chan] = std::min(minsam[chan], outsam); + max_samples[chan] = std::max(max_samples[chan], sample); + min_samples[chan] = std::min(min_samples[chan], sample); m_n[chan]++; - //mean = means / (double) m_n; - mean[chan] += 5.0 / static_cast<double>(m_wo.sample_rate()) * (outsam - mean[chan]); - outsam = (outsam - mean[chan]) * m_amp; - outsam = std::max(-32000.0, outsam); - outsam = std::min(32000.0, outsam); - m_samples[chan] = static_cast<int>(outsam); + auto val(sample * m_amp); + if (m_auto && plib::abs(val) > 1.0) + { + do + { + m_amp /= 2.0; + val = sample * m_amp; + } while (plib::abs(val) > 1.0); + // FIXME: log this in state and provide on verbose output + //printf("dynamic amplification adjusted to %f at %f\n", m_amp, time); + } + m_samples[chan] = val; } - std::vector<double> mean; - std::vector<double> means; - std::vector<double> maxsam; - std::vector<double> minsam; + std::vector<double> max_samples; + std::vector<double> min_samples; std::vector<std::size_t> m_n; - std::vector<int> m_samples; + std::vector<double> m_samples; double m_last_time; private: std::ostream &m_fo; double m_amp; + bool m_auto; wav_t m_wo; }; -class vcdwriter +class vcd_writer { public: @@ -296,7 +436,7 @@ public: ANALOG }; - vcdwriter(std::ostream &fo, const std::vector<pstring> &channels, + vcd_writer(std::ostream &fo, const std::vector<pstring> &channels, format_e format, double high_level = 2.0, double low_level = 1.0) : m_channels(channels.size()) , m_last_time(0) @@ -318,9 +458,9 @@ public: { // $var real 64 N1X1 N1X1 $end if (format == ANALOG) - write(pstring("$var real 64 ") + m_ids[i++] + " " + ch + " $end\n"); + write("$var real 64 " + m_ids[i++] + " " + ch + " $end\n"); else if (format == DIGITAL) - write(pstring("$var wire 1 ") + m_ids[i++] + " " + ch + " $end\n"); + write("$var wire 1 " + m_ids[i++] + " " + ch + " $end\n"); } write("$enddefinitions $end\n"); if (format == ANALOG) @@ -328,36 +468,37 @@ public: write("$dumpvars\n"); //r0.0 N1X1 for (i = 0; i < channels.size(); i++) - write(pstring("r0.0 ") + m_ids[i] + "\n"); + write("r0.0 " + m_ids[i] + "\n"); write("$end\n"); } } - void process(std::size_t chan, double time, double outsam) + void process(std::size_t chan, double time, double sample) { if (time > m_last_time) { - write(pstring("#") + plib::to_string(static_cast<std::int64_t>(m_last_time * 1e9)) + " "); + write("#" + plib::to_string(static_cast<std::int64_t>(m_last_time * 1e9)) + " "); write(m_buf + "\n"); m_buf = ""; m_last_time = time; } if (m_format == ANALOG) - m_buf += "r" + plib::to_string(outsam)+ " " + m_ids[chan] + " "; + m_buf += "r" + plib::to_string(sample)+ " " + m_ids[chan] + " "; else { - if (outsam >= m_high_level) - m_buf += pstring("1") + m_ids[chan] + " "; - else if (outsam <= m_low_level) - m_buf += pstring("0") + m_ids[chan] + " "; + if (sample >= m_high_level) + m_buf += "1" + m_ids[chan] + " "; + else if (sample <= m_low_level) + m_buf += "0" + m_ids[chan] + " "; } } private: void write(const pstring &line) { - m_fo.write(line.c_str(), static_cast<std::streamsize>(plib::strlen(line.c_str()))); + const putf8string u8line(line); + m_fo.write(u8line.c_str(), static_cast<std::streamsize>(plib::strlen(u8line.c_str()))); } std::size_t m_channels; @@ -371,23 +512,99 @@ private: format_e m_format; }; +class tab_writer +{ +public: + + enum format_e + { + DIGITAL, + ANALOG + }; + + tab_writer(std::ostream &fo, const std::vector<pstring> &channels, + double start, double inc, std::size_t samples) + : m_last_time(0) + , m_next_time(start) + , m_fo(fo) + , m_inc(inc) + , m_samples(samples) + , m_buf(channels.size()) + , m_n(0) + { + } + + void process(std::size_t chan, double time, double sample) + { + if (time > m_last_time) + { + if (m_n < m_samples) + { + while (m_next_time < time && m_n < m_samples) + { + pstring o; + for (auto &e : m_buf) + { + o += pstring(",") + plib::to_string(e); // FIXME: locale!! + } + write(o.substr(1) + "\n"); + m_n++; + m_next_time += m_inc; + } + } + m_last_time = time; + } + m_buf[chan] = sample; + } + +private: + void write(const pstring &line) + { + const putf8string u8line(line); + m_fo.write(u8line.c_str(), static_cast<std::streamsize>(plib::strlen(u8line.c_str()))); + } + + double m_last_time; + double m_next_time; + + std::ostream &m_fo; + std::vector<pstring> m_ids; + double m_inc; + std::size_t m_samples; + std::vector<double> m_buf; + std::size_t m_n; +}; + class nlwav_app : public plib::app { public: nlwav_app() : plib::app(), - opt_fmt(*this, "f", "format", 0, std::vector<pstring>({"wav","vcda","vcdd"}), - "output format. Available options are wav|vcda|vcdd." - " wav : multichannel wav output" - " vcda : analog VCD output" - " vcdd : digital VCD output" + opt_fmt(*this, "f", "format", 0, std::vector<pstring>({"wav16s","wav32s","wav32f","vcda","vcdd", "tab"}), + "output format. Available options are wav16s|wav32s|wav32f|vcda|vcdd|tab.\n" + " wav16s : multichannel wav output 16 bit signed\n" + " wav32s : multichannel wav output 32 bit signed\n" + " wav32f : multichannel wav output 32 bit float\n" + " vcda : analog VCD output\n" + " vcdd : digital VCD output\n" + " tab : sampled output\n" " Digital signals are created using the --high and --low options" ), opt_out(*this, "o", "output", "-", "output file"), + opt_grp1(*this, "wav options", "These options apply to wav output only"), opt_rate(*this, "r", "rate", 48000, "sample rate of output file"), - opt_amp(*this, "a", "amp", 10000.0, "amplification after mean correction (wav only)"), - opt_high(*this, "u", "high", 2.0, "minimum input for high level (vcdd only)"), - opt_low(*this, "l", "low", 1.0, "maximum input for low level (vcdd only)"), + opt_amp(*this, "a", "amp", 10000.0, "amplification after mean correction"), + opt_lowpass(*this, "", "lowpass", 20000.0, "lowpass filter frequency.\nDefault {1:.0} Hz."), + opt_highpass(*this, "", "highpass", 20.0, "highpass filter frequency.\nDefault is {1:.0} Hz."), + opt_hp_boost(*this, "", "hp-boost", "enable highpass boost to filter out initial click."), + opt_grp2(*this, "vcdd options", "These options apply to vcdd output only"), + opt_high(*this, "u", "high", 2.0, "minimum input for high level"), + opt_low(*this, "l", "low", 1.0, "maximum input for low level"), + opt_grp3(*this, "tab options", "These options apply to sampled output only"), + opt_start(*this, "s", "start", 0.0, "time when sampling starts"), + opt_inc(*this, "i", "increment", 0.001, "time between samples"), + opt_samples(*this, "n", "samples", 1000000,"number of samples"), + opt_grp4(*this, "General options", "These options always apply"), opt_verb(*this, "v", "verbose", "be verbose - this produces lots of output"), opt_quiet(*this,"q", "quiet", "be quiet - no warnings"), opt_args(*this, "input file(s)"), @@ -395,24 +612,39 @@ public: opt_help(*this, "h", "help", "display help and exit"), opt_ex1(*this, "./nlwav -f vcdd -o x.vcd log_V*", "convert all files starting with \"log_V\" into a digital vcd file"), - opt_ex2(*this, "./nlwav -f wav -o x.wav log_V*", - "convert all files starting with \"log_V\" into a multichannel wav file"), - m_outstrm(nullptr) + opt_ex2(*this, "./nlwav -f wav16s -o x.wav log_V*", + "convert all files starting with \"log_V\" into a multichannel wav file (16bit, signed)"), + opt_ex3(*this, "./nlwav -f tab -o x.tab -s 0.0000005 -i 0.000001 -n 256 log_BLUE.log", + "convert file log_BLUE.log to sampled output. First sample at 500ns " + "followed by 255 samples every micro-second.") {} int execute() override; pstring usage() override; private: - void convert_wav(); - void convert_vcd(vcdwriter::format_e format); + void convert_wav(std::ostream &output, wav_t::format fmt); + void convert_vcd(std::ostream &output, vcd_writer::format_e format); + void convert_tab(std::ostream &output); + void convert(const pstring &output_file); plib::option_str_limit<unsigned> opt_fmt; plib::option_str opt_out; + plib::option_group opt_grp1; plib::option_num<std::size_t> opt_rate; plib::option_num<double> opt_amp; + plib::option_num<double> opt_lowpass; + plib::option_num<double> opt_highpass; + plib::option_bool opt_hp_boost; + plib::option_group opt_grp2; plib::option_num<double> opt_high; plib::option_num<double> opt_low; + plib::option_group opt_grp3; + plib::option_num<double> opt_start; + plib::option_num<double> opt_inc; + plib::option_num<std::size_t> opt_samples; + + plib::option_group opt_grp4; plib::option_bool opt_verb; plib::option_bool opt_quiet; plib::option_args opt_args; @@ -420,145 +652,205 @@ private: plib::option_bool opt_help; plib::option_example opt_ex1; plib::option_example opt_ex2; - std::vector<plib::unique_ptr<std::istream>> m_instrms; - std::ostream *m_outstrm; + plib::option_example opt_ex3; + std::vector<std::unique_ptr<std::istream>> m_in_streams; }; -void nlwav_app::convert_wav() +void nlwav_app::convert_wav(std::ostream &output, wav_t::format fmt) { - double dt = 1.0 / static_cast<double>(opt_rate()); + double dt = plib::reciprocal(static_cast<double>(opt_rate())); + auto num_channels = m_in_streams.size(); - plib::unique_ptr<wavwriter> wo = plib::make_unique<wavwriter>(*m_outstrm, opt_out() != "-", m_instrms.size(), opt_rate(), opt_amp()); - plib::unique_ptr<aggregator> ago = plib::make_unique<aggregator>(m_instrms.size(), dt, aggregator::callback_type(&wavwriter::process, wo.get())); - aggregator::callback_type agcb = log_processor::callback_type(&aggregator::process, ago.get()); + auto wo = plib::make_unique<wav_writer, arena>(output, opt_out() != "-", fmt, num_channels, opt_rate(), opt_amp()); + auto ago = plib::make_unique<aggregator, arena>(num_channels, dt, aggregator::callback_type(&wav_writer::process, wo.get())); + auto fgo_hp = plib::make_unique<filter_hp, arena>(opt_highpass(), opt_hp_boost(), num_channels, filter_hp::callback_type(&aggregator::process, ago.get())); + auto fgo_lp = plib::make_unique<filter_lp, arena>(opt_lowpass(), num_channels, filter_lp::callback_type(&filter_hp::process, fgo_hp.get())); - log_processor lp(m_instrms.size(), agcb); + auto top_cb = log_processor::callback_type(&filter_lp::process, fgo_lp.get()); - lp.process(m_instrms); + log_processor lp(num_channels, top_cb); + + lp.process(m_in_streams); if (!opt_quiet()) { #if 0 - perr("Mean (low freq filter): {}\n", wo->mean); - perr("Mean (static): {}\n", wo->means / static_cast<double>(wo->m_n)); - perr("Amp + {}\n", 32000.0 / (wo->maxsam - wo->mean)); - perr("Amp - {}\n", -32000.0 / (wo->minsam - wo->mean)); + std_err("Mean (low freq filter): {}\n", wo->mean); + std_err("Mean (static): {}\n", wo->means / static_cast<double>(wo->m_n)); + std_err("Amp + {}\n", 32000.0 / (wo->max_samples - wo->mean)); + std_err("Amp - {}\n", -32000.0 / (wo->min_samples - wo->mean)); #endif } } -void nlwav_app::convert_vcd(vcdwriter::format_e format) +void nlwav_app::convert_vcd(std::ostream &output, vcd_writer::format_e format) { - plib::unique_ptr<vcdwriter> wo = plib::make_unique<vcdwriter>(*m_outstrm, opt_args(), + arena::unique_ptr<vcd_writer> wo = plib::make_unique<vcd_writer, arena>(output, opt_args(), format, opt_high(), opt_low()); - log_processor::callback_type agcb = log_processor::callback_type(&vcdwriter::process, wo.get()); + log_processor::callback_type agcb = log_processor::callback_type(&vcd_writer::process, wo.get()); - log_processor lp(m_instrms.size(), agcb); + log_processor lp(m_in_streams.size(), agcb); - lp.process(m_instrms); + lp.process(m_in_streams); if (!opt_quiet()) { #if 0 - perr("Mean (low freq filter): {}\n", wo->mean); - perr("Mean (static): {}\n", wo->means / static_cast<double>(wo->m_n)); - perr("Amp + {}\n", 32000.0 / (wo->maxsam - wo->mean)); - perr("Amp - {}\n", -32000.0 / (wo->minsam - wo->mean)); + std_err("Mean (low freq filter): {}\n", wo->mean); + std_err("Mean (static): {}\n", wo->means / static_cast<double>(wo->m_n)); + std_err("Amp + {}\n", 32000.0 / (wo->max_samples - wo->mean)); + std_err("Amp - {}\n", -32000.0 / (wo->min_samples - wo->mean)); #endif } } +void nlwav_app::convert_tab(std::ostream &output) +{ + + auto wo = plib::make_unique<tab_writer, arena>(output, opt_args(), + opt_start(), opt_inc(), opt_samples()); + log_processor::callback_type agcb = log_processor::callback_type(&tab_writer::process, wo.get()); + + log_processor lp(m_in_streams.size(), agcb); + + lp.process(m_in_streams); + +} + + pstring nlwav_app::usage() { return help("Convert netlist log files into wav files.\n", "nlwav [OPTION] ... [FILE] ..."); } +template <typename F> +static void open_ostream_and_exec(const pstring &fname, bool binary, F func) +{ + if (fname != "-") + { + // FIXME: binary depends on format! + plib::ofstream output_stream(plib::filesystem::u8path(fname), + binary ? (std::ios::out | std::ios::binary) : std::ios::out); + if (output_stream.fail()) + throw plib::file_open_e(fname); + output_stream.imbue(std::locale::classic()); + func(output_stream); + } + else + { + std::cout.imbue(std::locale::classic()); + // FIXME: switch to binary on windows +#ifdef _WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif + func(std::cout); + } +} + +void nlwav_app::convert(const pstring &output_file) +{ + switch (opt_fmt()) + { + case 0: + open_ostream_and_exec(output_file, true, [this](std::ostream &output) { convert_wav(output, wav_t::s16); }); + break; + case 1: + open_ostream_and_exec(output_file, true, [this](std::ostream &output) { convert_wav(output, wav_t::s32); }); + break; + case 2: + open_ostream_and_exec(output_file, true, [this](std::ostream &output) { convert_wav(output, wav_t::f32); }); + break; + case 3: + open_ostream_and_exec(output_file, false, [this](std::ostream &output) { convert_vcd(output, vcd_writer::ANALOG); }); + break; + case 4: + open_ostream_and_exec(output_file, false, [this](std::ostream &output) { convert_vcd(output, vcd_writer::DIGITAL); }); + break; + case 5: + open_ostream_and_exec(output_file, false, [this](std::ostream &output) { convert_tab(output); }); + break; + default: + // tease compiler - can't happen + break; + } +} int nlwav_app::execute() { - for (auto &i : opt_args()) - pout(pstring("Hello : ") + i + "\n"); if (opt_help()) { - pout(usage()); + std_out(usage()); return 0; } if (opt_version()) { - pout( + std_out( "nlwav (netlist) 0.1\n" - "Copyright (C) 2019 Couriersud\n" - "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>.\n" + "Copyright (C) 2021 Couriersud\n" + "License BSD-3-Clause\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n\n" "Written by Couriersud.\n"); return 0; } - m_outstrm = (opt_out() == "-" ? &std::cout : plib::pnew<std::ofstream>(plib::filesystem::u8path(opt_out()))); - if (m_outstrm->fail()) - throw plib::file_open_e(opt_out()); - m_outstrm->imbue(std::locale::classic()); - - for (auto &oi: opt_args()) + try { - plib::unique_ptr<std::istream> fin; - - if (oi == "-") + for (const auto &oi: opt_args()) { - auto temp(plib::make_unique<std::stringstream>()); - plib::copystream(*temp, std::cin); - fin = std::move(temp); + std::unique_ptr<std::istream> fin; + if (oi == "-") + { + auto temp(std::make_unique<std::stringstream>()); + plib::copy_stream(*temp, std::cin); + fin = std::move(temp); + } + else + { + fin = std::make_unique<plib::ifstream>(plib::filesystem::u8path(oi), std::ios::in); + if (fin->fail()) + throw plib::file_open_e(oi); + } + fin->imbue(std::locale::classic()); + m_in_streams.push_back(std::move(fin)); } - else - fin = plib::make_unique<std::ifstream>(plib::filesystem::u8path(oi)); - fin->imbue(std::locale::classic()); - m_instrms.push_back(std::move(fin)); - } - switch (opt_fmt()) + convert(opt_out()); + } + catch (plib::pexception &e) { - case 0: - convert_wav(); break; - case 1: - convert_vcd(vcdwriter::ANALOG); break; - case 2: - convert_vcd(vcdwriter::DIGITAL); break; - default: - // tease compiler - can't happen - break; + std_err("Exception caught: {}\n", e.text()); + return 1; } - - if (opt_out() != "-") - plib::pdelete(m_outstrm); - return 0; } PMAIN(nlwav_app) -/* -Der Daten-Abschnitt enth??lt die Abtastwerte: -Offset L??nge Inhalt Beschreibung -36 (0x24) 4 'data' Header-Signatur -40 (0x28) 4 <length> L??nge des Datenblocks, max. <Dateigr????e>?????????44 - -0 (0x00) char 4 'RIFF' -4 (0x04) unsigned 4 <Dateigr????e>?????????8 -8 (0x08) char 4 'WAVE' - -Der fmt-Abschnitt (24 Byte) beschreibt das Format der einzelnen Abtastwerte: -Offset L??nge Inhalt Beschreibung -12 (0x0C) 4 'fmt ' Header-Signatur (folgendes Leerzeichen beachten) -16 (0x10) 4 <fmt length> L??nge des restlichen fmt-Headers (16 Bytes) -20 (0x14) 2 <format tag> Datenformat der Abtastwerte (siehe separate Tabelle weiter unten) -22 (0x16) 2 <channels> Anzahl der Kan??le: 1 = mono, 2 = stereo; mittlerweile sind auch mehr als 2 Kan??le (z. B. f??r Raumklang) m??glich.[2] -24 (0x18) 4 <sample rate> Samples pro Sekunde je Kanal (z. B. 44100) -28 (0x1C) 4 <bytes/second> Abtastrate????????Frame-Gr????e -32 (0x20) 2 <block align> Frame-Gr????e = <Anzahl der Kan??le>????????((<Bits/Sample (eines Kanals)>???+???7)???/???8) (Division ohne Rest) -34 (0x22) 2 <bits/sample> Anzahl der Datenbits pro Samplewert je Kanal (z. B. 12) -*/ +// spell-checker:disable +// +// Der Daten-Abschnitt enth??lt die Abtastwerte: +// Offset L??nge Inhalt Beschreibung +// 36 (0x24) 4 'data' Header-Signatur +// 40 (0x28) 4 <length> L??nge des Datenblocks, max. <Dateigr????e>?????????44 +// +// 0 (0x00) char 4 'RIFF' +// 4 (0x04) unsigned 4 <Dateigr????e>?????????8 +// 8 (0x08) char 4 'WAVE' +// +// Der fmt-Abschnitt (24 Byte) beschreibt das Format der einzelnen Abtastwerte: +// Offset L??nge Inhalt Beschreibung +// 12 (0x0C) 4 'fmt ' Header-Signatur (folgendes Leerzeichen beachten) +// 16 (0x10) 4 <fmt length> L??nge des restlichen fmt-Headers (16 Bytes) +// 20 (0x14) 2 <format tag> Datenformat der Abtastwerte (siehe separate Tabelle weiter unten) +// 22 (0x16) 2 <channels> Anzahl der Kan??le: 1 = mono, 2 = stereo; mittlerweile sind auch mehr als 2 Kan??le (z. B. f??r Raumklang) m??glich.[2] +// 24 (0x18) 4 <sample rate> Samples pro Sekunde je Kanal (z. B. 44100) +// 28 (0x1C) 4 <bytes/second> Abtastrate????????Frame-Gr????e +// 32 (0x20) 2 <block align> Frame-Gr????e = <Anzahl der Kan??le>????????((<Bits/Sample (eines Kanals)>???+???7)???/???8) (Division ohne Rest) +// 34 (0x22) 2 <bits/sample> Anzahl der Datenbits pro Samplewert je Kanal (z. B. 12) +// +// spell-checker:enable |