diff --git a/awg-cxx/build.rs b/awg-cxx/build.rs
index 29aff690a6bcd9ccc96d7c971a080ccb33e22446..bb443af11a54b7185c741ebb52d09d46b7c81425 100644
--- a/awg-cxx/build.rs
+++ b/awg-cxx/build.rs
@@ -6,21 +6,21 @@ fn main() {
     println!("cargo:rustc-link-search=/usr/lib64");
     println!("cargo:rustc-link-lib=spcm_linux");
 
-    println!("cargo:rustc-link-search=/opt/pylon/lib");
-    println!("cargo:rustc-link-lib=pylonbase");
-    println!("cargo:rustc-link-lib=pylonutility");
-    println!("cargo:rustc-link-lib=GenApi_gcc_v3_1_Basler_pylon");
-    println!("cargo:rustc-link-lib=GCBase_gcc_v3_1_Basler_pylon");
+    // println!("cargo:rustc-link-search=/opt/pylon/lib");
+    // println!("cargo:rustc-link-lib=pylonbase");
+    // println!("cargo:rustc-link-lib=pylonutility");
+    // println!("cargo:rustc-link-lib=GenApi_gcc_v3_1_Basler_pylon");
+    // println!("cargo:rustc-link-lib=GCBase_gcc_v3_1_Basler_pylon");
 
     cxx_build::bridge("lib/awg_ffi.rs")
         .cpp(true)
         .warnings(false)
         .flag("-std=c++14")
         .include("include")
-        .include("awg-control/Cpp/lib")
+        .include("awg-control/Cpp/lib/devices")
         .include("awg-control/Cpp/lib/devices/driver_header")
-        .include("/opt/pylon/include")
-        .include("/usr/include/opencv4")
+        // .include("/opt/pylon/include")
+        // .include("/usr/include/opencv4")
         .file("awg-control/Cpp/lib/devices/AWG.cpp")
         .file("lib/awg.cc")
         .compile("awg-cxx");
diff --git a/awg-cxx/include/awg.h b/awg-cxx/include/awg.h
index 7df74a27dc52e038b04c7ccedd20b11a4c7133e5..8b033b11d484eeab1694b20ef4a87f9a5ebed879 100644
--- a/awg-cxx/include/awg.h
+++ b/awg-cxx/include/awg.h
@@ -3,9 +3,9 @@
 #include <vector>
 #include "rust/cxx.h"
 #include "awg-cxx/awg-control/Cpp/lib/devices/AWG.h"
-#include "awg-cxx/awg-control/Cpp/lib/devices/basler.h"
-#include "awg-cxx/awg-control/Cpp/lib/waveform.h"
-#include "awg-cxx/awg-control/Cpp/lib/uniformization.h"
+// #include "awg-cxx/awg-control/Cpp/lib/devices/basler.h"
+// #include "awg-cxx/awg-control/Cpp/lib/waveform.h"
+// #include "awg-cxx/awg-control/Cpp/lib/uniformization.h"
 
 // class PageAlignedMem {
 // public:
@@ -39,41 +39,41 @@ private:
     AWG awg;
 };
 
-class BaslerBox {
-public:
-    BaslerBox(int);
-    ~BaslerBox();
+// class BaslerBox {
+// public:
+//     BaslerBox(int);
+//     ~BaslerBox();
 
-    BaslerCam &get_cam();
-private:
-    BaslerCam cam;
-};
+//     BaslerCam &get_cam();
+// private:
+//     BaslerCam cam;
+// };
 
-class UniformizationParams {
-public:
-    UniformizationParams(
-        std::string source,
-        double polarizability,
-        double mean_depth,
-        double step_size,
-        double error_threshold,
-        int max_iters,
-        int num_imaging_avg,
-        int num_tweezer
-    );
-    ~UniformizationParams();
-
-    Uniformization::Params to_raw();
-private:
-    std::string source;
-    double polarizability;
-    double mean_depth;
-    double step_size;
-    double error_threshold;
-    int max_iters;
-    int num_imaging_avg;
-    int num_tweezers;
-};
+// class UniformizationParams {
+// public:
+//     UniformizationParams(
+//         std::string source,
+//         double polarizability,
+//         double mean_depth,
+//         double step_size,
+//         double error_threshold,
+//         int max_iters,
+//         int num_imaging_avg,
+//         int num_tweezer
+//     );
+//     ~UniformizationParams();
+
+//     Uniformization::Params to_raw();
+// private:
+//     std::string source;
+//     double polarizability;
+//     double mean_depth;
+//     double step_size;
+//     double error_threshold;
+//     int max_iters;
+//     int num_imaging_avg;
+//     int num_tweezers;
+// };
 
 std::unique_ptr<AWGBox> new_awg(int idx);
 
@@ -150,17 +150,17 @@ int64_t awg_get_clock_out_freq(AWGBox &awg);
 // void arraywaveform_set_freq_resolution(ArrayWaveform &waveform, ulong resolution);
 // ulong arraywaveform_get_freq_resolution(ArrayWaveform &waveform);
 
-void arraywaveform_set_freq_tones_spaced(ArrayWaveform &waveform, int center, int spacing, int num_tones);
-void arraywaveform_set_freq_tones(ArrayWaveform &waveform, const std::vector<int> &tones);
-rust::Vec<int> arraywaveform_get_freq_tones(ArrayWaveform &waveform);
-void arraywaveform_set_phases(ArrayWaveform &waveform, const std::vector<double> &phases);
-rust::Vec<double> arraywaveform_get_phases(ArrayWaveform &waveform);
-void arraywaveform_set_amplitudes(ArrayWaveform &waveform, const std::vector<double>& amplitudes);
-rust::Vec<double> arraywaveform_get_amplitudes(ArrayWaveform &waveform);
-void arraywaveform_set_default_params(ArrayWaveform &waveform);
-void arraywaveform_save_params(ArrayWaveform &waveform, const std::string &filename);
-void arraywaveform_load_params(ArrayWaveform &waveform, const std::string &filename);
-void arraywaveform_print_params(ArrayWaveform &waveform);
+// void arraywaveform_set_freq_tones_spaced(ArrayWaveform &waveform, int center, int spacing, int num_tones);
+// void arraywaveform_set_freq_tones(ArrayWaveform &waveform, const std::vector<int> &tones);
+// rust::Vec<int> arraywaveform_get_freq_tones(ArrayWaveform &waveform);
+// void arraywaveform_set_phases(ArrayWaveform &waveform, const std::vector<double> &phases);
+// rust::Vec<double> arraywaveform_get_phases(ArrayWaveform &waveform);
+// void arraywaveform_set_amplitudes(ArrayWaveform &waveform, const std::vector<double>& amplitudes);
+// rust::Vec<double> arraywaveform_get_amplitudes(ArrayWaveform &waveform);
+// void arraywaveform_set_default_params(ArrayWaveform &waveform);
+// void arraywaveform_save_params(ArrayWaveform &waveform, const std::string &filename);
+// void arraywaveform_load_params(ArrayWaveform &waveform, const std::string &filename);
+// void arraywaveform_print_params(ArrayWaveform &waveform);
 
 // ulong arraywaveform_get_min_sample_len(ArrayWaveform &waveform, ulong sampling_rate, ulong frequency_resolution);
 // ulong arraywaveform_get_sample_len(ArrayWaveform &waveform, double tau, ulong sampling_rate, ulong frequency_resolution);
@@ -168,38 +168,38 @@ void arraywaveform_print_params(ArrayWaveform &waveform);
 // std::unique_ptr<WaveformData> arraywaveform_get_static_waveform(ArrayWaveform &waveform);
 // std::unique_ptr<WaveformData> arraywaveform_get_trick_waveform(ArrayWaveform &waveform, const std::vector<int> &site_index, double df, double tau_move, double tau_stay);
 
-void arraywaveform_save_waveform(ArrayWaveform &waveform, const std::string &filename);
+// void arraywaveform_save_waveform(ArrayWaveform &waveform, const std::string &filename);
 
-std::unique_ptr<PageAlignedMem> waveformdata_get_mem(WaveformData &data);
-int64_t waveformdata_get_datalen(WaveformData &data);
+// std::unique_ptr<PageAlignedMem> waveformdata_get_mem(WaveformData &data);
+// int64_t waveformdata_get_datalen(WaveformData &data);
 
 /******************************************************************************/
 
-std::unique_ptr<BaslerBox> new_basler(int index);
-
-void basler_print_device_info(BaslerBox &basler);
-void basler_set_exposure(BaslerBox &basler, double exposure_time);
-double basler_get_exposure(BaslerBox &basler);
-void basler_set_frame_rate(BaslerBox &basler, uint frame_rate);
-void basler_set_frame_rate_max(BaslerBox &basler);
-void basler_set_gain(BaslerBox &basler, double gain);
-double basler_get_gain(BaslerBox &basler);
-void basler_set_roi(BaslerBox &basler, uint offset_x, uint offset_y, uint width, uint height);
-uint basler_get_offset_x(BaslerBox &basler);
-uint basler_get_offset_y(BaslerBox &basler);
-uint basler_get_width(BaslerBox &basler);
-uint basler_get_height(BaslerBox &basler);
-void basler_set_acquisition_mode(BaslerBox &basler, const std::string &mode);
-void basler_print_available_acquisition_modes(BaslerBox &basler);
-
-void basler_start_grabbing(BaslerBox &basler);
-void basler_stop_grabbing(BaslerBox &basler);
-bool basler_is_grabbing(BaslerBox &basler);
-rust::Vec<uint8_t> basler_get_image(BaslerBox &basler);
+// std::unique_ptr<BaslerBox> new_basler(int index);
+
+// void basler_print_device_info(BaslerBox &basler);
+// void basler_set_exposure(BaslerBox &basler, double exposure_time);
+// double basler_get_exposure(BaslerBox &basler);
+// void basler_set_frame_rate(BaslerBox &basler, uint frame_rate);
+// void basler_set_frame_rate_max(BaslerBox &basler);
+// void basler_set_gain(BaslerBox &basler, double gain);
+// double basler_get_gain(BaslerBox &basler);
+// void basler_set_roi(BaslerBox &basler, uint offset_x, uint offset_y, uint width, uint height);
+// uint basler_get_offset_x(BaslerBox &basler);
+// uint basler_get_offset_y(BaslerBox &basler);
+// uint basler_get_width(BaslerBox &basler);
+// uint basler_get_height(BaslerBox &basler);
+// void basler_set_acquisition_mode(BaslerBox &basler, const std::string &mode);
+// void basler_print_available_acquisition_modes(BaslerBox &basler);
+
+// void basler_start_grabbing(BaslerBox &basler);
+// void basler_stop_grabbing(BaslerBox &basler);
+// bool basler_is_grabbing(BaslerBox &basler);
+// rust::Vec<uint8_t> basler_get_image(BaslerBox &basler);
 
 /******************************************************************************/
 
-std::unique_ptr<UniformizationParams> new_uniformizationparams(const std::string &source, double polarizability, double mean_depth, double step_size, double error_threshold, int max_iters, int num_imaging_avg, int num_tweezers);
+// std::unique_ptr<UniformizationParams> new_uniformizationparams(const std::string &source, double polarizability, double mean_depth, double step_size, double error_threshold, int max_iters, int num_imaging_avg, int num_tweezers);
 
-void run_uniformization(AWGBox &awg, BaslerBox &basler, ArrayWaveform &waveform, const UniformizationParams &params);
+// void run_uniformization(AWGBox &awg, BaslerBox &basler, ArrayWaveform &waveform, const UniformizationParams &params);
 
diff --git a/awg-cxx/lib/awg.cc b/awg-cxx/lib/awg.cc
index d806aa33f267ed85779e7effe95374320283353b..9088e17078928bc2efbe1b42110e0c769497927e 100644
--- a/awg-cxx/lib/awg.cc
+++ b/awg-cxx/lib/awg.cc
@@ -1,5 +1,5 @@
 #include "awg-cxx/awg-control/Cpp/lib/devices/AWG.h"
-#include "awg-cxx/awg-control/Cpp/lib/devices/basler.h"
+// #include "awg-cxx/awg-control/Cpp/lib/devices/basler.h"
 #include "awg-cxx/include/awg.h"
 #include "rust/cxx.h"
 
@@ -215,6 +215,7 @@ AWGBox::AWGBox(int idx) {
 }
 
 AWGBox::~AWGBox() {
+    this->awg.cardStop();
     this->awg.close();
 }
 
@@ -562,10 +563,10 @@ int64_t awg_get_clock_out_freq(AWGBox &awg) {
 //     waveform.setFreqTone(tones);
 // }
 
-rust::Vec<int> arraywaveform_get_freq_tones(ArrayWaveform &waveform) {
-    std::vector<int> tones = waveform.getFreqTone();
-    return conv_to_int_vec(tones);
-}
+// rust::Vec<int> arraywaveform_get_freq_tones(ArrayWaveform &waveform) {
+//     std::vector<int> tones = waveform.getFreqTone();
+//     return conv_to_int_vec(tones);
+// }
 
 // void arraywaveform_set_phases(
 //     ArrayWaveform &waveform,
@@ -574,10 +575,10 @@ rust::Vec<int> arraywaveform_get_freq_tones(ArrayWaveform &waveform) {
 //     waveform.setPhase(phases);
 // }
 
-rust::Vec<double> arraywaveform_get_phases(ArrayWaveform &waveform) {
-    std::vector<double> phases = waveform.getPhase();
-    return conv_to_f64_vec(phases);
-}
+// rust::Vec<double> arraywaveform_get_phases(ArrayWaveform &waveform) {
+//     std::vector<double> phases = waveform.getPhase();
+//     return conv_to_f64_vec(phases);
+// }
 
 // void arraywaveform_set_amplitudes(
 //     ArrayWaveform &waveform,
@@ -586,10 +587,10 @@ rust::Vec<double> arraywaveform_get_phases(ArrayWaveform &waveform) {
 //     waveform.setAmplitude(amplitudes);
 // }
 
-rust::Vec<double> arraywaveform_get_amplitudes(ArrayWaveform &waveform) {
-    std::vector<double> amplitudes = waveform.getAmplitude();
-    return conv_to_f64_vec(amplitudes);
-}
+// rust::Vec<double> arraywaveform_get_amplitudes(ArrayWaveform &waveform) {
+//     std::vector<double> amplitudes = waveform.getAmplitude();
+//     return conv_to_f64_vec(amplitudes);
+// }
 
 // void arraywaveform_set_default_params(ArrayWaveform &waveform) {
 //     waveform.setDefaultParam();
@@ -630,36 +631,36 @@ rust::Vec<double> arraywaveform_get_amplitudes(ArrayWaveform &waveform) {
 //     return waveform.getSampleLen(tau, sampling_rate, frequency_resolution);
 // }
 
-std::unique_ptr<WaveformData> arraywaveform_get_static_waveform(
-    ArrayWaveform &waveform
-) {
-    std::pair<void *, int64_t> memsize = waveform.getStaticWaveform();
-    std::unique_ptr<WaveformData>
-        data_ptr_unique(new WaveformData(memsize.first, memsize.second));
-    // auto [mem, datalen] = waveform.getStaticWaveform();
-    // std::unique_ptr<WaveformData>
-    //     data_ptr_unique(new WaveformData(mem, datalen));
-    return data_ptr_unique;
-}
-
-std::unique_ptr<WaveformData> arraywaveform_get_trick_waveform(
-    ArrayWaveform &waveform,
-    const std::vector<int> &site_index,
-    double df,
-    double tau_move,
-    double tau_stay
-) {
-    std::set<int> sites = int_vector_to_int_set(site_index);
-    std::pair<void *, int64_t> memsize
-        = waveform.getTrickWaveform(sites, df, tau_move, tau_stay);
-    std::unique_ptr<WaveformData>
-        data_ptr_unique(new WaveformData(memsize.first, memsize.second));
-    // auto [mem, datalen]
-    //     = waveform.getTrickWaveform(sites, df, tau_move, tau_stay);
-    // std::unique_ptr<WaveformData>
-    //     data_ptr_unique(new WaveformData(mem, datalen));
-    return data_ptr_unique;
-}
+// std::unique_ptr<WaveformData> arraywaveform_get_static_waveform(
+//     ArrayWaveform &waveform
+// ) {
+//     std::pair<void *, int64_t> memsize = waveform.getStaticWaveform();
+//     std::unique_ptr<WaveformData>
+//         data_ptr_unique(new WaveformData(memsize.first, memsize.second));
+//     // auto [mem, datalen] = waveform.getStaticWaveform();
+//     // std::unique_ptr<WaveformData>
+//     //     data_ptr_unique(new WaveformData(mem, datalen));
+//     return data_ptr_unique;
+// }
+
+// std::unique_ptr<WaveformData> arraywaveform_get_trick_waveform(
+//     ArrayWaveform &waveform,
+//     const std::vector<int> &site_index,
+//     double df,
+//     double tau_move,
+//     double tau_stay
+// ) {
+//     std::set<int> sites = int_vector_to_int_set(site_index);
+//     std::pair<void *, int64_t> memsize
+//         = waveform.getTrickWaveform(sites, df, tau_move, tau_stay);
+//     std::unique_ptr<WaveformData>
+//         data_ptr_unique(new WaveformData(memsize.first, memsize.second));
+//     // auto [mem, datalen]
+//     //     = waveform.getTrickWaveform(sites, df, tau_move, tau_stay);
+//     // std::unique_ptr<WaveformData>
+//     //     data_ptr_unique(new WaveformData(mem, datalen));
+//     return data_ptr_unique;
+// }
 
 // void arraywaveform_save_waveform(
 //     ArrayWaveform &waveform,
@@ -676,176 +677,176 @@ std::unique_ptr<WaveformData> arraywaveform_get_trick_waveform(
 //     return mem_ptr_unique;
 // }
 
-int64_t waveformdata_get_datalen(WaveformData &data) {
-    return data.get_datalen();
-}
+// int64_t waveformdata_get_datalen(WaveformData &data) {
+//     return data.get_datalen();
+// }
 
 /******************************************************************************/
 
-BaslerBox::BaslerBox(int index) {
-    this->cam.open(index);
-}
+// BaslerBox::BaslerBox(int index) {
+//     this->cam.open(index);
+// }
 
-BaslerBox::~BaslerBox() { }
+// BaslerBox::~BaslerBox() { }
 
-BaslerCam &BaslerBox::get_cam() {
-    return this->cam;
-}
+// BaslerCam &BaslerBox::get_cam() {
+//     return this->cam;
+// }
 
-std::unique_ptr<BaslerBox> new_basler(int index) {
-    std::unique_ptr<BaslerBox>
-        basler_ptr_unique(new BaslerBox(index));
-    return basler_ptr_unique;
-}
+// std::unique_ptr<BaslerBox> new_basler(int index) {
+//     std::unique_ptr<BaslerBox>
+//         basler_ptr_unique(new BaslerBox(index));
+//     return basler_ptr_unique;
+// }
 
-void basler_print_device_info(BaslerBox &basler) {
-    basler.get_cam().printDeviceInfo();
-}
+// void basler_print_device_info(BaslerBox &basler) {
+//     basler.get_cam().printDeviceInfo();
+// }
 
-void basler_set_exposure(BaslerBox &basler, double exposure_time) {
-    basler.get_cam().setExposure(exposure_time);
-}
+// void basler_set_exposure(BaslerBox &basler, double exposure_time) {
+//     basler.get_cam().setExposure(exposure_time);
+// }
 
-double basler_get_exposure(BaslerBox &basler) {
-    return basler.get_cam().getExposure();
-}
+// double basler_get_exposure(BaslerBox &basler) {
+//     return basler.get_cam().getExposure();
+// }
 
-void basler_set_frame_rate(BaslerBox &basler, uint frame_rate) {
-    basler.get_cam().setFrameRate(frame_rate);
-}
+// void basler_set_frame_rate(BaslerBox &basler, uint frame_rate) {
+//     basler.get_cam().setFrameRate(frame_rate);
+// }
 
-void basler_set_frame_rate_max(BaslerBox &basler) {
-    basler.get_cam().setFrameRateMax();
-}
+// void basler_set_frame_rate_max(BaslerBox &basler) {
+//     basler.get_cam().setFrameRateMax();
+// }
 
-void basler_set_gain(BaslerBox &basler, double gain) {
-    basler.get_cam().setGain(gain);
-}
+// void basler_set_gain(BaslerBox &basler, double gain) {
+//     basler.get_cam().setGain(gain);
+// }
 
-double basler_get_gain(BaslerBox &basler) {
-    return basler.get_cam().getGain();
-}
+// double basler_get_gain(BaslerBox &basler) {
+//     return basler.get_cam().getGain();
+// }
 
-void basler_set_roi(BaslerBox &basler, uint offset_x, uint offset_y, uint width, uint height) {
-    basler.get_cam().setROI(offset_x, offset_y, width, height);
-}
+// void basler_set_roi(BaslerBox &basler, uint offset_x, uint offset_y, uint width, uint height) {
+//     basler.get_cam().setROI(offset_x, offset_y, width, height);
+// }
 
-uint basler_get_offset_x(BaslerBox &basler) {
-    return basler.get_cam().getOffsetX();
-}
+// uint basler_get_offset_x(BaslerBox &basler) {
+//     return basler.get_cam().getOffsetX();
+// }
 
-uint basler_get_offset_y(BaslerBox &basler) {
-    return basler.get_cam().getOffsetY();
-}
+// uint basler_get_offset_y(BaslerBox &basler) {
+//     return basler.get_cam().getOffsetY();
+// }
 
-uint basler_get_width(BaslerBox &basler) {
-    return basler.get_cam().getWidth();
-}
+// uint basler_get_width(BaslerBox &basler) {
+//     return basler.get_cam().getWidth();
+// }
 
-uint basler_get_height(BaslerBox &basler) {
-    return basler.get_cam().getHeight();
-}
+// uint basler_get_height(BaslerBox &basler) {
+//     return basler.get_cam().getHeight();
+// }
 
-void basler_set_acquisition_mode(BaslerBox &basler, const std::string &mode) {
-    basler.get_cam().setAcquisitionMode(mode);
-}
+// void basler_set_acquisition_mode(BaslerBox &basler, const std::string &mode) {
+//     basler.get_cam().setAcquisitionMode(mode);
+// }
 
-void basler_print_available_acquisition_modes(BaslerBox &basler) {
-    basler.get_cam().printAvailAcqModes();
-}
+// void basler_print_available_acquisition_modes(BaslerBox &basler) {
+//     basler.get_cam().printAvailAcqModes();
+// }
 
-void basler_start_grabbing(BaslerBox &basler) {
-    basler.get_cam().startGrabbing();
-}
+// void basler_start_grabbing(BaslerBox &basler) {
+//     basler.get_cam().startGrabbing();
+// }
 
-void basler_stop_grabbing(BaslerBox &basler) {
-    basler.get_cam().stopGrabbing();
-}
+// void basler_stop_grabbing(BaslerBox &basler) {
+//     basler.get_cam().stopGrabbing();
+// }
 
-bool basler_is_grabbing(BaslerBox &basler) {
-    return basler.get_cam().isGrabbing();
-}
+// bool basler_is_grabbing(BaslerBox &basler) {
+//     return basler.get_cam().isGrabbing();
+// }
 
-rust::Vec<uint8_t> basler_get_image(BaslerBox &basler) {
-    std::vector<uint8_t> image = basler.get_cam().getImage();
-    return to_u8_vec(image);
-}
+// rust::Vec<uint8_t> basler_get_image(BaslerBox &basler) {
+//     std::vector<uint8_t> image = basler.get_cam().getImage();
+//     return to_u8_vec(image);
+// }
 
-/******************************************************************************/
+// /******************************************************************************/
 
-UniformizationParams::UniformizationParams(
-    std::string source,
-    double polarizability,
-    double mean_depth,
-    double step_size,
-    double error_threshold,
-    int max_iters,
-    int num_imaging_avg,
-    int num_tweezers
-) {
-    this->source = source;
-    this->polarizability = polarizability;
-    this->mean_depth = mean_depth;
-    this->step_size = step_size;
-    this->error_threshold = error_threshold;
-    this->max_iters = max_iters;
-    this->num_imaging_avg = num_imaging_avg;
-    this->num_tweezers = num_tweezers;
-}
-
-UniformizationParams::~UniformizationParams() { }
-
-Uniformization::Params UniformizationParams::to_raw() {
-    Uniformization::Params params;
-    params.probeScanPath = this->source;
-    params.polarizability = this->polarizability;
-    params.meanDepth = this->mean_depth;
-    params.stepSize = this->step_size;
-    params.errorThreshold = this->error_threshold;
-    params.maxLoop = this->max_iters;
-    params.numImgingAvg = this->num_imaging_avg;
-    params.numTweezer = this->num_tweezers;
-    return params;
-}
-
-std::unique_ptr<UniformizationParams> new_uniformizationparams(
-    const std::string &source,
-    double polarizability,
-    double mean_depth,
-    double step_size,
-    double error_threshold,
-    int max_iters,
-    int num_imaging_avg,
-    int num_tweezers
-) {
-    std::unique_ptr<UniformizationParams>
-        params_ptr_unique(
-            new UniformizationParams(
-                source,
-                polarizability,
-                mean_depth,
-                step_size,
-                error_threshold,
-                max_iters,
-                num_imaging_avg,
-                num_tweezers
-            )
-        );
-    return params_ptr_unique;
-}
+// UniformizationParams::UniformizationParams(
+//     std::string source,
+//     double polarizability,
+//     double mean_depth,
+//     double step_size,
+//     double error_threshold,
+//     int max_iters,
+//     int num_imaging_avg,
+//     int num_tweezers
+// ) {
+//     this->source = source;
+//     this->polarizability = polarizability;
+//     this->mean_depth = mean_depth;
+//     this->step_size = step_size;
+//     this->error_threshold = error_threshold;
+//     this->max_iters = max_iters;
+//     this->num_imaging_avg = num_imaging_avg;
+//     this->num_tweezers = num_tweezers;
+// }
 
-void run_uniformization(
-    AWGBox &awg,
-    BaslerBox &basler,
-    ArrayWaveform &waveform,
-    UniformizationParams &params
-) {
-    const Uniformization::Params raw_params = params.to_raw();
-    Uniformization::run(
-        awg.get_awg(),
-        basler.get_cam(),
-        waveform,
-        raw_params
-    );
-}
+// UniformizationParams::~UniformizationParams() { }
+
+// Uniformization::Params UniformizationParams::to_raw() {
+//     Uniformization::Params params;
+//     params.probeScanPath = this->source;
+//     params.polarizability = this->polarizability;
+//     params.meanDepth = this->mean_depth;
+//     params.stepSize = this->step_size;
+//     params.errorThreshold = this->error_threshold;
+//     params.maxLoop = this->max_iters;
+//     params.numImgingAvg = this->num_imaging_avg;
+//     params.numTweezer = this->num_tweezers;
+//     return params;
+// }
+
+// std::unique_ptr<UniformizationParams> new_uniformizationparams(
+//     const std::string &source,
+//     double polarizability,
+//     double mean_depth,
+//     double step_size,
+//     double error_threshold,
+//     int max_iters,
+//     int num_imaging_avg,
+//     int num_tweezers
+// ) {
+//     std::unique_ptr<UniformizationParams>
+//         params_ptr_unique(
+//             new UniformizationParams(
+//                 source,
+//                 polarizability,
+//                 mean_depth,
+//                 step_size,
+//                 error_threshold,
+//                 max_iters,
+//                 num_imaging_avg,
+//                 num_tweezers
+//             )
+//         );
+//     return params_ptr_unique;
+// }
+
+// void run_uniformization(
+//     AWGBox &awg,
+//     BaslerBox &basler,
+//     ArrayWaveform &waveform,
+//     UniformizationParams &params
+// ) {
+//     const Uniformization::Params raw_params = params.to_raw();
+//     Uniformization::run(
+//         awg.get_awg(),
+//         basler.get_cam(),
+//         waveform,
+//         raw_params
+//     );
+// }
 
diff --git a/awg-cxx/lib/awg.rs b/awg-cxx/lib/awg.rs
index a5d1a7296d3fe9ec85db4f9088101d55e5a04721..09c698fe3818dc4fdb79e04d4eedb2ada5486313 100644
--- a/awg-cxx/lib/awg.rs
+++ b/awg-cxx/lib/awg.rs
@@ -7,23 +7,24 @@
 use std::{
     collections::HashSet,
     ffi::{ OsStr, OsString },
-    f64::consts::TAU,
-    fmt,
-    fs,
-    io::{ Read, Write },
-    path::{ Path, PathBuf },
+    // f64::consts::TAU,
+    // fmt,
+    // fs,
+    // io::{ Read, Write },
+    // path::{ Path, PathBuf },
+    path::{ Path },
     pin::Pin,
 };
 use cxx::{ let_cxx_string, CxxVector, UniquePtr, kind::Trivial };
 use thiserror::Error;
 use crate::{
     awg_ffi::ffi,
-    basler::Basler,
+    // basler::Basler,
     config::{
         TriggerConfig,
         ChannelConfig,
-        SequenceStepConfig,
-        SequenceKind,
+        SequenceSegmentsConfig,
+        // SequenceKind,
         Config,
         ConfigError,
     },
@@ -43,9 +44,6 @@ pub enum AWGError {
     // #[error("bad waveform filename '{0:?}': must end in '.csv'")]
     // WaveformCSV(OsString),
 
-    #[error("bad waveform data filename '{0:?}': must end in '.txt'")]
-    WaveformTXT(OsString),
-
     // #[error("uninitialized waveform parameters")]
     // WaveformUninit,
 
@@ -82,38 +80,38 @@ pub enum AWGError {
     // #[error("invalid amplitude setting: number of amplitudes must match frequency tone and phase settings")]
     // NumAmplitudes,
 
-    #[error("invalid amplitude {0}: must be [0, 2^15 - 1]")]
-    InvalidAmplitude(f64),
+    // #[error("invalid waveform amplitude {0}: must be 0..2^15-1")]
+    // InvalidAmplitude(f64),
 
-    #[error("uniformization error: {0}")]
-    UniformizationError(String),
+    // #[error("uniformization error: {0}")]
+    // UniformizationError(String),
 
-    #[error("uninitialized uniformization parameters")]
-    UniformizationUninit,
+    // #[error("uninitialized uniformization parameters")]
+    // UniformizationUninit,
 
-    #[error("invalid uniformization waveform source '{0}': file does not exist")]
-    InvalidUniformizationSource(String),
+    // #[error("invalid uniformization waveform source '{0}': file does not exist")]
+    // InvalidUniformizationSource(String),
 
-    #[error("invalid polarizability {0}: setting only makes sense for negative values")]
-    InvalidPolarizability(f64),
+    // #[error("invalid polarizability {0}: setting only makes sense for negative values")]
+    // InvalidPolarizability(f64),
 
-    #[error("invalid mean depth {0}: setting only makes sense for positive values")]
-    InvalidMeanDepth(f64),
+    // #[error("invalid mean depth {0}: setting only makes sense for positive values")]
+    // InvalidMeanDepth(f64),
 
-    #[error("invalid step size {0}: setting only makes sense for positive values")]
-    InvalidStepSize(f64),
+    // #[error("invalid step size {0}: setting only makes sense for positive values")]
+    // InvalidStepSize(f64),
 
-    #[error("invalid error threshold {0}: setting only makes sense for positive values")]
-    InvalidErrorThreshold(f64),
+    // #[error("invalid error threshold {0}: setting only makes sense for positive values")]
+    // InvalidErrorThreshold(f64),
 
-    #[error("invalid max iters {0}: must be at least 1")]
-    InvalidMaxIters(usize),
+    // #[error("invalid max iters {0}: must be at least 1")]
+    // InvalidMaxIters(usize),
 
-    #[error("invalid imaging average {0}: must be at least 1")]
-    InvalidNumImagingAvg(usize),
+    // #[error("invalid imaging average {0}: must be at least 1")]
+    // InvalidNumImagingAvg(usize),
 
-    #[error("invalid tweezer array size {0}: must be at least 1")]
-    InvalidNumTweezers(usize),
+    // #[error("invalid tweezer array size {0}: must be at least 1")]
+    // InvalidNumTweezers(usize),
 
     #[error("config error: {0}")]
     ConfigError(#[from] ConfigError),
@@ -125,13 +123,13 @@ impl AWGError {
         Self::DeviceError(except.what().to_string())
     }
 
-    fn as_waveform_err(except: cxx::Exception) -> Self {
-        Self::WaveformError(except.what().to_string())
-    }
+    // fn as_waveform_err(except: cxx::Exception) -> Self {
+    //     Self::WaveformError(except.what().to_string())
+    // }
 
-    fn as_uniformization_err(except: cxx::Exception) -> Self {
-        Self::UniformizationError(except.what().to_string())
-    }
+    // fn as_uniformization_err(except: cxx::Exception) -> Self {
+    //     Self::UniformizationError(except.what().to_string())
+    // }
 }
 
 fn collect_cxx_vector<'a, I, T>(vals: I) -> UniquePtr<CxxVector<T>>
@@ -157,58 +155,58 @@ macro_rules! impl_debug_boring {
 
 /// Represents a thin wrapper around a (thin wrapper around a) raw C++ `void*`
 /// pointing to a page-aligned slice of memory.
-pub struct PageAlignedMem<'a> {
-    ptr: UniquePtr<ffi::PageAlignedMem>,
-    wf_ref: &'a ArrayWaveform,
-}
+// pub struct PageAlignedMem<'a> {
+//     ptr: UniquePtr<ffi::PageAlignedMem>,
+//     wf_ref: &'a ArrayWaveform,
+// }
 
-impl<'a> std::fmt::Debug for PageAlignedMem<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "PageAlignedMem")
-    }
-}
+// impl<'a> std::fmt::Debug for PageAlignedMem<'a> {
+//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+//         write!(f, "PageAlignedMem")
+//     }
+// }
 
-impl<'a> PageAlignedMem<'a> {
-    fn pinned(&mut self) -> Pin<&mut ffi::PageAlignedMem> { self.ptr.pin_mut() }
-}
+// impl<'a> PageAlignedMem<'a> {
+//     fn pinned(&mut self) -> Pin<&mut ffi::PageAlignedMem> { self.ptr.pin_mut() }
+// }
 
 /// Thin wrapper around a raw C++ `void*` pointing to a waveform array stored at
 /// a page-aligned slice of memory, along with the array's length.
-pub struct WaveformDataPtr<'a> {
-    ptr: UniquePtr<ffi::WaveformData>,
-    wf_ref: &'a ArrayWaveform,
-}
-
-impl<'a> std::fmt::Debug for WaveformDataPtr<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "WaveformDataPtr")
-    }
-}
-
-impl<'a> TryFrom<WaveformDataPtr<'a>> for (PageAlignedMem<'a>, u64) {
-    type Error = AWGError;
-
-    fn try_from(wf_data_ptr: WaveformDataPtr<'a>) -> AWGResult<Self> {
-        wf_data_ptr.into_memsize()
-    }
-}
-
-impl<'a> WaveformDataPtr<'a> {
-    fn pinned(&mut self) -> Pin<&mut ffi::WaveformData> { self.ptr.pin_mut() }
-
-    fn into_memsize(self) -> AWGResult<(PageAlignedMem<'a>, u64)> {
-        unsafe {
-            let Self { mut ptr, wf_ref } = self;
-            ffi::waveformdata_get_mem(ptr.pin_mut())
-                .map(|ptr| PageAlignedMem { ptr, wf_ref })
-                .map_err(AWGError::as_waveform_err)
-                .and_then(|buf| {
-                    ffi::waveformdata_get_datalen(ptr.pin_mut())
-                        .map(|len| (buf, len as u64))
-                        .map_err(AWGError::as_waveform_err)
-                })
-        }
-    }
+// pub struct WaveformDataPtr<'a> {
+//     ptr: UniquePtr<ffi::WaveformData>,
+//     wf_ref: &'a ArrayWaveform,
+// }
+
+// impl<'a> std::fmt::Debug for WaveformDataPtr<'a> {
+//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+//         write!(f, "WaveformDataPtr")
+//     }
+// }
+
+// impl<'a> TryFrom<WaveformDataPtr<'a>> for (PageAlignedMem<'a>, u64) {
+//     type Error = AWGError;
+
+//     fn try_from(wf_data_ptr: WaveformDataPtr<'a>) -> AWGResult<Self> {
+//         wf_data_ptr.into_memsize()
+//     }
+// }
+
+// impl<'a> WaveformDataPtr<'a> {
+//     fn pinned(&mut self) -> Pin<&mut ffi::WaveformData> { self.ptr.pin_mut() }
+
+//     fn into_memsize(self) -> AWGResult<(PageAlignedMem<'a>, u64)> {
+//         unsafe {
+//             let Self { mut ptr, wf_ref } = self;
+//             ffi::waveformdata_get_mem(ptr.pin_mut())
+//                 .map(|ptr| PageAlignedMem { ptr, wf_ref })
+//                 .map_err(AWGError::as_waveform_err)
+//                 .and_then(|buf| {
+//                     ffi::waveformdata_get_datalen(ptr.pin_mut())
+//                         .map(|len| (buf, len as u64))
+//                         .map_err(AWGError::as_waveform_err)
+//                 })
+//         }
+//     }
 
     // pub fn get_mem(&mut self) -> AWGResult<PageAlignedMem> {
     //     unsafe {
@@ -225,7 +223,7 @@ impl<'a> WaveformDataPtr<'a> {
     //             .map_err(|err| AWGError::WaveformError(err.to_string()))
     //     }
     // }
-}
+// }
 
 macro_rules! cpp_enum {
     (
@@ -490,24 +488,6 @@ cpp_enum!(
     }
 );
 
-/// AWG card index.
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum AWGIndex {
-    Zero = 0,
-    One = 1,
-}
-
-impl TryFrom<i32> for AWGIndex {
-    type Error = AWGError;
-
-    fn try_from(i: i32) -> AWGResult<Self> {
-        match i {
-            0 => Ok(Self::Zero),
-            1 => Ok(Self::One),
-            x => Err(AWGError::InvalidCardIndex(x)),
-        }
-    }
-}
 
 /// AWG channel pair for use in [`AWG::set_channel_diff_mode`].
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -573,7 +553,7 @@ impl_debug_boring!(AWG);
 
 impl AWG {
     /// Open a new connection to the AWG by index.
-    pub fn connect(idx: AWGIndex) -> AWGResult<Self> {
+    pub fn connect(idx: usize) -> AWGResult<Self> {
         unsafe {
             ffi::new_awg(idx as i32)
                 .map(|awg| Self { awg })
@@ -596,11 +576,10 @@ impl AWG {
     }
 
     /// Get the AWG card index.
-    pub fn get_card_idx(&mut self) -> AWGResult<AWGIndex> {
+    pub fn get_card_idx(&mut self) -> AWGResult<i32> {
         unsafe {
             ffi::awg_get_card_idx(self.pinned())
                 .map_err(AWGError::as_device_err)
-                .and_then(|idx| idx.try_into())
         }
     }
 
@@ -997,7 +976,7 @@ impl AWG {
     }
 
     /// Initialize sequence replay mode.
-    pub fn init_replay_mode_seq(&mut self, n_segments: usize)
+    pub fn init_replay_mode_seq(&mut self, n_segments: u32)
         -> AWGResult<&mut Self>
     {
         unsafe {
@@ -1032,70 +1011,90 @@ impl AWG {
     }
 
     /// Write buffered data into a memory segment in sequence replay mode.
-    pub fn write_seq_mode_segment<'a, P>(
+    // pub fn write_seq_mode_segment<'a, P>(
+    //     &mut self,
+    //     segment: i32,
+    //     data_buffer: P,
+    // ) -> AWGResult<&mut Self>
+    // where P: TryInto<(PageAlignedMem<'a>, u64), Error = AWGError>
+    // {
+    //     let (mut p_data_buffer, size) = data_buffer.try_into()?;
+    //     unsafe {
+    //         ffi::awg_write_seq_mode_segment(
+    //             self.pinned(),
+    //             segment,
+    //             p_data_buffer.pinned(),
+    //             size as i32,
+    //         )
+    //         .map_err(AWGError::as_device_err)?;
+    //         Ok(self)
+    //     }
+    // }
+
+    pub fn write_seq_mode_segment<P>(
         &mut self,
         segment: i32,
-        data_buffer: P,
+        filename: P,
     ) -> AWGResult<&mut Self>
-    where P: TryInto<(PageAlignedMem<'a>, u64), Error = AWGError>
+    where P: AsRef<Path>
     {
-        let (mut p_data_buffer, size) = data_buffer.try_into()?;
         unsafe {
-            ffi::awg_write_seq_mode_segment(
-                self.pinned(),
-                segment,
-                p_data_buffer.pinned(),
-                size as i32,
-            )
-            .map_err(AWGError::as_device_err)?;
+            let path_osstr: &OsStr = filename.as_ref().as_os_str();
+            let path_str: &str
+                = path_osstr.to_str()
+                .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
+            let_cxx_string!(fname = path_str);
+            ffi::awg_write_seq_mode_segment(self.pinned(), segment, &fname)
+                .map_err(AWGError::as_device_err)?;
             Ok(self)
         }
+
     }
 
     /// Prepare the board for data transfer.
-    pub fn prep_data_transfer<'a, P>(
-        &mut self,
-        data_buffer: P,
-        buf_type: BufferType,
-        dir: TransferDir,
-        notify_size: u32,
-        board_mem_offs: u64,
-    ) -> AWGResult<&mut Self>
-    where P: TryInto<(PageAlignedMem<'a>, u64), Error = AWGError>
-    {
-        let (mut p_data_buffer, buffer_len) = data_buffer.try_into()?;
-        unsafe {
-            ffi::awg_prep_data_transfer(
-                self.pinned(),
-                p_data_buffer.pinned(),
-                buffer_len,
-                buf_type as i32,
-                dir as i32,
-                notify_size,
-                board_mem_offs,
-            )
-            .map_err(AWGError::as_device_err)?;
-            Ok(self)
-        }
-    }
+    // pub fn prep_data_transfer<'a, P>(
+    //     &mut self,
+    //     data_buffer: P,
+    //     buf_type: BufferType,
+    //     dir: TransferDir,
+    //     notify_size: u32,
+    //     board_mem_offs: u64,
+    // ) -> AWGResult<&mut Self>
+    // where P: TryInto<(PageAlignedMem<'a>, u64), Error = AWGError>
+    // {
+    //     let (mut p_data_buffer, buffer_len) = data_buffer.try_into()?;
+    //     unsafe {
+    //         ffi::awg_prep_data_transfer(
+    //             self.pinned(),
+    //             p_data_buffer.pinned(),
+    //             buffer_len,
+    //             buf_type as i32,
+    //             dir as i32,
+    //             notify_size,
+    //             board_mem_offs,
+    //         )
+    //         .map_err(AWGError::as_device_err)?;
+    //         Ok(self)
+    //     }
+    // }
 
-    /// Start a data transfer.
-    pub fn start_data_transfer(&mut self) -> AWGResult<&mut Self> {
-        unsafe {
-            ffi::awg_start_data_transfer(self.pinned())
-                .map_err(AWGError::as_device_err)?;
-            Ok(self)
-        }
-    }
+    // /// Start a data transfer.
+    // pub fn start_data_transfer(&mut self) -> AWGResult<&mut Self> {
+    //     unsafe {
+    //         ffi::awg_start_data_transfer(self.pinned())
+    //             .map_err(AWGError::as_device_err)?;
+    //         Ok(self)
+    //     }
+    // }
 
-    /// Stop the current data transfer.
-    pub fn stop_data_transfer(&mut self) -> AWGResult<&mut Self> {
-        unsafe {
-            ffi::awg_stop_data_transfer(self.pinned())
-                .map_err(AWGError::as_device_err)?;
-            Ok(self)
-        }
-    }
+    // /// Stop the current data transfer.
+    // pub fn stop_data_transfer(&mut self) -> AWGResult<&mut Self> {
+    //     unsafe {
+    //         ffi::awg_stop_data_transfer(self.pinned())
+    //             .map_err(AWGError::as_device_err)?;
+    //         Ok(self)
+    //     }
+    // }
 
     /// Set the clock mode.
     pub fn set_clock_mode(&mut self, mode: ClockMode) -> AWGResult<&mut Self> {
@@ -1146,129 +1145,129 @@ impl AWG {
     pub fn set_trigger_config(&mut self, config: &TriggerConfig)
         -> AWGResult<&mut Self>
     {
-        // validate before actually setting anything
-        config.check()?;
+        // config validation should have already been done at this point
         // channel termination impedance for both channels
         self.set_trig_term(config.termination)?;
         // ext0 settings
         self.set_trig_mode(TriggerChannel::Ext0, config.ext0.mode)?;
         self.set_trig_level(TriggerChannel::Ext0, config.ext0.level)?;
         self.set_trig_rearm_level(TriggerChannel::Ext0, config.ext0.rearm_level)?;
+        self.set_trig_coupling(TriggerChannel::Ext0, config.ext0.coupling)?;
         // ext1 settings
         self.set_trig_mode(TriggerChannel::Ext1, config.ext1.mode)?;
         self.set_trig_level(TriggerChannel::Ext1, config.ext1.level)?;
         self.set_trig_rearm_level(TriggerChannel::Ext1, config.ext1.rearm_level)?;
+        self.set_trig_coupling(TriggerChannel::Ext1, config.ext1.coupling)?;
         // masks
+        // self.set_trig_mask_and(&config.masks_and)?;
         self.set_trig_mask_or(&config.masks_or)?;
-        self.set_trig_mask_and(&config.masks_and)?;
         Ok(self)
     }
 
-    pub fn set_channels_config(&mut self, channels: &[ChannelConfig])
+    pub fn set_channels_config(&mut self, channels: &ChannelConfig)
         -> AWGResult<&mut Self>
     {
-        // validate before actually setting anything
-        Config::check_channels(channels)?;
-        // reset all channels
-        self.set_active_channels([])?;
+        // config validation should have already been done at this point
+        let enabled_channels: Vec<usize>
+            = channels.enabled_channels
+                .iter()
+                .enumerate()
+                .filter_map(|(index, &b)| (b).then_some(index))
+                .collect::<Vec<_>>();
         // set channel states and params
-        channels.iter()
-            .map(|ch| {
-                self.set_channel(
-                    ch.channel as i32,
-                    ch.amplitude as i32,
-                    ch.stop_level,
-                    ch.enabled,
-                )
-                .map(|_| ())
-            })
-            .collect::<AWGResult<Vec<()>>>()?;
+        let enabled_channels_i32: Vec<i32>
+            = enabled_channels.iter().map(|&ch| ch as i32).collect();
+        self.set_active_channels(&enabled_channels_i32)?;
+        for ch in enabled_channels {
+            self.set_channel_amp(ch as i32, channels.amplitudes[ch])?;
+            self.set_channel_stop_level(ch as i32, channels.stop_levels[ch])?;
+            self.set_channel_output(ch as i32, true)?;
+        }
         Ok(self)
     }
 
     pub fn load_sequence_loop_config(
         &mut self,
-        sequence_loop: &[SequenceStepConfig],
-        sampling_rate: Option<u64>,
-        frequency_resolution: Option<u64>,
+        sequence_loop: &[SequenceSegmentsConfig],
     ) -> AWGResult<&mut Self>
     {
-        // validate before actually setting anything
-        if sequence_loop.is_empty() {
-            eprintln!(
-                "AWG::load_sequence_loop_config: \
-                called on an empty sequence loop"
-            );
-            return Ok(self);
-        }
-        Config::check_sequence_steps(sequence_loop)?;
+        // validition should have already been done at this point
 
         // load waveform param data
-        let waveform_steps: Vec<(WaveformParams, &SequenceStepConfig)>
-            = sequence_loop.iter()
-            .map(|step| WaveformParams::load(&step.source).map(|wf| (wf, step)))
-            .collect::<AWGResult<Vec<_>>>()?;
-        let sampling_rate
-            = sampling_rate.unwrap_or_else(|| {
-                waveform_steps.iter()
-                    .map(|(wf, _)| wf.get_sampling_rate().unwrap())
-                    .max()
-                    .unwrap()
-            });
-        let frequency_resolution
-            = frequency_resolution.unwrap_or_else(|| {
-                waveform_steps.iter()
-                    .map(|(wf, _)| wf.get_frequency_resolution().unwrap())
-                    .max()
-                    .unwrap()
-            });
-        let n_segments: usize
-            = 2.0_f64.powi(
-                (waveform_steps.len() as f64).log2().ceil() as i32
-            ) as usize;
-
-        // generate and write waveforms
-        let waveform_steps: Vec<(ArrayWaveform, &SequenceStepConfig)>
-            = waveform_steps.into_iter()
-            .map(|(mut wf_params, step)| {
-                wf_params.set_sampling_rate(sampling_rate);
-                wf_params.set_frequency_resolution(frequency_resolution);
-                wf_params.to_waveform()
-                    .map(|wf| (wf, step))
-            })
-            .collect::<AWGResult<Vec<_>>>()?;
-        self.init_replay_mode_seq(n_segments)?;
-        self.set_sample_rate(sampling_rate as i64)?;
-        let mut wf_data_ptr: WaveformDataPtr;
-        for (k, (mut wf, step_conf)) in waveform_steps.into_iter().enumerate() {
-            wf_data_ptr
-                = match step_conf.kind {
-                    SequenceKind::Static => wf.get_static_waveform(),
-                    SequenceKind::Trick => todo!(),
-                }?;
-            // let (mut mem, size) = wf_data_ptr.into_memsize()?;
-            self.write_seq_mode_segment(k as i32, wf_data_ptr)?;
-            // self.write_seq_mode_segment(k as i32, &mut mem, size as i32)?;
+        // let waveform_steps: Vec<(WaveformParams, &SequenceStepConfig)>
+        //     = sequence_loop.iter()
+        //     .map(|step| WaveformParams::load(&step.source).map(|wf| (wf, step)))
+        //     .collect::<AWGResult<Vec<_>>>()?;
+        // let sampling_rate
+        //     = sampling_rate.unwrap_or_else(|| {
+        //         waveform_steps.iter()
+        //             .map(|(wf, _)| wf.get_sampling_rate().unwrap())
+        //             .max()
+        //             .unwrap()
+        //     });
+        // let frequency_resolution
+        //     = frequency_resolution.unwrap_or_else(|| {
+        //         waveform_steps.iter()
+        //             .map(|(wf, _)| wf.get_frequency_resolution().unwrap())
+        //             .max()
+        //             .unwrap()
+        //     });
+        // let n_segments: usize
+        //     = 2.0_f64.powi(
+        //         (waveform_steps.len() as f64).log2().ceil() as i32
+        //     ) as usize;
+
+        // // generate and write waveforms
+        // let waveform_steps: Vec<(ArrayWaveform, &SequenceStepConfig)>
+        //     = waveform_steps.into_iter()
+        //     .map(|(mut wf_params, step)| {
+        //         wf_params.set_sampling_rate(sampling_rate);
+        //         wf_params.set_frequency_resolution(frequency_resolution);
+        //         wf_params.to_waveform()
+        //             .map(|wf| (wf, step))
+        //     })
+        //     .collect::<AWGResult<Vec<_>>>()?;
+        // self.init_replay_mode_seq(n_segments)?;
+        // self.set_sample_rate(sampling_rate as i64)?;
+        // let mut wf_data_ptr: WaveformDataPtr;
+        // for (k, (mut wf, step_conf)) in waveform_steps.into_iter().enumerate() {
+        //     wf_data_ptr
+        //         = match step_conf.kind {
+        //             SequenceKind::Static => wf.get_static_waveform(),
+        //             SequenceKind::Trick => todo!(),
+        //         }?;
+        //     // let (mut mem, size) = wf_data_ptr.into_memsize()?;
+        //     self.write_seq_mode_segment(k as i32, wf_data_ptr)?;
+        //     // self.write_seq_mode_segment(k as i32, &mut mem, size as i32)?;
+        //     self.set_seq_mode_step(
+        //         k as u64,
+        //         k as u64,
+        //         step_conf.next_step as u64,
+        //         step_conf.n_loop as u64,
+        //         step_conf.condition,
+        //     )?;
+        // }
+        for (seg_index, seg_config) in sequence_loop.iter().enumerate() {
+            self.write_seq_mode_segment(seg_index as i32, &seg_config.source)?;
             self.set_seq_mode_step(
-                k as u64,
-                k as u64,
-                step_conf.next_step as u64,
-                step_conf.n_loop as u64,
-                step_conf.condition,
+                seg_config.step as u64,
+                seg_index as u64,
+                seg_config.next_step as u64,
+                seg_config.n_loop as u64,
+                seg_config.loop_condition,
             )?;
         }
         Ok(self)
     }
 
     pub fn set_config(&mut self, config: &Config) -> AWGResult<&mut Self> {
+        config.check()?;
+        self.reset()?;
         self.set_sample_rate(config.sampling_rate as i64)?;
         self.set_trigger_config(&config.trigger)?;
+        self.init_replay_mode_seq(config.num_segments)?;
+        self.load_sequence_loop_config(&config.sequence_segments)?;
         self.set_channels_config(&config.active_channels)?;
-        self.load_sequence_loop_config(
-            &config.sequence_steps,
-            Some(config.sampling_rate),
-            Some(config.frequency_resolution),
-        )?;
         Ok(self)
     }
 
@@ -1291,1041 +1290,1039 @@ impl AWG {
     // }
 }
 
-/// Safe interface to waveform parameter configuration.
-///
-/// All parameters must be initialized before an [`ArrayWaveform`] can be
-/// generated:
-/// - [`tones`][Self::set_tones]: Frequency tones in hertz
-/// - [`phases`][Self::set_phases]: Initial phases for each tone in radians
-/// - [`amplitudes`][Self::set_amplitudes]: Amplitudes for each tone
-/// - [`sampling_rate`][Self::set_sampling_rate]: Sampling rate in hertz
-/// - [`frequency_resolution`][Self::set_frequency_resolution]: Frequency
-///
-/// resolution in hertz
-#[derive(Clone, Debug)]
-pub struct WaveformParams {
-    tones: Option<Vec<i32>>,
-    phases: Option<Vec<f64>>,
-    amplitudes: Option<Vec<f64>>,
-    sampling_rate: Option<u64>,
-    frequency_resolution: Option<u64>,
-}
-
-impl Default for WaveformParams {
-    fn default() -> Self { Self::new() }
-}
-
-impl WaveformParams {
-    /// Create a new, uninitialized set of waveform parameters.
-    pub fn new() -> Self {
-        Self {
-            tones: None,
-            phases: None,
-            amplitudes: None,
-            sampling_rate: None,
-            frequency_resolution: None,
-        }
-    }
-
-    /// Return `true` if all parameters have been initialized.
-    pub fn is_init(&self) -> bool {
-        self.has_tones()
-            && self.has_phases()
-            && self.has_amplitudes()
-            && self.has_sampling_rate()
-            && self.has_frequency_resolution()
-    }
-
-    /// Write current parameter settings to a file.
-    ///
-    /// The file is formatted as a CSV file:
-    /// ```text
-    /// sampling_rate
-    /// frequency_resolution
-    /// tones[0],tones[1],...
-    /// phases[0],phases[1],...
-    /// amplitudes[0],amplitudes[1],...
-    /// ```
-    ///
-    /// The given file name must have a `.csv` extension.
-    pub fn save<P>(&self, outfile: P) -> AWGResult<&Self>
-    where P: AsRef<Path>
-    {
-        self.is_init().then_some(())
-            .ok_or(AWGError::WaveformUninit)?;
-        let outfile = outfile.as_ref();
-        outfile.extension().and_then(|ext| (ext == "csv").then_some(()))
-            .ok_or(AWGError::WaveformCSV(outfile.as_os_str().to_os_string()))?;
-        let mut out
-            = fs::OpenOptions::new()
-            .write(true)
-            .create(true)
-            .truncate(true)
-            .open(outfile)
-            .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
-        writeln!(&mut out, "{}", self.sampling_rate.unwrap())
-            .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
-        writeln!(&mut out, "{}", self.frequency_resolution.unwrap())
-            .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
-        writeln!(&mut out, "{}",
-            self.tones.as_ref().unwrap().iter()
-                .map(|f| f.to_string())
-                .collect::<Vec<String>>()
-                .join(",")
-        )
-        .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
-        writeln!(&mut out, "{}",
-            self.phases.as_ref().unwrap().iter()
-                .map(|ph| ph.to_string())
-                .collect::<Vec<String>>()
-                .join(",")
-        )
-        .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
-        writeln!(&mut out, "{}",
-            self.amplitudes.as_ref().unwrap().iter()
-                .map(|a| a.to_string())
-                .collect::<Vec<String>>()
-                .join(",")
-        )
-        .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
-        Ok(self)
-    }
-
-    /// Load parameter settings from a file.
-    ///
-    /// The file is expected as a CSV file:
-    /// ```text
-    /// sampling_rate
-    /// frequency_resolution
-    /// tones[0],tones[1],...
-    /// phases[0],phases[1],...
-    /// amplitudes[0],amplitudes[1],...
-    /// ```
-    ///
-    /// The given file name must have a `.csv` extension.
-    pub fn load<P>(infile: P) -> AWGResult<Self>
-    where P: AsRef<Path>
-    {
-        let infile = infile.as_ref();
-        infile.extension().and_then(|ext| (ext == "csv").then_some(()))
-            .ok_or(AWGError::WaveformCSV(infile.as_os_str().to_os_string()))?;
-        let mut buf = String::new();
-        fs::OpenOptions::new()
-            .read(true)
-            .open(infile)
-            .map_err(|err| AWGError::WaveformParseError(err.to_string()))?
-            .read_to_string(&mut buf)
-            .map_err(|err| AWGError::WaveformParseError(err.to_string()))?;
-        let mut lines = buf.split('\n');
-        let sampling_rate: u64
-            = lines.next()
-            .ok_or(AWGError::WaveformParseError(
-                "missing sampling rate".to_string()
-            ))?
-            .parse::<u64>()
-            .map_err(|err| AWGError::WaveformParseError(err.to_string()))?;
-        let frequency_resolution: u64
-            = lines.next()
-            .ok_or(AWGError::WaveformParseError(
-                "missing frequency resolution".to_string()
-            ))?
-            .parse::<u64>()
-            .map_err(|err| AWGError::WaveformParseError(err.to_string()))?;
-        let tones: Vec<i32>
-            = lines.next()
-            .ok_or(AWGError::WaveformParseError(
-                "missing frequency tones".to_string()
-            ))?
-            .split(',')
-            .map(|f| {
-                f.parse::<i32>()
-                .map_err(|err| AWGError::WaveformParseError(err.to_string()))
-            })
-            .collect::<AWGResult<Vec<i32>>>()?;
-        let phases: Vec<f64>
-            = lines.next()
-            .ok_or(AWGError::WaveformParseError(
-                "missing phases".to_string()
-            ))?
-            .split(',')
-            .map(|ph| {
-                ph.parse::<f64>()
-                .map_err(|err| AWGError::WaveformParseError(err.to_string()))
-            })
-            .collect::<AWGResult<Vec<f64>>>()?;
-        let amplitudes: Vec<f64>
-            = lines.next()
-            .ok_or(AWGError::WaveformParseError(
-                "missing amplitudes".to_string()
-            ))?
-            .split(',')
-            .map(|a| {
-                a.parse::<f64>()
-                .map_err(|err| AWGError::WaveformParseError(err.to_string()))
-            })
-            .collect::<AWGResult<Vec<f64>>>()?;
-        let data = Self {
-            tones: Some(tones),
-            phases: Some(phases),
-            amplitudes: Some(amplitudes),
-            sampling_rate: Some(sampling_rate),
-            frequency_resolution: Some(frequency_resolution),
-        };
-        Ok(data)
-    }
-
-    /// Return `true` if frequency tones have been initialized.
-    pub fn has_tones(&self) -> bool { self.tones.is_some() }
-
-    /// Get the frequency tones in hertz, if initialized.
-    pub fn get_tones(&self) -> Option<&Vec<i32>> {
-        self.tones.as_ref()
-    }
-
-    /// Set the frequency tones in hertz.
-    pub fn set_tones<'a, I>(&mut self, tones: I) -> AWGResult<&mut Self>
-    where I: IntoIterator<Item = &'a i32>
-    {
-        let tones: Vec<i32> = tones.into_iter().copied().collect();
-        if let Some(phases) = &self.phases {
-            (tones.len() == phases.len()).then_some(())
-                .ok_or(AWGError::NumTones)?;
-        }
-        if let Some(amplitudes) = &self.amplitudes {
-            (tones.len() == amplitudes.len()).then_some(())
-                .ok_or(AWGError::NumTones)?;
-        }
-        self.tones = Some(tones);
-        Ok(self)
-    }
-
-    /// Set the frequency tones using a center frequency and a uniform spacing,
-    /// both in hertz.
-    pub fn set_tones_spaced(&mut self, center: i32, spacing: i32, num: usize)
-        -> AWGResult<&mut Self>
-    {
-        if let Some(phases) = &self.phases {
-            (num == phases.len()).then_some(())
-                .ok_or(AWGError::NumTones)?;
-        }
-        if let Some(amplitudes) = &self.amplitudes {
-            (num == amplitudes.len()).then_some(())
-                .ok_or(AWGError::NumTones)?;
-        }
-        let f0: i32 = center - spacing * num as i32 / 2;
-        let tones: Vec<i32>
-            = (0..num as i32).map(|k| f0 + spacing * k).collect();
-        self.tones = Some(tones);
-        Ok(self)
-    }
-
-    /// Return `true` if initial phases have been initialized.
-    pub fn has_phases(&self) -> bool { self.phases.is_some() }
-
-    /// Get the initial phases of each tone in radians.
-    pub fn get_phases(&self) -> Option<&Vec<f64>> {
-        self.phases.as_ref()
-    }
-
-    /// Set the initial phases of each tone in radians.
-    pub fn set_phases<'a, I>(&mut self, phases: I) -> AWGResult<&mut Self>
-    where I: IntoIterator<Item = &'a f64>
-    {
-        let phases: Vec<f64>
-            = phases.into_iter().copied().map(|ph| ph % TAU).collect();
-        if let Some(tones) = &self.tones {
-            (phases.len() == tones.len()).then_some(())
-                .ok_or(AWGError::NumPhases)?;
-        }
-        if let Some(amplitudes) = &self.amplitudes {
-            (phases.len() == amplitudes.len()).then_some(())
-                .ok_or(AWGError::NumPhases)?;
-        }
-        self.phases = Some(phases);
-        Ok(self)
-    }
-
-    /// Return `true` if amplitudes have been initialized.
-    pub fn has_amplitudes(&self) -> bool { self.amplitudes.is_some() }
-
-    /// Get the amplitudes of each tone.
-    pub fn get_amplitudes(&self) -> Option<&Vec<f64>> {
-        self.amplitudes.as_ref()
-    }
-
-    /// Set the amplitudes of each tone.
-    ///
-    /// Each amplitude must lie in the range `[0, 2^15 - 1)`.
-    pub fn set_amplitudes<'a, I>(&mut self, amplitudes: I)
-        -> AWGResult<&mut Self>
-    where I: IntoIterator<Item = &'a f64>
-    {
-        let amplitudes: Vec<f64>
-            = amplitudes.into_iter().copied()
-            .map(|a| {
-                (0.0..32767.0).contains(&a)
-                    .then_some(a)
-                    .ok_or(AWGError::InvalidAmplitude(a))
-            })
-            .collect::<AWGResult<Vec<f64>>>()?;
-        if let Some(tones) = &self.tones {
-            (amplitudes.len() == tones.len()).then_some(())
-                .ok_or(AWGError::NumAmplitudes)?;
-        }
-        if let Some(phases) = &self.phases {
-            (amplitudes.len() == phases.len()).then_some(())
-                .ok_or(AWGError::NumAmplitudes)?;
-        }
-        self.amplitudes = Some(amplitudes);
-        Ok(self)
-    }
-
-    /// Return `true` if the sampling rate has been initialized.
-    pub fn has_sampling_rate(&self) -> bool {
-        self.sampling_rate.is_some()
-    }
-
-    /// Get the sampling rate in hertz.
-    pub fn get_sampling_rate(&self) -> Option<u64> {
-        self.sampling_rate
-    }
-
-    /// Set the sampling rate in hertz.
-    pub fn set_sampling_rate(&mut self, sampling_rate: u64) -> &mut Self {
-        self.sampling_rate = Some(sampling_rate);
-        self
-    }
-
-    /// Return `true` if the frequency resolution has been initialized.
-    pub fn has_frequency_resolution(&self) -> bool {
-        self.frequency_resolution.is_some()
-    }
-
-    /// Get the frequency resolution in hertz.
-    pub fn get_frequency_resolution(&self) -> Option<u64> {
-        self.frequency_resolution
-    }
-
-    /// Set the frequency resolution in hertz.
-    pub fn set_frequency_resolution(&mut self, resolution: u64) -> &mut Self {
-        self.frequency_resolution = Some(resolution);
-        self
-    }
-
-    /// Generate an [`ArrayWaveform`] from the current set of parameters if all
-    /// have been initialized.
-    pub fn to_waveform(&self) -> AWGResult<ArrayWaveform> {
-        self.is_init().then_some(())
-            .ok_or(AWGError::WaveformUninit)?;
-        let mut waveform = ArrayWaveform::new_uninit();
-        waveform
-            .set_freq_tones(self.tones.as_ref().unwrap())?
-            .set_phases(self.phases.as_ref().unwrap())?
-            .set_amplitudes(self.amplitudes.as_ref().unwrap())?
-            .set_sampling_rate(self.sampling_rate.unwrap())?
-            .set_freq_resolution(self.frequency_resolution.unwrap())?;
-        Ok(waveform)
-    }
-}
-
-impl TryFrom<&WaveformParams> for ArrayWaveform {
-    type Error = AWGError;
-
-    fn try_from(params: &WaveformParams) -> AWGResult<Self> {
-        params.to_waveform()
-    }
-}
-
-impl TryFrom<WaveformParams> for ArrayWaveform {
-    type Error = AWGError;
-
-    fn try_from(params: WaveformParams) -> AWGResult<Self> {
-        params.to_waveform()
-    }
-}
-
-impl fmt::Display for WaveformParams {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        writeln!(f, "WaveformParams {{")?;
-
-        write!(f, "  tones: [")?;
-        if let Some(tones) = &self.tones {
-            let n = tones.len();
-            for (k, tone) in tones.iter().enumerate() {
-                tone.fmt(f)?;
-                if k < n - 1 { write!(f, ", ")?; }
-            }
-        } else {
-            write!(f, "<uninit>")?;
-        }
-        writeln!(f, "],")?;
-
-        write!(f, "  phases: [")?;
-        if let Some(phases) = &self.phases {
-            let n = phases.len();
-            for (k, phase) in phases.iter().enumerate() {
-                phase.fmt(f)?;
-                if k < n - 1 { write!(f, ", ")?; }
-            }
-        } else {
-            write!(f, "<uninit>")?;
-        }
-        writeln!(f, "],")?;
-
-        write!(f, "  amplitudes: [")?;
-        if let Some(amplitudes) = &self.amplitudes {
-            let n = amplitudes.len();
-            for (k, amplitude) in amplitudes.iter().enumerate() {
-                amplitude.fmt(f)?;
-                if k < n - 1 { write!(f, ", ")?; }
-            }
-        } else {
-            write!(f, "<uninit>")?;
-        }
-        writeln!(f, "],")?;
-
-        write!(f, "  sampling_rate: ")?;
-        if let Some(sampling_rate) = self.sampling_rate {
-            sampling_rate.fmt(f)?;
-        } else {
-            write!(f, "<uninit>")?;
-        }
-        writeln!(f, ",")?;
-
-        write!(f, "  frequency_resolution: ")?;
-        if let Some(frequency_resolution) = self.frequency_resolution {
-            frequency_resolution.fmt(f)?;
-        } else {
-            write!(f, "<uninit>")?;
-        }
-        writeln!(f, ",")?;
-
-        write!(f, "}}")?;
-        Ok(())
-    }
-}
-
-/// Data type for array waveform generation.
-pub struct ArrayWaveform {
-    data: UniquePtr<ffi::ArrayWaveform>,
-}
-
-impl_debug_boring!(ArrayWaveform);
-
-impl Default for ArrayWaveform {
-    fn default() -> Self { Self::new_uninit() }
-}
-
-impl ArrayWaveform {
-    fn pinned(&mut self) -> Pin<&mut ffi::ArrayWaveform> { self.data.pin_mut() }
-
-    /// Create a new, uninitialized waveform.
-    ///
-    /// # Safety
-    /// After this constructor is called, all private member variables are set
-    /// to 0 or `nullptr` values. It is the programmer's responsibility to
-    /// ensure that all properties and memory addresses are properly
-    /// initialized.
-    fn new_uninit() -> Self {
-        unsafe {
-            Self { data: ffi::new_arraywaveform().unwrap() }
-        }
-    }
-
-    /// Get a pointer to the associated data buffer.
-    pub fn get_data_buffer(&mut self) -> AWGResult<PageAlignedMem> {
-        unsafe {
-            ffi::arraywaveform_get_data_buffer(self.pinned())
-                .map(|ptr| PageAlignedMem { ptr, wf_ref: self })
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Get the number of samples in the current waveform.
-    pub fn get_data_len(&mut self) -> AWGResult<u64> {
-        unsafe {
-            ffi::arraywaveform_get_data_len(self.pinned())
-                .map(|len| len as u64)
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Set the sampling rate in hertz.
-    fn set_sampling_rate(&mut self, rate: u64) -> AWGResult<&mut Self> {
-        unsafe {
-            ffi::arraywaveform_set_sampling_rate(self.pinned(), rate)
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Get the sampling rate in hertz.
-    pub fn get_sampling_rate(&mut self) -> AWGResult<u64> {
-        unsafe {
-            ffi::arraywaveform_get_sampling_rate(self.pinned())
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Set the frequency resolution in hertz.
-    fn set_freq_resolution(&mut self, resolution: u64)
-        -> AWGResult<&mut Self>
-    {
-        unsafe {
-            ffi::arraywaveform_set_freq_resolution(self.pinned(), resolution)
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Get the frequency resolution in hertz.
-    pub fn get_freq_resolution(&mut self) -> AWGResult<u64> {
-        unsafe {
-            ffi::arraywaveform_get_freq_resolution(self.pinned())
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Set the constituent tones using a fixed center frequency and uniform
-    /// spacing in hertz.
-    fn set_freq_tones_spaced(
-        &mut self,
-        center: i32,
-        spacing: i32,
-        num_tones: u16,
-    ) -> AWGResult<&mut Self>
-    {
-        unsafe {
-            ffi::arraywaveform_set_freq_tones_spaced(
-                self.pinned(), center, spacing, num_tones.into())
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Set the constituent tones to arbitrary values in hertz.
-    fn set_freq_tones<'a, I>(&mut self, tones: I) -> AWGResult<&mut Self>
-    where I: IntoIterator<Item = &'a i32>
-    {
-        unsafe {
-            let tones_cxx: UniquePtr<CxxVector<i32>>
-                = collect_cxx_vector(tones);
-            ffi::arraywaveform_set_freq_tones(
-                self.pinned(), tones_cxx.as_ref().unwrap())
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Get a list of contituent tones.
-    pub fn get_freq_tones(&mut self) -> AWGResult<Vec<i32>> {
-        unsafe {
-            ffi::arraywaveform_get_freq_tones(self.pinned())
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Set the initial phases in radians of all tones.
-    fn set_phases<'a, I>(&mut self, phases: I) -> AWGResult<&mut Self>
-    where I: IntoIterator<Item = &'a f64>
-    {
-        unsafe {
-            let phases_cxx: UniquePtr<CxxVector<f64>>
-                = collect_cxx_vector(phases);
-            ffi::arraywaveform_set_phases(
-                self.pinned(), phases_cxx.as_ref().unwrap())
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Get a list of initial phases in radians for each tone.
-    pub fn get_phases(&mut self) -> AWGResult<Vec<f64>> {
-        unsafe {
-            ffi::arraywaveform_get_phases(self.pinned())
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Set the amplitudes of all tones.
-    ///
-    /// Each amplitude must be in the range `[0, 2^15 - 1)`
-    fn set_amplitudes<'a, I>(&mut self, amplitudes: I)
-        -> AWGResult<&mut Self>
-    where I: IntoIterator<Item = &'a f64>
-    {
-        unsafe {
-            let amplitudes: Vec<f64>
-                = amplitudes.into_iter().copied()
-                .map(|a| {
-                    (0.0..32767.0).contains(&a)
-                        .then_some(a)
-                        .ok_or(AWGError::InvalidAmplitude(a))
-                })
-                .collect::<AWGResult<Vec<f64>>>()?;
-            let amplitudes_cxx: UniquePtr<CxxVector<f64>>
-                = collect_cxx_vector(&amplitudes);
-            ffi::arraywaveform_set_amplitudes(
-                self.pinned(), amplitudes_cxx.as_ref().unwrap())
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Get a list of amplitudes for each tone.
-    pub fn get_amplitudes(&mut self) -> AWGResult<Vec<f64>> {
-        unsafe {
-            ffi::arraywaveform_get_amplitudes(self.pinned())
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Set phases to 0 and amplitudes to 2000.
-    fn set_default_params(&mut self) -> AWGResult<&mut Self> {
-        unsafe {
-            ffi::arraywaveform_set_default_params(self.pinned())
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Save the current waveform parameters to a file in CSV format.
-    fn save_params<P>(&mut self, filename: P) -> AWGResult<&mut Self>
-    where P: AsRef<Path>
-    {
-        unsafe {
-            let path_osstr: &OsStr = filename.as_ref().as_os_str();
-            let path_str: &str
-                = path_osstr.to_str()
-                .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
-            path_str.ends_with(".csv").then_some(())
-                .ok_or(AWGError::WaveformCSV(path_osstr.to_os_string()))?;
-            let_cxx_string!(fname = path_str);
-            ffi::arraywaveform_save_params(self.pinned(), &fname)
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Load waveform parameters from a file.
-    ///
-    /// The file must be in CSV format.
-    fn load_params<P>(&mut self, filename: P) -> AWGResult<&mut Self>
-    where P: AsRef<Path>
-    {
-        unsafe {
-            let path_osstr: &OsStr = filename.as_ref().as_os_str();
-            let path_str: &str
-                = path_osstr.to_str()
-                .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
-            path_str.ends_with(".csv").then_some(())
-                .ok_or(AWGError::WaveformCSV(path_osstr.to_os_string()))?;
-            let_cxx_string!(fname = path_str);
-            ffi::arraywaveform_load_params(self.pinned(), &fname)
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Print the current waveform parameters.
-    pub fn print_params(&mut self) -> AWGResult<&mut Self> {
-        unsafe {
-            ffi::arraywaveform_print_params(self.pinned())
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-
-    /// Get the minimum data length that fulfills rounding constraints for the
-    /// given sampling rate and frequency resolution (in hertz).
-    pub fn get_min_sample_len(
-        &mut self,
-        sampling_rate: u64,
-        frequency_resolution: u64,
-    ) -> AWGResult<u64>
-    {
-        unsafe {
-            ffi::arraywaveform_get_min_sample_len(
-                self.pinned(), sampling_rate, frequency_resolution)
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Get a data array length close to the specified runtime tau (in seconds)
-    /// that also fulfills rounding constraints for the given sampling rate and
-    /// frequency resolution (in hertz).
-    pub fn get_sample_len(
-        &mut self,
-        tau: f64,
-        sampling_rate: u64,
-        frequency_resolution: u64,
-    ) -> AWGResult<u64>
-    {
-        unsafe {
-            ffi::arraywaveform_get_sample_len(
-                self.pinned(), tau, sampling_rate, frequency_resolution)
-                .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Generate a static frequency waveform from the current set of parameters.
-    ///
-    /// The returned [`WaveformDataPtr`] is passed a reference to `self` upon
-    /// creation for safety.
-    pub fn get_static_waveform(&mut self) -> AWGResult<WaveformDataPtr> {
-        unsafe {
-            ffi::arraywaveform_get_static_waveform(self.pinned())
-                .map(|ptr| WaveformDataPtr { ptr, wf_ref: &*self })
-                .map_err(AWGError::as_waveform_err)
-            }
-    }
-
-    /// Generate a tricky-trick waveform from the current set of parameters.
-    ///
-    /// - `sites`: Iterable of array site indices for which the tricky-trick
-    ///   will be performed
-    /// - `df`: Frequency to move by in hertz
-    /// - `tau_move`: Moving time in seconds
-    /// - `tau_stay`: Wait time in seconds
-    ///
-    /// The returned [`WaveformDataPtr`] is passed a reference to `self` upon
-    /// creation for safety.
-    pub fn get_trick_waveform<'a, I>(
-        &mut self,
-        sites: I,
-        df: f64,
-        tau_move: f64,
-        tau_stay: f64,
-    ) -> AWGResult<WaveformDataPtr>
-    where I: IntoIterator<Item = &'a i32>
-    {
-        unsafe {
-            let sites_cxx: UniquePtr<CxxVector<i32>>
-                = collect_cxx_vector(sites);
-            ffi::arraywaveform_get_trick_waveform(
-                self.pinned(),
-                sites_cxx.as_ref().unwrap(),
-                df,
-                tau_move,
-                tau_stay,
-            )
-            .map(|ptr| WaveformDataPtr { ptr, wf_ref: &*self })
-            .map_err(AWGError::as_waveform_err)
-        }
-    }
-
-    /// Save the generated waveform to a CSV-formatted file.
-    pub fn save_waveform<P>(&mut self, filename: P) -> AWGResult<&mut Self>
-    where P: AsRef<Path>
-    {
-        unsafe {
-            let path_osstr: &OsStr = filename.as_ref().as_os_str();
-            let path_str: &str
-                = path_osstr.to_str()
-                .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
-            path_str.ends_with(".csv").then_some(())
-                .ok_or(AWGError::WaveformCSV(path_osstr.to_os_string()))?;
-            let_cxx_string!(fname = path_str);
-            ffi::arraywaveform_save_waveform(self.pinned(), &fname)
-                .map_err(AWGError::as_waveform_err)?;
-            Ok(self)
-        }
-    }
-}
-
-/// Parameters to the tweezer uniformization routine.
-///
-/// All parameters must be initialized before they can be passed to
-/// [`run_uniformization`]:
-/// - [`source`][Self::set_source] source waveform file
-/// - [`polarizability`][Self::set_polarizability] expected atomic
-///   polarizability of the 3P1 ∣F = 3/2, mF = -3/2⟩ state in kHz/μK
-/// - [`mean_depth`][Self::set_mean_depth] target mean tweezer depth in
-///   μK
-/// - [`step_size`][Self::set_step_size] step size to use for the optimization
-///   algorithm
-/// - [`error_threshold`][Self::set_error_threshold] threshold for termination
-///   to use in the optimization algorithm
-/// - [`max_iters`][Self::set_max_iters] maximum number of iterations to use in
-///   the optimization algorithm
-/// - [`num_imaging_avg`][Self::set_num_imaging_avg] number of Basler images to
-///   average for tweezer power measurements
-/// - [`num_tweezers`][Self::set_num_tweezers] tweezer array size
-#[derive(Clone, Debug)]
-pub struct UniformizationParams {
-    source: Option<PathBuf>,
-    polarizability: Option<f64>,
-    mean_depth: Option<f64>,
-    step_size: Option<f64>,
-    error_threshold: Option<f64>,
-    max_iters: Option<usize>,
-    num_imaging_avg: Option<usize>,
-    num_tweezers: Option<usize>,
-}
-
-impl Default for UniformizationParams {
-    fn default() -> Self { Self::new() }
-}
-
-impl UniformizationParams {
-    /// Create a new, uninitialized uniformization config.
-    pub fn new() -> Self {
-        Self {
-            source: None,
-            polarizability: None,
-            mean_depth: None,
-            step_size: None,
-            error_threshold: None,
-            max_iters: None,
-            num_imaging_avg: None,
-            num_tweezers: None,
-        }
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    pub fn new_all<P>(
-        source: P,
-        polarizability: f64,
-        mean_depth: f64,
-        step_size: f64,
-        error_threshold: f64,
-        max_iters: usize,
-        num_imaging_avg: usize,
-        num_tweezers: usize,
-    ) -> Self
-    where P: AsRef<Path>
-    {
-        Self {
-            source: Some(source.as_ref().to_path_buf()),
-            polarizability: Some(polarizability),
-            mean_depth: Some(mean_depth),
-            step_size: Some(step_size),
-            error_threshold: Some(error_threshold),
-            max_iters: Some(max_iters),
-            num_imaging_avg: Some(num_imaging_avg),
-            num_tweezers: Some(num_tweezers),
-        }
-    }
-
-    /// Return `true` if all parameters have been initialized.
-    pub fn is_init(&self) -> bool {
-        self.has_source()
-            && self.has_polarizability()
-            && self.has_mean_depth()
-            && self.has_step_size()
-            && self.has_error_threshold()
-            && self.has_max_iters()
-            && self.has_num_imaging_avg()
-            && self.has_num_tweezers()
-    }
-
-    /// Return `true` if the source waveform file has been set.
-    pub fn has_source(&self) -> bool { self.source.is_some() }
-
-    /// Get the source waveform file.
-    pub fn get_source(&self) -> Option<&PathBuf> {
-        self.source.as_ref()
-    }
-
-    /// Set the source waveform file.
-    pub fn set_source<P>(&mut self, source: P) -> AWGResult<&mut Self>
-    where P: AsRef<Path>
-    {
-        let source = source.as_ref().to_path_buf();
-        let source_string = source.display().to_string();
-        source.exists().then_some(())
-            .ok_or(AWGError::InvalidUniformizationSource(source_string))?;
-        self.source = Some(source);
-        Ok(self)
-    }
-
-    /// Return `true` if the polarizability has been set.
-    pub fn has_polarizability(&self) -> bool { self.polarizability.is_some() }
-
-    /// Get the polarizability in kHz/μK.
-    pub fn get_polarizability(&self) -> Option<f64> { self.polarizability }
-
-    /// Set the polarizability in kHz/μK.
-    pub fn set_polarizability(&mut self, polarizability: f64)
-        -> AWGResult<&mut Self>
-    {
-        (polarizability < 0.0).then_some(())
-            .ok_or(AWGError::InvalidPolarizability(polarizability))?;
-        self.polarizability = Some(polarizability);
-        Ok(self)
-    }
-
-    /// Return `true` if the mean tweezer depth has been set.
-    pub fn has_mean_depth(&self) -> bool { self.mean_depth.is_some() }
-
-    /// Get the mean tweezer depth in μK.
-    pub fn get_mean_depth(&self) -> Option<f64> { self.mean_depth }
-
-    pub fn set_mean_depth(&mut self, mean_depth: f64) -> AWGResult<&mut Self> {
-        (mean_depth > 0.0).then_some(())
-            .ok_or(AWGError::InvalidMeanDepth(mean_depth))?;
-        self.mean_depth = Some(mean_depth);
-        Ok(self)
-    }
-
-    /// Return `true` if the step size has been set.
-    pub fn has_step_size(&self) -> bool { self.step_size.is_some() }
-
-    /// Get the step size.
-    pub fn get_step_size(&self) -> Option<f64> { self.step_size }
-
-    /// Set the step size.
-    pub fn set_step_size(&mut self, step_size: f64) -> AWGResult<&mut Self> {
-        (step_size > 0.0).then_some(())
-            .ok_or(AWGError::InvalidStepSize(step_size))?;
-        self.step_size = Some(step_size);
-        Ok(self)
-    }
-
-    /// Return `true` if the error threshold has been set.
-    pub fn has_error_threshold(&self) -> bool { self.error_threshold.is_some() }
-
-    /// Get the error threshold.
-    pub fn get_error_threshold(&self) -> Option<f64> { self.error_threshold }
-
-    pub fn set_error_threshold(&mut self, error_threshold: f64)
-        -> AWGResult<&mut Self>
-    {
-        (error_threshold > 0.0).then_some(())
-            .ok_or(AWGError::InvalidErrorThreshold(error_threshold))?;
-        self.error_threshold = Some(error_threshold);
-        Ok(self)
-    }
-
-    /// Return `true` if the maximum iterations has been set.
-    pub fn has_max_iters(&self) -> bool { self.max_iters.is_some() }
-
-    /// Get the max iters.
-    pub fn get_max_iters(&self) -> Option<usize> { self.max_iters }
-
-    /// Set the max iters.
-    pub fn set_max_iters(&mut self, max_iters: usize) -> AWGResult<&mut Self> {
-        (max_iters > 0).then_some(())
-            .ok_or(AWGError::InvalidMaxIters(max_iters))?;
-        self.max_iters = Some(max_iters);
-        Ok(self)
-    }
-
-    /// Return `true` if the number of tweezer images to average has been set.
-    pub fn has_num_imaging_avg(&self) -> bool { self.num_imaging_avg.is_some() }
-
-    /// Get the number of images to average over.
-    pub fn get_num_imaging_avg(&self) -> Option<usize> { self.num_imaging_avg }
-
-    /// Set the number of images to average over.
-    pub fn set_num_imaging_avg(&mut self, num_imaging_avg: usize)
-        -> AWGResult<&mut Self>
-    {
-        (num_imaging_avg > 0).then_some(())
-            .ok_or(AWGError::InvalidNumImagingAvg(num_imaging_avg))?;
-        self.num_imaging_avg = Some(num_imaging_avg);
-        Ok(self)
-    }
-
-    pub fn has_num_tweezers(&self) -> bool { self.num_tweezers.is_some() }
-
-    /// Get the number of tweezers.
-    pub fn get_num_tweezers(&self) -> Option<usize> { self.num_tweezers }
-
-    /// Set the number of tweezers.
-    pub fn set_num_tweezers(&mut self, num_tweezers: usize)
-        -> AWGResult<&mut Self>
-    {
-        (num_tweezers > 0).then_some(())
-            .ok_or(AWGError::InvalidNumTweezers(num_tweezers))?;
-        self.num_tweezers = Some(num_tweezers);
-        Ok(self)
-    }
-
-    /// Convert to the corresponding C++ data type.
-    fn to_ffi_type(&self) -> AWGResult<UniquePtr<ffi::UniformizationParams>> {
-        self.check()?;
-        let source_osstr: &OsStr = self.source.as_ref().unwrap().as_os_str();
-        let source_str: &str
-            = source_osstr.to_str()
-            .ok_or(AWGError::PathError(source_osstr.to_os_string()))?;
-        let_cxx_string!(source = source_str);
-        unsafe {
-            let ffi_params: UniquePtr<ffi::UniformizationParams>
-                = ffi::new_uniformizationparams(
-                    &source,
-                    self.polarizability.unwrap(),
-                    self.mean_depth.unwrap(),
-                    self.step_size.unwrap(),
-                    self.error_threshold.unwrap(),
-                    self.max_iters.unwrap() as i32,
-                    self.num_imaging_avg.unwrap() as i32,
-                    self.num_tweezers.unwrap() as i32,
-                )
-                .map_err(AWGError::as_uniformization_err)?;
-            Ok(ffi_params)
-        }
-    }
-
-    /// Check that all necessary conditions are satisfied.
-    pub fn check(&self) -> AWGResult<()> {
-        self.is_init().then_some(())
-            .ok_or(AWGError::UniformizationUninit)?;
-        if let Some(source) = self.source.as_ref() {
-            let source_string = source.display().to_string();
-            source.exists().then_some(())
-                .ok_or(AWGError::InvalidUniformizationSource(source_string))?;
-        }
-        if let Some(polarizability) = self.polarizability {
-            (polarizability < 0.0).then_some(())
-                .ok_or(AWGError::InvalidPolarizability(polarizability))?;
-        }
-        if let Some(mean_depth) = self.mean_depth {
-            (mean_depth > 0.0).then_some(())
-                .ok_or(AWGError::InvalidMeanDepth(mean_depth))?;
-        }
-        if let Some(step_size) = self.step_size {
-            (step_size > 0.0).then_some(())
-                .ok_or(AWGError::InvalidStepSize(step_size))?;
-        }
-        if let Some(error_threshold) = self.error_threshold {
-            (error_threshold > 0.0).then_some(())
-                .ok_or(AWGError::InvalidErrorThreshold(error_threshold))?;
-        }
-        if let Some(max_iters) = self.max_iters {
-            (max_iters > 0).then_some(())
-                .ok_or(AWGError::InvalidMaxIters(max_iters))?;
-        }
-        if let Some(num_imaging_avg) = self.num_imaging_avg {
-            (num_imaging_avg > 0).then_some(())
-                .ok_or(AWGError::InvalidNumImagingAvg(num_imaging_avg))?;
-        }
-        if let Some(num_tweezers) = self.num_tweezers {
-            (num_tweezers > 0).then_some(())
-                .ok_or(AWGError::InvalidNumTweezers(num_tweezers))?;
-        }
-        Ok(())
-    }
-}
-
-pub fn run_uniformization<A, B>(
-    mut awg: A,
-    mut basler: B,
-    waveform: &mut ArrayWaveform,
-    params: &UniformizationParams,
-) -> AWGResult<()>
-where
-    A: std::ops::DerefMut<Target = AWG>,
-    B: std::ops::DerefMut<Target = Basler>,
-{
-    unsafe {
-        ffi::run_uniformization(
-            awg.deref_mut().pinned(),
-            basler.deref_mut().pinned(),
-            waveform.pinned(),
-            params.to_ffi_type()?.as_ref().unwrap(),
-        )
-        .map_err(AWGError::as_uniformization_err)
-    }
-}
-
+//  Safe interface to waveform parameter configuration.
+// 
+//  All parameters must be initialized before an [`ArrayWaveform`] can be
+//  generated:
+//  - [`tones`][Self::set_tones]: Frequency tones in hertz
+//  - [`phases`][Self::set_phases]: Initial phases for each tone in radians
+//  - [`amplitudes`][Self::set_amplitudes]: Amplitudes for each tone
+//  - [`sampling_rate`][Self::set_sampling_rate]: Sampling rate in hertz
+//  - [`frequency_resolution`][Self::set_frequency_resolution]: Frequency resolution in hertz
+
+// #[derive(Clone, Debug)]
+// pub struct WaveformParams {
+//     tones: Option<Vec<i32>>,
+//     phases: Option<Vec<f64>>,
+//     amplitudes: Option<Vec<f64>>,
+//     sampling_rate: Option<u64>,
+//     frequency_resolution: Option<u64>,
+// }
+
+// impl Default for WaveformParams {
+//     fn default() -> Self { Self::new() }
+// }
+
+// impl WaveformParams {
+//     /// Create a new, uninitialized set of waveform parameters.
+//     pub fn new() -> Self {
+//         Self {
+//             tones: None,
+//             phases: None,
+//             amplitudes: None,
+//             sampling_rate: None,
+//             frequency_resolution: None,
+//         }
+//     }
+
+//     /// Return `true` if all parameters have been initialized.
+//     pub fn is_init(&self) -> bool {
+//         self.has_tones()
+//             && self.has_phases()
+//             && self.has_amplitudes()
+//             && self.has_sampling_rate()
+//             && self.has_frequency_resolution()
+//     }
+
+//     /// Write current parameter settings to a file.
+//     ///
+//     /// The file is formatted as a CSV file:
+//     /// ```text
+//     /// sampling_rate
+//     /// frequency_resolution
+//     /// tones[0],tones[1],...
+//     /// phases[0],phases[1],...
+//     /// amplitudes[0],amplitudes[1],...
+//     /// ```
+//     ///
+//     /// The given file name must have a `.csv` extension.
+//     pub fn save<P>(&self, outfile: P) -> AWGResult<&Self>
+//     where P: AsRef<Path>
+//     {
+//         self.is_init().then_some(())
+//             .ok_or(AWGError::WaveformUninit)?;
+//         let outfile = outfile.as_ref();
+//         outfile.extension().and_then(|ext| (ext == "csv").then_some(()))
+//             .ok_or(AWGError::WaveformCSV(outfile.as_os_str().to_os_string()))?;
+//         let mut out
+//             = fs::OpenOptions::new()
+//             .write(true)
+//             .create(true)
+//             .truncate(true)
+//             .open(outfile)
+//             .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
+//         writeln!(&mut out, "{}", self.sampling_rate.unwrap())
+//             .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
+//         writeln!(&mut out, "{}", self.frequency_resolution.unwrap())
+//             .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
+//         writeln!(&mut out, "{}",
+//             self.tones.as_ref().unwrap().iter()
+//                 .map(|f| f.to_string())
+//                 .collect::<Vec<String>>()
+//                 .join(",")
+//         )
+//         .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
+//         writeln!(&mut out, "{}",
+//             self.phases.as_ref().unwrap().iter()
+//                 .map(|ph| ph.to_string())
+//                 .collect::<Vec<String>>()
+//                 .join(",")
+//         )
+//         .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
+//         writeln!(&mut out, "{}",
+//             self.amplitudes.as_ref().unwrap().iter()
+//                 .map(|a| a.to_string())
+//                 .collect::<Vec<String>>()
+//                 .join(",")
+//         )
+//         .map_err(|err| AWGError::WaveformWriteError(err.to_string()))?;
+//         Ok(self)
+//     }
+
+//     /// Load parameter settings from a file.
+//     ///
+//     /// The file is expected as a CSV file:
+//     /// ```text
+//     /// sampling_rate
+//     /// frequency_resolution
+//     /// tones[0],tones[1],...
+//     /// phases[0],phases[1],...
+//     /// amplitudes[0],amplitudes[1],...
+//     /// ```
+//     ///
+//     /// The given file name must have a `.csv` extension.
+//     pub fn load<P>(infile: P) -> AWGResult<Self>
+//     where P: AsRef<Path>
+//     {
+//         let infile = infile.as_ref();
+//         infile.extension().and_then(|ext| (ext == "csv").then_some(()))
+//             .ok_or(AWGError::WaveformCSV(infile.as_os_str().to_os_string()))?;
+//         let mut buf = String::new();
+//         fs::OpenOptions::new()
+//             .read(true)
+//             .open(infile)
+//             .map_err(|err| AWGError::WaveformParseError(err.to_string()))?
+//             .read_to_string(&mut buf)
+//             .map_err(|err| AWGError::WaveformParseError(err.to_string()))?;
+//         let mut lines = buf.split('\n');
+//         let sampling_rate: u64
+//             = lines.next()
+//             .ok_or(AWGError::WaveformParseError(
+//                 "missing sampling rate".to_string()
+//             ))?
+//             .parse::<u64>()
+//             .map_err(|err| AWGError::WaveformParseError(err.to_string()))?;
+//         let frequency_resolution: u64
+//             = lines.next()
+//             .ok_or(AWGError::WaveformParseError(
+//                 "missing frequency resolution".to_string()
+//             ))?
+//             .parse::<u64>()
+//             .map_err(|err| AWGError::WaveformParseError(err.to_string()))?;
+//         let tones: Vec<i32>
+//             = lines.next()
+//             .ok_or(AWGError::WaveformParseError(
+//                 "missing frequency tones".to_string()
+//             ))?
+//             .split(',')
+//             .map(|f| {
+//                 f.parse::<i32>()
+//                 .map_err(|err| AWGError::WaveformParseError(err.to_string()))
+//             })
+//             .collect::<AWGResult<Vec<i32>>>()?;
+//         let phases: Vec<f64>
+//             = lines.next()
+//             .ok_or(AWGError::WaveformParseError(
+//                 "missing phases".to_string()
+//             ))?
+//             .split(',')
+//             .map(|ph| {
+//                 ph.parse::<f64>()
+//                 .map_err(|err| AWGError::WaveformParseError(err.to_string()))
+//             })
+//             .collect::<AWGResult<Vec<f64>>>()?;
+//         let amplitudes: Vec<f64>
+//             = lines.next()
+//             .ok_or(AWGError::WaveformParseError(
+//                 "missing amplitudes".to_string()
+//             ))?
+//             .split(',')
+//             .map(|a| {
+//                 a.parse::<f64>()
+//                 .map_err(|err| AWGError::WaveformParseError(err.to_string()))
+//             })
+//             .collect::<AWGResult<Vec<f64>>>()?;
+//         let data = Self {
+//             tones: Some(tones),
+//             phases: Some(phases),
+//             amplitudes: Some(amplitudes),
+//             sampling_rate: Some(sampling_rate),
+//             frequency_resolution: Some(frequency_resolution),
+//         };
+//         Ok(data)
+//     }
+
+//     /// Return `true` if frequency tones have been initialized.
+//     pub fn has_tones(&self) -> bool { self.tones.is_some() }
+
+//     /// Get the frequency tones in hertz, if initialized.
+//     pub fn get_tones(&self) -> Option<&Vec<i32>> {
+//         self.tones.as_ref()
+//     }
+
+//     /// Set the frequency tones in hertz.
+//     pub fn set_tones<'a, I>(&mut self, tones: I) -> AWGResult<&mut Self>
+//     where I: IntoIterator<Item = &'a i32>
+//     {
+//         let tones: Vec<i32> = tones.into_iter().copied().collect();
+//         if let Some(phases) = &self.phases {
+//             (tones.len() == phases.len()).then_some(())
+//                 .ok_or(AWGError::NumTones)?;
+//         }
+//         if let Some(amplitudes) = &self.amplitudes {
+//             (tones.len() == amplitudes.len()).then_some(())
+//                 .ok_or(AWGError::NumTones)?;
+//         }
+//         self.tones = Some(tones);
+//         Ok(self)
+//     }
+
+//     /// Set the frequency tones using a center frequency and a uniform spacing,
+//     /// both in hertz.
+//     pub fn set_tones_spaced(&mut self, center: i32, spacing: i32, num: usize)
+//         -> AWGResult<&mut Self>
+//     {
+//         if let Some(phases) = &self.phases {
+//             (num == phases.len()).then_some(())
+//                 .ok_or(AWGError::NumTones)?;
+//         }
+//         if let Some(amplitudes) = &self.amplitudes {
+//             (num == amplitudes.len()).then_some(())
+//                 .ok_or(AWGError::NumTones)?;
+//         }
+//         let f0: i32 = center - spacing * num as i32 / 2;
+//         let tones: Vec<i32>
+//             = (0..num as i32).map(|k| f0 + spacing * k).collect();
+//         self.tones = Some(tones);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if initial phases have been initialized.
+//     pub fn has_phases(&self) -> bool { self.phases.is_some() }
+
+//     /// Get the initial phases of each tone in radians.
+//     pub fn get_phases(&self) -> Option<&Vec<f64>> {
+//         self.phases.as_ref()
+//     }
+
+//     /// Set the initial phases of each tone in radians.
+//     pub fn set_phases<'a, I>(&mut self, phases: I) -> AWGResult<&mut Self>
+//     where I: IntoIterator<Item = &'a f64>
+//     {
+//         let phases: Vec<f64>
+//             = phases.into_iter().copied().map(|ph| ph % TAU).collect();
+//         if let Some(tones) = &self.tones {
+//             (phases.len() == tones.len()).then_some(())
+//                 .ok_or(AWGError::NumPhases)?;
+//         }
+//         if let Some(amplitudes) = &self.amplitudes {
+//             (phases.len() == amplitudes.len()).then_some(())
+//                 .ok_or(AWGError::NumPhases)?;
+//         }
+//         self.phases = Some(phases);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if amplitudes have been initialized.
+//     pub fn has_amplitudes(&self) -> bool { self.amplitudes.is_some() }
+
+//     /// Get the amplitudes of each tone.
+//     pub fn get_amplitudes(&self) -> Option<&Vec<f64>> {
+//         self.amplitudes.as_ref()
+//     }
+
+//     /// Set the amplitudes of each tone.
+//     ///
+//     /// Each amplitude must lie in the range `[0, 2^15 - 1)`.
+//     pub fn set_amplitudes<'a, I>(&mut self, amplitudes: I)
+//         -> AWGResult<&mut Self>
+//     where I: IntoIterator<Item = &'a f64>
+//     {
+//         let amplitudes: Vec<f64>
+//             = amplitudes.into_iter().copied()
+//             .map(|a| {
+//                 (0.0..32767.0).contains(&a)
+//                     .then_some(a)
+//                     .ok_or(AWGError::InvalidAmplitude(a))
+//             })
+//             .collect::<AWGResult<Vec<f64>>>()?;
+//         if let Some(tones) = &self.tones {
+//             (amplitudes.len() == tones.len()).then_some(())
+//                 .ok_or(AWGError::NumAmplitudes)?;
+//         }
+//         if let Some(phases) = &self.phases {
+//             (amplitudes.len() == phases.len()).then_some(())
+//                 .ok_or(AWGError::NumAmplitudes)?;
+//         }
+//         self.amplitudes = Some(amplitudes);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the sampling rate has been initialized.
+//     pub fn has_sampling_rate(&self) -> bool {
+//         self.sampling_rate.is_some()
+//     }
+
+//     /// Get the sampling rate in hertz.
+//     pub fn get_sampling_rate(&self) -> Option<u64> {
+//         self.sampling_rate
+//     }
+
+//     /// Set the sampling rate in hertz.
+//     pub fn set_sampling_rate(&mut self, sampling_rate: u64) -> &mut Self {
+//         self.sampling_rate = Some(sampling_rate);
+//         self
+//     }
+
+//     /// Return `true` if the frequency resolution has been initialized.
+//     pub fn has_frequency_resolution(&self) -> bool {
+//         self.frequency_resolution.is_some()
+//     }
+
+//     /// Get the frequency resolution in hertz.
+//     pub fn get_frequency_resolution(&self) -> Option<u64> {
+//         self.frequency_resolution
+//     }
+
+//     /// Set the frequency resolution in hertz.
+//     pub fn set_frequency_resolution(&mut self, resolution: u64) -> &mut Self {
+//         self.frequency_resolution = Some(resolution);
+//         self
+//     }
+
+//     /// Generate an [`ArrayWaveform`] from the current set of parameters if all
+//     /// have been initialized.
+//     pub fn to_waveform(&self) -> AWGResult<ArrayWaveform> {
+//         self.is_init().then_some(())
+//             .ok_or(AWGError::WaveformUninit)?;
+//         let mut waveform = ArrayWaveform::new_uninit();
+//         waveform
+//             .set_freq_tones(self.tones.as_ref().unwrap())?
+//             .set_phases(self.phases.as_ref().unwrap())?
+//             .set_amplitudes(self.amplitudes.as_ref().unwrap())?
+//             .set_sampling_rate(self.sampling_rate.unwrap())?
+//             .set_freq_resolution(self.frequency_resolution.unwrap())?;
+//         Ok(waveform)
+//     }
+// }
+
+// impl TryFrom<&WaveformParams> for ArrayWaveform {
+//     type Error = AWGError;
+
+//     fn try_from(params: &WaveformParams) -> AWGResult<Self> {
+//         params.to_waveform()
+//     }
+// }
+
+// impl TryFrom<WaveformParams> for ArrayWaveform {
+//     type Error = AWGError;
+
+//     fn try_from(params: WaveformParams) -> AWGResult<Self> {
+//         params.to_waveform()
+//     }
+// }
+
+// impl fmt::Display for WaveformParams {
+//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+//         writeln!(f, "WaveformParams {{")?;
+
+//         write!(f, "  tones: [")?;
+//         if let Some(tones) = &self.tones {
+//             let n = tones.len();
+//             for (k, tone) in tones.iter().enumerate() {
+//                 tone.fmt(f)?;
+//                 if k < n - 1 { write!(f, ", ")?; }
+//             }
+//         } else {
+//             write!(f, "<uninit>")?;
+//         }
+//         writeln!(f, "],")?;
+
+//         write!(f, "  phases: [")?;
+//         if let Some(phases) = &self.phases {
+//             let n = phases.len();
+//             for (k, phase) in phases.iter().enumerate() {
+//                 phase.fmt(f)?;
+//                 if k < n - 1 { write!(f, ", ")?; }
+//             }
+//         } else {
+//             write!(f, "<uninit>")?;
+//         }
+//         writeln!(f, "],")?;
+
+//         write!(f, "  amplitudes: [")?;
+//         if let Some(amplitudes) = &self.amplitudes {
+//             let n = amplitudes.len();
+//             for (k, amplitude) in amplitudes.iter().enumerate() {
+//                 amplitude.fmt(f)?;
+//                 if k < n - 1 { write!(f, ", ")?; }
+//             }
+//         } else {
+//             write!(f, "<uninit>")?;
+//         }
+//         writeln!(f, "],")?;
+
+//         write!(f, "  sampling_rate: ")?;
+//         if let Some(sampling_rate) = self.sampling_rate {
+//             sampling_rate.fmt(f)?;
+//         } else {
+//             write!(f, "<uninit>")?;
+//         }
+//         writeln!(f, ",")?;
+
+//         write!(f, "  frequency_resolution: ")?;
+//         if let Some(frequency_resolution) = self.frequency_resolution {
+//             frequency_resolution.fmt(f)?;
+//         } else {
+//             write!(f, "<uninit>")?;
+//         }
+//         writeln!(f, ",")?;
+
+//         write!(f, "}}")?;
+//         Ok(())
+//     }
+// }
+
+// /// Data type for array waveform generation.
+// pub struct ArrayWaveform {
+//     data: UniquePtr<ffi::ArrayWaveform>,
+// }
+
+// impl_debug_boring!(ArrayWaveform);
+
+// impl Default for ArrayWaveform {
+//     fn default() -> Self { Self::new_uninit() }
+// }
+
+// impl ArrayWaveform {
+//     fn pinned(&mut self) -> Pin<&mut ffi::ArrayWaveform> { self.data.pin_mut() }
+
+//     /// Create a new, uninitialized waveform.
+//     ///
+//     /// # Safety
+//     /// After this constructor is called, all private member variables are set
+//     /// to 0 or `nullptr` values. It is the programmer's responsibility to
+//     /// ensure that all properties and memory addresses are properly
+//     /// initialized.
+//     fn new_uninit() -> Self {
+//         unsafe {
+//             Self { data: ffi::new_arraywaveform().unwrap() }
+//         }
+//     }
+
+//     /// Get a pointer to the associated data buffer.
+//     pub fn get_data_buffer(&mut self) -> AWGResult<PageAlignedMem> {
+//         unsafe {
+//             ffi::arraywaveform_get_data_buffer(self.pinned())
+//                 .map(|ptr| PageAlignedMem { ptr, wf_ref: self })
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Get the number of samples in the current waveform.
+//     pub fn get_data_len(&mut self) -> AWGResult<u64> {
+//         unsafe {
+//             ffi::arraywaveform_get_data_len(self.pinned())
+//                 .map(|len| len as u64)
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Set the sampling rate in hertz.
+//     fn set_sampling_rate(&mut self, rate: u64) -> AWGResult<&mut Self> {
+//         unsafe {
+//             ffi::arraywaveform_set_sampling_rate(self.pinned(), rate)
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Get the sampling rate in hertz.
+//     pub fn get_sampling_rate(&mut self) -> AWGResult<u64> {
+//         unsafe {
+//             ffi::arraywaveform_get_sampling_rate(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Set the frequency resolution in hertz.
+//     fn set_freq_resolution(&mut self, resolution: u64)
+//         -> AWGResult<&mut Self>
+//     {
+//         unsafe {
+//             ffi::arraywaveform_set_freq_resolution(self.pinned(), resolution)
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Get the frequency resolution in hertz.
+//     pub fn get_freq_resolution(&mut self) -> AWGResult<u64> {
+//         unsafe {
+//             ffi::arraywaveform_get_freq_resolution(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Set the constituent tones using a fixed center frequency and uniform
+//     /// spacing in hertz.
+//     fn set_freq_tones_spaced(
+//         &mut self,
+//         center: i32,
+//         spacing: i32,
+//         num_tones: u16,
+//     ) -> AWGResult<&mut Self>
+//     {
+//         unsafe {
+//             ffi::arraywaveform_set_freq_tones_spaced(
+//                 self.pinned(), center, spacing, num_tones.into())
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Set the constituent tones to arbitrary values in hertz.
+//     fn set_freq_tones<'a, I>(&mut self, tones: I) -> AWGResult<&mut Self>
+//     where I: IntoIterator<Item = &'a i32>
+//     {
+//         unsafe {
+//             let tones_cxx: UniquePtr<CxxVector<i32>>
+//                 = collect_cxx_vector(tones);
+//             ffi::arraywaveform_set_freq_tones(
+//                 self.pinned(), tones_cxx.as_ref().unwrap())
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Get a list of contituent tones.
+//     pub fn get_freq_tones(&mut self) -> AWGResult<Vec<i32>> {
+//         unsafe {
+//             ffi::arraywaveform_get_freq_tones(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Set the initial phases in radians of all tones.
+//     fn set_phases<'a, I>(&mut self, phases: I) -> AWGResult<&mut Self>
+//     where I: IntoIterator<Item = &'a f64>
+//     {
+//         unsafe {
+//             let phases_cxx: UniquePtr<CxxVector<f64>>
+//                 = collect_cxx_vector(phases);
+//             ffi::arraywaveform_set_phases(
+//                 self.pinned(), phases_cxx.as_ref().unwrap())
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Get a list of initial phases in radians for each tone.
+//     pub fn get_phases(&mut self) -> AWGResult<Vec<f64>> {
+//         unsafe {
+//             ffi::arraywaveform_get_phases(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Set the amplitudes of all tones.
+//     ///
+//     /// Each amplitude must be in the range `[0, 2^15 - 1)`
+//     fn set_amplitudes<'a, I>(&mut self, amplitudes: I)
+//         -> AWGResult<&mut Self>
+//     where I: IntoIterator<Item = &'a f64>
+//     {
+//         unsafe {
+//             let amplitudes: Vec<f64>
+//                 = amplitudes.into_iter().copied()
+//                 .map(|a| {
+//                     (0.0..32767.0).contains(&a)
+//                         .then_some(a)
+//                         .ok_or(AWGError::InvalidAmplitude(a))
+//                 })
+//                 .collect::<AWGResult<Vec<f64>>>()?;
+//             let amplitudes_cxx: UniquePtr<CxxVector<f64>>
+//                 = collect_cxx_vector(&amplitudes);
+//             ffi::arraywaveform_set_amplitudes(
+//                 self.pinned(), amplitudes_cxx.as_ref().unwrap())
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Get a list of amplitudes for each tone.
+//     pub fn get_amplitudes(&mut self) -> AWGResult<Vec<f64>> {
+//         unsafe {
+//             ffi::arraywaveform_get_amplitudes(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Set phases to 0 and amplitudes to 2000.
+//     fn set_default_params(&mut self) -> AWGResult<&mut Self> {
+//         unsafe {
+//             ffi::arraywaveform_set_default_params(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Save the current waveform parameters to a file in CSV format.
+//     fn save_params<P>(&mut self, filename: P) -> AWGResult<&mut Self>
+//     where P: AsRef<Path>
+//     {
+//         unsafe {
+//             let path_osstr: &OsStr = filename.as_ref().as_os_str();
+//             let path_str: &str
+//                 = path_osstr.to_str()
+//                 .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
+//             path_str.ends_with(".csv").then_some(())
+//                 .ok_or(AWGError::WaveformCSV(path_osstr.to_os_string()))?;
+//             let_cxx_string!(fname = path_str);
+//             ffi::arraywaveform_save_params(self.pinned(), &fname)
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Load waveform parameters from a file.
+//     ///
+//     /// The file must be in CSV format.
+//     fn load_params<P>(&mut self, filename: P) -> AWGResult<&mut Self>
+//     where P: AsRef<Path>
+//     {
+//         unsafe {
+//             let path_osstr: &OsStr = filename.as_ref().as_os_str();
+//             let path_str: &str
+//                 = path_osstr.to_str()
+//                 .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
+//             path_str.ends_with(".csv").then_some(())
+//                 .ok_or(AWGError::WaveformCSV(path_osstr.to_os_string()))?;
+//             let_cxx_string!(fname = path_str);
+//             ffi::arraywaveform_load_params(self.pinned(), &fname)
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Print the current waveform parameters.
+//     pub fn print_params(&mut self) -> AWGResult<&mut Self> {
+//         unsafe {
+//             ffi::arraywaveform_print_params(self.pinned())
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+
+//     /// Get the minimum data length that fulfills rounding constraints for the
+//     /// given sampling rate and frequency resolution (in hertz).
+//     pub fn get_min_sample_len(
+//         &mut self,
+//         sampling_rate: u64,
+//         frequency_resolution: u64,
+//     ) -> AWGResult<u64>
+//     {
+//         unsafe {
+//             ffi::arraywaveform_get_min_sample_len(
+//                 self.pinned(), sampling_rate, frequency_resolution)
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Get a data array length close to the specified runtime tau (in seconds)
+//     /// that also fulfills rounding constraints for the given sampling rate and
+//     /// frequency resolution (in hertz).
+//     pub fn get_sample_len(
+//         &mut self,
+//         tau: f64,
+//         sampling_rate: u64,
+//         frequency_resolution: u64,
+//     ) -> AWGResult<u64>
+//     {
+//         unsafe {
+//             ffi::arraywaveform_get_sample_len(
+//                 self.pinned(), tau, sampling_rate, frequency_resolution)
+//                 .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Generate a static frequency waveform from the current set of parameters.
+//     ///
+//     /// The returned [`WaveformDataPtr`] is passed a reference to `self` upon
+//     /// creation for safety.
+//     pub fn get_static_waveform(&mut self) -> AWGResult<WaveformDataPtr> {
+//         unsafe {
+//             ffi::arraywaveform_get_static_waveform(self.pinned())
+//                 .map(|ptr| WaveformDataPtr { ptr, wf_ref: &*self })
+//                 .map_err(AWGError::as_waveform_err)
+//             }
+//     }
+
+//     /// Generate a tricky-trick waveform from the current set of parameters.
+//     ///
+//     /// - `sites`: Iterable of array site indices for which the tricky-trick
+//     ///   will be performed
+//     /// - `df`: Frequency to move by in hertz
+//     /// - `tau_move`: Moving time in seconds
+//     /// - `tau_stay`: Wait time in seconds
+//     ///
+//     /// The returned [`WaveformDataPtr`] is passed a reference to `self` upon
+//     /// creation for safety.
+//     pub fn get_trick_waveform<'a, I>(
+//         &mut self,
+//         sites: I,
+//         df: f64,
+//         tau_move: f64,
+//         tau_stay: f64,
+//     ) -> AWGResult<WaveformDataPtr>
+//     where I: IntoIterator<Item = &'a i32>
+//     {
+//         unsafe {
+//             let sites_cxx: UniquePtr<CxxVector<i32>>
+//                 = collect_cxx_vector(sites);
+//             ffi::arraywaveform_get_trick_waveform(
+//                 self.pinned(),
+//                 sites_cxx.as_ref().unwrap(),
+//                 df,
+//                 tau_move,
+//                 tau_stay,
+//             )
+//             .map(|ptr| WaveformDataPtr { ptr, wf_ref: &*self })
+//             .map_err(AWGError::as_waveform_err)
+//         }
+//     }
+
+//     /// Save the generated waveform to a CSV-formatted file.
+//     pub fn save_waveform<P>(&mut self, filename: P) -> AWGResult<&mut Self>
+//     where P: AsRef<Path>
+//     {
+//         unsafe {
+//             let path_osstr: &OsStr = filename.as_ref().as_os_str();
+//             let path_str: &str
+//                 = path_osstr.to_str()
+//                 .ok_or(AWGError::PathError(path_osstr.to_os_string()))?;
+//             path_str.ends_with(".csv").then_some(())
+//                 .ok_or(AWGError::WaveformCSV(path_osstr.to_os_string()))?;
+//             let_cxx_string!(fname = path_str);
+//             ffi::arraywaveform_save_waveform(self.pinned(), &fname)
+//                 .map_err(AWGError::as_waveform_err)?;
+//             Ok(self)
+//         }
+//     }
+// }
+
+// /// Parameters to the tweezer uniformization routine.
+// ///
+// /// All parameters must be initialized before they can be passed to
+// /// [`run_uniformization`]:
+// /// - [`source`][Self::set_source] source waveform file
+// /// - [`polarizability`][Self::set_polarizability] expected atomic
+// ///   polarizability of the 3P1 ∣F = 3/2, mF = -3/2⟩ state in kHz/μK
+// /// - [`mean_depth`][Self::set_mean_depth] target mean tweezer depth in
+// ///   μK
+// /// - [`step_size`][Self::set_step_size] step size to use for the optimization
+// ///   algorithm
+// /// - [`error_threshold`][Self::set_error_threshold] threshold for termination
+// ///   to use in the optimization algorithm
+// /// - [`max_iters`][Self::set_max_iters] maximum number of iterations to use in
+// ///   the optimization algorithm
+// /// - [`num_imaging_avg`][Self::set_num_imaging_avg] number of Basler images to
+// ///   average for tweezer power measurements
+// /// - [`num_tweezers`][Self::set_num_tweezers] tweezer array size
+// #[derive(Clone, Debug)]
+// pub struct UniformizationParams {
+//     source: Option<PathBuf>,
+//     polarizability: Option<f64>,
+//     mean_depth: Option<f64>,
+//     step_size: Option<f64>,
+//     error_threshold: Option<f64>,
+//     max_iters: Option<usize>,
+//     num_imaging_avg: Option<usize>,
+//     num_tweezers: Option<usize>,
+// }
+
+// impl Default for UniformizationParams {
+//     fn default() -> Self { Self::new() }
+// }
+
+// impl UniformizationParams {
+//     /// Create a new, uninitialized uniformization config.
+//     pub fn new() -> Self {
+//         Self {
+//             source: None,
+//             polarizability: None,
+//             mean_depth: None,
+//             step_size: None,
+//             error_threshold: None,
+//             max_iters: None,
+//             num_imaging_avg: None,
+//             num_tweezers: None,
+//         }
+//     }
+
+//     #[allow(clippy::too_many_arguments)]
+//     pub fn new_all<P>(
+//         source: P,
+//         polarizability: f64,
+//         mean_depth: f64,
+//         step_size: f64,
+//         error_threshold: f64,
+//         max_iters: usize,
+//         num_imaging_avg: usize,
+//         num_tweezers: usize,
+//     ) -> Self
+//     where P: AsRef<Path>
+//     {
+//         Self {
+//             source: Some(source.as_ref().to_path_buf()),
+//             polarizability: Some(polarizability),
+//             mean_depth: Some(mean_depth),
+//             step_size: Some(step_size),
+//             error_threshold: Some(error_threshold),
+//             max_iters: Some(max_iters),
+//             num_imaging_avg: Some(num_imaging_avg),
+//             num_tweezers: Some(num_tweezers),
+//         }
+//     }
+
+//     /// Return `true` if all parameters have been initialized.
+//     pub fn is_init(&self) -> bool {
+//         self.has_source()
+//             && self.has_polarizability()
+//             && self.has_mean_depth()
+//             && self.has_step_size()
+//             && self.has_error_threshold()
+//             && self.has_max_iters()
+//             && self.has_num_imaging_avg()
+//             && self.has_num_tweezers()
+//     }
+
+//     /// Return `true` if the source waveform file has been set.
+//     pub fn has_source(&self) -> bool { self.source.is_some() }
+
+//     /// Get the source waveform file.
+//     pub fn get_source(&self) -> Option<&PathBuf> {
+//         self.source.as_ref()
+//     }
+
+//     /// Set the source waveform file.
+//     pub fn set_source<P>(&mut self, source: P) -> AWGResult<&mut Self>
+//     where P: AsRef<Path>
+//     {
+//         let source = source.as_ref().to_path_buf();
+//         let source_string = source.display().to_string();
+//         source.exists().then_some(())
+//             .ok_or(AWGError::InvalidUniformizationSource(source_string))?;
+//         self.source = Some(source);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the polarizability has been set.
+//     pub fn has_polarizability(&self) -> bool { self.polarizability.is_some() }
+
+//     /// Get the polarizability in kHz/μK.
+//     pub fn get_polarizability(&self) -> Option<f64> { self.polarizability }
+
+//     /// Set the polarizability in kHz/μK.
+//     pub fn set_polarizability(&mut self, polarizability: f64)
+//         -> AWGResult<&mut Self>
+//     {
+//         (polarizability < 0.0).then_some(())
+//             .ok_or(AWGError::InvalidPolarizability(polarizability))?;
+//         self.polarizability = Some(polarizability);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the mean tweezer depth has been set.
+//     pub fn has_mean_depth(&self) -> bool { self.mean_depth.is_some() }
+
+//     /// Get the mean tweezer depth in μK.
+//     pub fn get_mean_depth(&self) -> Option<f64> { self.mean_depth }
+
+//     pub fn set_mean_depth(&mut self, mean_depth: f64) -> AWGResult<&mut Self> {
+//         (mean_depth > 0.0).then_some(())
+//             .ok_or(AWGError::InvalidMeanDepth(mean_depth))?;
+//         self.mean_depth = Some(mean_depth);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the step size has been set.
+//     pub fn has_step_size(&self) -> bool { self.step_size.is_some() }
+
+//     /// Get the step size.
+//     pub fn get_step_size(&self) -> Option<f64> { self.step_size }
+
+//     /// Set the step size.
+//     pub fn set_step_size(&mut self, step_size: f64) -> AWGResult<&mut Self> {
+//         (step_size > 0.0).then_some(())
+//             .ok_or(AWGError::InvalidStepSize(step_size))?;
+//         self.step_size = Some(step_size);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the error threshold has been set.
+//     pub fn has_error_threshold(&self) -> bool { self.error_threshold.is_some() }
+
+//     /// Get the error threshold.
+//     pub fn get_error_threshold(&self) -> Option<f64> { self.error_threshold }
+
+//     pub fn set_error_threshold(&mut self, error_threshold: f64)
+//         -> AWGResult<&mut Self>
+//     {
+//         (error_threshold > 0.0).then_some(())
+//             .ok_or(AWGError::InvalidErrorThreshold(error_threshold))?;
+//         self.error_threshold = Some(error_threshold);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the maximum iterations has been set.
+//     pub fn has_max_iters(&self) -> bool { self.max_iters.is_some() }
+
+//     /// Get the max iters.
+//     pub fn get_max_iters(&self) -> Option<usize> { self.max_iters }
+
+//     /// Set the max iters.
+//     pub fn set_max_iters(&mut self, max_iters: usize) -> AWGResult<&mut Self> {
+//         (max_iters > 0).then_some(())
+//             .ok_or(AWGError::InvalidMaxIters(max_iters))?;
+//         self.max_iters = Some(max_iters);
+//         Ok(self)
+//     }
+
+//     /// Return `true` if the number of tweezer images to average has been set.
+//     pub fn has_num_imaging_avg(&self) -> bool { self.num_imaging_avg.is_some() }
+
+//     /// Get the number of images to average over.
+//     pub fn get_num_imaging_avg(&self) -> Option<usize> { self.num_imaging_avg }
+
+//     /// Set the number of images to average over.
+//     pub fn set_num_imaging_avg(&mut self, num_imaging_avg: usize)
+//         -> AWGResult<&mut Self>
+//     {
+//         (num_imaging_avg > 0).then_some(())
+//             .ok_or(AWGError::InvalidNumImagingAvg(num_imaging_avg))?;
+//         self.num_imaging_avg = Some(num_imaging_avg);
+//         Ok(self)
+//     }
+
+//     pub fn has_num_tweezers(&self) -> bool { self.num_tweezers.is_some() }
+
+//     /// Get the number of tweezers.
+//     pub fn get_num_tweezers(&self) -> Option<usize> { self.num_tweezers }
+
+//     /// Set the number of tweezers.
+//     pub fn set_num_tweezers(&mut self, num_tweezers: usize)
+//         -> AWGResult<&mut Self>
+//     {
+//         (num_tweezers > 0).then_some(())
+//             .ok_or(AWGError::InvalidNumTweezers(num_tweezers))?;
+//         self.num_tweezers = Some(num_tweezers);
+//         Ok(self)
+//     }
+
+//     /// Convert to the corresponding C++ data type.
+//     fn to_ffi_type(&self) -> AWGResult<UniquePtr<ffi::UniformizationParams>> {
+//         self.check()?;
+//         let source_osstr: &OsStr = self.source.as_ref().unwrap().as_os_str();
+//         let source_str: &str
+//             = source_osstr.to_str()
+//             .ok_or(AWGError::PathError(source_osstr.to_os_string()))?;
+//         let_cxx_string!(source = source_str);
+//         unsafe {
+//             let ffi_params: UniquePtr<ffi::UniformizationParams>
+//                 = ffi::new_uniformizationparams(
+//                     &source,
+//                     self.polarizability.unwrap(),
+//                     self.mean_depth.unwrap(),
+//                     self.step_size.unwrap(),
+//                     self.error_threshold.unwrap(),
+//                     self.max_iters.unwrap() as i32,
+//                     self.num_imaging_avg.unwrap() as i32,
+//                     self.num_tweezers.unwrap() as i32,
+//                 )
+//                 .map_err(AWGError::as_uniformization_err)?;
+//             Ok(ffi_params)
+//         }
+//     }
+
+//     /// Check that all necessary conditions are satisfied.
+//     pub fn check(&self) -> AWGResult<()> {
+//         self.is_init().then_some(())
+//             .ok_or(AWGError::UniformizationUninit)?;
+//         if let Some(source) = self.source.as_ref() {
+//             let source_string = source.display().to_string();
+//             source.exists().then_some(())
+//                 .ok_or(AWGError::InvalidUniformizationSource(source_string))?;
+//         }
+//         if let Some(polarizability) = self.polarizability {
+//             (polarizability < 0.0).then_some(())
+//                 .ok_or(AWGError::InvalidPolarizability(polarizability))?;
+//         }
+//         if let Some(mean_depth) = self.mean_depth {
+//             (mean_depth > 0.0).then_some(())
+//                 .ok_or(AWGError::InvalidMeanDepth(mean_depth))?;
+//         }
+//         if let Some(step_size) = self.step_size {
+//             (step_size > 0.0).then_some(())
+//                 .ok_or(AWGError::InvalidStepSize(step_size))?;
+//         }
+//         if let Some(error_threshold) = self.error_threshold {
+//             (error_threshold > 0.0).then_some(())
+//                 .ok_or(AWGError::InvalidErrorThreshold(error_threshold))?;
+//         }
+//         if let Some(max_iters) = self.max_iters {
+//             (max_iters > 0).then_some(())
+//                 .ok_or(AWGError::InvalidMaxIters(max_iters))?;
+//         }
+//         if let Some(num_imaging_avg) = self.num_imaging_avg {
+//             (num_imaging_avg > 0).then_some(())
+//                 .ok_or(AWGError::InvalidNumImagingAvg(num_imaging_avg))?;
+//         }
+//         if let Some(num_tweezers) = self.num_tweezers {
+//             (num_tweezers > 0).then_some(())
+//                 .ok_or(AWGError::InvalidNumTweezers(num_tweezers))?;
+//         }
+//         Ok(())
+//     }
+// }
+
+// pub fn run_uniformization<A, B>(
+//     mut awg: A,
+//     mut basler: B,
+//     waveform: &mut ArrayWaveform,
+//     params: &UniformizationParams,
+// ) -> AWGResult<()>
+// where
+//     A: std::ops::DerefMut<Target = AWG>,
+//     B: std::ops::DerefMut<Target = Basler>,
+// {
+//     unsafe {
+//         ffi::run_uniformization(
+//             awg.deref_mut().pinned(),
+//             basler.deref_mut().pinned(),
+//             waveform.pinned(),
+//             params.to_ffi_type()?.as_ref().unwrap(),
+//         )
+//         .map_err(AWGError::as_uniformization_err)
+//     }
+// }
diff --git a/awg-cxx/lib/awg_ffi.rs b/awg-cxx/lib/awg_ffi.rs
index 53bb031cfb35660eb975ad4b814b0b37bd0d4dbc..90d0de484999d52177a292c612caded9236da0e2 100644
--- a/awg-cxx/lib/awg_ffi.rs
+++ b/awg-cxx/lib/awg_ffi.rs
@@ -3,8 +3,8 @@ pub(crate) mod ffi {
 
     unsafe extern "C++" {
         include!("awg-cxx/awg-control/Cpp/lib/devices/AWG.h");
-        include!("awg-cxx/awg-control/Cpp/lib/devices/basler.h");
-        include!("awg-cxx/awg-control/Cpp/lib/waveform.h");
+        // include!("awg-cxx/awg-control/Cpp/lib/devices/basler.h");
+        // include!("awg-cxx/awg-control/Cpp/lib/waveform.h");
         include!("awg-cxx/include/awg.h");
     }
 
@@ -77,75 +77,75 @@ pub(crate) mod ffi {
 
         /**********************************************************************/
 
-        type ArrayWaveform;
-        type WaveformData;
+        // type ArrayWaveform;
+        // type WaveformData;
 
-        unsafe fn new_arraywaveform() -> Result<UniquePtr<ArrayWaveform>>;
+        // unsafe fn new_arraywaveform() -> Result<UniquePtr<ArrayWaveform>>;
 
-        unsafe fn arraywaveform_get_data_buffer(waveform: Pin<&mut ArrayWaveform>) -> Result<UniquePtr<PageAlignedMem>>;
-        unsafe fn arraywaveform_get_data_len(waveform: Pin<&mut ArrayWaveform>) -> Result<i64>;
+        // unsafe fn arraywaveform_get_data_buffer(waveform: Pin<&mut ArrayWaveform>) -> Result<UniquePtr<PageAlignedMem>>;
+        // unsafe fn arraywaveform_get_data_len(waveform: Pin<&mut ArrayWaveform>) -> Result<i64>;
 
-        unsafe fn arraywaveform_set_sampling_rate(waveform: Pin<&mut ArrayWaveform>, rate: u64) -> Result<()>;
-        unsafe fn arraywaveform_get_sampling_rate(waveform: Pin<&mut ArrayWaveform>) -> Result<u64>;
-        unsafe fn arraywaveform_set_freq_resolution(waveform: Pin<&mut ArrayWaveform>, resolution: u64) -> Result<()>;
-        unsafe fn arraywaveform_get_freq_resolution(waveform: Pin<&mut ArrayWaveform>) -> Result<u64>;
+        // unsafe fn arraywaveform_set_sampling_rate(waveform: Pin<&mut ArrayWaveform>, rate: u64) -> Result<()>;
+        // unsafe fn arraywaveform_get_sampling_rate(waveform: Pin<&mut ArrayWaveform>) -> Result<u64>;
+        // unsafe fn arraywaveform_set_freq_resolution(waveform: Pin<&mut ArrayWaveform>, resolution: u64) -> Result<()>;
+        // unsafe fn arraywaveform_get_freq_resolution(waveform: Pin<&mut ArrayWaveform>) -> Result<u64>;
 
-        unsafe fn arraywaveform_set_freq_tones_spaced(waveform: Pin<&mut ArrayWaveform>, center: i32, spacing: i32, num_tones: i32) -> Result<()>;
-        unsafe fn arraywaveform_set_freq_tones(waveform: Pin<&mut ArrayWaveform>, tones: &CxxVector<i32>) -> Result<()>;
-        unsafe fn arraywaveform_get_freq_tones(waveform: Pin<&mut ArrayWaveform>) -> Result<Vec<i32>>;
-        unsafe fn arraywaveform_set_phases(waveform: Pin<&mut ArrayWaveform>, phases: &CxxVector<f64>) -> Result<()>;
-        unsafe fn arraywaveform_get_phases(waveform: Pin<&mut ArrayWaveform>) -> Result<Vec<f64>>;
-        unsafe fn arraywaveform_set_amplitudes(waveform: Pin<&mut ArrayWaveform>, amplitudes: &CxxVector<f64>) -> Result<()>;
-        unsafe fn arraywaveform_get_amplitudes(waveform: Pin<&mut ArrayWaveform>) -> Result<Vec<f64>>;
-        unsafe fn arraywaveform_set_default_params(waveform: Pin<&mut ArrayWaveform>) -> Result<()>;
-        unsafe fn arraywaveform_save_params(waveform: Pin<&mut ArrayWaveform>, filename: &CxxString) -> Result<()>;
-        unsafe fn arraywaveform_load_params(waveform: Pin<&mut ArrayWaveform>, filename: &CxxString) -> Result<()>;
-        unsafe fn arraywaveform_print_params(waveform: Pin<&mut ArrayWaveform>) -> Result<()>;
+        // unsafe fn arraywaveform_set_freq_tones_spaced(waveform: Pin<&mut ArrayWaveform>, center: i32, spacing: i32, num_tones: i32) -> Result<()>;
+        // unsafe fn arraywaveform_set_freq_tones(waveform: Pin<&mut ArrayWaveform>, tones: &CxxVector<i32>) -> Result<()>;
+        // unsafe fn arraywaveform_get_freq_tones(waveform: Pin<&mut ArrayWaveform>) -> Result<Vec<i32>>;
+        // unsafe fn arraywaveform_set_phases(waveform: Pin<&mut ArrayWaveform>, phases: &CxxVector<f64>) -> Result<()>;
+        // unsafe fn arraywaveform_get_phases(waveform: Pin<&mut ArrayWaveform>) -> Result<Vec<f64>>;
+        // unsafe fn arraywaveform_set_amplitudes(waveform: Pin<&mut ArrayWaveform>, amplitudes: &CxxVector<f64>) -> Result<()>;
+        // unsafe fn arraywaveform_get_amplitudes(waveform: Pin<&mut ArrayWaveform>) -> Result<Vec<f64>>;
+        // unsafe fn arraywaveform_set_default_params(waveform: Pin<&mut ArrayWaveform>) -> Result<()>;
+        // unsafe fn arraywaveform_save_params(waveform: Pin<&mut ArrayWaveform>, filename: &CxxString) -> Result<()>;
+        // unsafe fn arraywaveform_load_params(waveform: Pin<&mut ArrayWaveform>, filename: &CxxString) -> Result<()>;
+        // unsafe fn arraywaveform_print_params(waveform: Pin<&mut ArrayWaveform>) -> Result<()>;
 
-        unsafe fn arraywaveform_get_min_sample_len(waveform: Pin<&mut ArrayWaveform>, sampling_rate: u64, frequency_resolution: u64) -> Result<u64>;
-        unsafe fn arraywaveform_get_sample_len(waveform: Pin<&mut ArrayWaveform>, tau: f64, sampling_rate: u64, frequency_resolution: u64) -> Result<u64>;
+        // unsafe fn arraywaveform_get_min_sample_len(waveform: Pin<&mut ArrayWaveform>, sampling_rate: u64, frequency_resolution: u64) -> Result<u64>;
+        // unsafe fn arraywaveform_get_sample_len(waveform: Pin<&mut ArrayWaveform>, tau: f64, sampling_rate: u64, frequency_resolution: u64) -> Result<u64>;
 
-        unsafe fn arraywaveform_get_static_waveform(waveform: Pin<&mut ArrayWaveform>) -> Result<UniquePtr<WaveformData>>;
-        unsafe fn arraywaveform_get_trick_waveform(waveform: Pin<&mut ArrayWaveform>, site_index: &CxxVector<i32>, df: f64, tau_move: f64, tau_stay: f64) -> Result<UniquePtr<WaveformData>>;
+        // unsafe fn arraywaveform_get_static_waveform(waveform: Pin<&mut ArrayWaveform>) -> Result<UniquePtr<WaveformData>>;
+        // unsafe fn arraywaveform_get_trick_waveform(waveform: Pin<&mut ArrayWaveform>, site_index: &CxxVector<i32>, df: f64, tau_move: f64, tau_stay: f64) -> Result<UniquePtr<WaveformData>>;
 
-        unsafe fn arraywaveform_save_waveform(waveform: Pin<&mut ArrayWaveform>, filename: &CxxString) -> Result<()>;
+        // unsafe fn arraywaveform_save_waveform(waveform: Pin<&mut ArrayWaveform>, filename: &CxxString) -> Result<()>;
 
-        unsafe fn waveformdata_get_mem(data: Pin<&mut WaveformData>) -> Result<UniquePtr<PageAlignedMem>>;
-        unsafe fn waveformdata_get_datalen(data: Pin<&mut WaveformData>) -> Result<i64>;
+        // unsafe fn waveformdata_get_mem(data: Pin<&mut WaveformData>) -> Result<UniquePtr<PageAlignedMem>>;
+        // unsafe fn waveformdata_get_datalen(data: Pin<&mut WaveformData>) -> Result<i64>;
 
-        /**********************************************************************/
+        // /**********************************************************************/
 
-        type BaslerBox;
-
-        unsafe fn new_basler(index: i32) -> Result<UniquePtr<BaslerBox>>;
-
-        unsafe fn basler_print_device_info(basler: Pin<&mut BaslerBox>) -> Result<()>;
-        unsafe fn basler_set_exposure(basler: Pin<&mut BaslerBox>, exposure_time: f64) -> Result<()>;
-        unsafe fn basler_get_exposure(basler: Pin<&mut BaslerBox>) -> Result<f64>;
-        unsafe fn basler_set_frame_rate(basler: Pin<&mut BaslerBox>, frame_rate: u32) -> Result<()>;
-        unsafe fn basler_set_frame_rate_max(basler: Pin<&mut BaslerBox>) -> Result<()>;
-        unsafe fn basler_set_gain(basler: Pin<&mut BaslerBox>, gain: f64) -> Result<()>;
-        unsafe fn basler_get_gain(basler: Pin<&mut BaslerBox>) -> Result<f64>;
-        unsafe fn basler_set_roi(basler: Pin<&mut BaslerBox>, offset_x: u32, offset_y: u32, width: u32, height: u32) -> Result<()>;
-        unsafe fn basler_get_offset_x(basler: Pin<&mut BaslerBox>) -> Result<u32>;
-        unsafe fn basler_get_offset_y(basler: Pin<&mut BaslerBox>) -> Result<u32>;
-        unsafe fn basler_get_width(basler: Pin<&mut BaslerBox>) -> Result<u32>;
-        unsafe fn basler_get_height(basler: Pin<&mut BaslerBox>) -> Result<u32>;
-        unsafe fn basler_set_acquisition_mode(basler: Pin<&mut BaslerBox>, mode: &CxxString) -> Result<()>;
-        unsafe fn basler_print_available_acquisition_modes(basler: Pin<&mut BaslerBox>) -> Result<()>;
-
-        unsafe fn basler_start_grabbing(basler: Pin<&mut BaslerBox>) -> Result<()>;
-        unsafe fn basler_stop_grabbing(basler: Pin<&mut BaslerBox>) -> Result<()>;
-        unsafe fn basler_is_grabbing(basler: Pin<&mut BaslerBox>) -> Result<bool>;
-        unsafe fn basler_get_image(basler: Pin<&mut BaslerBox>) -> Result<Vec<u8>>;
+        // type BaslerBox;
 
-        /**********************************************************************/
+        // unsafe fn new_basler(index: i32) -> Result<UniquePtr<BaslerBox>>;
+
+        // unsafe fn basler_print_device_info(basler: Pin<&mut BaslerBox>) -> Result<()>;
+        // unsafe fn basler_set_exposure(basler: Pin<&mut BaslerBox>, exposure_time: f64) -> Result<()>;
+        // unsafe fn basler_get_exposure(basler: Pin<&mut BaslerBox>) -> Result<f64>;
+        // unsafe fn basler_set_frame_rate(basler: Pin<&mut BaslerBox>, frame_rate: u32) -> Result<()>;
+        // unsafe fn basler_set_frame_rate_max(basler: Pin<&mut BaslerBox>) -> Result<()>;
+        // unsafe fn basler_set_gain(basler: Pin<&mut BaslerBox>, gain: f64) -> Result<()>;
+        // unsafe fn basler_get_gain(basler: Pin<&mut BaslerBox>) -> Result<f64>;
+        // unsafe fn basler_set_roi(basler: Pin<&mut BaslerBox>, offset_x: u32, offset_y: u32, width: u32, height: u32) -> Result<()>;
+        // unsafe fn basler_get_offset_x(basler: Pin<&mut BaslerBox>) -> Result<u32>;
+        // unsafe fn basler_get_offset_y(basler: Pin<&mut BaslerBox>) -> Result<u32>;
+        // unsafe fn basler_get_width(basler: Pin<&mut BaslerBox>) -> Result<u32>;
+        // unsafe fn basler_get_height(basler: Pin<&mut BaslerBox>) -> Result<u32>;
+        // unsafe fn basler_set_acquisition_mode(basler: Pin<&mut BaslerBox>, mode: &CxxString) -> Result<()>;
+        // unsafe fn basler_print_available_acquisition_modes(basler: Pin<&mut BaslerBox>) -> Result<()>;
+
+        // unsafe fn basler_start_grabbing(basler: Pin<&mut BaslerBox>) -> Result<()>;
+        // unsafe fn basler_stop_grabbing(basler: Pin<&mut BaslerBox>) -> Result<()>;
+        // unsafe fn basler_is_grabbing(basler: Pin<&mut BaslerBox>) -> Result<bool>;
+        // unsafe fn basler_get_image(basler: Pin<&mut BaslerBox>) -> Result<Vec<u8>>;
+
+        // /**********************************************************************/
 
-        type UniformizationParams;
+        // type UniformizationParams;
 
-        unsafe fn new_uniformizationparams(source: &CxxString, polarizability: f64, mean_depth: f64, step_size: f64, error_threshold: f64, max_iters: i32, num_imaging_avg: i32, num_tweezers: i32) -> Result<UniquePtr<UniformizationParams>>;
+        // unsafe fn new_uniformizationparams(source: &CxxString, polarizability: f64, mean_depth: f64, step_size: f64, error_threshold: f64, max_iters: i32, num_imaging_avg: i32, num_tweezers: i32) -> Result<UniquePtr<UniformizationParams>>;
 
-        unsafe fn run_uniformization(awg: Pin<&mut AWGBox>, basler: Pin<&mut BaslerBox>, waveform: Pin<&mut ArrayWaveform>, params: &UniformizationParams) -> Result<()>;
+        // unsafe fn run_uniformization(awg: Pin<&mut AWGBox>, basler: Pin<&mut BaslerBox>, waveform: Pin<&mut ArrayWaveform>, params: &UniformizationParams) -> Result<()>;
 
     }
 }
diff --git a/awg-cxx/lib/config.rs b/awg-cxx/lib/config.rs
index 405725dd26c0d349c46cd5b6bf9359055274ad79..eacf53e240d03f98c60834b2df6f038b52591028 100644
--- a/awg-cxx/lib/config.rs
+++ b/awg-cxx/lib/config.rs
@@ -9,6 +9,7 @@ use crate::awg::{
     TriggerMask,
     TriggerMode,
     TriggerTerm,
+    TriggerCoupling
 };
 
 #[derive(Error, Debug)]
@@ -16,15 +17,24 @@ pub enum ConfigError {
     #[error("invalid trigger level {0} for {1}: must be in the range -10000..+10000 mV")]
     InvalidTriggerLevel(i32, &'static str),
 
-    #[error("number of active channels must not be 3 and ≤ 4")]
+    #[error("length of enabled_channels, amplitudes, and stop_levels must be 4")]
+    ChannelLength,
+
+    #[error("number of enabled channels must not be 3 and <= 4")]
     NumChannels,
 
-    #[error("channel {0}: amplitude must be in range 0..2^15 - 1")]
+    #[error("channel {0}: amplitude must be in range 80..2500 mV")]
     InvalidAmplitude(usize),
 
     #[error("invalid next_step index {0} at step {1}")]
     InvalidStepIndex(usize, usize),
 
+    #[error("number of memory segments must be power of 2, got {0}")]
+    InvalidNumSegments(u32),
+
+    #[error("number of sequence segments must be less than or equal to number of memory segments, got {0}")]
+    InvalidNumSequenceSegments(usize),
+
     // #[error("non-unique segment index {0} at step {1}")]
     // NonUniqueSegmentIndex(usize, usize),
 
@@ -39,6 +49,7 @@ pub struct TriggerParams {
     pub mode: TriggerMode,
     pub level: i32,
     pub rearm_level: i32,
+    pub coupling: TriggerCoupling,
 }
 
 /// Configuration for the two `ext*` trigger channels.
@@ -72,19 +83,32 @@ impl TriggerConfig {
 
 /// Output channel configuration.
 ///
-/// Channel amplitudes must be in the range 0..2^15 - 1.
-#[derive(Copy, Clone, Debug)]
+/// Channel amplitudes must be in the range 80..2500 mV.
+#[derive(Clone, Debug)]
 pub struct ChannelConfig {
-    pub channel: usize,
-    pub enabled: bool,
-    pub amplitude: u32, // ∊ [0, 2^15 - 1]
-    pub stop_level: ChannelStopLevel,
+    pub enabled_channels: [bool; 4],
+    pub amplitudes: [i32; 4], // ∊ [80, 2500] mV
+    pub stop_levels: Vec<ChannelStopLevel>, // must be 4 elements, use of vec here is not ideal, but okay for now
 }
 
 impl ChannelConfig {
     pub fn check(&self) -> ConfigResult<()> {
-        (0..32767_u32).contains(&self.amplitude).then_some(())
-            .ok_or(ConfigError::InvalidAmplitude(self.channel))?;
+        let channels = &self.enabled_channels;
+        (self.stop_levels.len() == 4)
+            .then_some(())
+            .ok_or(ConfigError::ChannelLength)?;
+        (channels.iter().filter(|&&c| c).count() != 3)
+            .then_some(())
+            .ok_or(ConfigError::NumChannels)?;
+        self.amplitudes
+            .iter()
+            .enumerate()
+            .map(|(i, &amp)| {
+                (80..=2500).contains(&amp).then_some(())
+                    .ok_or(ConfigError::InvalidAmplitude(i))
+            })
+            .collect::<ConfigResult<Vec<_>>>()?;
+
         Ok(())
     }
 }
@@ -98,23 +122,24 @@ pub enum SequenceKind {
 
 /// Sequence step configuration.
 #[derive(Clone, Debug)]
-pub struct SequenceStepConfig {
+pub struct SequenceSegmentsConfig {
     pub source: PathBuf,
-    pub kind: SequenceKind,
-    pub next_step: usize, // < number of steps
+    pub step: usize, // step index associated with this segment
+    pub next_step: usize, // next step index to jump to
     pub n_loop: u32,
-    pub condition: SeqLoopCondition,
+    pub loop_condition: SeqLoopCondition,
 }
 
 /// Overall AWG configuration.
 #[derive(Clone, Debug)]
 pub struct Config {
+    pub r#use: bool, // whether to use the AWG
     pub index: usize,
     pub sampling_rate: u64, // ≥ 1
-    pub frequency_resolution: u64, // ≥ 1
+    pub num_segments: u32, // must be a power of 2
     pub trigger: TriggerConfig,
-    pub active_channels: Vec<ChannelConfig>,
-    pub sequence_steps: Vec<SequenceStepConfig>,
+    pub active_channels: ChannelConfig,
+    pub sequence_segments: Vec<SequenceSegmentsConfig>,
 }
 
 impl Config {
@@ -124,70 +149,44 @@ impl Config {
         trigger.check()
     }
 
-    pub(crate) fn check_channels(channels: &[ChannelConfig])
+    pub(crate) fn check_channels(channels: &ChannelConfig)
         -> ConfigResult<()>
     {
-        let n_channels = channels.len();
-        (n_channels <= 4 && n_channels != 3).then_some(())
-            .ok_or(ConfigError::NumChannels)?;
-        channels.iter()
-            .map(|ch| ch.check())
-            .collect::<ConfigResult<Vec<()>>>()?;
-        Ok(())
+        channels.check()
     }
 
-    pub(crate) fn check_sequence_steps(steps: &[SequenceStepConfig])
+    pub(crate) fn check_sequence_segments(segments: &[SequenceSegmentsConfig])
         -> ConfigResult<()>
     {
-        let n_steps = steps.len();
-        let step_range = 0..n_steps;
-        // let mut segments: Vec<usize> = (0..n_steps).collect();
-        steps.iter().enumerate()
-            .map(|(k, step)| {
-                Ok((k, step))
-                    // .and_then(|(k, step)| {
-                    //     step_range.contains(&step.segment)
-                    //         .then_some((k, step))
-                    //         .ok_or(ConfigError::InvalidStepIndex(
-                    //             step.segment, k
-                    //         ))
-                    // })
-                    // .and_then(|(k, step)| {
-                    //     segments.iter().enumerate()
-                    //         .find_map(|(i, seg)| {
-                    //             (*seg == step.segment).then_some(i)
-                    //         })
-                    //         .map(|i| {
-                    //             segments.swap_remove(i);
-                    //             (k, step)
-                    //         })
-                    //         .ok_or(ConfigError::NonUniqueSegmentIndex(
-                    //             step.segment, k
-                    //         ))
-                    // })
-                    .and_then(|(k, step)| {
-                        step_range.contains(&step.next_step)
-                            .then_some((k, step))
-                            .ok_or(ConfigError::InvalidStepIndex(
-                                step.next_step, k
-                            ))
-                    })
-                    .and_then(|(k, step)| {
-                        step.source.exists().then_some((k, step))
+        segments.iter().enumerate()
+            .map(|(k, seg)| {
+                Ok((k, seg))
+                    .and_then(|(k, seg)| {
+                        (seg.source.exists() && seg.source.ends_with(".txt"))
+                            .then_some((k, seg))
                             .ok_or(ConfigError::MissingWaveformSource(
-                                step.source.display().to_string(), k
+                                seg.source.display().to_string(), k
                             ))
                     })
             })
             .collect::<ConfigResult<Vec<_>>>()?;
         Ok(())
-    }
+    } //
 
-    /// Verify that all data satisfies necessary constraints.
+    /// Verify that all data satisfies necessary constraints, commenting out since 
+    /// this is already checked at the camera control config level.
     pub fn check(&self) -> ConfigResult<()> {
-        Self::check_trigger_params(&self.trigger)?;
-        Self::check_channels(&self.active_channels)?;
-        Self::check_sequence_steps(&self.sequence_steps)?;
+        // let n_segments = self.num_segments;
+        // (n_segments & (n_segments - 1) == 0) // this checks if n_segments is a power of 2
+        //     .then_some(())
+        //     .ok_or(ConfigError::InvalidNumSegments(n_segments))?;
+        // let n_seq_segments = &self.sequence_segments.len();
+        // (*n_seq_segments as u32 > n_segments)
+        //     .then_some(())
+        //     .ok_or(ConfigError::InvalidNumSequenceSegments(*n_seq_segments))?;
+        // Self::check_trigger_params(&self.trigger)?;
+        // Self::check_channels(&self.active_channels)?;
+        // Self::check_sequence_segments(&self.sequence_segments)?;
         Ok(())
     }
 }
diff --git a/awg-cxx/lib/lib.rs b/awg-cxx/lib/lib.rs
index ad29185a7ea11d60e3ff475e8a3ffcddbdf56882..4a53e557a6d7d13e5ccd7c7b45db28b573ea017b 100644
--- a/awg-cxx/lib/lib.rs
+++ b/awg-cxx/lib/lib.rs
@@ -2,7 +2,7 @@
 
 pub(crate) mod awg_ffi;
 pub mod awg;
-pub mod basler;
+// pub mod basler;
 pub mod config;
 pub mod prelude;
 
diff --git a/awg-cxx/lib/prelude.rs b/awg-cxx/lib/prelude.rs
index 4a71ddbc23178a5e36d4e17e80e62d2712e46470..291850e29facd0e2397fc9c7b028ae75f13f3ae6 100644
--- a/awg-cxx/lib/prelude.rs
+++ b/awg-cxx/lib/prelude.rs
@@ -1,11 +1,11 @@
 pub use crate::{
     awg::{
         AWG,
-        ArrayWaveform,
-        PageAlignedMem,
-        WaveformDataPtr,
-        WaveformParams,
-        BufferType,
+        // ArrayWaveform,
+        // PageAlignedMem,
+        // WaveformDataPtr,
+        // WaveformParams,
+        // BufferType,
         ChannelPair,
         ChannelStopLevel,
         ClockMode,
@@ -17,23 +17,23 @@ pub use crate::{
         TriggerMask,
         TriggerMode,
         TriggerTerm,
-        UniformizationParams,
+        // UniformizationParams,
         AWGError,
         AWGResult,
     },
-    basler::{
-        Basler,
-        Config as BaslerConfig,
-        AcquisitionMode,
-        BaslerError,
-        BaslerResult,
-    },
+    // basler::{
+    //     Basler,
+    //     Config as BaslerConfig,
+    //     AcquisitionMode,
+    //     BaslerError,
+    //     BaslerResult,
+    // },
     config::{
         TriggerParams,
         TriggerConfig,
         ChannelConfig,
         SequenceKind,
-        SequenceStepConfig,
+        SequenceSegmentsConfig,
         Config,
         ConfigError,
         ConfigResult,
diff --git a/config.toml b/config.toml
index 63f5aae3e2bf43865aa2d75ab9d81666e57fffa8..7bebff88be54bf55ef9cf6c73d40794236d6ded4 100644
--- a/config.toml
+++ b/config.toml
@@ -78,7 +78,7 @@ enabled = false
 image_dim = [3, 3] # [int, int] : [w, h]
 n_loop = 3 # int : images per loop
 mode = "bright" # str : bright, dark
-box = [9, 3, 3, 3] # [int, int, int, int] : sub-ROI for photon counting
+counting_box = [9, 3, 3, 3] # [int, int, int, int] : sub-ROI for photon counting
 threshold = 12.0 # float
 
 [processing.ff_series]
@@ -86,61 +86,62 @@ enabled = false
 image_dim = [3, 3] # [int, int] : [w, h]
 n_loop = 14 # int : images per loop
 mode = "xor" # str : xor, xnor, alternate
-box = [9, 3, 3, 3] # [int, int, int, int] : sub-ROI for photon counting
+counting_box = [9, 3, 3, 3] # [int, int, int, int] : sub-ROI for photon counting
 threshold = 12.0 # float
 
 [awg]
-index = 0 # int : 0, 1
+use = false # bool
+index = 1 # int : 0, 1
 sampling_rate = 614400000 # int, Hz
-frequency_resolution = 1000 # int, Hz
+num_segments = 2 # int : must be pow(2)
+# frequency_resolution = 1000 # int, Hz
 
 [awg.trigger]
 masks_or = ["ext0"] # [str] : none, software, ext0, ext1
-masks_and = [] # [str] : none, software, ext0, ext1
+masks_and = ["none"] # [str] : none, ext0, ext1
 termination = "fiftyohm" # str : high, fiftyohm
 
 [awg.trigger.ext0]
 mode = "pos" # str : none, pos, neg, both, high, low, winenter, winleave, inwin, outsidewin, pos_rearm, neg_rearm
-level = 2500 # int : [-10000, +10000] mV
-rearm_level = 2500 # int : [-10000, +10000] mV
+level = 1000 # int : [-10000, +10000] mV
+rearm_level = 1000 # int : [-10000, +10000] mV, used only in "win*, *win, *_rearm" modes
+coupling = "dc" # str : dc, ac
 
 [awg.trigger.ext1]
 mode = "none" # str : none, pos, neg, both, high, low, winenter, winleave, inwin, outsidewin, pos_rearm, neg_rearm
-level = 2500 # int : [-10000, +10000] mV
-rearm_level = 2500 # int : [-10000, +10000] mV
-
-[[awg.active_channels]]
-channel = 0 # int
-enabled = true # bool
-amplitude = 2500 # int : 0..2^15-1
-stop_level = "zero" # str : low, high, hold-last, zero
-
-[[awg.sequence_steps]]
-source = "<file>" # str
-kind = "static" # str : static, trick
-segment = 0 # int
-next_segment = 1 # int
+level = 1000 # int : [-10000, +10000] mV
+rearm_level = 1000 # int : [-10000, +10000] mV
+coupling = "dc" # str : dc, ac
+
+[awg.active_channels]
+enabled_channels = [true, false, false, false] # [bool, bool, bool, bool] : [ch0, ch1, ch2, ch3]
+amplitudes = [2500, 2500, 2500, 2500] # [80, 2500] mV
+stop_levels = ["zero", "zero", "zero", "zero"] # str : low, high, hold-last, zero
+
+[[awg.sequence_segments]]
+source = "/home/coveylab/Documents/Control/awg-control/Python/data/wfmPython.txt" # str
+step = 0 # int
+next_step = 0 # int
 n_loop = 1 # int : ≥ 1
-condition = "ontrigger" # str : always, ontrigger, end
-
-[[awg.sequence_steps]]
-source = "<file>" # str
-kind = "static" # str : static, trick
-segment = 1 # int
-next_segment = 0 # int
-n_loop = 1 # int
-condition = "ontrigger" # str : always, ontrigger, end
-
-[awg.uniformization]
-source = "/home/coveylab/Documents/Data/atomic_data/20231013/probe-scan_000" # str
-# polarizability = -1.24e-3 # float : kHz/μK (H-polarized tweezers, measured 2023.08.08)
-polarizability_kHz_uK = -5.6e-3 # float : kHz/μK (V-polarized tweezers, measured 2023.08.08)
-mean_depth_uK = 575.0 # float : μK
-step_size = 50.0 # float : 0..2^15 - 1
-error_threshold = 0.003 # float
-max_iters = 50 # int
-num_imaging_avg = 10 # int
-num_tweezers = 20 # int
+loop_condition = "ontrigger" # str : always, ontrigger, end
+
+# [[awg.sequence_segments]]
+# source = "<file>" # str
+# step = 1 # int
+# next_step = 0 # int
+# n_loop = 1 # int : ≥ 1
+# loop_condition = "ontrigger" # str : always, ontrigger, end
+
+# [awg.uniformization]
+# source = "/home/coveylab/Documents/Data/atomic_data/20231013/probe-scan_000" # str
+# # polarizability = -1.24e-3 # float : kHz/μK (H-polarized tweezers, measured 2023.08.08)
+# polarizability_kHz_uK = -5.6e-3 # float : kHz/μK (V-polarized tweezers, measured 2023.08.08)
+# mean_depth_uK = 575.0 # float : μK
+# step_size = 50.0 # float : 0..2^15 - 1
+# error_threshold = 0.003 # float
+# max_iters = 50 # int
+# num_imaging_avg = 10 # int
+# num_tweezers = 20 # int
 
 [timetagger]
 use = false # bool
@@ -151,11 +152,11 @@ stop_channel = 3 # int
 binwidth_sec = 0.015 # float
 n_bins = 1 # int
 
-[basler]
-index = 0 # int
-exposure_time_us = 1200.0 # float
-gain = 0.0 # float
-acquisition_mode = "continuous" # str : continuous, <?...>
-frame_rate = 10 # int
-roi = [0, 500, 0, 500] # [xmin, xmax, ymin, ymax]
+# [basler]
+# index = 0 # int
+# exposure_time_us = 1200.0 # float
+# gain = 0.0 # float
+# acquisition_mode = "continuous" # str : continuous, <?...>
+# frame_rate = 10 # int
+# roi = [0, 500, 0, 500] # [xmin, xmax, ymin, ymax]
 
diff --git a/librs/actions.rs b/librs/actions.rs
index 3f412bbdd3d6c6ea8c8e9188b8eea3994f91bd84..9efa646c5f434a033930a616e1a23debfb365ca2 100644
--- a/librs/actions.rs
+++ b/librs/actions.rs
@@ -65,6 +65,9 @@ pub enum ActionError {
     #[error("AWG error: {0}")]
     AWGError(#[from] awg::AWGError),
 
+    #[error("AWG mutability lock has been violated")]
+    AWGMutabilityLock,
+
     #[error("timetagger mutability lock has been violated")]
     TaggerMutabilityLock,
 
@@ -109,6 +112,11 @@ pub static ACTION_REGISTRY: phf::Map<&'static str, ActionFn>
         "acquire" => acquire,
         "camera-info" => camera_info,
         "temperature" => temperature,
+        "temperature-monitor" => temperature_monitor,
+        "awg-run" =>awg_run,
+        "awg-stop" => awg_stop,
+        "awg-trigger" => awg_trigger,
+        "awg-reload-config" => awg_reload_config,
         // "get-capabilities" => get_capabilities,
     };
 
@@ -269,6 +277,39 @@ pub fn temperature(
     }
 }
 
+pub fn temperature_monitor(
+    devices: Arc<ArcDevices>,
+    _config: &config::Config,
+    _args: &[String],
+) -> ActionResult<()>
+{
+    struct Stop;
+    let cam = devices.camera();
+    let (tx, rx) = unbounded::<Stop>();
+    println!("Press ENTER to stop:");
+    let slave = std::thread::spawn(move || -> ActionResult<()> {
+        let mut line: String = "".to_string();
+        loop {
+            if rx.try_recv().is_ok() {
+                println!();
+                break;
+            }
+            let (temperature, status): (f32, andor::TemperatureStatus)
+                = cam.get_temperature()?;
+            print_flush!("\r{}", " ".repeat(line.len()));
+            line = format!("  Temperature: {:+.1}C; {:?} ", temperature, status);
+            print_flush!("\r{}", line);
+            std::thread::sleep(std::time::Duration::from_millis(1000));
+        }
+        Ok(())
+    });
+
+    let mut line = String::new();
+    std::io::stdin().read_line(&mut line)?;
+    tx.send(Stop).expect("termination channel closed unexpectedly");
+    return jointhrough!(slave);
+}
+
 macro_rules! convert_cmap_bound {
     ($val:expr) => {
         if $val < 0.0 {
@@ -392,13 +433,9 @@ pub fn acquire(
                 = outpath.join(format!("{}_{:03}.npy", out_name, counter));
         }
         outfile = Some(test_out.clone());
-        let mut file_name = test_out.file_stem().unwrap().to_owned();
-        file_name.push(format!("_tt.npy"));
         outfile_tt = Some(
-            // test_out.with_file_name(
-            //     format!("{:?}_tt.npy", test_out.file_stem().unwrap().to_str().unwrap())
-            // )
-            test_out.with_file_name(file_name)
+            test_out.with_file_name(
+                format!("{:?}_tt.npy", test_out.file_stem().unwrap()))
         );
         println!("Save data to file '{}'", outfile.as_ref().unwrap().display());
     }
@@ -513,6 +550,78 @@ pub fn acquire(
     Ok(())
 }
 
+pub fn awg_run(
+    devices: Arc<ArcDevices>,
+    _config: &config::Config,
+    _args: &[String],
+) -> ActionResult<()>
+{
+    if let Some(awg_arc) = devices.awg().as_mut() {
+        awg_arc.lock()
+            .map_err(|_| ActionError::AWGMutabilityLock)?
+            .card_run()?
+            .force_trigger()?
+        ;
+        println!("AWG is running");
+    } else {
+        println!("AWG is not connected");
+    }
+    Ok(())
+}
+
+pub fn awg_stop(
+    devices: Arc<ArcDevices>,
+    _config: &config::Config,
+    _args: &[String],
+) -> ActionResult<()>
+{
+    if let Some(awg_arc) = devices.awg().as_mut() {
+        awg_arc.lock()
+            .map_err(|_| ActionError::AWGMutabilityLock)?
+            .card_stop()?;
+        println!("AWG is stopped");
+    } else {
+        println!("AWG is not connected");
+    }
+    Ok(())
+}
+
+pub fn awg_trigger(
+    devices: Arc<ArcDevices>,
+    _config: &config::Config,
+    _args: &[String],
+) -> ActionResult<()>
+{
+    if let Some(awg_arc) = devices.awg().as_mut() {
+        awg_arc.lock()
+            .map_err(|_| ActionError::AWGMutabilityLock)?
+            .force_trigger()?;
+        println!("AWG is triggered");
+    } else {
+        println!("AWG is not connected");
+    }
+    Ok(())
+}
+
+pub fn awg_reload_config(
+    devices: Arc<ArcDevices>,
+    config: &config::Config,
+    _args: &[String],
+) -> ActionResult<()>
+{
+    if let Some(awg_arc) = devices.awg().as_mut() {
+        awg_arc.lock()
+            .map_err(|_| ActionError::AWGMutabilityLock)?
+            .card_stop()?
+            .set_config(&config.awg.clone().into())?;
+        println!("AWG config reloaded");
+    } else {
+        println!("AWG is not connected");
+    }
+    Ok(())
+}
+    
+
 /// Acquisition abort signal.
 pub struct AbortAcquisition;
 
diff --git a/librs/cli.rs b/librs/cli.rs
index caff2b8b635bb0a34667ea7e43a19244a2b7ee99..8648e65bac6f78c8907749479f204a6c14389720 100644
--- a/librs/cli.rs
+++ b/librs/cli.rs
@@ -647,6 +647,31 @@ Available commands:",
                                 continue;
                             }
                         }
+                        if config.awg.r#use && !devices.has_awg() {
+                            if let Some(dev) = Arc::get_mut(&mut devices) {
+                                let awg_index: usize = config.awg.index;
+                                let mut awg
+                                    = continue_err!(AWG::connect(awg_index));
+                                awg.set_config(&config.awg.clone().into())?;
+                                dev.use_awg(awg);
+                            } else {
+                                println!(
+                                    "Error: device mutability check failed. \
+                                    This shouldn't happen!"
+                                );
+                                continue;
+                            }
+                        } else if !config.awg.r#use && devices.has_awg() {
+                            if let Some(dev) = Arc::get_mut(&mut devices) {
+                                dev.remove_awg();
+                            } else {
+                                println!(
+                                    "Error: device mutability check failed. \
+                                    This shouldn't happen!"
+                                );
+                                continue;
+                            }
+                        }
                         let action_res
                             = action_f(
                                 devices.clone(),
diff --git a/librs/colors.rs b/librs/colors.rs
index 0c940f5271da67641bf8a232b3b118bd408cdca8..207de2502c408abb5a5e2e47e54617b9859cf3c2 100644
--- a/librs/colors.rs
+++ b/librs/colors.rs
@@ -55,9 +55,6 @@ pub enum ColorMap {
     /// Linear scale from blue through gold to white.
     Pix(Pix),
 
-    /// Linear scale from black to 556nm (modeled as `#b6ff00`).
-    AtomWL(AtomWL),
-
     /// Specify a custom colormap as a list of colors at different points with
     /// linear interpolation in between.
     LinearSegmentedColorMap(LinearSegmentedColorMap),
@@ -116,11 +113,6 @@ impl ColorMap {
         Self::Pix(Pix::new())
     }
 
-    /// Create a new [AtomWL] colormap.
-    pub fn new_atom_wl() -> Self {
-        return Self::AtomWL(AtomWL::new());
-    }
-
     /// Create a new linear segmented colormap.
     pub fn new_linear_segmented_colormap(colormap: LinearSegmentedColorMap)
         -> Self
@@ -145,7 +137,6 @@ impl ColorMap {
             Self::Vibrant(v) => v.c(x),
             Self::Artsy(a) => a.c(x),
             Self::Pix(p) => p.c(x),
-            Self::AtomWL(a) => a.c(x),
             Self::FuncColorMap(f) => f.c(x),
             Self::LinearSegmentedColorMap(l) => l.c(x),
         }
@@ -353,15 +344,6 @@ make_linsegcolormap!(
     }
 );
 
-make_linsegcolormap!(
-    "Linear scale black -> 556nm (`#b6ff00`)",
-    AtomWL,
-    colors: {
-        0.000 => (  0,   0,   0),
-        1.000 => (182, 255,   0),
-    }
-);
-
 /// Color scale based on a provided function.
 #[derive(Copy, Clone, Debug)]
 pub struct FuncColorMap {
diff --git a/librs/config.rs b/librs/config.rs
index 24eb5054c59bde11cc3a99b109314442fab41bb4..bc505145afb433430b180d346c05996d2f626de3 100644
--- a/librs/config.rs
+++ b/librs/config.rs
@@ -322,12 +322,29 @@ impl From<AWGTriggerTerm> for awg::TriggerTerm {
     }
 }
 
+/// awg.trigger.coupling
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
+pub enum AWGTriggerCoupling {
+    dc,
+    ac,
+}
+
+impl From<AWGTriggerCoupling> for awg::TriggerCoupling {
+    fn from(coupling: AWGTriggerCoupling) -> Self {
+        match coupling {
+            AWGTriggerCoupling::dc => Self::DC,
+            AWGTriggerCoupling::ac => Self::AC,
+        }
+    }
+}
+
 /// `awg.trigger.ext0`, `awg.trigger.ext1`
 #[derive(Copy, Clone, Debug, Deserialize)]
 pub struct AWGTriggerParams {
     pub mode: AWGTriggerMode,
     pub level: i32,
     pub rearm_level: i32,
+    pub coupling: AWGTriggerCoupling, // 
 }
 
 impl From<AWGTriggerParams> for awg::TriggerParams {
@@ -336,6 +353,7 @@ impl From<AWGTriggerParams> for awg::TriggerParams {
             mode: params.mode.into(),
             level: params.level,
             rearm_level: params.rearm_level,
+            coupling: params.coupling.into(),
         }
     }
 }
@@ -407,21 +425,20 @@ impl From<AWGChannelStopLevel> for awg::ChannelStopLevel {
 }
 
 /// `awg.active_channels[...]`
-#[derive(Copy, Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Deserialize)]
 pub struct AWGChannel {
-    pub channel: usize,
-    pub enabled: bool,
-    pub amplitude: u32, // ∊ [0, 2^15 - 1]
-    pub stop_level: AWGChannelStopLevel,
+    pub enabled_channels: [bool; 4], // ∈ {true, false}
+    pub amplitudes: [i32; 4], // ∊ [80, 2500] mV
+    pub stop_levels: Vec<AWGChannelStopLevel>, // must be 4 elements, use of vec here is not ideal, but okay for now
 }
 
 impl From<AWGChannel> for awg::ChannelConfig {
     fn from(channel_conf: AWGChannel) -> Self {
         Self {
-            channel: channel_conf.channel,
-            enabled: channel_conf.enabled,
-            amplitude: channel_conf.amplitude,
-            stop_level: channel_conf.stop_level.into(),
+            enabled_channels: channel_conf.enabled_channels,
+            amplitudes: channel_conf.amplitudes,
+            stop_levels: channel_conf.stop_levels.into_iter()
+                .map(|sl| sl.into()).collect(),
         }
     }
 }
@@ -462,79 +479,79 @@ impl From<AWGSequenceCondition> for awg::SeqLoopCondition {
 
 /// `awg.sequence_steps[...]`
 #[derive(Clone, Debug, Deserialize)]
-pub struct AWGSequenceStep {
+pub struct AWGSequenceSegment {
     pub source: PathBuf,
-    pub kind: AWGSequenceKind,
+    pub step: usize,
     pub next_step: usize, // < number of steps
     pub n_loop: u32,
-    pub condition: AWGSequenceCondition,
+    pub loop_condition: AWGSequenceCondition,
 }
 
-impl From<AWGSequenceStep> for awg::SequenceStepConfig {
-    fn from(seq_step: AWGSequenceStep) -> Self {
+impl From<AWGSequenceSegment> for awg::SequenceSegmentsConfig {
+    fn from(seq_seg: AWGSequenceSegment) -> Self {
         Self {
-            source: seq_step.source,
-            kind: seq_step.kind.into(),
-            next_step: seq_step.next_step,
-            n_loop: seq_step.n_loop,
-            condition: seq_step.condition.into(),
+            source: seq_seg.source,
+            step: seq_seg.step,
+            next_step: seq_seg.next_step,
+            n_loop: seq_seg.n_loop,
+            loop_condition: seq_seg.loop_condition.into(),
         }
     }
 }
 
 /// `awg.uniformization`
-#[derive(Clone, Debug, Deserialize)]
-pub struct AWGUniformizationParams {
-    pub source: PathBuf,
-    pub polarizability_kHz_uK: f64,
-    pub mean_depth_uK: f64,
-    pub step_size: f64,
-    pub error_threshold: f64,
-    pub max_iters: usize,
-    pub num_imaging_avg: usize,
-    pub num_tweezers: usize,
-}
-
-impl From<AWGUniformizationParams> for awg::UniformizationParams {
-    fn from(params: AWGUniformizationParams) -> Self {
-        Self::new_all(
-            params.source,
-            params.polarizability_kHz_uK,
-            params.mean_depth_uK,
-            params.step_size,
-            params.error_threshold,
-            params.max_iters,
-            params.num_imaging_avg,
-            params.num_tweezers,
-        )
-    }
-}
+// #[derive(Clone, Debug, Deserialize)]
+// pub struct AWGUniformizationParams {
+//     pub source: PathBuf,
+//     pub polarizability_kHz_uK: f64,
+//     pub mean_depth_uK: f64,
+//     pub step_size: f64,
+//     pub error_threshold: f64,
+//     pub max_iters: usize,
+//     pub num_imaging_avg: usize,
+//     pub num_tweezers: usize,
+// }
+
+// impl From<AWGUniformizationParams> for awg::UniformizationParams {
+//     fn from(params: AWGUniformizationParams) -> Self {
+//         Self::new_all(
+//             params.source,
+//             params.polarizability_kHz_uK,
+//             params.mean_depth_uK,
+//             params.step_size,
+//             params.error_threshold,
+//             params.max_iters,
+//             params.num_imaging_avg,
+//             params.num_tweezers,
+//         )
+//     }
+// }
 
 /// `awg`
 #[derive(Clone, Debug, Deserialize)]
 pub struct AWG {
+    pub r#use: bool,
     pub index: usize,
     pub sampling_rate: u64, // ≥ 1
-    pub frequency_resolution: u64, // ≥ 1
+    pub num_segments: u32,
     pub trigger: AWGTrigger,
-    pub active_channels: Vec<AWGChannel>,
-    pub sequence_steps: Vec<AWGSequenceStep>,
-    pub uniformization: AWGUniformizationParams,
+    pub active_channels: AWGChannel,
+    pub sequence_segments: Vec<AWGSequenceSegment>,
+    // pub uniformization: AWGUniformizationParams,
 }
 
 impl From<AWG> for awg::Config {
     fn from(awg_conf: AWG) -> Self {
         Self {
+            r#use: awg_conf.r#use,
             index: awg_conf.index,
             sampling_rate: awg_conf.sampling_rate,
-            frequency_resolution: awg_conf.frequency_resolution,
+            num_segments: awg_conf.num_segments,
             trigger: awg_conf.trigger.into(),
-            active_channels:
-                awg_conf.active_channels.into_iter()
-                    .map(|ch| ch.into()).collect(),
-            sequence_steps:
-                awg_conf.sequence_steps.into_iter()
-                    .map(|step| step.into()).collect(),
+            active_channels: awg_conf.active_channels.into(),
+            sequence_segments:
+                awg_conf.sequence_segments.into_iter()
+                    .map(|seg| seg.into()).collect(),
         }
     }
 }
@@ -552,729 +569,337 @@ pub struct TimeTagger {
 }
 
 /// `basler.acquisition_mode`
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
-pub enum BaslerAcquisitionMode {
-    continuous,
-    // ...?
-}
-
-impl From<BaslerAcquisitionMode> for awg::AcquisitionMode {
-    fn from(mode: BaslerAcquisitionMode) -> Self {
-        match mode {
-            BaslerAcquisitionMode::continuous => Self::Continuous,
-        }
-    }
-}
+// #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
+// pub enum BaslerAcquisitionMode {
+//     continuous,
+//     // ...?
+// }
+
+// impl From<BaslerAcquisitionMode> for awg::AcquisitionMode {
+//     fn from(mode: BaslerAcquisitionMode) -> Self {
+//         match mode {
+//             BaslerAcquisitionMode::continuous => Self::Continuous,
+//         }
+//     }
+// }
 
 /// `basler`
-#[derive(Copy, Clone, Debug, Deserialize)]
-pub struct Basler {
-    pub index: usize,
-    pub exposure_time_us: f64,
-    pub gain: f64,
-    pub acquisition_mode: BaslerAcquisitionMode,
-    pub frame_rate: u32,
-    pub roi: [usize; 4],
-}
-
-/// Shorthand for creating [`ConfigSpecItem`]s.
-#[macro_export]
-macro_rules! spec {
-    ( Bool ) => { ConfigSpecItem::Value(Verifier::TypeVer(TypeVer::Bool)) };
-    ( Int ) => { ConfigSpecItem::Value(Verifier::TypeVer(TypeVer::Int)) };
-    ( Float ) => { ConfigSpecItem::Value(Verifier::TypeVer(TypeVer::Float)) };
-    ( Datetime ) => { ConfigSpecItem::Value(Verifier::TypeVer(TypeVer::Datetime)) };
-    ( Array ) => { ConfigSpecItem::Value(Verifier::TypeVer(TypeVer::Array)) };
-    ( TypedArray { [ $( $t:expr ),* $(,)? ], finite: $f:expr } ) => {
-        ConfigSpecItem::Value(
-            Verifier::TypeVer(
-                TypeVer::TypedArray { types: vec![$( $t ),*], finite: $f }
-            )
-        )
-    };
-    ( Str ) => { ConfigSpecItem::Value(Verifier::TypeVer(TypeVer::Str)) };
-    ( IntRange { ($min:expr, $max:expr) } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::IntRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: false,
-                    incl_end: false,
-                }
-            )
-        )
-    };
-    ( IntRange { ($min:expr, $max:expr)= } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::IntRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: false,
-                    incl_end: true,
-                }
-            )
-        )
-    };
-    ( IntRange { =($min:expr, $max:expr) } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::IntRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: true,
-                    incl_end: false,
-                }
-            )
-        )
-    };
-    ( IntRange { =($min:expr, $max:expr)= } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::IntRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: true,
-                    incl_end: true,
-                }
-            )
-        )
-    };
-    ( FloatRange { ($min:expr, $max:expr) } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::FloatRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: false,
-                    incl_end: false,
-                }
-            )
-        )
-    };
-    ( FloatRange { ($min:expr, $max:expr)= } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::FloatRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: false,
-                    incl_end: true,
-                }
-            )
-        )
-    };
-    ( FloatRange { =($min:expr, $max:expr) } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::FloatRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: true,
-                    incl_end: false,
-                }
-            )
-        )
-    };
-    ( FloatRange { =($min:expr, $max:expr)= } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::FloatRange {
-                    min: $min,
-                    max: $max,
-                    incl_start: true,
-                    incl_end: true,
-                }
-            )
-        )
-    };
-    ( Table { $( $key:literal => $val:expr ),* $(,)? } ) => {
-        ConfigSpecItem::Table(
-            ConfigSpec::from_iter([
-                $(
-                    ($key.to_string(), $val)
-                ),*
-            ])
-        )
-    };
-    ( StrCollection { $( $s:expr ),* $(,)? } ) => {
-        ConfigSpecItem::Value(
-            Verifier::ValueVer(
-                ValueVer::StrCollection(
-                    HashSet::from_iter([
-                        $( $s.to_string() ),*
-                    ])
-                )
-            )
-        )
-    };
-}
-
-/// Sugared [`std::collections::HashMap`] representing a specification for the
-/// values and structure of a TOML-formatted config file.
-#[derive(Clone, Debug)]
-pub struct ConfigSpec {
-    spec: HashMap<String, ConfigSpecItem>
-}
-
-impl AsRef<HashMap<String, ConfigSpecItem>> for ConfigSpec {
-    fn as_ref(&self) -> &HashMap<String, ConfigSpecItem> { &self.spec }
-}
-
-impl From<HashMap<String, ConfigSpecItem>> for ConfigSpec {
-    fn from(spec: HashMap<String, ConfigSpecItem>) -> Self { Self { spec } }
-}
-
-impl FromIterator<(String, ConfigSpecItem)> for ConfigSpec {
-    fn from_iter<I>(iter: I) -> Self
-    where I: IntoIterator<Item = (String, ConfigSpecItem)>
-    {
-        return Self { spec: iter.into_iter().collect() };
-    }
-}
-
-/// Create a [`ConfigSpec`].
-///
-/// Expects `str` literals for keys and [`ConfigSpecItem`]s for values. See also
-/// [`spec`].
-#[macro_export]
-macro_rules! config_spec {
-    ( $( $key:literal => $val:expr ),* $(,)? ) => {
-        ConfigSpec::from_iter([
-            $(
-                ($key.to_string(), $val)
-            ),*
-        ])
-    }
-}
-
-impl Default for ConfigSpec {
-    fn default() -> Self {
-        return config_spec!(
-            "output" => spec!(Table {
-                "save" => spec!(Bool),
-                "datadir" => spec!(Str),
-                "dir" => spec!(Str),
-                "name" => spec!(Str),
-                "counter" => spec!(Int),
-                "show_frames" => spec!(Table {
-                    "mode" => spec!(StrCollection {
-                        "off",
-                        "first",
-                        "last",
-                        "all",
-                    }),
-                    "color_scale" => spec!(StrCollection {
-                        "gray",
-                        "grayclip",
-                        "paperscale",
-                        "plasma",
-                        "vibrant",
-                        "artsy",
-                        "pix",
-                        "atomwl",
-                    }),
-                    "colorbar_limits" => spec!(TypedArray {
-                        [TypeVer::Float, TypeVer::Float],
-                        finite: true
-                    }),
-                }),
-            }),
-            "camera" => spec!(Table {
-                "acquisition_mode" => spec!(StrCollection {
-                    "single",
-                    "accum",
-                    "kinetic",
-                    "fast_kinetic",
-                    "cont",
-                }),
-                "roi" => spec!(TypedArray {
-                    [TypeVer::Int, TypeVer::Int, TypeVer::Int, TypeVer::Int],
-                    finite: true
-                }),
-                "bin" => spec!(TypedArray {
-                    [TypeVer::Int, TypeVer::Int],
-                    finite: true
-                }),
-                "exposure_time_ms" => spec!(FloatRange { =(1e-3, 1e3)= }),
-                "cooler" => spec!(Table {
-                    "on" => spec!(Bool),
-                    "fan_mode" => spec!(StrCollection {
-                        "full",
-                        "low",
-                        "off",
-                    }),
-                    "temperature_c" => spec!(FloatRange { =(-100.0, 30.0)= }),
-                }),
-                "amp" => spec!(Table {
-                    "em_gain" => spec!(IntRange { =(2, 300)= }),
-                    "preamp_gain_idx" => spec!(IntRange { =(0, 1)= }),
-                    "oamp" => spec!(IntRange { =(0, 1)= }),
-                }),
-                "shutter" => spec!(Table {
-                    "mode" => spec!(StrCollection {
-                        "auto",
-                        "open",
-                        "closed",
-                        "fvb",
-                        "any",
-                    }),
-                    "ttl_mode" => spec!(IntRange { =(0, 1)= }),
-                    "open_time_ms"
-                        => spec!(FloatRange { =(27.0, f64::INFINITY) }),
-                    "close_time_ms"
-                        => spec!(FloatRange { =(27.0, f64::INFINITY) }),
-                }),
-                "trigger" => spec!(Table {
-                    "mode" => spec!(StrCollection {
-                        "int",
-                        "ext",
-                        "ext_start",
-                        "ext_exp",
-                        "software",
-                        "ext_chargeshift",
-                    }),
-                }),
-                "read" => spec!(Table {
-                    "mode" => spec!(StrCollection {
-                        "fvb",
-                        "single_track",
-                        "multi_track",
-                        "random_track",
-                        "image",
-                    }),
-                    "hsspeed_idx" => spec!(IntRange { =(0, 3)= }),
-                    "vsspeed_idx" => spec!(IntRange { =(0, 3)= }),
-                }),
-                "kinetic" => spec!(Table {
-                    "buffer_size" => spec!(IntRange { =(1, i64::MAX)= }),
-                    "cycle_time"
-                        => spec!(FloatRange { =(0.0, f64::INFINITY) }),
-                    "num_acc" => spec!(IntRange { =(1, i64::MAX)= }),
-                    "cycle_time_acc"
-                        => spec!(FloatRange { =(0.0, f64::INFINITY) }),
-                    "num_prescan" => spec!(IntRange { =(0, i64::MAX)= }),
-                }),
-            }),
-            "processing" => spec!(Table {
-                "show" => spec!(StrCollection {
-                    "off",
-                    "first",
-                    "last",
-                    "all",
-                }),
-                "color_scale" => spec!(StrCollection {
-                    "gray",
-                    "grayclip",
-                    "paperscale",
-                    "plasma",
-                    "vibrant",
-                    "artsy",
-                    "pix",
-                    "atomwl",
-                }),
-                "colorbar_limits" => spec!(TypedArray {
-                    [TypeVer::Float, TypeVer::Float],
-                    finite: true
-                }),
-                "count_bias" => spec!(Float),
-                "qe" => spec!(FloatRange { =(0.0, 1.0)= }),
-                "window_avg" => spec!(Table {
-                    "enabled" => spec!(Bool),
-                    "image_dim" => spec!(TypedArray {
-                        [TypeVer::Int, TypeVer::Int],
-                        finite: true
-                    }),
-                    "window_size" => spec!(IntRange { =(1, i64::MAX)= }),
-                    "rolling_avg" => spec!(Bool),
-                }),
-                "ff_prep" => spec!(Table {
-                    "enabled" => spec!(Bool),
-                    "image_dim" => spec!(TypedArray {
-                        [TypeVer::Int, TypeVer::Int],
-                        finite: true
-                    }),
-                    "n_loop" => spec!(IntRange { =(1, i64::MAX)= }),
-                    "mode" => spec!(StrCollection { "bright", "dark" }),
-                    "box" => spec!(TypedArray {
-                        [
-                            TypeVer::Int,
-                            TypeVer::Int,
-                            TypeVer::Int,
-                            TypeVer::Int,
-                        ],
-                        finite: true
-                    }),
-                    "threshold" => spec!(FloatRange { =(0.0, f64::INFINITY) }),
-                }),
-                "ff_series" => spec!(Table {
-                    "enabled" => spec!(Bool),
-                    "image_dim" => spec!(TypedArray {
-                        [TypeVer::Int, TypeVer::Int],
-                        finite: true
-                    }),
-                    "n_loop" => spec!(IntRange { =(1, i64::MAX)= }),
-                    "mode" => spec!(StrCollection {
-                        "xor",
-                        "xnor",
-                        "alternate",
-                    }),
-                    "box" => spec!(TypedArray {
-                        [
-                            TypeVer::Int,
-                            TypeVer::Int,
-                            TypeVer::Int,
-                            TypeVer::Int,
-                        ],
-                        finite: true
-                    }),
-                    "threshold" => spec!(FloatRange { =(0.0, f64::INFINITY) }),
-                }),
-            }),
-            "timetagger" => spec!(Table {
-                "use" => spec!(Bool),
-                "serial" => spec!(Str),
-                "counter_channel" => spec!(Int),
-                "start_channel" => spec!(Int),
-                "stop_channel" => spec!(Int),
-                "binwidth_sec" => spec!(Float),
-                "n_bins" => spec!(Int),
-            }),
-        );
-    }
-}
-
-impl ConfigSpec {
-    pub fn from_spec(spec: HashMap<String, ConfigSpecItem>) -> Self {
-        return Self { spec };
-    }
-
-    fn verify_ok(&self, value: Value) -> ConfigResult<Value> {
-        if let Value::Table(mut tab) = value {
-            let mut data = Table::new();
-            let mut val: Value;
-            let mut valstr: String;
-            for (key, spec_item) in self.spec.iter() {
-                val = match (spec_item, tab.remove(key)) {
-                    (ConfigSpecItem::Table(spec_table), Some(v)) => {
-                        spec_table.verify_ok(v)
-                    },
-                    (ConfigSpecItem::Value(verifier), Some(v)) => {
-                        match verifier {
-                            Verifier::TypeVer(type_ver) => {
-                                valstr = v.to_string();
-                                type_ver.verify_ok_or(
-                                    v,
-                                    ConfigError::InvalidType(
-                                        key.clone(),
-                                        type_ver.to_string(),
-                                        valstr,
-                                    )
-                                )
-                            },
-                            Verifier::ValueVer(value_ver) => {
-                                valstr = v.to_string();
-                                value_ver.verify_ok_or(
-                                    v,
-                                    ConfigError::InvalidValue(
-                                        key.clone(),
-                                        value_ver.to_string(),
-                                        valstr,
-                                    )
-                                )
-                            },
-                        }
-                    },
-                    _ => Err(ConfigError::MissingKey(key.clone())),
-                }?;
-                data.insert(key.clone(), val);
-            }
-            return Ok(Value::Table(data));
-        } else {
-            return Err(ConfigError::IncompatibleStructure);
-        }
-    }
-
-    /// Return `Ok` if `table` matches the specification, `Err` otherwise.
-    ///
-    /// The keys of `table` are filtered to contain only those in the
-    /// specification.
-    pub fn verify(&self, table: Table) -> ConfigResult<Config> {
-        let data: Table
-            = self.verify_ok(Value::Table(table))?
-            .try_into()
-            .unwrap();
-        return Ok(Config { data });
-    }
-}
-
-/// Sugared [`toml::Table`] holding configuration values.
-#[derive(Clone, Debug)]
+// #[derive(Copy, Clone, Debug, Deserialize)]
+// pub struct Basler {
+//     pub index: usize,
+//     pub exposure_time_us: f64,
+//     pub gain: f64,
+//     pub acquisition_mode: BaslerAcquisitionMode,
+//     pub frame_rate: u32,
+//     pub roi: [usize; 4],
+// }
+
+// impl From<Basler> for awg::BaslerConfig {
+//     fn from(config: Basler) -> Self {
+//         Self {
+//             exposure_time_us: config.exposure_time_us,
+//             gain: config.gain,
+//             acquisition_mode: config.acquisition_mode.into(),
+//             frame_rate: config.frame_rate,
+//             roi: config.roi,
+//         }
+//     }
+// }
+
+/// Everything.
+#[derive(Clone, Debug, Deserialize)]
 pub struct Config {
-    data: Table
-}
-
-impl AsRef<Table> for Config {
-    fn as_ref(&self) -> &Table { &self.data }
+    pub output: Output,
+    pub camera: Camera,
+    pub processing: Processing,
+    pub awg: AWG,
+    pub timetagger: TimeTagger,
+    // pub basler: Basler,
 }
 
 impl Config {
-    /// Create a new [`Config`] with verification against [`spec`].
-    pub fn new(data: Table, spec: &ConfigSpec) -> ConfigResult<Self> {
-        return spec.verify(data);
-    }
-
-    fn table_get_path<'a, K>(table: &Table, mut keys: Peekable<K>)
-        -> Option<&Value>
-    where K: Iterator<Item = &'a str>
-    {
-        return if let Some(key) = keys.next() {
-            match (table.get(key), keys.peek()) {
-                (Some(Value::Table(tab)), Some(_)) => {
-                    Self::table_get_path(tab, keys)
-                },
-                (x, None) => x,
-                (Some(_), Some(_)) => None,
-                (None, _) => None,
-            }
-        } else {
-            unreachable!()
-        };
-    }
-
-    /// Access a key path in `self`, returning `Some` if the complete path
-    /// exists, `None` otherwise.
-    pub fn get_path<'a, K>(&self, keys: K) -> Option<&Value>
-    where K: IntoIterator<Item = &'a str>
-    {
-        return Self::table_get_path(&self.data, keys.into_iter().peekable());
-    }
-
-    /// Access a key path in `self` where the individual keys in the path are
-    /// separated by `'.'`. Returns `Some` if the complete path exists, `None`
-    /// otherwise.
-    pub fn get_path_s(&self, keys: &str) -> Option<&Value> {
-        return self.get_path(keys.split('.'));
-    }
-
-    /// Access a key path in `self` and attempt to convert its type to `T`,
-    /// returning `Some(T)` if the complete path exists and the type is
-    /// convertible, `None` otherwise.
-    pub fn get_path_into<'a, 'de, K, T>(&self, keys: K) -> Option<T>
-    where
-        T: Deserialize<'de>,
-        K: IntoIterator<Item = &'a str>,
-    {
-        return match self.get_path(keys) {
-            Some(x) => x.clone().try_into().ok(),
-            None => None,
-        };
-    }
-
-    /// Access a key path in `self`, where individual keys in the path are
-    /// separated by `'.'`, and attempt to convert its type to `T`, returning
-    /// `Some(T)` if the complete path exists and the type is convertible,
-    /// `None` otherwise.
-    pub fn get_path_s_into<'de, T>(&self, keys: &str) -> Option<T>
-    where T: Deserialize<'de>
-    {
-        return match self.get_path_s(keys) {
-            Some(x) => x.clone().try_into().ok(),
-            None => None,
-        };
-    }
-
-    fn table_get_path_ok<'a, K>(table: &Table, mut keys: Peekable<K>)
-        -> ConfigResult<&Value>
-    where K: Iterator<Item = &'a str>
-    {
-        return if let Some(key) = keys.next() {
-            match (table.get(key), keys.peek()) {
-                (Some(Value::Table(tab)), Some(_)) => {
-                    Self::table_get_path_ok(tab, keys)
-                },
-                (x, None) => {
-                    x.ok_or_else(|| ConfigError::MissingKey(key.to_string()))
-                },
-                (Some(_), Some(k))
-                    => Err(ConfigError::KeyPathTooLong(k.to_string())),
-                (None, _) => Err(ConfigError::MissingKey(key.to_string())),
-            }
-        } else {
-            unreachable!()
-        };
-    }
-
-    /// Access a key path in `self`, returning `Ok` if the complete path
-    /// exists, `Err` otherwise.
-    pub fn get_path_ok<'a, K>(&self, keys: K) -> ConfigResult<&Value>
-    where K: IntoIterator<Item = &'a str>
-    {
-        return Self::table_get_path_ok(&self.data, keys.into_iter().peekable());
-    }
-
-    /// Access a key path in `self` where the individual keys in the path are
-    /// separated by `'.'`. Returns `Ok` if the complete path exists, `Err`
-    /// otherwise.
-    pub fn get_path_s_ok(&self, keys: &str) -> ConfigResult<&Value> {
-        return self.get_path_ok(keys.split('.'));
-    }
-
-    /// Access a key path in `self` and attempt to convert its type to `T`,
-    /// returning `Ok(T)` if the complete path exists and the type is
-    /// convertible, `Err` otherwise.
-    pub fn get_path_ok_into<'a, 'de, K, T>(&self, keys: K) -> ConfigResult<T>
-    where
-        T: Deserialize<'de>,
-        K: IntoIterator<Item = &'a str>,
-    {
-        return self.get_path_ok(keys)
-            .and_then(|x| {
-                x.clone()
-                    .try_into()
-                    .map_err(|_| {
-                        ConfigError::FailedTypeConversion(x.to_string())
-                    })
-            });
-    }
-
-    /// Access a key path in `self`, where individual keys in the path are
-    /// separated by `'.'`, and attempt to convert its type to `T`, returning
-    /// `Ok(T)` if the complete path exists and the type is convertible, `Err`
-    /// otherwise.
-    pub fn get_path_s_ok_into<'de, T>(&self, keys: &str) -> ConfigResult<T>
-    where T: Deserialize<'de>
-    {
-        return self.get_path_s_ok(keys)
-            .and_then(|x| {
-                x.clone()
-                    .try_into()
-                    .map_err(|_| {
-                        ConfigError::FailedTypeConversion(x.to_string())
+    fn check_shutter_times(&self) -> ConfigResult<()> {
+        (self.camera.shutter.open_time_ms >= 27.0).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.shutter.open_time_ms.to_string(),
+                "camera.shutter.open_time_ms".to_string(),
+                "≥ 27.0",
+            ))?;
+        (self.camera.shutter.close_time_ms >= 27.0).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.shutter.close_time_ms.to_string(),
+                "camera.shutter.close_time_ms".to_string(),
+                "≥ 27.0",
+            ))?;
+        Ok(())
+    }
+
+    fn check_shift_speed(&self) -> ConfigResult<()> {
+        const IDX: &[usize] = &[0, 1, 2, 3];
+        IDX.contains(&self.camera.read.hsspeed_idx).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.read.hsspeed_idx.to_string(),
+                "camera.read.hsspeed_idx".to_string(),
+                "one of {0, 1, 2, 3}",
+            ))?;
+        IDX.contains(&self.camera.read.vsspeed_idx).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.read.vsspeed_idx.to_string(),
+                "camera.read.vsspeed_idx".to_string(),
+                "one of {0, 1, 2, 3}",
+            ))?;
+        Ok(())
+    }
+
+    fn check_kinetic_params(&self) -> ConfigResult<()> {
+        (self.camera.kinetic.buffer_size >= 1).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.kinetic.buffer_size.to_string(),
+                "camera.kinetic.buffer_size".to_string(),
+                "≥ 1",
+            ))?;
+        (self.camera.kinetic.cycle_time >= 0.0).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.kinetic.cycle_time.to_string(),
+                "camera.kinetic.cycle_time".to_string(),
+                "≥ 0.0",
+            ))?;
+        (self.camera.kinetic.num_acc >= 1).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.kinetic.num_acc.to_string(),
+                "camera.kinetic.num_acc".to_string(),
+                "≥ 1",
+            ))?;
+        (self.camera.kinetic.cycle_time_acc >= 0.0).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.kinetic.cycle_time_acc.to_string(),
+                "camera.kinetic.cycle_time_acc".to_string(),
+                "≥ 0.0",
+            ))?;
+        Ok(())
+    }
+
+    fn check_roi(&self) -> ConfigResult<()> {
+        let xmin = self.camera.roi[0];
+        let xmax = self.camera.roi[1];
+        (xmin < xmax).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                format!("{:?}", self.camera.roi),
+                "camera.roi".to_string(),
+                "xmin < xmax",
+            ))?;
+        let ymin = self.camera.roi[2];
+        let ymax = self.camera.roi[3];
+        (ymin < ymax).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                format!("{:?}", self.camera.roi),
+                "camera.roi".to_string(),
+                "ymin < ymax",
+            ))?;
+        Ok(())
+    }
+
+    fn check_camera_temperature(&self) -> ConfigResult<()> {
+        (-100.0..30.0).contains(&self.camera.cooler.temperature_c).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.cooler.temperature_c.to_string(),
+                "camera.cooler.temperature_c".to_string(),
+                "in range -100..+30",
+            ))?;
+        Ok(())
+    }
+
+    fn check_camera_params(&self) -> ConfigResult<()> {
+        (self.camera.exposure_time_ms > 0.0).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.camera.exposure_time_ms.to_string(),
+                "camera.exposure_time_ms".to_string(),
+                "> 0.0",
+            ))?;
+        Ok(())
+    }
+
+    fn check_windowavg(&self) -> ConfigResult<()> {
+        (self.processing.window_avg.window_size >= 1).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.processing.window_avg.window_size.to_string(),
+                "processing.window_avg.window_size".to_string(),
+                "≥ 1",
+            ))?;
+        Ok(())
+    }
+
+    fn check_awg_trigger_params(&self) -> ConfigResult<()> {
+        let volt_range = -10000..=10000;
+        volt_range.contains(&self.awg.trigger.ext0.level).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.awg.trigger.ext0.level.to_string(),
+                "awg.trigger.ext0.level".to_string(),
+                "in range -10000..+10000 mV",
+            ))?;
+        volt_range.contains(&self.awg.trigger.ext0.rearm_level).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.awg.trigger.ext0.rearm_level.to_string(),
+                "awg.trigger.ext0.rearm_level".to_string(),
+                "in range -10000..+10000 mV",
+            ))?;
+        volt_range.contains(&self.awg.trigger.ext1.level).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.awg.trigger.ext1.level.to_string(),
+                "awg.trigger.ext1.level".to_string(),
+                "in range -10000..+10000 mV",
+            ))?;
+        volt_range.contains(&self.awg.trigger.ext1.rearm_level).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.awg.trigger.ext1.rearm_level.to_string(),
+                "awg.trigger.ext1.rearm_level".to_string(),
+                "in range -10000..+10000 mV",
+            ))?;
+        Ok(())
+    }
+
+
+    fn check_awg_channels(&self) -> ConfigResult<()> {
+        (self.awg.active_channels.stop_levels.len() == 4).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                "[{...}]".to_string(),
+                "awg.active_channels.stop_levels".to_string(),
+                "length must be 4",
+            ))?;
+        let channels = &self.awg.active_channels.enabled_channels;
+        (channels.iter().filter(|&&c| c).count() != 3).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                "[{...}]".to_string(),
+                "awg.active_channels.enabled_channels".to_string(),
+                "number of enabled channels must not be 3",
+            ))?;
+        self.awg.active_channels.amplitudes.iter().enumerate()
+            .map(|(k, &amp)| {
+                (80..=2500).contains(&amp).then_some(())
+                    .ok_or(ConfigError::InvalidValue(
+                        amp.to_string(),
+                        format!("awg.active_channels.amplitudes[{}]", k),
+                        "in range 80..2500 mV",
+                    ))
+            })
+            .collect::<ConfigResult<Vec<()>>>()?;
+        
+        Ok(())
+    }
+
+    fn check_awg_sequence_steps(&self) -> ConfigResult<()> {
+        self.awg.sequence_segments.iter().enumerate()
+            .map(|(k, seg)| {
+                Ok((k, seg))
+                    .and_then(|(k, seg)| {
+                        (seg.source.exists() 
+                            && seg.source.to_str().unwrap_or("").ends_with(".txt")
+                        )
+                        .then_some((k, seg))
+                        .ok_or(ConfigError::InvalidValue(
+                            seg.source.to_string_lossy().to_string(),
+                            format!("awg.sequence_segments[{}].source", k),
+                            "file must exist and end with .txt",
+                        ))
                     })
-            });
-    }
-
-    /// Alias for [`Self::get_path_s_ok_into`].
-    pub fn a<'de, T>(&self, keys: &str) -> ConfigResult<T>
-    where T: Deserialize<'de>
+            })
+            .collect::<ConfigResult<Vec<_>>>()?;
+        Ok(())
+    }
+
+    fn check_awg_params(&self) -> ConfigResult<()> {
+        let n_segs = self.awg.num_segments;
+        (n_segs & (n_segs - 1) == 0) // n_segs must be a power of two
+            .then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                n_segs.to_string(),
+                "awg.num_segments".to_string(),
+                "must be a power of two",
+            ))?;
+
+        (self.awg.sampling_rate >= 1).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.awg.sampling_rate.to_string(),
+                "awg.sampling_rate".to_string(),
+                "≥ 1",
+            ))?;
+        self.check_awg_trigger_params()?;
+        self.check_awg_channels()?;
+        self.check_awg_sequence_steps()?;
+        Ok(())
+    }
+
+    fn check_timetagger_params(&self) -> ConfigResult<()> {
+        (self.timetagger.binwidth_sec > 0.0).then_some(())
+            .ok_or(ConfigError::InvalidValue(
+                self.timetagger.binwidth_sec.to_string(),
+                "timetagger.binwidth_sec".to_string(),
+                "> 0.0",
+            ))?;
+        Ok(())
+    }
+
+    // fn check_basler_params(&self) -> ConfigResult<()> {
+    //     (self.basler.exposure_time_us > 0.0).then_some(())
+    //         .ok_or(ConfigError::InvalidValue(
+    //             self.basler.exposure_time_us.to_string(),
+    //             "basler.exposure_time_us".to_string(),
+    //             "> 0.0",
+    //         ))?;
+    //     (self.basler.frame_rate > 0).then_some(())
+    //         .ok_or(ConfigError::InvalidValue(
+    //             self.basler.frame_rate.to_string(),
+    //             "basler.frame_rate".to_string(),
+    //             "> 0",
+    //         ))?;
+    //     (
+    //         self.basler.roi[0] < self.basler.roi[1]
+    //         && self.basler.roi[1] <= awg::Basler::SENSOR_WIDTH
+    //         && self.basler.roi[2] < self.basler.roi[3]
+    //         && self.basler.roi[3] <= awg::Basler::SENSOR_HEIGHT
+    //     ).then_some(())
+    //         .ok_or(ConfigError::InvalidValue(
+    //             format!("{:?}", self.basler.roi),
+    //             "basler.roi".to_string(),
+    //             "xmin < xmax < 3840, ymin < ymax < 2740",
+    //         ))?;
+    //     Ok(())
+    // }
+
+    /// Verify that all necessary conditions are satisfied.
+    pub fn check(&self) -> ConfigResult<()> {
+        self.check_shutter_times()?;
+        self.check_shift_speed()?;
+        self.check_kinetic_params()?;
+        self.check_roi()?;
+        self.check_camera_temperature()?;
+        self.check_camera_params()?;
+        self.check_windowavg()?;
+        self.check_awg_params()?;
+        self.check_timetagger_params()?;
+        // self.check_basler_params()?;
+        Ok(())
+    }
+
+
+    /// Load a config from a file and verify that all necessary conditions are
+    /// satisfied.
+    pub fn load<P>(infile: P) -> ConfigResult<Self>
+    where P: AsRef<Path>
     {
-        return self.get_path_s_ok_into(keys);
-    }
-}
-
-/// Load a [`Config`] from the given path, optionally verifying against a
-/// specification.
-fn load_config<P>(infile: P, verify: Option<&ConfigSpec>)
-    -> ConfigResult<Config>
-where P: AsRef<Path>
-{
-    let infile_str: String = infile.as_ref().display().to_string();
-    let table: Table
-        = fs::read_to_string(infile)
-        .map_err(|_| ConfigError::FileRead(infile_str.clone()))?
-        .parse()
-        .map_err(|_| ConfigError::FileParse(infile_str.clone()))?;
-    return if let Some(config_spec) = verify {
-        config_spec.verify(table)
-    } else {
-        Ok(Config { data: table })
-    };
-}
-
-/// Load a [`Config`] from the given path, verifying aginst
-/// [`ConfigSpec::default`]
-pub fn load_def_config<P>(infile: P) -> ConfigResult<Config>
-where P: AsRef<Path>
-{
-    return load_config(infile, Some(DEF_CONFIG_SPEC.deref()));
-}
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    fn create_table_good() -> Table {
-        return "[suptab]\nf1 = true\nf2 = \"foo\"\nf3 = [1.0, 1]"
-            .parse::<Table>().unwrap();
-    }
-
-    fn create_table_bad() -> Table {
-        return "[suptab]\nf1 = true\nf2 = \"foo\"\nf3 = [1, 1.0]"
-            .parse::<Table>().unwrap();
-    }
-
-    fn create_spec() -> ConfigSpec {
-        return config_spec!(
-            "suptab" => spec!(Table {
-                "f1" => spec!(Bool),
-                "f2" => spec!(StrCollection { "foo", "bar" }),
-                "f3" => spec!(TypedArray {
-                    [TypeVer::Float, TypeVer::Int],
-                    finite: true
-                }),
-            }),
-        );
-    }
-
-    #[test]
-    fn verify_config_good() -> ConfigResult<()> {
-        let table = create_table_good();
-        let spec = create_spec();
-        spec.verify(table)?;
-        return Ok(());
-    }
-
-    #[test]
-    #[should_panic]
-    fn verify_config_bad() -> () {
-        let table = create_table_bad();
-        let spec = create_spec();
-        spec.verify(table).expect("should fail!");
-        return ();
-    }
-
-    #[test]
-    fn key_path_access() -> ConfigResult<()> {
-        let table = create_table_good();
-        let spec = create_spec();
-        let config = spec.verify(table)?;
-        config.get_path_ok(["suptab", "f1"])?;
-        config.get_path_s_ok("suptab.f1")?;
-        return Ok(());
-    }
-
-    #[test]
-    #[should_panic]
-    fn key_path_access_bad() -> () {
-        let table = create_table_good();
-        let spec = create_spec();
-        let config = spec.verify(table).expect("won't fail");
-        config.get_path_ok(["suptab", "f4"]).expect("should fail!");
-        return ();
-    }
-
-    #[test]
-    fn key_path_access_convert() -> ConfigResult<()> {
-        let table = create_table_good();
-        let spec = create_spec();
-        let config = spec.verify(table)?;
-        let f1: bool = config.get_path_ok_into(["suptab", "f1"])?;
-        assert_eq!(f1, true);
-        return Ok(());
-    }
-
-    #[test]
-    #[should_panic]
-    fn key_path_access_convert_bad() -> () {
-        let table = create_table_good();
-        let spec = create_spec();
-        let config = spec.verify(table).expect("won't fail");
-        let f1: f64 = config.get_path_ok_into(["suptab", "f1"])
-            .expect("should fail!");
-        assert_eq!(f1, 1.0);
-        return ()
+        let infile_str: String = infile.as_ref().display().to_string();
+        let config_str: String
+            = fs::read_to_string(infile)
+            .map_err(|err| {
+                ConfigError::FileRead(infile_str.clone(), err.to_string())
+            })?;
+        let config: Self
+            = toml::from_str(&config_str)
+            .map_err(|err| {
+                ConfigError::FileParse(infile_str.clone(), err.to_string())
+            })?;
+        config.check()?;
+        Ok(config)
     }
 }
 
diff --git a/src/cli.rs b/src/cli.rs
index 44e682a019b6ffa24cc383e4d1dc8b5ca4206da6..50f8f8f946d063c16261b027bce2ea64da0b36d2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -76,7 +76,16 @@ fn main() -> Result<()> {
     }
 
     // TODO
-    let awg: Option<AWG> = None;
+    let mut awg: Option<AWG>
+        = config.awg.r#use
+        .then(|| {
+            println!("Connect to AWG");
+            AWG::connect(config.awg.index)
+        })
+        .transpose()?;
+    if awg.is_some() {
+        AWG::set_config(awg.as_mut().unwrap(), &config.awg.into())?;
+    }
 
     let tagger: Option<TimeTaggerUltra>
         = config.timetagger.r#use