diff --git a/gwinc/ifo/Aplus/__init__.py b/gwinc/ifo/Aplus/__init__.py
index a4dedee87ba3188a855be601e365ff98c3b833ae..bef20eb75510d1f4d7babac3262d6f89852713bf 100644
--- a/gwinc/ifo/Aplus/__init__.py
+++ b/gwinc/ifo/Aplus/__init__.py
@@ -1,8 +1,10 @@
-from gwinc.ifo.noises import *
 from gwinc.ifo import PLOT_STYLE
+from gwinc import noise
+from gwinc import nb
+from gwinc.ifo.noises import Strain
 
 
-class QuantumVacuum(nb.Budget):
+class Quantum(nb.Budget):
     """Quantum Vacuum
 
     """
@@ -12,13 +14,13 @@ class QuantumVacuum(nb.Budget):
     )
 
     noises = [
-        QuantumVacuumAS,
-        QuantumVacuumArm,
-        QuantumVacuumSEC,
-        QuantumVacuumFilterCavity,
-        QuantumVacuumInjection,
-        QuantumVacuumReadout,
-        QuantumVacuumQuadraturePhase,
+        noise.quantum.AS,
+        noise.quantum.Arm,
+        noise.quantum.SEC,
+        noise.quantum.FilterCavity,
+        noise.quantum.Injection,
+        noise.quantum.Readout,
+        noise.quantum.QuadraturePhase,
     ]
 
 
@@ -27,15 +29,15 @@ class Aplus(nb.Budget):
     name = 'A+'
 
     noises = [
-        QuantumVacuum,
-        Seismic,
-        Newtonian,
-        SuspensionThermal,
-        CoatingBrownian,
-        CoatingThermoOptic,
-        SubstrateBrownian,
-        SubstrateThermoElastic,
-        ExcessGas,
+        Quantum,
+        noise.seismic.Seismic,
+        noise.newtonian.Newtonian,
+        noise.suspensionthermal.SuspensionThermal,
+        noise.coatingthermal.CoatingBrownian,
+        noise.coatingthermal.CoatingThermoOptic,
+        noise.substratethermal.SubstrateBrownian,
+        noise.substratethermal.SubstrateThermoElastic,
+        noise.residualgas.ResidualGas,
     ]
 
     calibrations = [
diff --git a/gwinc/ifo/CE1/__init__.py b/gwinc/ifo/CE1/__init__.py
index 586d3f73218721c748e49cfe98ac41347822859b..04594b3d33fc9b77c1ff80698c54bd027ee7a57a 100644
--- a/gwinc/ifo/CE1/__init__.py
+++ b/gwinc/ifo/CE1/__init__.py
@@ -1,8 +1,10 @@
-from gwinc.ifo.noises import *
 from gwinc.ifo import PLOT_STYLE
+from gwinc import noise
+from gwinc import nb
+from gwinc.ifo.noises import Strain
 
 
-class QuantumVacuum(nb.Budget):
+class Quantum(nb.Budget):
     """Quantum Vacuum
 
     """
@@ -12,13 +14,13 @@ class QuantumVacuum(nb.Budget):
     )
 
     noises = [
-        QuantumVacuumAS,
-        QuantumVacuumArm,
-        QuantumVacuumSEC,
-        QuantumVacuumFilterCavity,
-        QuantumVacuumInjection,
-        QuantumVacuumReadout,
-        QuantumVacuumQuadraturePhase,
+        noise.quantum.AS,
+        noise.quantum.Arm,
+        noise.quantum.SEC,
+        noise.quantum.FilterCavity,
+        noise.quantum.Injection,
+        noise.quantum.Readout,
+        noise.quantum.QuadraturePhase,
     ]
 
 
@@ -35,9 +37,9 @@ class Newtonian(nb.Budget):
     )
 
     noises = [
-        NewtonianRayleigh,
-        NewtonianBody,
-        NewtonianInfrasound,
+        noise.newtonian.Rayleigh,
+        noise.newtonian.Body,
+        noise.newtonian.Infrasound,
     ]
 
 
@@ -54,8 +56,8 @@ class Coating(nb.Budget):
     )
 
     noises = [
-        CoatingBrownian,
-        CoatingThermoOptic,
+        noise.coatingthermal.CoatingBrownian,
+        noise.coatingthermal.CoatingThermoOptic,
     ]
 
 
@@ -72,8 +74,8 @@ class Substrate(nb.Budget):
     )
 
     noises = [
-        SubstrateBrownian,
-        SubstrateThermoElastic,
+        noise.substratethermal.SubstrateBrownian,
+        noise.substratethermal.SubstrateThermoElastic,
     ]
 
 
@@ -82,13 +84,13 @@ class CE1(nb.Budget):
     name = 'Cosmic Explorer 1'
 
     noises = [
-        QuantumVacuum,
-        Seismic,
+        Quantum,
+        noise.seismic.Seismic,
         Newtonian,
-        SuspensionThermal,
+        noise.suspensionthermal.SuspensionThermal,
         Coating,
         Substrate,
-        ExcessGas,
+        noise.residualgas.ResidualGas,
     ]
 
     calibrations = [
diff --git a/gwinc/ifo/CE2silica/__init__.py b/gwinc/ifo/CE2silica/__init__.py
index 8cbfa37fea241ec50a648a14fe11c37ed3bc7ec4..4af888b20e148aa2a0da83db3655c37fbca17b20 100644
--- a/gwinc/ifo/CE2silica/__init__.py
+++ b/gwinc/ifo/CE2silica/__init__.py
@@ -1,8 +1,10 @@
-from gwinc.ifo.noises import *
 from gwinc.ifo import PLOT_STYLE
+from gwinc import noise
+from gwinc import nb
+from gwinc.ifo.noises import Strain
 
 
-class QuantumVacuum(nb.Budget):
+class Quantum(nb.Budget):
     """Quantum Vacuum
 
     """
@@ -12,13 +14,13 @@ class QuantumVacuum(nb.Budget):
     )
 
     noises = [
-        QuantumVacuumAS,
-        QuantumVacuumArm,
-        QuantumVacuumSEC,
-        QuantumVacuumFilterCavity,
-        QuantumVacuumInjection,
-        QuantumVacuumReadout,
-        QuantumVacuumQuadraturePhase,
+        noise.quantum.AS,
+        noise.quantum.Arm,
+        noise.quantum.SEC,
+        noise.quantum.FilterCavity,
+        noise.quantum.Injection,
+        noise.quantum.Readout,
+        noise.quantum.QuadraturePhase,
     ]
 
 
@@ -35,9 +37,9 @@ class Newtonian(nb.Budget):
     )
 
     noises = [
-        NewtonianRayleigh,
-        NewtonianBody,
-        NewtonianInfrasound,
+        noise.newtonian.Rayleigh,
+        noise.newtonian.Body,
+        noise.newtonian.Infrasound,
     ]
 
 
@@ -54,8 +56,8 @@ class Coating(nb.Budget):
     )
 
     noises = [
-        CoatingBrownian,
-        CoatingThermoOptic,
+        noise.coatingthermal.CoatingBrownian,
+        noise.coatingthermal.CoatingThermoOptic,
     ]
 
 
@@ -72,8 +74,8 @@ class Substrate(nb.Budget):
     )
 
     noises = [
-        SubstrateBrownian,
-        SubstrateThermoElastic,
+        noise.substratethermal.SubstrateBrownian,
+        noise.substratethermal.SubstrateThermoElastic,
     ]
 
 
@@ -82,13 +84,13 @@ class CE2silica(nb.Budget):
     name = 'Cosmic Explorer 2 (Silica)'
 
     noises = [
-        QuantumVacuum,
-        Seismic,
+        Quantum,
+        noise.seismic.Seismic,
         Newtonian,
-        SuspensionThermal,
+        noise.suspensionthermal.SuspensionThermal,
         Coating,
         Substrate,
-        ExcessGas,
+        noise.residualgas.ResidualGas,
     ]
 
     calibrations = [
diff --git a/gwinc/ifo/CE2silicon/__init__.py b/gwinc/ifo/CE2silicon/__init__.py
index 1c43d3b7bc7322097625ec9142013c2ccce452c2..2dca9814ce0289e485985c4d54261afa18b44ec4 100644
--- a/gwinc/ifo/CE2silicon/__init__.py
+++ b/gwinc/ifo/CE2silicon/__init__.py
@@ -1,8 +1,10 @@
-from gwinc.ifo.noises import *
 from gwinc.ifo import PLOT_STYLE
+from gwinc import noise
+from gwinc import nb
+from gwinc.ifo.noises import Strain
 
 
-class QuantumVacuum(nb.Budget):
+class Quantum(nb.Budget):
     """Quantum Vacuum
 
     """
@@ -12,13 +14,13 @@ class QuantumVacuum(nb.Budget):
     )
 
     noises = [
-        QuantumVacuumAS,
-        QuantumVacuumArm,
-        QuantumVacuumSEC,
-        QuantumVacuumFilterCavity,
-        QuantumVacuumInjection,
-        QuantumVacuumReadout,
-        QuantumVacuumQuadraturePhase,
+        noise.quantum.AS,
+        noise.quantum.Arm,
+        noise.quantum.SEC,
+        noise.quantum.FilterCavity,
+        noise.quantum.Injection,
+        noise.quantum.Readout,
+        noise.quantum.QuadraturePhase,
     ]
 
 
@@ -35,9 +37,9 @@ class Newtonian(nb.Budget):
     )
 
     noises = [
-        NewtonianRayleigh,
-        NewtonianBody,
-        NewtonianInfrasound,
+        noise.newtonian.Rayleigh,
+        noise.newtonian.Body,
+        noise.newtonian.Infrasound,
     ]
 
 
@@ -54,8 +56,8 @@ class Coating(nb.Budget):
     )
 
     noises = [
-        CoatingBrownian,
-        CoatingThermoOptic,
+        noise.coatingthermal.CoatingBrownian,
+        noise.coatingthermal.CoatingThermoOptic,
     ]
 
 
@@ -72,9 +74,9 @@ class Substrate(nb.Budget):
     )
 
     noises = [
-        ITMThermoRefractive,
-        SubstrateBrownian,
-        SubstrateThermoElastic,
+        noise.substratethermal.ITMThermoRefractive,
+        noise.substratethermal.SubstrateBrownian,
+        noise.substratethermal.SubstrateThermoElastic,
     ]
 
 
@@ -83,13 +85,13 @@ class CE2silicon(nb.Budget):
     name = 'Cosmic Explorer 2 (Silicon)'
 
     noises = [
-        QuantumVacuum,
-        Seismic,
+        Quantum,
+        noise.seismic.Seismic,
         Newtonian,
-        SuspensionThermal,
+        noise.suspensionthermal.SuspensionThermal,
         Coating,
         Substrate,
-        ExcessGas,
+        noise.residualgas.ResidualGas,
     ]
 
     calibrations = [
diff --git a/gwinc/ifo/Voyager/__init__.py b/gwinc/ifo/Voyager/__init__.py
index 41612d3feb03e8fc846a99665430188741e8b53b..c7dcf2e1dab377592d44136a32050d4f33eb1fd6 100644
--- a/gwinc/ifo/Voyager/__init__.py
+++ b/gwinc/ifo/Voyager/__init__.py
@@ -1,8 +1,10 @@
-from gwinc.ifo.noises import *
 from gwinc.ifo import PLOT_STYLE
+from gwinc import noise
+from gwinc import nb
+from gwinc.ifo.noises import Strain
 
 
-class QuantumVacuum(nb.Budget):
+class Quantum(nb.Budget):
     """Quantum Vacuum
 
     """
@@ -12,12 +14,12 @@ class QuantumVacuum(nb.Budget):
     )
 
     noises = [
-        QuantumVacuumAS,
-        QuantumVacuumArm,
-        QuantumVacuumSEC,
-        QuantumVacuumFilterCavity,
-        QuantumVacuumInjection,
-        QuantumVacuumReadout,
+        noise.quantum.AS,
+        noise.quantum.Arm,
+        noise.quantum.SEC,
+        noise.quantum.FilterCavity,
+        noise.quantum.Injection,
+        noise.quantum.Readout,
     ]
 
 
@@ -26,16 +28,16 @@ class Voyager(nb.Budget):
     name = 'Voyager'
 
     noises = [
-        QuantumVacuum,
-        Seismic,
-        Newtonian,
-        SuspensionThermal,
-        CoatingBrownian,
-        CoatingThermoOptic,
-        ITMThermoRefractive,
-        SubstrateBrownian,
-        SubstrateThermoElastic,
-        ExcessGas,
+        Quantum,
+        noise.seismic.Seismic,
+        noise.newtonian.Newtonian,
+        noise.suspensionthermal.SuspensionThermal,
+        noise.coatingthermal.CoatingBrownian,
+        noise.coatingthermal.CoatingThermoOptic,
+        noise.substratethermal.ITMThermoRefractive,
+        noise.substratethermal.SubstrateBrownian,
+        noise.substratethermal.SubstrateThermoElastic,
+        noise.residualgas.ResidualGas,
     ]
 
     calibrations = [
diff --git a/gwinc/ifo/aLIGO/__init__.py b/gwinc/ifo/aLIGO/__init__.py
index 2e299e53626f3c602d2b3a9cf2c0b15949ebc727..5b0d2457d0612670ee196cf805fb24c14a1ffd7a 100644
--- a/gwinc/ifo/aLIGO/__init__.py
+++ b/gwinc/ifo/aLIGO/__init__.py
@@ -1,8 +1,10 @@
-from gwinc.ifo.noises import *
 from gwinc.ifo import PLOT_STYLE
+from gwinc import noise
+from gwinc import nb
+from gwinc.ifo.noises import Strain
 
 
-class QuantumVacuum(nb.Budget):
+class Quantum(nb.Budget):
     """Quantum Vacuum
 
     """
@@ -12,10 +14,10 @@ class QuantumVacuum(nb.Budget):
     )
 
     noises = [
-        QuantumVacuumAS,
-        QuantumVacuumArm,
-        QuantumVacuumSEC,
-        QuantumVacuumReadout,
+        noise.quantum.AS,
+        noise.quantum.Arm,
+        noise.quantum.SEC,
+        noise.quantum.Readout,
     ]
 
 
@@ -24,15 +26,15 @@ class aLIGO(nb.Budget):
     name = 'Advanced LIGO'
 
     noises = [
-        QuantumVacuum,
-        Seismic,
-        Newtonian,
-        SuspensionThermal,
-        CoatingBrownian,
-        CoatingThermoOptic,
-        SubstrateBrownian,
-        SubstrateThermoElastic,
-        ExcessGas,
+        Quantum,
+        noise.seismic.Seismic,
+        noise.newtonian.Newtonian,
+        noise.suspensionthermal.SuspensionThermal,
+        noise.coatingthermal.CoatingBrownian,
+        noise.coatingthermal.CoatingThermoOptic,
+        noise.substratethermal.SubstrateBrownian,
+        noise.substratethermal.SubstrateThermoElastic,
+        noise.residualgas.ResidualGas,
     ]
 
     calibrations = [
diff --git a/gwinc/ifo/noises.py b/gwinc/ifo/noises.py
index 7666a1611d0912334fc5eacd907a53d4d6a4947b..1e48bacc4f70ec3a109fcc459bf3fa5908e4c3c1 100644
--- a/gwinc/ifo/noises.py
+++ b/gwinc/ifo/noises.py
@@ -1,4 +1,3 @@
-import copy
 import numpy as np
 from numpy import pi, sin, exp, sqrt
 
@@ -6,7 +5,6 @@ from .. import logger
 from .. import const
 from ..struct import Struct
 from .. import nb
-from .. import noise
 from .. import suspension
 
 
@@ -14,30 +12,6 @@ from .. import suspension
 # helper functions
 ############################################################
 
-def mirror_struct(ifo, tm):
-    """Create a "mirror" Struct for a LIGO core optic
-
-    This is a copy of the ifo.Materials Struct, containing Substrate
-    and Coating sub-Structs, as well as some basic geometrical
-    properties of the optic.
-
-    """
-    # NOTE: we deepcopy this struct since we'll be modifying it (and
-    # it would otherwise be stored by reference)
-    mirror = copy.deepcopy(ifo.Materials)
-    optic = ifo.Optics.get(tm)
-    if 'CoatLayerOpticalThickness' in optic:
-        mirror.Coating.dOpt = optic['CoatLayerOpticalThickness']
-    else:
-        T = optic.Transmittance
-        dL = optic.CoatingThicknessLown
-        dCap = optic.CoatingThicknessCap
-        mirror.Coating.dOpt = noise.coatingthermal.getCoatDopt(mirror, T, dL, dCap=dCap)
-    mirror.update(optic)
-    mirror.MassVolume = pi * mirror.MassRadius**2 * mirror.MassThickness
-    mirror.MirrorMass = mirror.MassVolume * mirror.Substrate.MassDensity
-    return mirror
-
 
 def arm_cavity(ifo):
     L = ifo.Infrastructure.Length
@@ -167,557 +141,3 @@ class Force(nb.Calibration):
     def calc(self):
         mass = mirror_struct(self.ifo, 'ETM').MirrorMass
         return (mass * (2*pi*self.freq)**2)**2
-
-
-############################################################
-# noise sources
-############################################################
-
-#########################
-# quantum
-#########################
-
-class QuantumVacuum(nb.Noise):
-    """Quantum Vacuum
-
-    """
-    style = dict(
-        label='Quantum Vacuum',
-        color='#ad03de',
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        total = np.zeros_like(quantum.ASvac)
-        for nn in quantum.values():
-            total += nn
-        return total
-
-
-class QuantumVacuumAS(nb.Noise):
-    """Quantum vacuum from the AS port
-
-    """
-    style = dict(
-        label='AS Port Vacuum',
-        color='xkcd:emerald green'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.ASvac
-
-
-class QuantumVacuumArm(nb.Noise):
-    """Quantum vacuum due to arm cavity loss
-
-    """
-    style = dict(
-        label='Arm Loss',
-        color='xkcd:orange brown'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.Arm
-
-
-class QuantumVacuumSEC(nb.Noise):
-    """Quantum vacuum due to SEC loss
-
-    """
-    style = dict(
-        label='SEC Loss',
-        color='xkcd:cerulean'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.SEC
-
-
-class QuantumVacuumFilterCavity(nb.Noise):
-    """Quantum vacuum due to filter cavity loss
-
-    """
-    style = dict(
-        label='Filter Cavity Loss',
-        color='xkcd:goldenrod'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.FC
-
-
-class QuantumVacuumInjection(nb.Noise):
-    """Quantum vacuum due to injection loss
-
-    """
-    style = dict(
-        label='Injection Loss',
-        color='xkcd:fuchsia'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.Injection
-
-
-class QuantumVacuumReadout(nb.Noise):
-    """Quantum vacuum due to readout loss
-
-    """
-    style = dict(
-        label='Readout Loss',
-        color='xkcd:mahogany'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.PD
-
-
-class QuantumVacuumQuadraturePhase(nb.Noise):
-    """Quantum vacuum noise due to quadrature phase noise
-    """
-    style = dict(
-        label='Quadrature Phase',
-        color='xkcd:slate'
-    )
-
-    @nb.precomp(quantum=noise.quantum.precomp_quantum)
-    def calc(self, quantum):
-        return quantum.Phase
-
-
-class StandardQuantumLimit(nb.Noise):
-    """Standard Quantum Limit
-
-    """
-    style = dict(
-        label="Standard Quantum Limit",
-        color="#000000",
-        linestyle=":",
-    )
-
-    def calc(self):
-        ETM = mirror_struct(self.ifo, 'ETM')
-        return 8 * const.hbar / (ETM.MirrorMass * (2 * np.pi * self.freq) ** 2)
-
-
-#########################
-# seismic
-#########################
-
-def Seismic_constructor(direction):
-    """Seismic noise for a single direction
-
-    """
-    if direction == 'horiz':
-        label = 'Horizontal'
-        color = 'xkcd:muted blue'
-    elif direction == 'vert':
-        label = 'Vertical'
-        color = 'xkcd:brick red'
-
-    class SeismicDirection(nb.Noise):
-        name = label
-        style = dict(
-            label=label,
-            color=color,
-        )
-
-        @nb.precomp(sustf=suspension.precomp_suspension)
-        def calc(self, sustf):
-            nt, nr = noise.seismic.platform_motion(self.freq, self.ifo)
-            n = noise.seismic.seismic_suspension_filtered(sustf, nt, direction)
-            return n * 4
-
-    return SeismicDirection
-
-
-class Seismic(nb.Budget):
-    """Seismic
-
-    """
-    style = dict(
-        label='Seismic',
-        color='#855700',
-    )
-
-    noises = [
-        Seismic_constructor('vert'),
-        Seismic_constructor('horiz'),
-    ]
-
-
-#########################
-# Newtonian
-#########################
-
-class Newtonian(nb.Noise):
-    """Newtonian Gravity
-
-    """
-    style = dict(
-        label='Newtonian Gravity',
-        color='#15b01a',
-    )
-
-    def calc(self):
-        n = noise.newtonian.gravg(self.freq, self.ifo.Seismic)
-        return n * 4
-
-
-class NewtonianRayleigh(nb.Noise):
-    """Newtonian Gravity, Rayleigh waves
-
-    """
-    style = dict(
-        label='Newtonian (Rayleigh waves)',
-        color='#1b2431',
-    )
-
-    def calc(self):
-        n = noise.newtonian.gravg_rayleigh(self.freq, self.ifo.Seismic)
-        return n * 2
-
-
-class NewtonianBody(nb.Noise):
-    """Newtonian Gravity, body waves
-
-    """
-    style = dict(
-        label='Newtonian (body waves)',
-        color='#85a3b2',
-    )
-
-    def calc(self):
-        np = noise.newtonian.gravg_pwave(self.freq, self.ifo.Seismic)
-        ns = noise.newtonian.gravg_swave(self.freq, self.ifo.Seismic)
-        return (np + ns) * 4
-
-
-class NewtonianInfrasound(nb.Noise):
-    """Newtonian Gravity, infrasound
-
-    """
-    style = dict(
-        label='Newtonian (infrasound)',
-        color='#ffa62b',
-    )
-
-    def calc(self):
-        n = noise.newtonian.atmois(self.freq, self.ifo.Atmospheric, self.ifo.Seismic)
-        return n * 2
-
-
-#########################
-# suspension thermal
-#########################
-
-def SuspensionThermal_constructor(stage_num, direction):
-    """Suspension thermal for a single stage in one direction
-
-    """
-    if stage_num == 0:
-        stage_name = 'Top'
-        color = 'xkcd:orangeish'
-    elif stage_num == 1:
-        stage_name = 'UIM'
-        color = 'xkcd:mustard'
-    elif stage_num == 2:
-        stage_name = 'PUM'
-        color = 'xkcd:turquoise'
-    elif stage_num == 3:
-        stage_name = 'Test mass'
-        color = 'xkcd:bright purple'
-
-    if direction == 'horiz':
-        name0 = 'Horiz' + stage_name
-        label = 'Horiz. ' + stage_name
-        linestyle = '-'
-    elif direction == 'vert':
-        name0 = 'Vert' + stage_name
-        label = 'Vert. ' + stage_name
-        linestyle = '--'
-
-    class SuspensionThermalStage(nb.Noise):
-        name = name0
-        style = dict(
-            label=label,
-            color=color,
-            linestyle=linestyle,
-            alpha=0.7,
-        )
-
-        @nb.precomp(sustf=suspension.precomp_suspension)
-        def calc(self, sustf):
-            n = noise.suspensionthermal.susptherm_stage(
-                self.freq, self.ifo.Suspension, sustf, stage_num, direction)
-            return abs(n) * 4
-
-    return SuspensionThermalStage
-
-
-class SuspensionThermal(nb.Budget):
-    """Suspension Thermal
-
-    """
-
-    name = 'SuspensionThermal'
-
-    style = dict(
-        label='Suspension Thermal',
-        color='#0d75f8',
-    )
-
-    noises = [
-        SuspensionThermal_constructor(0, 'horiz'),
-        SuspensionThermal_constructor(1, 'horiz'),
-        SuspensionThermal_constructor(2, 'horiz'),
-        SuspensionThermal_constructor(3, 'horiz'),
-        SuspensionThermal_constructor(0, 'vert'),
-        SuspensionThermal_constructor(1, 'vert'),
-        SuspensionThermal_constructor(2, 'vert'),
-    ]
-
-
-#########################
-# coating thermal
-#########################
-
-class CoatingBrownian(nb.Noise):
-    """Coating Brownian
-
-    """
-    style = dict(
-        label='Coating Brownian',
-        color='#fe0002',
-    )
-
-    def calc(self):
-        ITM = mirror_struct(self.ifo, 'ITM')
-        ETM = mirror_struct(self.ifo, 'ETM')
-        cavity = arm_cavity(self.ifo)
-        wavelength = self.ifo.Laser.Wavelength
-        nITM = noise.coatingthermal.coating_brownian(
-            self.freq, ITM, wavelength, cavity.wBeam_ITM
-        )
-        nETM = noise.coatingthermal.coating_brownian(
-            self.freq, ETM, wavelength, cavity.wBeam_ETM
-        )
-        return (nITM + nETM) * 2
-
-
-class CoatingThermoOptic(nb.Noise):
-    """Coating Thermo-Optic
-
-    """
-    style = dict(
-        label='Coating Thermo-Optic',
-        color='#02ccfe',
-        linestyle='--',
-    )
-
-    def calc(self):
-        wavelength = self.ifo.Laser.Wavelength
-        materials = self.ifo.Materials
-        ITM = mirror_struct(self.ifo, 'ITM')
-        ETM = mirror_struct(self.ifo, 'ETM')
-        cavity = arm_cavity(self.ifo)
-        nITM, junk1, junk2, junk3 = noise.coatingthermal.coating_thermooptic(
-            self.freq, ITM, wavelength, cavity.wBeam_ITM,
-        )
-        nETM, junk1, junk2, junk3 = noise.coatingthermal.coating_thermooptic(
-            self.freq, ETM, wavelength, cavity.wBeam_ETM,
-        )
-        return (nITM + nETM) * 2
-
-
-#########################
-# substrate thermal
-#########################
-
-class ITMThermoRefractive(nb.Noise):
-    """ITM Thermo-Refractive
-
-    """
-    style = dict(
-        label='ITM Thermo-Refractive',
-        color='#448ee4',
-        linestyle='--',
-    )
-
-    def calc(self):
-        power = ifo_power(self.ifo)
-        gPhase = power.finesse * 2/np.pi
-        cavity = arm_cavity(self.ifo)
-        n = noise.substratethermal.substrate_thermorefractive(
-            self.freq, self.ifo.Materials, cavity.wBeam_ITM)
-        return n * 2 / gPhase**2
-
-
-class SubstrateBrownian(nb.Noise):
-    """Substrate Brownian
-
-    """
-    style = dict(
-        label='Substrate Brownian',
-        color='#fb7d07',
-        linestyle='--',
-    )
-
-    def calc(self):
-        cavity = arm_cavity(self.ifo)
-        nITM = noise.substratethermal.substrate_brownian(
-            self.freq, self.ifo.Materials, cavity.wBeam_ITM)
-        nETM = noise.substratethermal.substrate_brownian(
-            self.freq, self.ifo.Materials, cavity.wBeam_ETM)
-        return (nITM + nETM) * 2
-
-
-class SubstrateThermoElastic(nb.Noise):
-    """Substrate Thermo-Elastic
-
-    """
-    style = dict(
-        label='Substrate Thermo-Elastic',
-        color='#f5bf03',
-        linestyle='--',
-    )
-
-    def calc(self):
-        cavity = arm_cavity(self.ifo)
-        nITM = noise.substratethermal.substrate_thermoelastic(
-            self.freq, self.ifo.Materials, cavity.wBeam_ITM)
-        nETM = noise.substratethermal.substrate_thermoelastic(
-            self.freq, self.ifo.Materials, cavity.wBeam_ETM)
-        return (nITM + nETM) * 2
-
-
-#########################
-# residual gas
-#########################
-
-# FIXME HACK: it's unclear if a phase noise in the arms like
-# the excess gas noise should get the same dhdL strain
-# calibration as the other displacement noises.  However, we
-# would like to use the one Strain calibration for all noises,
-# so we need to divide by the sinc_sqr here to undo the
-# application of the dhdl in the Strain calibration.  But this
-# is ultimately a superfluous extra calculation with the only
-# to provide some simplication in the Budget definition, so
-# should be re-evaluated at some point.
-
-RESGAS_STYLES = dict(
-    H2 = dict(
-        label='H$_2$',
-        color='xkcd:red orange',
-    ),
-
-    N2 = dict(
-        label='N$_2$',
-        color='xkcd:emerald',
-    ),
-
-    H2O = dict(
-        label='H$_2$O',
-        color='xkcd:water blue',
-    ),
-
-    O2 = dict(
-        label='O$_2$',
-        color='xkcd:grey',
-    ),
-)
-
-
-def ResidualGasScattering_constructor(species_name):
-    """Residual gas scattering for a single species
-
-    """
-
-    class GasScatteringSpecies(nb.Noise):
-        name = 'Scattering' + species_name
-        style = dict(
-            label=RESGAS_STYLES[species_name]['label'] + ' scattering',
-            color=RESGAS_STYLES[species_name]['color'],
-            linestyle='-',
-        )
-
-        def calc(self):
-            cavity = arm_cavity(self.ifo)
-            species = self.ifo.Infrastructure.ResidualGas[species_name]
-            n = noise.residualgas.residual_gas_scattering_arm(
-                self.freq, self.ifo, cavity, species)
-            dhdl_sqr, sinc_sqr = dhdl(self.freq, self.ifo.Infrastructure.Length)
-            return n * 2 / sinc_sqr
-
-    return GasScatteringSpecies
-
-
-def ResidualGasDamping_constructor(species_name):
-    """Reisidual gas damping for a single species
-
-    """
-
-    class GasDampingSpecies(nb.Noise):
-        name = 'Damping' + species_name
-        style = dict(
-            label=RESGAS_STYLES[species_name]['label'] + ' damping',
-            color=RESGAS_STYLES[species_name]['color'],
-            linestyle='--',
-        )
-
-        @nb.precomp(sustf=suspension.precomp_suspension)
-        def calc(self, sustf):
-            rg = self.ifo.Infrastructure.ResidualGas
-            species = rg[species_name]
-            squeezed_film = rg.get('SqueezedFilm', Struct())
-
-            if squeezed_film is None:
-                raise ValueError('Must specify either excess damping or a gap')
-
-            # Calculate squeezed film for ETM and ITM seperately if either is given
-            # explicitly. If only one is given, it is not computed for the other one.
-            if ('ETM' in squeezed_film) or ('ITM' in squeezed_film):
-                squeezed_film_ETM = squeezed_film.get('ETM', Struct())
-                squeezed_film_ITM = squeezed_film.get('ITM', Struct())
-                n_ETM = noise.residualgas.residual_gas_damping_test_mass(
-                    self.freq, self.ifo, species, sustf, squeezed_film_ETM)
-                n_ITM = noise.residualgas.residual_gas_damping_test_mass(
-                    self.freq, self.ifo, species, sustf, squeezed_film_ITM)
-                n = 2 * (n_ETM + n_ITM)
-
-            # Otherwise the same calculation is used for both.
-            else:
-                n = 4 * noise.residualgas.residual_gas_damping_test_mass(
-                    self.freq, self.ifo, species, sustf, squeezed_film)
-
-            return n
-
-    return GasDampingSpecies
-
-
-class ExcessGas(nb.Budget):
-    """Excess Gas
-
-    """
-    style = dict(
-        label='Residual Gas',
-        color='#add00d',
-        linestyle='-',
-    )
-
-    noises = [
-        ResidualGasScattering_constructor('H2'),
-        ResidualGasScattering_constructor('N2'),
-        ResidualGasScattering_constructor('H2O'),
-        ResidualGasScattering_constructor('O2'),
-        ResidualGasDamping_constructor('H2'),
-        ResidualGasDamping_constructor('N2'),
-        ResidualGasDamping_constructor('H2O'),
-        ResidualGasDamping_constructor('O2'),
-    ]
diff --git a/gwinc/noise/coatingthermal.py b/gwinc/noise/coatingthermal.py
index a53cc3b59034c0a9e20756021b9b55828b7b5e48..03e746df10f7aa408dec6d62688fe79077cb71c9 100644
--- a/gwinc/noise/coatingthermal.py
+++ b/gwinc/noise/coatingthermal.py
@@ -4,10 +4,61 @@
 from __future__ import division, print_function
 import numpy as np
 from numpy import pi, exp, real, imag, sqrt, sin, cos, sinh, cosh, ceil, log
+import copy
 
 from .. import const
 from ..const import BESSEL_ZEROS as zeta
 from ..const import J0M as j0m
+from .. import nb
+from ..ifo.noises import arm_cavity
+
+
+class CoatingBrownian(nb.Noise):
+    """Coating Brownian
+
+    """
+    style = dict(
+        label='Coating Brownian',
+        color='#fe0002',
+    )
+
+    def calc(self):
+        ITM = mirror_struct(self.ifo, 'ITM')
+        ETM = mirror_struct(self.ifo, 'ETM')
+        cavity = arm_cavity(self.ifo)
+        wavelength = self.ifo.Laser.Wavelength
+        nITM = coating_brownian(
+            self.freq, ITM, wavelength, cavity.wBeam_ITM
+        )
+        nETM = coating_brownian(
+            self.freq, ETM, wavelength, cavity.wBeam_ETM
+        )
+        return (nITM + nETM) * 2
+
+
+class CoatingThermoOptic(nb.Noise):
+    """Coating Thermo-Optic
+
+    """
+    style = dict(
+        label='Coating Thermo-Optic',
+        color='#02ccfe',
+        linestyle='--',
+    )
+
+    def calc(self):
+        wavelength = self.ifo.Laser.Wavelength
+        materials = self.ifo.Materials
+        ITM = mirror_struct(self.ifo, 'ITM')
+        ETM = mirror_struct(self.ifo, 'ETM')
+        cavity = arm_cavity(self.ifo)
+        nITM, _, _, _ = coating_thermooptic(
+            self.freq, ITM, wavelength, cavity.wBeam_ITM,
+        )
+        nETM, _, _, _ = coating_thermooptic(
+            self.freq, ETM, wavelength, cavity.wBeam_ETM,
+        )
+        return (nITM + nETM) * 2
 
 
 def coating_brownian(f, mirror, wavelength, wBeam, power=None):
@@ -1028,3 +1079,28 @@ def interpretLossAngles(coat):
             lossBlown = lossSlown = lambda f: coat.Philown
 
     return lossBhighn, lossShighn, lossBlown, lossSlown
+
+
+def mirror_struct(ifo, tm):
+    """Create a "mirror" Struct for a LIGO core optic
+
+    This is a copy of the ifo.Materials Struct, containing Substrate
+    and Coating sub-Structs, as well as some basic geometrical
+    properties of the optic.
+
+    """
+    # NOTE: we deepcopy this struct since we'll be modifying it (and
+    # it would otherwise be stored by reference)
+    mirror = copy.deepcopy(ifo.Materials)
+    optic = ifo.Optics.get(tm)
+    if 'CoatLayerOpticalThickness' in optic:
+        mirror.Coating.dOpt = optic['CoatLayerOpticalThickness']
+    else:
+        T = optic.Transmittance
+        dL = optic.CoatingThicknessLown
+        dCap = optic.CoatingThicknessCap
+        mirror.Coating.dOpt = getCoatDopt(mirror, T, dL, dCap=dCap)
+    mirror.update(optic)
+    mirror.MassVolume = pi * mirror.MassRadius**2 * mirror.MassThickness
+    mirror.MirrorMass = mirror.MassVolume * mirror.Substrate.MassDensity
+    return mirror
diff --git a/gwinc/noise/newtonian.py b/gwinc/noise/newtonian.py
index db17432f0c292381f09196e921bdb4d807165ac5..2e4688465e75e514549ab951e30f9b5db8717bd7 100644
--- a/gwinc/noise/newtonian.py
+++ b/gwinc/noise/newtonian.py
@@ -9,6 +9,64 @@ import scipy.special as sp
 
 from .seismic import seismic_ground_NLNM
 from .. import const
+from .. import nb
+
+
+class Newtonian(nb.Noise):
+    """Newtonian Gravity
+
+    """
+    style = dict(
+        label='Newtonian Gravity',
+        color='#15b01a',
+    )
+
+    def calc(self):
+        n = gravg(self.freq, self.ifo.Seismic)
+        return n * 4
+
+
+class Rayleigh(nb.Noise):
+    """Newtonian Gravity, Rayleigh waves
+
+    """
+    style = dict(
+        label='Rayleigh waves',
+        color='#1b2431',
+    )
+
+    def calc(self):
+        n = gravg_rayleigh(self.freq, self.ifo.Seismic)
+        return n * 2
+
+
+class Body(nb.Noise):
+    """Newtonian Gravity, body waves
+
+    """
+    style = dict(
+        label='Body waves',
+        color='#85a3b2',
+    )
+
+    def calc(self):
+        np = gravg_pwave(self.freq, self.ifo.Seismic)
+        ns = gravg_swave(self.freq, self.ifo.Seismic)
+        return (np + ns) * 4
+
+
+class Infrasound(nb.Noise):
+    """Newtonian Gravity, infrasound
+
+    """
+    style = dict(
+        label='Infrasound)',
+        color='#ffa62b',
+    )
+
+    def calc(self):
+        n = atmois(self.freq, self.ifo.Atmospheric, self.ifo.Seismic)
+        return n * 2
 
 
 def gravg(f, seismic):
diff --git a/gwinc/noise/quantum.py b/gwinc/noise/quantum.py
index 1b4349327cc7717ee26f06d0bc00f7d91edba3ee..1fcdae1b6b56ee666f32b11432eac67593ae9015 100644
--- a/gwinc/noise/quantum.py
+++ b/gwinc/noise/quantum.py
@@ -11,10 +11,10 @@ from .. import logger
 from .. import const
 from ..struct import Struct
 from .. import nb
-from .. import suspension
+from ..suspension import precomp_suspension
 
 
-@nb.precomp(sustf=suspension.precomp_suspension)
+@nb.precomp(sustf=precomp_suspension)
 def precomp_quantum(f, ifo, sustf):
     from ..ifo import noises
     pc = Struct()
@@ -46,6 +46,137 @@ def precomp_quantum(f, ifo, sustf):
     return pc
 
 
+
+class QuantumVacuum(nb.Noise):
+    """Quantum Vacuum
+
+    """
+    style = dict(
+        label='Quantum Vacuum',
+        color='#ad03de',
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        total = np.zeros_like(quantum.ASvac)
+        for nn in quantum.values():
+            total += nn
+        return total
+
+
+class AS(nb.Noise):
+    """Quantum vacuum from the AS port
+
+    """
+    style = dict(
+        label='AS Port Vacuum',
+        color='xkcd:emerald green'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.ASvac
+
+
+class Arm(nb.Noise):
+    """Quantum vacuum due to arm cavity loss
+
+    """
+    style = dict(
+        label='Arm Loss',
+        color='xkcd:orange brown'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.Arm
+
+
+class SEC(nb.Noise):
+    """Quantum vacuum due to SEC loss
+
+    """
+    style = dict(
+        label='SEC Loss',
+        color='xkcd:cerulean'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.SEC
+
+
+class FilterCavity(nb.Noise):
+    """Quantum vacuum due to filter cavity loss
+
+    """
+    style = dict(
+        label='Filter Cavity Loss',
+        color='xkcd:goldenrod'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.FC
+
+
+class Injection(nb.Noise):
+    """Quantum vacuum due to injection loss
+
+    """
+    style = dict(
+        label='Injection Loss',
+        color='xkcd:fuchsia'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.Injection
+
+
+class Readout(nb.Noise):
+    """Quantum vacuum due to readout loss
+
+    """
+    style = dict(
+        label='Readout Loss',
+        color='xkcd:mahogany'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.PD
+
+
+class QuadraturePhase(nb.Noise):
+    """Quantum vacuum noise due to quadrature phase noise
+    """
+    style = dict(
+        label='Quadrature Phase',
+        color='xkcd:slate'
+    )
+
+    @nb.precomp(quantum=precomp_quantum)
+    def calc(self, quantum):
+        return quantum.Phase
+
+
+class StandardQuantumLimit(nb.Noise):
+    """Standard Quantum Limit
+
+    """
+    style = dict(
+        label="Standard Quantum Limit",
+        color="#000000",
+        linestyle=":",
+    )
+
+    def calc(self):
+        from .coatingthermal import mirror_struct
+        ETM = mirror_struct(self.ifo, 'ETM')
+        return 8 * const.hbar / (ETM.MirrorMass * (2 * np.pi * self.freq) ** 2)
+
+
 def sqzOptimalSqueezeAngle(Mifo, eta):
     vHD = np.array([[sin(eta), cos(eta)]])
     H = getProdTF(vHD, Mifo)[0]
diff --git a/gwinc/noise/residualgas.py b/gwinc/noise/residualgas.py
index df00e093933fd756282b314eef0504388c78ca09..39790eff92e43aa6a30884c0a09ccfe24762d3bc 100644
--- a/gwinc/noise/residualgas.py
+++ b/gwinc/noise/residualgas.py
@@ -6,6 +6,132 @@ from numpy import sqrt, log, pi
 
 from .. import const
 from .. import Struct
+from .. import nb
+from ..ifo.noises import dhdl, arm_cavity
+from ..suspension import precomp_suspension
+
+
+RESGAS_STYLES = dict(
+    H2 = dict(
+        label='H$_2$',
+        color='xkcd:red orange',
+    ),
+
+    N2 = dict(
+        label='N$_2$',
+        color='xkcd:emerald',
+    ),
+
+    H2O = dict(
+        label='H$_2$O',
+        color='xkcd:water blue',
+    ),
+
+    O2 = dict(
+        label='O$_2$',
+        color='xkcd:grey',
+    ),
+)
+
+
+# FIXME HACK: it's unclear if a phase noise in the arms like
+# the excess gas noise should get the same dhdL strain
+# calibration as the other displacement noises.  However, we
+# would like to use the one Strain calibration for all noises,
+# so we need to divide by the sinc_sqr here to undo the
+# application of the dhdl in the Strain calibration.  But this
+# is ultimately a superfluous extra calculation with the only
+# to provide some simplication in the Budget definition, so
+# should be re-evaluated at some point.
+
+
+def ResidualGasScattering_constructor(species_name):
+    """Residual gas scattering for a single species
+
+    """
+
+    class GasScatteringSpecies(nb.Noise):
+        name = 'Scattering' + species_name
+        style = dict(
+            label=RESGAS_STYLES[species_name]['label'] + ' scattering',
+            color=RESGAS_STYLES[species_name]['color'],
+            linestyle='-',
+        )
+
+        def calc(self):
+            cavity = arm_cavity(self.ifo)
+            species = self.ifo.Infrastructure.ResidualGas[species_name]
+            n = residual_gas_scattering_arm(
+                self.freq, self.ifo, cavity, species)
+            dhdl_sqr, sinc_sqr = dhdl(self.freq, self.ifo.Infrastructure.Length)
+            return n * 2 / sinc_sqr
+
+    return GasScatteringSpecies
+
+
+def ResidualGasDamping_constructor(species_name):
+    """Reisidual gas damping for a single species
+
+    """
+
+    class GasDampingSpecies(nb.Noise):
+        name = 'Damping' + species_name
+        style = dict(
+            label=RESGAS_STYLES[species_name]['label'] + ' damping',
+            color=RESGAS_STYLES[species_name]['color'],
+            linestyle='--',
+        )
+
+        @nb.precomp(sustf=precomp_suspension)
+        def calc(self, sustf):
+            rg = self.ifo.Infrastructure.ResidualGas
+            species = rg[species_name]
+            squeezed_film = rg.get('SqueezedFilm', Struct())
+
+            if squeezed_film is None:
+                raise ValueError('Must specify either excess damping or a gap')
+
+            # Calculate squeezed film for ETM and ITM seperately if either is given
+            # explicitly. If only one is given, it is not computed for the other one.
+            if ('ETM' in squeezed_film) or ('ITM' in squeezed_film):
+                squeezed_film_ETM = squeezed_film.get('ETM', Struct())
+                squeezed_film_ITM = squeezed_film.get('ITM', Struct())
+                n_ETM = residual_gas_damping_test_mass(
+                    self.freq, self.ifo, species, sustf, squeezed_film_ETM)
+                n_ITM = residual_gas_damping_test_mass(
+                    self.freq, self.ifo, species, sustf, squeezed_film_ITM)
+                n = 2 * (n_ETM + n_ITM)
+
+            # Otherwise the same calculation is used for both.
+            else:
+                n = 4 * residual_gas_damping_test_mass(
+                    self.freq, self.ifo, species, sustf, squeezed_film)
+
+            return n
+
+    return GasDampingSpecies
+
+
+class ResidualGas(nb.Budget):
+    """Residual Gas
+
+    """
+    style = dict(
+        label='Residual Gas',
+        color='#add00d',
+        linestyle='-',
+    )
+
+    noises = [
+        ResidualGasScattering_constructor('H2'),
+        ResidualGasScattering_constructor('N2'),
+        ResidualGasScattering_constructor('H2O'),
+        ResidualGasScattering_constructor('O2'),
+        ResidualGasDamping_constructor('H2'),
+        ResidualGasDamping_constructor('N2'),
+        ResidualGasDamping_constructor('H2O'),
+        ResidualGasDamping_constructor('O2'),
+    ]
 
 
 def residual_gas_scattering_arm(f, ifo, cavity, species):
diff --git a/gwinc/noise/seismic.py b/gwinc/noise/seismic.py
index 296e51ddf0045bb0ec3395b531c43b90e41d587f..ab6b07e3524b3e41a9e70171fcd8c8c76165c4ac 100644
--- a/gwinc/noise/seismic.py
+++ b/gwinc/noise/seismic.py
@@ -5,6 +5,51 @@ from __future__ import division
 import numpy as np
 from scipy.interpolate import PchipInterpolator as interp1d
 
+from .. import nb
+from ..suspension import precomp_suspension
+
+
+def Seismic_constructor(direction):
+    """Seismic noise for a single direction
+
+    """
+    if direction == 'horiz':
+        label = 'Horizontal'
+        color = 'xkcd:muted blue'
+    elif direction == 'vert':
+        label = 'Vertical'
+        color = 'xkcd:brick red'
+
+    class SeismicDirection(nb.Noise):
+        name = label
+        style = dict(
+            label=label,
+            color=color,
+        )
+
+        @nb.precomp(sustf=precomp_suspension)
+        def calc(self, sustf):
+            nt, nr = platform_motion(self.freq, self.ifo)
+            n = seismic_suspension_filtered(sustf, nt, direction)
+            return n * 4
+
+    return SeismicDirection
+
+
+class Seismic(nb.Budget):
+    """Seismic
+
+    """
+    style = dict(
+        label='Seismic',
+        color='#855700',
+    )
+
+    noises = [
+        Seismic_constructor('vert'),
+        Seismic_constructor('horiz'),
+    ]
+
 
 def seismic_suspension_filtered(sustf, in_trans, direction):
     """Seismic displacement noise for single suspended test mass.
diff --git a/gwinc/noise/substratethermal.py b/gwinc/noise/substratethermal.py
index d6ec92ed77a2f4a2b395e112008fb090aa8065bc..fd9fa94330d86c40504c0d640a6ea2eb4c950345 100644
--- a/gwinc/noise/substratethermal.py
+++ b/gwinc/noise/substratethermal.py
@@ -10,6 +10,65 @@ import scipy.integrate
 from .. import const
 from ..const import BESSEL_ZEROS as zeta
 from ..const import J0M as j0m
+from .. import nb
+from ..ifo.noises import arm_cavity, ifo_power
+
+
+class ITMThermoRefractive(nb.Noise):
+    """ITM Thermo-Refractive
+
+    """
+    style = dict(
+        label='ITM Thermo-Refractive',
+        color='#448ee4',
+        linestyle='--',
+    )
+
+    def calc(self):
+        power = ifo_power(self.ifo)
+        gPhase = power.finesse * 2/np.pi
+        cavity = arm_cavity(self.ifo)
+        n = substrate_thermorefractive(
+            self.freq, self.ifo.Materials, cavity.wBeam_ITM)
+        return n * 2 / gPhase**2
+
+
+class SubstrateBrownian(nb.Noise):
+    """Substrate Brownian
+
+    """
+    style = dict(
+        label='Substrate Brownian',
+        color='#fb7d07',
+        linestyle='--',
+    )
+
+    def calc(self):
+        cavity = arm_cavity(self.ifo)
+        nITM = substrate_brownian(
+            self.freq, self.ifo.Materials, cavity.wBeam_ITM)
+        nETM = substrate_brownian(
+            self.freq, self.ifo.Materials, cavity.wBeam_ETM)
+        return (nITM + nETM) * 2
+
+
+class SubstrateThermoElastic(nb.Noise):
+    """Substrate Thermo-Elastic
+
+    """
+    style = dict(
+        label='Substrate Thermo-Elastic',
+        color='#f5bf03',
+        linestyle='--',
+    )
+
+    def calc(self):
+        cavity = arm_cavity(self.ifo)
+        nITM = substrate_thermoelastic(
+            self.freq, self.ifo.Materials, cavity.wBeam_ITM)
+        nETM = substrate_thermoelastic(
+            self.freq, self.ifo.Materials, cavity.wBeam_ETM)
+        return (nITM + nETM) * 2
 
 
 def substrate_thermorefractive(f, materials, wBeam, exact=False):
diff --git a/gwinc/noise/suspensionthermal.py b/gwinc/noise/suspensionthermal.py
index d73cd69b8257c5c8e11a61c9ec61c523581319f3..6184dd7bd8a2f33ae765df2d9cce59e2d51118dd 100644
--- a/gwinc/noise/suspensionthermal.py
+++ b/gwinc/noise/suspensionthermal.py
@@ -6,7 +6,76 @@ import numpy as np
 from numpy import pi, imag
 
 from ..const import kB
-from ..suspension import getJointParams
+from ..suspension import getJointParams, precomp_suspension
+from .. import nb
+
+
+
+def SuspensionThermal_constructor(stage_num, direction):
+    """Suspension thermal for a single stage in one direction
+
+    """
+    if stage_num == 0:
+        stage_name = 'Top'
+        color = 'xkcd:orangeish'
+    elif stage_num == 1:
+        stage_name = 'UIM'
+        color = 'xkcd:mustard'
+    elif stage_num == 2:
+        stage_name = 'PUM'
+        color = 'xkcd:turquoise'
+    elif stage_num == 3:
+        stage_name = 'Test mass'
+        color = 'xkcd:bright purple'
+
+    if direction == 'horiz':
+        name0 = 'Horiz' + stage_name
+        label = 'Horiz. ' + stage_name
+        linestyle = '-'
+    elif direction == 'vert':
+        name0 = 'Vert' + stage_name
+        label = 'Vert. ' + stage_name
+        linestyle = '--'
+
+    class SuspensionThermalStage(nb.Noise):
+        name = name0
+        style = dict(
+            label=label,
+            color=color,
+            linestyle=linestyle,
+            alpha=0.7,
+        )
+
+        @nb.precomp(sustf=precomp_suspension)
+        def calc(self, sustf):
+            n = susptherm_stage(
+                self.freq, self.ifo.Suspension, sustf, stage_num, direction)
+            return abs(n) * 4
+
+    return SuspensionThermalStage
+
+
+class SuspensionThermal(nb.Budget):
+    """Suspension Thermal
+
+    """
+
+    name = 'SuspensionThermal'
+
+    style = dict(
+        label='Suspension Thermal',
+        color='#0d75f8',
+    )
+
+    noises = [
+        SuspensionThermal_constructor(0, 'horiz'),
+        SuspensionThermal_constructor(1, 'horiz'),
+        SuspensionThermal_constructor(2, 'horiz'),
+        SuspensionThermal_constructor(3, 'horiz'),
+        SuspensionThermal_constructor(0, 'vert'),
+        SuspensionThermal_constructor(1, 'vert'),
+        SuspensionThermal_constructor(2, 'vert'),
+    ]
 
 
 def getJointTempSusceptibility(sus, sustf, stage_num, isUpper, direction):