diff --git a/bilby/gw/__init__.py b/bilby/gw/__init__.py
index 3273cbd3e8358d2d91a3a44249c6dac3d00db287..d7ded8db4c4741c6c491e88a1c722447d3e2ba49 100644
--- a/bilby/gw/__init__.py
+++ b/bilby/gw/__init__.py
@@ -1,4 +1,4 @@
-from . import (calibration, conversion, detector, likelihood, prior, source,
+from . import (calibration, conversion, detector, likelihood, prior, series, source,
                utils, waveform_generator)
 
 from .waveform_generator import WaveformGenerator
diff --git a/bilby/gw/detector.py b/bilby/gw/detector.py
index d87ce2aaf8e9d6256424271de6ea82b8b4857d7b..03fb5b2ebef2ca780c8d5fcc23455e78d6b87e3a 100644
--- a/bilby/gw/detector.py
+++ b/bilby/gw/detector.py
@@ -11,6 +11,7 @@ from . import utils as gwutils
 from ..core import utils
 from ..core.utils import logger
 from .calibration import Recalibrate
+from .series import CoupledTimeAndFrequencySeries
 
 try:
     import gwpy
@@ -233,57 +234,14 @@ class InterferometerStrainData(object):
         self.roll_off = roll_off
         self.window_factor = 1
 
-        self._set_time_and_frequency_array_parameters(None, None, None)
+        self._times_and_frequencies = CoupledTimeAndFrequencySeries()
+        # self._set_time_and_frequency_array_parameters(None, None, None)
 
         self._frequency_domain_strain = None
         self._frequency_array = None
         self._time_domain_strain = None
         self._time_array = None
 
-    @property
-    def frequency_array(self):
-        """ Frequencies of the data in Hz """
-        if self._frequency_array is not None:
-            return self._frequency_array
-        else:
-            self._calculate_frequency_array()
-            return self._frequency_array
-
-    @frequency_array.setter
-    def frequency_array(self, frequency_array):
-        self._frequency_array = frequency_array
-
-    @property
-    def time_array(self):
-        """ Time of the data in seconds """
-        if self._time_array is not None:
-            return self._time_array
-        else:
-            self._calculate_time_array()
-            return self._time_array
-
-    @time_array.setter
-    def time_array(self, time_array):
-        self._time_array = time_array
-
-    def _calculate_time_array(self):
-        """ Calculate the time array """
-        if (self.sampling_frequency is None) or (self.duration is None):
-            raise ValueError(
-                "You have not specified the sampling_frequency and duration")
-
-        self.time_array = utils.create_time_series(
-            sampling_frequency=self.sampling_frequency, duration=self.duration,
-            starting_time=self.start_time)
-
-    def _calculate_frequency_array(self):
-        """ Calculate the frequency array """
-        if (self.sampling_frequency is None) or (self.duration is None):
-            raise ValueError(
-                "You have not specified the sampling_frequency and duration")
-        self.frequency_array = utils.create_frequency_series(
-            sampling_frequency=self.sampling_frequency, duration=self.duration)
-
     def time_within_data(self, time):
         """ Check if time is within the data span
 
@@ -503,10 +461,9 @@ class InterferometerStrainData(object):
         elif array is not None:
             if domain == 'time':
                 self.time_array = array
-                sampling_frequency, duration = utils.get_sampling_frequency_and_duration_from_time_array(array)
             elif domain == 'frequency':
                 self.frequency_array = array
-                sampling_frequency, duration = utils.get_sampling_frequency_and_duration_from_frequency_array(array)
+            return
         elif sampling_frequency is None or duration is None:
             raise ValueError(
                 "You must provide both sampling_frequency and duration")
@@ -744,9 +701,51 @@ class InterferometerStrainData(object):
         self.set_from_gwpy_timeseries(strain)
 
     def _set_time_and_frequency_array_parameters(self, duration, sampling_frequency, start_time):
-        self.sampling_frequency = sampling_frequency
-        self.duration = duration
-        self.start_time = start_time
+        self._times_and_frequencies = CoupledTimeAndFrequencySeries(duration=duration,
+                                                                    sampling_frequency=sampling_frequency,
+                                                                    start_time=start_time)
+
+    @property
+    def sampling_frequency(self):
+        return self._times_and_frequencies.sampling_frequency
+
+    @sampling_frequency.setter
+    def sampling_frequency(self, sampling_frequency):
+        self._times_and_frequencies.sampling_frequency = sampling_frequency
+
+    @property
+    def duration(self):
+        return self._times_and_frequencies.duration
+
+    @duration.setter
+    def duration(self, duration):
+        self._times_and_frequencies.duration = duration
+
+    @property
+    def start_time(self):
+        return self._times_and_frequencies.start_time
+
+    @start_time.setter
+    def start_time(self, start_time):
+        self._times_and_frequencies.start_time = start_time
+
+    @property
+    def frequency_array(self):
+        """ Frequencies of the data in Hz """
+        return self._times_and_frequencies.frequency_array
+
+    @frequency_array.setter
+    def frequency_array(self, frequency_array):
+        self._times_and_frequencies.frequency_array = frequency_array
+
+    @property
+    def time_array(self):
+        """ Time of the data in seconds """
+        return self._times_and_frequencies.time_array
+
+    @time_array.setter
+    def time_array(self, time_array):
+        self._times_and_frequencies.time_array = time_array
 
 
 class Interferometer(object):
diff --git a/bilby/gw/series.py b/bilby/gw/series.py
new file mode 100644
index 0000000000000000000000000000000000000000..aab94dee94d96db575af6ae6ee42c7b719250f7b
--- /dev/null
+++ b/bilby/gw/series.py
@@ -0,0 +1,123 @@
+from ..core import utils
+
+
+class CoupledTimeAndFrequencySeries(object):
+
+    def __init__(self, duration=None, sampling_frequency=None, start_time=0):
+        """ A waveform generator
+
+    Parameters
+    ----------
+    sampling_frequency: float, optional
+        The sampling frequency
+    duration: float, optional
+        Time duration of data
+    start_time: float, optional
+        Starting time of the time array
+        """
+        self.duration = duration
+        self.sampling_frequency = sampling_frequency
+        self.start_time = start_time
+        self._frequency_array_updated = False
+        self._time_array_updated = False
+
+    @property
+    def frequency_array(self):
+        """ Frequency array for the waveforms. Automatically updates if sampling_frequency or duration are updated.
+
+        Returns
+        -------
+        array_like: The frequency array
+        """
+        if self._frequency_array_updated is False:
+            if self.sampling_frequency and self.duration:
+                self.frequency_array = utils.create_frequency_series(
+                    sampling_frequency=self.sampling_frequency,
+                    duration=self.duration)
+            else:
+                raise ValueError('Can not calculate a frequency series without a '
+                                 'legitimate sampling_frequency ({}) or duration ({})'
+                                 .format(self.sampling_frequency, self.duration))
+
+        return self._frequency_array
+
+    @frequency_array.setter
+    def frequency_array(self, frequency_array):
+        self._frequency_array = frequency_array
+        self._sampling_frequency, self._duration = \
+            utils.get_sampling_frequency_and_duration_from_frequency_array(frequency_array)
+        self._frequency_array_updated = True
+
+    @property
+    def time_array(self):
+        """ Time array for the waveforms. Automatically updates if sampling_frequency or duration are updated.
+
+        Returns
+        -------
+        array_like: The time array
+        """
+
+        if self._time_array_updated is False:
+            if self.sampling_frequency and self.duration:
+                self._time_array = utils.create_time_series(
+                    sampling_frequency=self.sampling_frequency,
+                    duration=self.duration,
+                    starting_time=self.start_time)
+            else:
+                raise ValueError('Can not calculate a time series without a '
+                                 'legitimate sampling_frequency ({}) or duration ({})'
+                                 .format(self.sampling_frequency, self.duration))
+
+            self._time_array_updated = True
+        return self._time_array
+
+    @time_array.setter
+    def time_array(self, time_array):
+        self._time_array = time_array
+        self._sampling_frequency, self._duration = \
+            utils.get_sampling_frequency_and_duration_from_time_array(time_array)
+        self._start_time = time_array[0]
+        self._time_array_updated = True
+
+    @property
+    def duration(self):
+        """ Allows one to set the time duration and automatically updates the frequency and time array.
+
+        Returns
+        -------
+        float: The time duration.
+
+        """
+        return self._duration
+
+    @duration.setter
+    def duration(self, duration):
+        self._duration = duration
+        self._frequency_array_updated = False
+        self._time_array_updated = False
+
+    @property
+    def sampling_frequency(self):
+        """ Allows one to set the sampling frequency and automatically updates the frequency and time array.
+
+        Returns
+        -------
+        float: The sampling frequency.
+
+        """
+        return self._sampling_frequency
+
+    @sampling_frequency.setter
+    def sampling_frequency(self, sampling_frequency):
+        self._sampling_frequency = sampling_frequency
+        self._frequency_array_updated = False
+        self._time_array_updated = False
+
+    @property
+    def start_time(self):
+        return self._start_time
+
+    @start_time.setter
+    def start_time(self, start_time):
+        self._start_time = start_time
+        self._time_array_updated = False
diff --git a/bilby/gw/waveform_generator.py b/bilby/gw/waveform_generator.py
index 2166326170930c8caa7ee10241490895b48e01d1..6e9135731291f0f80172fea753583d5d5d22c2f0 100644
--- a/bilby/gw/waveform_generator.py
+++ b/bilby/gw/waveform_generator.py
@@ -1,5 +1,6 @@
 import numpy as np
 from ..core import utils
+from ..gw.series import CoupledTimeAndFrequencySeries
 
 
 class WaveformGenerator(object):
@@ -41,6 +42,9 @@ class WaveformGenerator(object):
         the WaveformGenerator object and initialised to `None`.
 
         """
+        self._times_and_frequencies = CoupledTimeAndFrequencySeries(duration=duration,
+                                                                    sampling_frequency=sampling_frequency,
+                                                                    start_time=start_time)
         self.duration = duration
         self.sampling_frequency = sampling_frequency
         self.start_time = start_time
@@ -57,8 +61,6 @@ class WaveformGenerator(object):
             self.waveform_arguments = dict()
         if isinstance(parameters, dict):
             self.parameters = parameters
-        self.__frequency_array_updated = False
-        self.__time_array_updated = False
 
     def __repr__(self):
         if self.frequency_domain_source_model is not None:
@@ -171,48 +173,6 @@ class WaveformGenerator(object):
                 model_strain[key] = transformation_function(transformed_model_strain[key], self.sampling_frequency)
         return model_strain
 
-    @property
-    def frequency_array(self):
-        """ Frequency array for the waveforms. Automatically updates if sampling_frequency or duration are updated.
-
-        Returns
-        -------
-        array_like: The frequency array
-        """
-        if self.__frequency_array_updated is False:
-            self.frequency_array = utils.create_frequency_series(
-                self.sampling_frequency,
-                self.duration)
-        return self.__frequency_array
-
-    @frequency_array.setter
-    def frequency_array(self, frequency_array):
-        self.__frequency_array = frequency_array
-        self.__frequency_array_updated = True
-
-    @property
-    def time_array(self):
-        """ Time array for the waveforms. Automatically updates if sampling_frequency or duration are updated.
-
-        Returns
-        -------
-        array_like: The time array
-        """
-
-        if self.__time_array_updated is False:
-            self.__time_array = utils.create_time_series(
-                self.sampling_frequency,
-                self.duration,
-                self.start_time)
-
-            self.__time_array_updated = True
-        return self.__time_array
-
-    @time_array.setter
-    def time_array(self, time_array):
-        self.__time_array = time_array
-        self.__time_array_updated = True
-
     @property
     def parameters(self):
         """ The dictionary of parameters for source model.
@@ -265,6 +225,34 @@ class WaveformGenerator(object):
                                  'model must be provided.')
         return set(utils.infer_parameters_from_function(model))
 
+    @property
+    def frequency_array(self):
+        """ Frequency array for the waveforms. Automatically updates if sampling_frequency or duration are updated.
+
+        Returns
+        -------
+        array_like: The frequency array
+        """
+        return self._times_and_frequencies.frequency_array
+
+    @frequency_array.setter
+    def frequency_array(self, frequency_array):
+        self._times_and_frequencies.frequency_array = frequency_array
+
+    @property
+    def time_array(self):
+        """ Time array for the waveforms. Automatically updates if sampling_frequency or duration are updated.
+
+        Returns
+        -------
+        array_like: The time array
+        """
+        return self._times_and_frequencies.time_array
+
+    @time_array.setter
+    def time_array(self, time_array):
+        self._times_and_frequencies.time_array = time_array
+
     @property
     def duration(self):
         """ Allows one to set the time duration and automatically updates the frequency and time array.
@@ -274,13 +262,11 @@ class WaveformGenerator(object):
         float: The time duration.
 
         """
-        return self.__duration
+        return self._times_and_frequencies.duration
 
     @duration.setter
     def duration(self, duration):
-        self.__duration = duration
-        self.__frequency_array_updated = False
-        self.__time_array_updated = False
+        self._times_and_frequencies.duration = duration
 
     @property
     def sampling_frequency(self):
@@ -291,19 +277,16 @@ class WaveformGenerator(object):
         float: The sampling frequency.
 
         """
-        return self.__sampling_frequency
+        return self._times_and_frequencies.sampling_frequency
 
     @sampling_frequency.setter
     def sampling_frequency(self, sampling_frequency):
-        self.__sampling_frequency = sampling_frequency
-        self.__frequency_array_updated = False
-        self.__time_array_updated = False
+        self._times_and_frequencies.sampling_frequency = sampling_frequency
 
     @property
     def start_time(self):
-        return self.__start_time
+        return self._times_and_frequencies.start_time
 
     @start_time.setter
-    def start_time(self, starting_time):
-        self.__start_time = starting_time
-        self.__time_array_updated = False
+    def start_time(self, start_time):
+        self._times_and_frequencies.start_time = start_time
diff --git a/test/detector_test.py b/test/detector_test.py
index 59efc53c8cb1e0986868e3c2a9a1263895882699..7ca202e6de1305c3e54e0194343cf06e5472cdd5 100644
--- a/test/detector_test.py
+++ b/test/detector_test.py
@@ -459,20 +459,20 @@ class TestInterferometerStrainData(unittest.TestCase):
             self.ifosd.frequency_domain_strain == frequency_domain_strain * self.ifosd.frequency_mask))
 
     def test_time_array_when_set(self):
-        test_array = np.array([1])
+        test_array = np.array([1, 2, 3])
         self.ifosd.time_array = test_array
-        self.assertTrue(test_array, self.ifosd.time_array)
-
-    @patch.object(bilby.core.utils, 'create_time_series')
-    def test_time_array_when_not_set(self, m):
-        self.ifosd.start_time = 3
-        self.ifosd.sampling_frequency = 1000
-        self.ifosd.duration = 5
-        m.return_value = 4
-        self.assertEqual(m.return_value, self.ifosd.time_array)
-        m.assert_called_with(sampling_frequency=self.ifosd.sampling_frequency,
-                             duration=self.ifosd.duration,
-                             starting_time=self.ifosd.start_time)
+        self.assertTrue(np.array_equal(test_array, self.ifosd.time_array))
+
+    def test_time_array_when_not_set(self):
+        with mock.patch('bilby.core.utils.create_time_series') as m:
+            self.ifosd.start_time = 3
+            self.ifosd.sampling_frequency = 1000
+            self.ifosd.duration = 5
+            m.return_value = 4
+            self.assertEqual(m.return_value, self.ifosd.time_array)
+            m.assert_called_with(sampling_frequency=1000,
+                                 duration=5,
+                                 starting_time=3)
 
     def test_time_array_without_sampling_frequency(self):
         self.ifosd.sampling_frequency = None
@@ -487,18 +487,18 @@ class TestInterferometerStrainData(unittest.TestCase):
             test = self.ifosd.time_array
 
     def test_frequency_array_when_set(self):
-        test_array = np.array([1])
+        test_array = np.array([1, 2, 3])
         self.ifosd.frequency_array = test_array
-        self.assertTrue(test_array, self.ifosd.frequency_array)
+        self.assertTrue(np.array_equal(test_array, self.ifosd.frequency_array))
 
-    @patch.object(bilby.core.utils, 'create_frequency_series')
-    def test_time_array_when_not_set(self, m):
-        self.ifosd.sampling_frequency = 1000
-        self.ifosd.duration = 5
-        m.return_value = 4
-        self.assertEqual(m.return_value, self.ifosd.frequency_array)
-        m.assert_called_with(sampling_frequency=self.ifosd.sampling_frequency,
-                             duration=self.ifosd.duration)
+    def test_frequency_array_when_not_set(self):
+        with mock.patch('bilby.core.utils.create_frequency_series') as m:
+            m.return_value = [1, 2, 3]
+            self.ifosd.sampling_frequency = 1000
+            self.ifosd.duration = 5
+            self.assertListEqual(m.return_value, self.ifosd.frequency_array)
+            m.assert_called_with(sampling_frequency=1000,
+                                 duration=5)
 
     def test_frequency_array_without_sampling_frequency(self):
         self.ifosd.sampling_frequency = None
@@ -565,8 +565,8 @@ class TestInterferometerStrainData(unittest.TestCase):
         m.return_value = 5
         self.ifosd.sampling_frequency = 200
         self.ifosd.duration = 4
-        self.ifosd._frequency_domain_strain = self.ifosd.frequency_array
         self.ifosd.sampling_frequency = 123
+        self.ifosd.frequency_domain_strain = self.ifosd.frequency_array
         self.assertEqual(m.return_value, self.ifosd.time_domain_strain)
 
     def test_time_domain_strain_not_set(self):
@@ -719,14 +719,32 @@ class TestInterferometerList(unittest.TestCase):
         self.assertEqual(self.ifo2, ifo_list[1])
 
     def test_init_inconsistent_duration(self):
+        self.frequency_arrays = np.linspace(0, 2048, 2049)
+        self.ifo2 = bilby.gw.detector.Interferometer(name=self.name2,
+                                                     power_spectral_density=self.power_spectral_density2,
+                                                     minimum_frequency=self.minimum_frequency2,
+                                                     maximum_frequency=self.maximum_frequency2, length=self.length2,
+                                                     latitude=self.latitude2, longitude=self.longitude2,
+                                                     elevation=self.elevation2,
+                                                     xarm_azimuth=self.xarm_azimuth2, yarm_azimuth=self.yarm_azimuth2,
+                                                     xarm_tilt=self.xarm_tilt2, yarm_tilt=self.yarm_tilt2)
         self.ifo2.strain_data.set_from_frequency_domain_strain(
-            np.linspace(0, 4096, 4097), sampling_frequency=4096, duration=3)
+            self.frequency_arrays, sampling_frequency=4096, duration=1)
         with self.assertRaises(ValueError):
             bilby.gw.detector.InterferometerList([self.ifo1, self.ifo2])
 
     def test_init_inconsistent_sampling_frequency(self):
+        self.frequency_arrays = np.linspace(0, 2048, 2049)
+        self.ifo2 = bilby.gw.detector.Interferometer(name=self.name2,
+                                                     power_spectral_density=self.power_spectral_density2,
+                                                     minimum_frequency=self.minimum_frequency2,
+                                                     maximum_frequency=self.maximum_frequency2, length=self.length2,
+                                                     latitude=self.latitude2, longitude=self.longitude2,
+                                                     elevation=self.elevation2,
+                                                     xarm_azimuth=self.xarm_azimuth2, yarm_azimuth=self.yarm_azimuth2,
+                                                     xarm_tilt=self.xarm_tilt2, yarm_tilt=self.yarm_tilt2)
         self.ifo2.strain_data.set_from_frequency_domain_strain(
-            np.linspace(0, 4096, 4097), sampling_frequency=234, duration=2)
+            self.frequency_arrays, sampling_frequency=2048, duration=2)
         with self.assertRaises(ValueError):
             bilby.gw.detector.InterferometerList([self.ifo1, self.ifo2])