diff --git a/Python/AWG.py b/Python/AWG.py
index 76cd0b3ca450f68bcd464e83cf5812f4307a549e..e1bf8f18cabc4d03d14ad7891430d7cae8ea63d9 100644
--- a/Python/AWG.py
+++ b/Python/AWG.py
@@ -34,7 +34,7 @@ class AWG:
         self.ch_amp = [0,0,0,0]  # channel output amplitude
         self.mode = ""  # current mode AWG is running on
 
-    def open(self, remote=False):
+    def open(self, remote=False, id=b'/dev/spcm0'):
         """
         opens and initializes instance variables
         :param remote: flag to determine remote connection, default is False
@@ -42,7 +42,7 @@ class AWG:
         if remote:
             self.card = spcm_hOpen(create_string_buffer(b'TCPIP::192.168.1.10::inst0::INSTR'))
         else:
-            self.card = spcm_hOpen(create_string_buffer(b'/dev/spcm0'))
+            self.card = spcm_hOpen(create_string_buffer(id))
         if self.card is None:
             sys.stdout.write("no card found...\n")
         else:
@@ -54,13 +54,14 @@ class AWG:
             spcm_dwGetParam_i32(self.card, SPC_MIINST_MAXADCVALUE,
                                 byref(self.full_scale))  # full scale value for data generation purpose
             name = szTypeToName(self.card_type.value)
-            sys.stdout.write("Found: {0} sn {1:05d}\n".format(name, self.serial_number.value))
-            sys.stdout.write("Sample Rate: {:.1f} MHz\n".format(self.sample_rate.value / 1000000))
-            sys.stdout.write("Memory size: {:.0f} MBytes\n".format(self.mem_size.value / 1024 / 1024))
-        self.check_error()
+            sys.stdout.write("Card: {0} sn {1:05d}\n".format(name, self.serial_number.value))
+            sys.stdout.write("Max sample Rate: {:.1f} MHz\n".format(self.sample_rate.value / 1000000))
+            sys.stdout.write("Memory size: {:.0f} MBytes\n\n".format(self.mem_size.value / 1024 / 1024))
+        # self.check_error()
 
     def close(self):
         spcm_vClose(self.card)
+        print("AWG is closed")
 
     def check_error(self, message="") -> bool:
         """
@@ -68,23 +69,21 @@ class AWG:
         :param message: caller defined string for debugging purposes
         :return: 1 if error is found, 0 otherwise
         """
-        flag = 0
-        msg = "Checking error at " + message + " ... "
-        if message != "":
-            flag = 1
-            sys.stdout.write(msg)
         err_reg = uint32(0)
         err_val = int32(0)
-        err_text = ''
+        err_text = ""
         err_code = spcm_dwGetErrorInfo_i32(self.card, byref(err_reg), byref(err_val), err_text)
         if err_code:
-            if flag:
-                sys.stdout.write(err_text)
-            else:
-                sys.stdout.write(msg + err_text)
+            print(
+                f"{message}\n"
+                f"error code (see spcerr.py): {hex(err_code)}\n"
+                # f"error text: {err_text}"
+                f"error register: {err_reg.value}\n"
+                f"error val: {err_val.value}\n"
+            )
+            self.close()
+            exit(0)
             return True
-        elif flag:
-            sys.stdout.write("no error\n")
         return False
 
     def run(self):
@@ -93,27 +92,28 @@ class AWG:
         """
         spcm_dwSetParam_i32(self.card, SPC_M2CMD,
                             M2CMD_CARD_START | M2CMD_CARD_ENABLETRIGGER)
-        self.check_error()
+        # self.check_error("Checking error at run")
 
     def stop(self):
         """
         stop the card, this is different from closing the card
         """
         spcm_dwSetParam_i32(self.card, SPC_M2CMD, M2CMD_CARD_STOP)
-        self.check_error()
+        # self.check_error("Checking error at stop")
 
     def reset(self):
         """
         resets the board, this clears all onboard memory and settings
         """
         spcm_dwSetParam_i32(self.card, SPC_M2CMD, M2CMD_CARD_RESET)
+        # self.check_error("Checking error at reset")
 
     def force_trigger(self):
         """
         force a trigger event, this completely mimics an actual trigger event
         """
         spcm_dwSetParam_i32(self.card, SPC_M2CMD, M2CMD_CARD_FORCETRIGGER)
-        self.check_error()
+        # self.check_error("Checking error at force_trigger")
 
     def set_sampling_rate(self, sr: int):
         """
@@ -122,9 +122,10 @@ class AWG:
         """
         self.sample_rate = int64(sr)
         spcm_dwSetParam_i64(self.card, SPC_SAMPLERATE, sr)
-        self.check_error()
+        # self.check_error("Checking error at set_sampling_rate")
+        print(f"Setting sampling rate to {self.sample_rate.value / 1e6} MHz")
 
-    def enable_channel(self, ch: int, amplitude: int=1000, stoplvl: str="ZERO"):
+    def toggle_channel(self, ch: int, amplitude: int=1000, stoplvl: str="ZERO"):
         """
         enable/disable individual channel and set its parameters
         :param ch: enables channel 0-3.
@@ -142,37 +143,12 @@ class AWG:
             spcm_dwSetParam_i64(self.card, SPC_CHENABLE, int64(2**ch))  # see CHANNEL0-3 in regs.py for detail
             spcm_dwSetParam_i32(self.card, SPC_ENABLEOUT0 + ch * (SPC_ENABLEOUT1 - SPC_ENABLEOUT0), 1)
             spcm_dwSetParam_i32(self.card, SPC_AMP0 + ch * (SPC_AMP1 - SPC_AMP0), amplitude)
-            spcm_dwSetParam_i32(self.card, SPC_CH0_STOPLEVEL + ch * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL),
-                                stopmask[stoplvl])
+            spcm_dwSetParam_i32(self.card, SPC_CH0_STOPLEVEL + ch * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL), stopmask[stoplvl])
         else:
             self.channel[ch] = 0
             spcm_dwSetParam_i32(self.card, SPC_ENABLEOUT0 + ch * (SPC_ENABLEOUT1 - SPC_ENABLEOUT0), 0)
-        self.check_error()
-
-    # Deprecated
-    # def set_channel(self, ch: list, amplitude: list=(1000,1000,1000,1000), stoplvl: list=(16,16,16,16)):
-    #     """
-    #     set and enable channel outputs, max number of channel is 4
-    #     :param ch: enables channel 0-3.
-    #     Example: [0,1,0,1] enables channel 1 and 3.
-    #     :param amplitude: sets output amplitude of each channel between 80-2500mV, default level is 1000mV.
-    #     Example: [80,1000,0,0] sets output level for channel 0 and 1 to be 80 and 1000 mV.
-    #     :param stoplvl: sets channels pause behavior, ZERO = 16, LOW = 2, HIGH = 4, HOLDLAST = 8.
-    #     Example: [16, 2, 8, 8]
-    #     """
-    #     self.channel = ch
-    #     mask = (CHANNEL0 & 1 * ch[0]) | \
-    #            (CHANNEL1 & 2 * ch[1]) | \
-    #            (CHANNEL2 & 4 * ch[2]) | \
-    #            (CHANNEL3 & 8 * ch[3])  # create channel mask
-    #     spcm_dwSetParam_i64(self.card, SPC_CHENABLE, int64(mask))  # activate channels
-    #     for i in range(4):
-    #         # enable channel outputs, set output amplitudes and pause behavior
-    #         if ch[i] == 0: continue
-    #         spcm_dwSetParam_i32(self.card, SPC_ENABLEOUT0 + i * (SPC_ENABLEOUT1 - SPC_ENABLEOUT0), 1)
-    #         spcm_dwSetParam_i32(self.card, SPC_AMP0 + i * (SPC_AMP1 - SPC_AMP0), amplitude[i])
-    #         spcm_dwSetParam_i32(self.card, SPC_CH0_STOPLEVEL + i * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL), stoplvl[i])
-    #     self.check_error()
+        # self.check_error("Checking error at enable_channel")
+        print("Channels enabled: ", self.channel)
 
     def set_trigger(self, **kwargs):
         """
@@ -188,19 +164,19 @@ class AWG:
             if key == "EXT1":
                 spcm_dwSetParam_i32(self.card, SPC_TRIG_ORMASK, SPC_TMASK_EXT1)  # using external channel 1 as trigger
                 spcm_dwSetParam_i32(self.card, SPC_TRIG_EXT1_MODE, value)
-        self.check_error()
+        # self.check_error("Checking error at set_trigger")
 
-    def get_aligned_array(self, size):
+    def get_aligned_buf(self, size):
         """
         returns a numpy array at a page-aligned memory location
         :param size: number of samples used for data calculation
-        :return: numpy array starting at the correct location
+        :return: 
         """
-        data_length_bytes = uint32(size * 2 * np.sum(self.channel))
+        data_length_bytes = int(size * 2 * np.sum(self.channel))
         buffer = pvAllocMemPageAligned(data_length_bytes)  # buffer now holds a page-aligned location
         buffer_data = cast(addressof(buffer), ptr16)  # cast it to int16 array
-        array = np.frombuffer(buffer_data, dtype=int16)
-        return array
+        # array = np.frombuffer(buffer_data, dtype=int16)
+        return buffer, buffer_data
 
     def set_sequence_mode(self, nseg: int):
         """
@@ -208,35 +184,42 @@ class AWG:
         :param nseg: number of segments the memory is divided into, must be powers of 2.
         """
         self.mode = "Sequence Replay"
-        spcm_dwSetParam_i32(self.card, SPC_CARDMODE, SPC_REP_STD_SEQUENCE)  # Sequence replay mode
-        spcm_dwSetParam_i32(self.card, SPC_SEQMODE_MAXSEGMENTS,nseg)  # set number of sequences the memory is divided into
-        self.check_error()
+        spcm_dwSetParam_i32(self.card, SPC_CARDMODE,            SPC_REP_STD_SEQUENCE)  # Sequence replay mode
+        spcm_dwSetParam_i32(self.card, SPC_SEQMODE_MAXSEGMENTS, nseg)  # set number of sequences the memory is divided into
+        spcm_dwSetParam_i32(self.card, SPC_SEQMODE_STARTSTEP,   0)  # set starting step to be 0
+        # self.check_error("Checking error at set_sequence_mode")
 
     def write_segment(self, data: np.ndarray, segment: int):
         """
-        write data onto a specified segment.
+        write data onto a specified segment in sequence replay mode
         :param data: numpy array containing waveform data
         :param segment: the segment to write on
         :return:
         """
+
         if self.mode != "Sequence Replay":
             print("Wrong method, current mode is: " + self.mode)
             return
         nch = np.sum(self.channel)  # number of activated channels
-        if data.dtype != int:
-            sys.stdout.write("data must be in int type\n")
-            return
-        if data.size > self.mem_size.value / np.sum(self.channel) / SPC_SEQMODE_MAXSEGMENTS:
-            sys.stdout.write("data is big")
+        # if data.dtype != int:
+        #     sys.stdout.write("data must be in int type\n")
+        #     return
+        if data.size > self.mem_size.value / np.sum(self.channel) / 2:
+            sys.stdout.write("data is too big")
             return
         spcm_dwSetParam_i32(self.card, SPC_SEQMODE_WRITESEGMENT, segment)  # set current segment to write on
         spcm_dwSetParam_i32(self.card, SPC_SEQMODE_SEGMENTSIZE,  data.size)  # set size of segment in unit of samples
+        
         # data transfer
-        # ptr = data.ctypes.data_as(POINTER(int16))
-        buflength = data.size * 2 * nch  # samples * (2 bytes/sample) * number of activated channels
-        spcm_dwDefTransfer_i64(self.card, SPCM_BUF_DATA, SPCM_DIR_PCTOCARD, int32(0), data, int64(0), buflength)
+        sample_len = data.size
+        buflength = uint32(sample_len * 2 * nch)  # samples * (2 bytes/sample) * number of activated channels
+        data_ptr = data.ctypes.data_as(ptr16)  # cast data array into a c-like array
+        buffer = pvAllocMemPageAligned(sample_len * 2)  # buffer now holds a page-aligned location
+        buffer_data = cast(addressof(buffer), ptr16)  # cast it to int16 array
+        memmove(buffer_data, data_ptr, sample_len * 2)  # moving data into the page-aligned block
+        spcm_dwDefTransfer_i64(self.card, SPCM_BUF_DATA, SPCM_DIR_PCTOCARD, int32(0), buffer, int64(0), buflength)
         spcm_dwSetParam_i32(self.card, SPC_M2CMD, M2CMD_DATA_STARTDMA | M2CMD_DATA_WAITDMA)
-        self.check_error()
+        # self.check_error("Checking error at write_segment")
 
     def configure_step(self, step: int, segment: int, nextstep: int, loop: int, condition: int):
         """
@@ -253,5 +236,5 @@ class AWG:
             return
         mask = (condition << 32) | (loop << 32) | (nextstep << 16) | segment  # 64-bit mask
         spcm_dwSetParam_i64(self.card, SPC_SEQMODE_STEPMEM0 + step, mask)
-        self.check_error()
+        # self.check_error("Checking error at configure_step")
 
diff --git a/Python/benchmarking.py b/Python/benchmarking.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e849322dfd0adf1a903aa33860d72b66fc737c5
--- /dev/null
+++ b/Python/benchmarking.py
@@ -0,0 +1,86 @@
+import numpy as np
+
+from waveform import *
+from cupyx.profiler import benchmark
+import cupy as cp
+
+
+def test(f_idx, t_idx):
+    paths, off = get_rearrange_paths(f_idx, t_idx)
+    create_moving_array_reduced(_sig, _table_cp, paths, off)
+
+
+def count(paths, path_table, off):
+    counter = 0
+    for i, j in paths:
+        if i == j:
+            continue
+        if (i, j) in path_table:
+            counter += 1
+        # else:
+        #     print((i, j), "not in path table")
+    for i in off:
+        counter += 1
+    return counter
+
+
+data = np.load("data/table-half_31.npz", allow_pickle=True)
+table = data['path_table'].item()
+twzr = data['wfm'].item()
+static_sig = data['static_sig']
+target = data['target']
+t_idx = np.nonzero(target)[0]
+nt = twzr.omega.size
+
+_table_cp = {}
+
+for key in table:
+    _table_cp[key] = cp.array(table[key])
+
+n_repeat = 500
+_sig = cp.array(static_sig)
+times = np.zeros((nt+1,2))
+filling_ratio = np.zeros((nt+1,2))
+n_move = np.zeros((nt+1,n_repeat))
+
+for i in range(nt+1):
+    f_prob = i/nt
+    calc_t = np.zeros(n_repeat)
+    ratio = np.zeros(n_repeat)
+    nm = np.zeros(n_repeat)
+    print(i, f_prob)
+    for j in range(n_repeat):
+        filled = np.random.rand(nt)
+        tr = filled < f_prob
+        fa = filled >= f_prob
+        filled[tr] = 1
+        filled[fa] = 0
+        f_idx = np.nonzero(filled)[0]
+        b = benchmark(
+            test,
+            (f_idx, t_idx),
+            n_repeat=1
+        )
+        # stuff to save
+        ratio[j] = f_idx.size / nt
+        calc_t[j] = b.gpu_times + b.cpu_times
+        paths, off = get_rearrange_paths(f_idx, t_idx)
+        nm[j] = count(paths, _table_cp, off)
+
+    # n_move[i,0] = np.mean(nm)
+    # n_move[i,1] = np.var(nm)
+    n_move[i] = nm
+    times[i,0] = np.mean(calc_t)
+    times[i,1] = np.var(calc_t)
+    filling_ratio[i,0] = np.mean(ratio)
+    filling_ratio[i,1] = np.var(ratio)
+
+np.savez(
+    f"data/reduced-benchmark_{nt}-half.npz",
+    wfm=twzr,
+    target=target,
+    filling_ratio=filling_ratio,
+    times=times,
+    n_move=n_move
+)
+
diff --git a/Python/control.py b/Python/control.py
new file mode 100644
index 0000000000000000000000000000000000000000..b247d892ad91cda73fb2ef0bf1dfc2f3b50af52c
--- /dev/null
+++ b/Python/control.py
@@ -0,0 +1,57 @@
+from AWG import *
+import code
+import readline
+import rlcompleter
+
+
+def trigger():
+    global awg
+    awg.force_trigger()
+    print("forcing a trigger...")
+
+
+def stop():
+    global awg
+    awg.stop()
+    print("stopping awg output...")
+
+
+def start():
+    global awg
+    awg.run()
+    awg.force_trigger()
+    print("starting awg output...")
+
+
+def close():
+    global awg
+    awg.close()
+    print("closing awg and quitting...")
+    exit(0)
+
+# load waveform data
+wfm_data = np.load("data/single.npz", allow_pickle=True)
+static_sig = wfm_data['signal']
+empty = np.zeros(static_sig.shape)
+wfm = wfm_data['wfm'].item()
+sampling_rate = wfm.sample_rate
+
+# AWG stuff
+awg = AWG()
+awg.open(id=b'/dev/spcm1')  # change this to b'/dev/spcm0' for top AWG
+awg.set_sampling_rate(sampling_rate)
+awg.toggle_channel(0, amplitude=2500)
+awg.set_trigger(EXT0=SPC_TM_POS)
+awg.set_sequence_mode(2)
+awg.write_segment(static_sig, segment=0)
+awg.write_segment(empty, segment=1)
+awg.configure_step(step=0, segment=0, nextstep=1, loop=1, condition=SPCSEQ_ENDLOOPONTRIG)
+awg.configure_step(step=1, segment=1, nextstep=0, loop=1, condition=SPCSEQ_ENDLOOPONTRIG)
+
+# console
+vars = globals()
+vars.update(locals())
+readline.set_completer(rlcompleter.Completer(vars).complete)
+readline.parse_and_bind("tab: complete")
+code.InteractiveConsole(vars).interact()
+
diff --git a/Python/make_waveforms.py b/Python/make_waveforms.py
new file mode 100644
index 0000000000000000000000000000000000000000..f767fa3eb82cdb3b36d104d5aec22a3849f01fa0
--- /dev/null
+++ b/Python/make_waveforms.py
@@ -0,0 +1,34 @@
+from waveform import *
+import os
+
+
+cf = MEGA(105)  # center frequency
+df = MEGA(2.5)  # delta frequency
+n = 2  # number of tones to generate in either direction of cf, total number of tweezer is 2*n+1
+nt = 2*n+1  # total number of tweezers
+# sampling_rate and sample_len must follow some relationship explained in the wiki page.
+sampling_rate = int(614.4e6)  # sampling rate
+sample_len = 512 * 600  # sample_len
+
+# amplitude uniformity adjustments, just make sure its called amps at the end
+scale = 2**11
+ampMax = scale/np.sqrt(nt)
+amps = ampMax * np.ones(nt)
+corr = np.array([1.53312825, 1.35073669, 1.252971, 1.06263741, 1.])
+amps *= np.sqrt(corr)
+
+# phase adjustments
+# phase = np.load("array_phase.npz")['phase'][:nt]  # phase table from Caltech
+
+twzr = Waveform(cf, df, n, sampling_rate)
+twzr.amplitude = amps
+# twzr.phi = phase
+static_sig = create_static_array(twzr, sample_len)
+empty = np.zeros(sample_len)
+
+
+path = "data"
+if not os.path.exists(path): os.mkdir(path)
+fname = os.path.join(path, "waveform_data.npz")
+np.savez(fname, signal=static_sig, wfm=twzr)
+
diff --git a/Python/small_array.py b/Python/small_array.py
index 3c4aa0303dc3bac22f17e93d4d67751d949c679b..151f568d8461fcdf4c1fae7dfca05c15ead31814 100644
--- a/Python/small_array.py
+++ b/Python/small_array.py
@@ -1,45 +1,69 @@
-from AWG import *
 from waveform import *
 
-sampling_rate = MEGA(625)
+np.random.seed(0)
 
+# parameters to play around 
+# ----------------------------------------------------------------------------
+cf = MEGA(105)  # center frequency
+df = MEGA(2.5)  # delta frequency
+n = 2  # number of tones to generate in either direction of cf, total number of tweezer is 2*n+1
+nt = 2*n+1  # total number of tweezers
+# sampling_rate and sample_len must follow some relationship explained in the wiki page.
+sampling_rate = int(614.4e6)  # sampling rate
+sample_len = 512 * 600  # sample_len
+
+# amplitude uniformity adjustments, just make sure its called amps at the end
+scale = 2**11
+ampMax = scale/np.sqrt(nt)
+amps = ampMax * np.ones(nt)
+corr = np.array([1.53312825, 1.35073669, 1.252971, 1.06263741, 1.])
+amps *= np.sqrt(corr)
+
+# phase adjustments
+phase = np.load("array_phase.npz")['phase'][:nt]  # phase table from Caltech
+# ----------------------------------------------------------------------------
+
+# MAGIC DO NOT CHANGE. one day i will understand, maybe
+# ----------------------------------------------------------------------------
+pvAllocMemPageAligned(sample_len * 2)
+# ----------------------------------------------------------------------------
+
+# setting up awg sequence, explained in wiki
+# ----------------------------------------------------------------------------
 awg = AWG()
-awg.open()
+awg.open(id=b'/dev/spcm1')  # change this to b'/dev/spcm0' for top AWG
 awg.set_sampling_rate(sampling_rate)
-awg.enable_channel(0)
+awg.toggle_channel(0, amplitude=2500)
 awg.set_trigger(EXT0=SPC_TM_POS)
 awg.set_sequence_mode(2)
 
-cf = MEGA(105)
-df = MEGA(2.5)
-n = 2
-# period = 1/cf
-sample_len = 512 * 100
-wave = Waveform(cf, df, n, sampling_rate)
-
-static_sig = create_static_array(wave, sample_len)
+twzr = Waveform(cf, df, n, sampling_rate)
+twzr.amplitude = amps
+twzr.phi = phase
+static_sig = create_static_array(twzr, sample_len)
+empty = np.zeros(sample_len)
 
-aligned_arr = awg.get_aligned_array(static_sig.size)
-adr = aligned_arr.ctypes.data
-aligned_arr[:] = static_sig.ctypes.data_as(POINTER(int16))
-adr = aligned_arr.ctypes.data
-
-awg.write_segment(aligned_arr, segment=0)
+awg.write_segment(static_sig, segment=0)
+awg.write_segment(empty, segment=1)
 awg.configure_step(step=0, segment=0, nextstep=1, loop=1, condition=SPCSEQ_ENDLOOPONTRIG)
 awg.configure_step(step=1, segment=1, nextstep=0, loop=1, condition=SPCSEQ_ENDLOOPONTRIG)
+# ----------------------------------------------------------------------------
 
-awg.force_trigger()
-
+# running the awg
+# ----------------------------------------------------------------------------
+awg.run()
+awg.force_trigger() # this is equivalement to an actual hardware trigger
+print("Outputing...")
 while True:
-    command = input("enter s to stop, e to output nothing: ")
-    if command == 's':
+    command = input("enter c to stop, s to switch sequence step: ")
+    if command == 'c':
         awg.stop()
-        print("stopping s=card")
+        print("stopping")
         break
-    elif command == 'e':
+    elif command == 's':
         awg.force_trigger()
-        print("switching to empty output")
+        print("switching sequence step")
+awg.stop()
+# ----------------------------------------------------------------------------
 
 awg.close()
-
-
diff --git a/Python/waveform.py b/Python/waveform.py
index ead0dced177a6ea379f43887c97f4b9d5cf9fd07..d5179d7e17316b6fb194ff397988b0cb76cdc981 100644
--- a/Python/waveform.py
+++ b/Python/waveform.py
@@ -1,9 +1,10 @@
+from typing import Dict, Tuple, Any
+from scipy.interpolate import interp1d
 from AWG import *
-import time
 
 
 class Waveform:
-    def __init__(self, cf: int, df: int, n: int, sample_rate: int):
+    def __init__(self, cf: int, df: int, n: int, sample_rate: int, freq_res):
         """
         helper class to store basic waveform information.
         :param cf: center frequency tone of tweezer array.
@@ -12,37 +13,44 @@ class Waveform:
         :param sample_rate: sampling rate of the AWG to generate correct number of samples.
         """
         # define some useful numbers
-        scale = 2**11  # Mingkun uses 2^11, caltech uses 2^15, maybe this is scaling up a float to int?
-        num_tz = 2*n + 1  # total number of tweezers to be generated
-        max_amp = scale / np.sqrt(num_tz)  # again, saw this from multiple sources, not sure why
-
-        # self.amplitude = max_amp * np.ones(num_tz)
-        self.amplitude: np.ndarray = max_amp  # uniform amplitude for now, this will eventually be tweaked finely with experiments
-        self.omega: np.ndarray = 2*np.pi * np.linspace(cf - n*df, cf + n*df, num_tz)  # frequency tones
-        self.phi: np.ndarray = 2*np.pi * np.random.rand(num_tz)  # random initial phases from 0-2pi, also will be tweaked finely with experiments
+        scale = 2 ** 12  # sets the amplitude scale, max must not exceed 2**15-1
+        num_tz = 2 * n + 1  # total number of tweezers to be generated
+        max_amp = scale / np.sqrt(num_tz)  # scale down by number of tweezers
+
+        self.amplitude = max_amp * np.ones(num_tz)
+        # self.amplitude: np.ndarray = max_amp  # uniform amplitude
+        self.omega = 2 * np.pi * np.linspace(cf - n * df, cf + n * df, num_tz)  # frequency tones
+        self.phi = 2 * np.pi * np.random.rand(num_tz)  # random initial phases from 0-2pi
         self.sample_rate: int = sample_rate
-        # self.debug = {
-        #     "mat1": 0,
-        #     "mat2": 0,
-        #     "mat3": 0
-        # }
+        self.freq_res = freq_res
+        # formula for minimum sample length
+        # sample_len_min = 512 * m
+        # m * 512 * freq_resolution / sampling_rate = k, k % 2 == 0
+        self.sample_len_min = 2 * sample_rate / freq_res  # !!! this only works for sample_rate = 614.4e6 !!!
 
 
-def create_static_array(wfm: Waveform, sample_len: int) -> np.ndarray:
+def create_static_array(wfm: Waveform) -> np.ndarray:
     """
     create a static-array-generating waveform with user set number of samples
     :param wfm: waveform object already initialized with basic parameters.
-    :param sample_len: total number of samples to generate, must be multiples of 512. Note that more sample != higher resolution.
     :return: returns a 1D array with static-array-generating waveform.
     """
     # construct time axis, t_total(s) = sample_len / sample_rate, dt = t_total / sample_len
-    t = np.arange(sample_len) / wfm.sample_rate
+    t = np.arange(wfm.sample_len_min) / wfm.sample_rate
 
     # calculate individual sin waves, sig_mat[i] corresponds to data for ith tweezer
-    sin_mat = wfm.amplitude * np.sin(np.outer(wfm.omega,t) + np.expand_dims(wfm.phi, axis=1))  # shape=(number of tweezers x sample_len)
+    # sin_mat = wfm.amplitude * np.sin(np.outer(wfm.omega,t) + np.expand_dims(wfm.phi, axis=1))  # shape=(number of tweezers x sample_len)
+    sin_mat = np.sin(
+        np.outer(wfm.omega, t) + np.expand_dims(wfm.phi, axis=1)
+        # shape=(number of tweezers x sample_len)
+    )
+    sin_mat = (wfm.amplitude * sin_mat.T).T  # this works, trust me
 
     # sum up all rows to get final signal
-    return np.sum(sin_mat, axis=0)
+    sig = np.sum(sin_mat, axis=0)
+    if np.max(sig) >= 2 ** 15 - 1:
+        print("Signal amp exceeds maximum")
+    return sig.astype(np.int16)
 
 
 def create_path_table(wfm: Waveform) -> any:
@@ -51,24 +59,33 @@ def create_path_table(wfm: Waveform) -> any:
     :param wfm: waveform object already initialized with basic parameters.
     :return: dim-3 ndarray
     """
+    # interpolate optimal amplitudes
+    # data = np.load("data/optimal_amps.npz")
+    w = wfm.omega
+    a = wfm.amplitude
+    omega_interp = interp1d(w, a, kind='cubic')
+
     # setup basic variables
-    twopi = 2*np.pi
+    twopi = 2 * np.pi
     vmax = KILO(20) * MEGA(1)  # convert units, 20 kHz/us -> 20e3 * 1e6 Hz/s
     dw_max = wfm.omega[-1] - wfm.omega[0]  # Longest move in frequency
     t_max = 2 * dw_max / vmax  # Longest move sets the maximum moving time
     a_max = -vmax * 2 / t_max  # maximum acceleration, negative sign because of magic
-    sample_len_max = int(np.ceil(t_max * 4/5 * wfm.sample_rate))  # get number of samples required for longest move, this sets the size of lookup table
-    sample_len_max += (512 - sample_len_max % 512)  # make overall length a multiple of 512 so AWG doesn't freak out
+
+    # get number of samples required for longest move,this sets the size of lookup table
+    sample_len = int(np.ceil(t_max * wfm.sample_rate))
+    # sample_len += (512 - sample_len % 512)  # make overall length a multiple of 512 so AWG doesn't freak out
+    sample_len += wfm.sample_len_min - sample_len % wfm.sample_len_min
 
     # now we calculate all possible trajectories, go to Group Notes/Projects/Rearrangement for detail
     n = len(wfm.omega)  # total number of tweezers
-    phi_paths = np.zeros((n, n, sample_len_max))  # lookup table to store all moves
-    t = np.arange(sample_len_max) / wfm.sample_rate  # time series
+    path_table = np.zeros((n, n, sample_len))  # lookup table to store all moves
+    t = np.arange(sample_len) / wfm.sample_rate  # time series
     # iterate! I think this part can be vectorized as well... but unnecessary.
     for i, omega_i in enumerate(wfm.omega):
         for j, omega_j in enumerate(wfm.omega):  # j is the target position, i is starting position
             if i == j:
-                phi_paths[i,i] = omega_i*t + wfm.phi[i]
+                path_table[i, i] = wfm.amplitude[i] * np.sin(omega_i * t + wfm.phi[i])
                 continue  # skip diagonal entries
             dw = omega_j - omega_i  # delta omega in the equation
             adw = abs(dw)
@@ -79,129 +96,240 @@ def create_path_table(wfm: Waveform) -> any:
             dphi = phi_j - phi_i  # delta phi in the equation
             if dphi < 0: dphi = abs(dphi) + twopi - phi_i  # warp around for negative phase shift
             t_tot = np.sqrt(abs(4 * dw / a_max))  # calculate minimum time to complete move
-            t_tot = ((t_tot - 6*dphi/adw) // (12*np.pi/adw) + 1) * 12*np.pi/adw  # extend move time to arrive at the correct phase
-            a = 4*dw/(t_tot**2)  # adjust acceleration accordingly to ensure we still get to omega_j
+            t_tot = ((t_tot - 6 * dphi / adw) // (
+                    12 * np.pi / adw) + 1) * 12 * np.pi / adw  # extend move time to arrive at the correct phase
+            a = 4 * dw / (t_tot ** 2)  # adjust acceleration accordingly to ensure we still get to omega_j
             end = int(np.ceil(t_tot * wfm.sample_rate))  # convert to an index in samples
             half = int(end / 2)  # index of sample half-way through the move where equation changes
+            # if end % 2 == 0: half += 1
             t1 = t[:half]  # first half of the move, slicing to make life easier
-            t2 = t[half:end] - t_tot/2  # time series for second half of the move
+            t2 = t[half:end] - t_tot / 2  # time series for second half of the move
 
-            # do calculation
-            phi_paths[i,j, :half] = wfm.phi[i] + omega_i*t1 + a/6*t1**3  # t<=T/2
-            phi_paths[i,j, half:end] = phi_paths[i,j,half-1] + (omega_i+a/2*(t_tot/2)**2)*t2 + a/2*t_tot/2*t2**2 - a/6*t2**3  # t>=T/2
-            phi_paths[i,j, end:] = omega_j*t[end:] + (phi_j - omega_j*t_tot) % twopi  # fill the rest with parameters of target wave
+            # interpolate amplitudes during the move
+            amps = np.zeros(sample_len)
+            inst_w = np.zeros(end)
+            inst_w[0] = omega_i
+            inst_w[-1] = omega_j
+            inst_w[1:half] = omega_i + 0.5 * a * t1[1:] ** 2
+            inst_w[half:end - 1] = omega_i + \
+                                   a / 2 * (t_tot / 2) ** 2 + \
+                                   a * t_tot / 2 * t2[:-1] - \
+                                   a / 2 * t2[:-1] ** 2
+            amps[:end] = omega_interp(inst_w)
+            amps[end:] = wfm.amplitude[j]
+
+            # calculate sine wave
+            path_table[i, j, :half] = wfm.phi[i] + omega_i * t1 + a / 6 * t1 ** 3  # t<=T/2
+            path_table[i, j, half:end] = path_table[i, j, half - 1] + \
+                                         (omega_i + a / 2 * (t_tot / 2) ** 2) * t2 + \
+                                         a / 2 * t_tot / 2 * t2 ** 2 - \
+                                         a / 6 * t2 ** 3  # t>=T/2
+            path_table[i, j, end:] = omega_j * t[end:] + (phi_j - omega_j * t_tot) % twopi
+            path_table[i, j] = amps * np.sin(path_table[i, j])
 
     # now compile everything into sine wave
-    phi_paths = wfm.amplitude * np.sin(phi_paths)
-    return phi_paths.astype(int), np.sum(phi_paths.diagonal().T, axis=0, dtype=int)
+    path_table = path_table.astype(np.int16)
+    return path_table
+    # return path_table.astype(int), np.sum(path_table.diagonal().T, axis=0, dtype=int)
 
 
-def create_moving_array(sig: np.ndarray, path_table: np.ndarray, paths: np.ndarray) -> np.ndarray:
+def create_path_table_reduced(
+        wfm: Waveform, target_idx, max_dist=np.inf
+) -> Tuple[Dict[Tuple[int, int], np.ndarray], np.ndarray]:
     """
-    create a rearranging signal that moves tweezers as specified by paths
+    create a dim-3 look up table where the table[i,j] contains a sine wave to move tweezer i to tweezer j
+    :param target_idx: indices of target pattern
+    :param max_dist: maximum move distance in indices
     :param wfm: waveform object already initialized with basic parameters.
-    :param sin_mat: 2d array where ith entry contains static sin wave for ith tweezer. See create_static_array for detail.
-    :param path_table: lookup table returned from create_path_table().
-    :param paths: 1d array filled with tuples indicating moving trajectories. Example: np.array([(1,0),(2,1)]) moves tweezer1->0,tweezer2->1
-    :return: 1D array with rearrangement-generating waveform.
+    :return: dictionary containing rearrange paths
     """
-    # for i,j in paths:
-    #     sin_mat[i] = phi_paths[i,j]
-    # fyi, line below is equivalent to the for loop above
-    # sin_mat[paths[:,0]] = path_table[paths[:,0], paths[:,1]]  # copy dynamic trajectories from path_table,
-    # sin_mat[np.setdiff1d(paths[:,1], paths[:,0])] = 0  # turn off tweezers that need to be turned off, moving 3-2, 2-1, 1-0 will turn off 0.
-    # return np.sum(sin_mat, axis=0)  # sum up all rows to get final signal
-    # return np.sum(sin_mat, axis=0), sin_mat/wfm.amplitude
-    sig += -np.sum(path_table[paths[:,0], paths[:,0]], axis=0) + np.sum(path_table[paths[:,0], paths[:,1]], axis=0)
+    # interpolate optimal amplitudes
+    # data = np.load("data/optimal_amps.npz")
+    w = wfm.omega
+    a = wfm.amplitude
+    omega_interp = interp1d(w, a, kind='cubic')
 
+    # obtain all move combinations:
+    n = len(wfm.omega)  # total number of tweezers
+    moves = []
+    dw_max = 0  # longest move, this sets the size of path_table
+    for i in range(n):
+        moves.append([])
+        for j in target_idx:
+            if i < j and True:  # only allow uni-direction moves
+                continue
+            if abs(i - j) <= max_dist:
+                moves[i].append(j)
+                dw = abs(wfm.omega[j] - wfm.omega[i])
+                if dw_max < dw: dw_max = dw
 
-def old_code():
+    # setup basic variables
+    twopi = 2 * np.pi
+    vmax = KILO(20) * MEGA(1)  # convert units, 20 kHz/us -> 20e3 * 1e6 Hz/s
+    t_max = 2 * dw_max / vmax  # Longest move sets the maximum moving time
+    a_max = vmax * 2 / t_max  # maximum acceleration, negative sign because of magic
+    # get number of samples required for longest move,this sets the size of lookup table
+    sample_len = int(np.ceil(t_max * wfm.sample_rate))
+    # sample_len += (512 - sample_len % 512)  # make overall length a multiple of 512 so AWG doesn't freak out
+    sample_len += wfm.sample_len_min - sample_len % wfm.sample_len_min
+    sample_len = int(sample_len)
+    # now we calculate all possible trajectories, go to Group Notes/Projects/Rearrangement for detail
+    path_table = {}  # lookup table to store all moves
+    static_sig = np.zeros(sample_len)  # for fast real-time waveform generation purposes
+    t = np.arange(sample_len) / wfm.sample_rate  # time series
+    # iterate! I think this part can be vectorized as well... but unnecessary.
+    for i in range(n):
+        omega_i = wfm.omega[i]
+        for j in moves[i]:  # j is the target position, i is starting position
+            omega_j = wfm.omega[j]
+            if i == j:
+                path = (
+                        wfm.amplitude[i] * np.sin(omega_i * t + wfm.phi[i])
+                ).astype(np.int16)
+                # path = omega_i * t + wfm.phi[i]
+                path_table[(i, i)] = path
+                static_sig += path
+                continue  # skip diagonal entries
+
+            path = np.zeros(sample_len)
+
+            # I advise reading through the notes page first before going further
+            dw = omega_j - omega_i  # delta omega in the equation
+            adw = abs(dw)
+            t_tot = np.sqrt(abs(4 * dw / a_max))  # calculate minimum time to complete move
+
+            phi_j = wfm.phi[j] % twopi  # wrap around two pi
+            phi_i = wfm.phi[i] % twopi
+            dphi = (phi_j - phi_i) % twopi  # delta phi in the equation
+            if dphi < 0: dphi = abs(dphi) + twopi - phi_i  # warp around for negative phase shift
+            t_tot += 12 * np.pi / adw - (
+                    (t_tot - 6 * dphi / adw) %
+                    (12 * np.pi / adw))  # extend move time to arrive at the correct phase
+            a = 4 * adw / (t_tot ** 2)  # adjust acceleration accordingly to ensure we still get to omega_j
+
+            end = int(np.round(t_tot * wfm.sample_rate))  # convert to an index in samples
+            # print(f'({i},{j}), {end}')
+            half = int(end / 2) + 1  # index of sample half-way through the move where equation changes
+            # t_tot = t[end]
+            t1 = t[:half]  # first half of the move, slicing to make life easier
+            t2 = t[half:end] - t_tot / 2  # time series for second half of the move
+            # a = 4 * adw / (t_tot ** 2)  # adjust acceleration accordingly to ensure we still get to omega_j
+
+            # interpolate amplitudes during the move
+            amps = np.zeros(sample_len)
+            inst_w = np.zeros(end)
+            inst_w[0] = omega_i
+            inst_w[-1] = omega_j
+            inst_w[1:half] = omega_i - 0.5 * a * t1[1:] ** 2
+            inst_w[half:end - 1] = omega_i - \
+                                   a / 2 * (t_tot / 2) ** 2 - \
+                                   a * t_tot / 2 * t2[:-1] + \
+                                   a / 2 * t2[:-1] ** 2
+            sw = omega_i
+            bw = omega_j
+            if omega_i > omega_j:
+                sw = omega_j
+                bw = omega_i
+            inst_w[inst_w < sw] = sw
+            inst_w[inst_w > bw] = bw
+            amps[:end] = omega_interp(inst_w)
+            amps[end:] = wfm.amplitude[j]
+            # frequency/phase diagnostic
+            # print(i,j)
+            # print(inst_w[-2] - omega_j)
+            # print(a*t_tot**3/24 % np.pi - dphi)
+            # print(end)
+            # print()
+
+            # calculate sine wave
+            path[:half] = wfm.phi[i] + omega_i * t1 - a / 6 * t1 ** 3  # t<=T/2
+            # ph = wfm.phi[i] + omega_i * t_tot / 2 + a / 6 * (t_tot / 2) ** 3
+            path[half:end] = path[half-1] + \
+                             (omega_i - a / 2 * (t_tot / 2) ** 2) * t2 - \
+                             a / 2 * t_tot / 2 * t2 ** 2 + \
+                             a / 6 * t2 ** 3  # t>=T/2
+            path[end:] = path[end-1] + omega_j * (t[end:] - t[end-1])
+            path = (amps * np.sin(path)).astype(np.int16)
+            path_table[(i, j)] = path
+
+    for key in path_table:
+        if key[0] != key[1]:
+            path_table[key] -= path_table[(key[1], key[1])]  # for fast real-time generation
+        # path_table[key] = path_table[key].astype(np.int16)
+
+    return path_table, static_sig.astype(np.int16)
+
+
+def get_rearrange_paths(
+        filled_idx: np.ndarray,
+        target_idx: np.ndarray,
+) -> Tuple[np.ndarray, np.ndarray]:
     """
-    collection of unused code that might be useful later...
+    Calculate rearranging paths.
+    :param filled_idx: indices of tweezer positions filled with atoms.
+    :param target_idx: indices of tweezer positions in target pattern.
+    :returns: 2d array containing rearranging paths. 1d array containing tweezer positions to be turned off.
     """
-    # def create_phase_paths_old(wfm):
-    #     # setup basic variables
-    #     vmax = KILO(20) * MEGA(1)  # 20 kHz/us -> 20 kHz * 1e6 / s
-    #     dw_max = wfm.omega[-1] - wfm.omega[0]  # Longest move in frequency
-    #     t_max = 2 * dw_max / vmax  # Longest move sets the maximum moving time
-    #     a = -vmax * 2 / t_max  # constant acceleration, negative sign because of magic
-    #     sample_len = int(np.ceil(t_max * wfm.sample_rate))  # get number of samples required for longest move
-    #     sample_len += (512 - sample_len % 512)  # make overall length a multiple of 512
-    #     # t = np.zeros(padding+sample_len)
-    #     t = np.arange(sample_len) / wfm.sample_rate  # get time series
-    #
-    #     # generate phi for every possible move
-    #     n = len(wfm.omega)  # total number of tweezers
-    #     phi_paths = np.zeros((n, n, sample_len))  # map to store all moves
-    #     for i, omega_i in enumerate(wfm.omega):
-    #         for j, omega_j in enumerate(wfm.omega):  # I set j to be the target position, i to be starting position
-    #             if i == j: continue  # skil diagonal entries
-    #             dw = omega_j - omega_i  # delta omega
-    #             t_tot = np.sqrt(abs(4 * dw / a))  # total time to travel dw, t_tot <= t_max
-    #             end = int(np.ceil(t_tot * wfm.sample_rate))  # total number of samples to move dw
-    #             half = int(end / 2)  # index of sample half-way through the move
-    #             t1 = t[:half]  # time series for first half of the move
-    #             t2 = t[half:end] - t_tot/2  # time series for second half of the move
-    #             # do calculation
-    #             phi_paths[i,j, :half] = wfm.phi[i] + omega_i*t1 + a/6*t1**3  # t<=T/2 note we are changing the ith tweezer
-    #             phi_paths[i,j, half:end] = phi_paths[i,j,half-1] + (omega_i+a/2*(t_tot/2)**2)*t2 + a/2*t_tot/2*t2**2 - a/6*t2**3  # t>=T/2
-    #             # phi_paths[i,j, half:end] = phi_paths[i,j,half-1] + (omega_i+a*(t_tot/2)**2)*t2 + a/2*t_tot/2*t2**2 - a/6*t2**3  # t>=T/2
-    #             phi_paths[i,j, end:] = omega_j*t[end:]  # fill the rest of the array with target frequency wave
-    #             # phi_paths[i,j, end:] = phi_paths[i,j, end-1]*t[end:]  # fill the rest of the array with same value
-    #     return phi_paths
-
-    # sig_mat = wfm.amplitude * np.sin(np.outer(wfm.omega,t) + wfm.phi[:, np.newaxis])  # shape=(number of tweezers x sample_len)
-
-    # self.debug["mat1"] = np.zeros((n, n, 2))  # for phase debugging
-    # for diagnostic purposes
-    # path_idx[i, j] = (t_tot, end)
-    # phi_const[i,j, :half] = wfm.phi[i]
-    # phi_const[i,j, half:end] = phi_const[i,j,half-1] +
-    # print(a, t_tot)
-    # return phi_paths, wfm.amplitude * np.sin(np.outer(wfm.omega,t) + np.expand_dims(wfm.phi, axis=1)), path_idx
+    n = 0
+    t_size = target_idx.size
+    f_size = filled_idx.size
+    reserve = f_size - t_size
+    # move_paths = []
+    # static_paths = []
+    paths = []
+    i = 0
+    j = 0
+    while i < f_size:
+        if j == t_size: break
+        if filled_idx[i] == target_idx[j]:
+            # paths.append((filled_idx[i], filled_idx[i]))
+            j += 1
+            i = j
+        elif (reserve > 0
+              and filled_idx[i] < target_idx[j]
+              and abs(filled_idx[i + 1] - target_idx[j]) < abs(filled_idx[i + 1] - target_idx[j])):
+            i += 1
+            reserve -= 1
+        else:
+            paths.append((filled_idx[i], target_idx[j]))
+            i += 1
+            j += 1
+    off = []
+    if reserve < 0:
+        for i in range(abs(reserve)):
+            off.append(target_idx[-1 - i])
+    return np.array(paths), np.array(off)
 
-    pass
 
+def create_moving_array(path_table: np.ndarray, paths: np.ndarray) -> np.ndarray:
+    """
+    create a rearranging signal that moves tweezers as specified by paths
+    :param path_table: lookup table returned from create_path_table().
+    :param paths: 2d array with moving trajectories, [:,0] stores start pos, [:,1] stores end pos.
+    """
+    return np.sum(path_table[paths[:, 0], paths[:, 1]], axis=0)
 
-def main():
-    # sample usage
-    np.random.seed(0)
-    cf = MEGA(80)
-    df = MEGA(1)
-    n = 3
-    # period = 1/cf
-    sample = 512*1000
-    rate = MEGA(625)
-    # rate = sample*cf/1e5
-    print(f"Sampling rate: {rate/1e6}MHz")
-    print(f"center f: {cf/1e6}MHz\n"
-          f"df: {df/1e6}MHz\n"
-          f"total n: {2*n+1}")
-
-    wave = Waveform(cf, df, n, rate)
-    static_sig = create_static_array(wave, sample)
-    static_sig = static_sig.astype(int)
-
-    table, move_sig = create_path_table(wave)
-    repath = np.array([(1,0), (2,1), (3,2)])
-    create_moving_array(move_sig, table, repath)
-
-    np.savetxt("data/static_signal.txt", static_sig, delimiter=',')
-    np.savetxt("data/move_signal.txt", move_sig, delimiter=',')
-    # np.savetxt("initial_phi.txt", wave.phi, delimiter=',')
-    # np.savetxt("sig_mat.txt", sig_mat, delimiter=',')
-
-
-def size_test(n):
-    # sample usage
-    np.random.seed(0)
-    cf = MEGA(80)
-    df = MEGA(1)
-    rate = MEGA(625)
-    wave = Waveform(cf, df, n, rate)
-    table = create_path_table(wave)
-    return table.nbytes
-
-
-# main()
 
+def create_moving_array_reduced(
+        sig: np.ndarray,
+        path_table: Dict,
+        paths: np.ndarray,
+        off: np.ndarray
+):
+    """
+    create a rearranging signal that moves tweezers as specified by paths.
+    :param sig: initially a static-array-generating waveform.
+    :param path_table: lookup table returned from create_path_table_reduced().
+    :param paths: 2d array with moving trajectories, [:,0] stores start pos, [:,1] stores end pos.
+    :param off: 1d array with tweezer indices that need to be set to 0.
+    """
+    for i, j in paths:
+        if i == j:
+            continue
+        if (i, j) in path_table:
+            sig += path_table[(i, j)]
+        # else:
+        #     print((i, j), "not in path table")
+    for i in off:
+        sig -= path_table[(i, i)]
+    pass
diff --git a/Python/waveform_plots.py b/Python/waveform_plots.py
new file mode 100644
index 0000000000000000000000000000000000000000..049429d217b864e39511299c51afb415c79d0eb5
--- /dev/null
+++ b/Python/waveform_plots.py
@@ -0,0 +1,41 @@
+import scipy.signal as scisig
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy.interpolate import interp1d
+import os
+from mpl_toolkits.axes_grid1 import make_axes_locatable
+
+
+file = np.load("data/waveform_data.npz", allow_pickle=True)
+signal = file['signal']
+wfm = file['wfm'].item()
+sr = wfm.sample_rate
+
+f, t, Sxx = scisig.stft(signal, fs=sr, nperseg=256*100)
+f /= 1e6
+t *= 1e3
+Sxx[abs(Sxx) < 0.01] = 0
+
+fig, ax = plt.subplots()
+im = ax.pcolormesh(t, f, np.abs(Sxx), shading='gouraud')
+plt.title("Signal Spectrogram Frequency")
+plt.ylabel('Frequency [MHz]')
+plt.xlabel('Time [ms]')
+plt.ylim(95,115)
+divider = make_axes_locatable(ax)
+cax = divider.append_axes("right", size="5%", pad=0.05)
+fig.colorbar(im, cax=cax)
+if True:
+    plt.savefig("data/Spectrogram-frequency.png", dpi=1200)
+
+fig, ax = plt.subplots()
+im = ax.pcolormesh(t, f, np.angle(Sxx), shading='gouraud')
+plt.title("Signal Spectrogram Phase")
+plt.ylabel('Frequency [MHz]')
+plt.xlabel('Time [ms]')
+plt.ylim(95,115)
+divider = make_axes_locatable(ax)
+cax = divider.append_axes("right", size="5%", pad=0.05)
+fig.colorbar(im, cax=cax)
+if True:
+    plt.savefig("data/Spectrogram-phase.png", dpi=1200)