diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -4,6 +4,11 @@
 Since `rhine` reexports modules from `dunai`,
 every major version in `dunai` triggers a major version in `rhine`.
 
+## 0.8.1
+
+* Support for GHC 9.2.4
+* Added `FirstResampling` and `Feedback` constructors to `SN`
+
 ## 0.8.0.0
 
 * Documentation improvements
diff --git a/rhine.cabal b/rhine.cabal
--- a/rhine.cabal
+++ b/rhine.cabal
@@ -1,6 +1,6 @@
 name:                rhine
 
-version:             0.8.0.1
+version:             0.8.1
 
 synopsis: Functional Reactive Programming with type-level clocks
 
@@ -41,12 +41,12 @@
 
 source-repository head
   type:     git
-  location: git@github.com:turion/rhine.git
+  location: https://github.com/turion/rhine.git
 
 source-repository this
   type:     git
-  location: git@github.com:turion/rhine.git
-  tag:      v0.8.0.1
+  location: https://github.com/turion/rhine.git
+  tag:      v0.8.1
 
 
 library
@@ -99,7 +99,7 @@
   -- other-extensions:
 
   -- Other library packages from which modules are imported.
-  build-depends:       base         >= 4.13 && < 4.16
+  build-depends:       base         >= 4.13 && < 4.17
                      , dunai        >= 0.8
                      , transformers >= 0.5
                      , time         >= 1.8
@@ -109,7 +109,8 @@
                      , deepseq      >= 1.4
                      , random       >= 1.1
                      , MonadRandom  >= 0.5
-                     , simple-affine-space
+                     -- Remove version pin when https://github.com/ivanperez-keera/dunai/issues/298 is resolved:
+                     , simple-affine-space == 0.1.1
                      , time-domain
 
   -- Directories containing source files.
diff --git a/src/FRP/Rhine/Clock.hs b/src/FRP/Rhine/Clock.hs
--- a/src/FRP/Rhine/Clock.hs
+++ b/src/FRP/Rhine/Clock.hs
@@ -7,7 +7,6 @@
 and certain general constructions of 'Clock's,
 such as clocks lifted along monad morphisms or time rescalings.
 -}
-{-# LANGUAGE Arrows #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
diff --git a/src/FRP/Rhine/Clock/Periodic.hs b/src/FRP/Rhine/Clock/Periodic.hs
--- a/src/FRP/Rhine/Clock/Periodic.hs
+++ b/src/FRP/Rhine/Clock/Periodic.hs
@@ -8,12 +8,10 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
-{-# LANGUAGE KindSignatures #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE TypeSynonymInstances #-}
 module FRP.Rhine.Clock.Periodic (Periodic (Periodic)) where
 
 -- base
@@ -45,7 +43,7 @@
   type Time (Periodic v) = Integer
   type Tag  (Periodic v) = ()
   initClock cl = return
-    ( cycleS (theList cl) >>> withSideEffect wait >>> (accumulateWith (+) 0) &&& arr (const ())
+    ( cycleS (theList cl) >>> withSideEffect wait >>> accumulateWith (+) 0 &&& arr (const ())
     , 0
     )
 
diff --git a/src/FRP/Rhine/Clock/Realtime/Audio.hs b/src/FRP/Rhine/Clock/Realtime/Audio.hs
--- a/src/FRP/Rhine/Clock/Realtime/Audio.hs
+++ b/src/FRP/Rhine/Clock/Realtime/Audio.hs
@@ -6,7 +6,6 @@
 {-# LANGUAGE Arrows #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE KindSignatures #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
diff --git a/src/FRP/Rhine/Clock/Realtime/Event.hs b/src/FRP/Rhine/Clock/Realtime/Event.hs
--- a/src/FRP/Rhine/Clock/Realtime/Event.hs
+++ b/src/FRP/Rhine/Clock/Realtime/Event.hs
@@ -22,7 +22,6 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE TypeSynonymInstances #-}
 module FRP.Rhine.Clock.Realtime.Event
   ( module FRP.Rhine.Clock.Realtime.Event
   , module Control.Monad.IO.Class
@@ -74,7 +73,7 @@
 -}
 runEventChanT :: MonadIO m => EventChanT event m a -> m a
 runEventChanT a = do
-  chan <- liftIO $ newChan
+  chan <- liftIO newChan
   runReaderT a chan
 
 {- | Remove ("run") an 'EventChanT' layer from the monad stack
@@ -156,7 +155,7 @@
       ( constM $ do
           chan  <- ask
           event <- liftIO $ readChan chan
-          time  <- liftIO $ getCurrentTime
+          time  <- liftIO getCurrentTime
           return (time, event)
       , initialTime
       )
diff --git a/src/FRP/Rhine/Clock/Realtime/Millisecond.hs b/src/FRP/Rhine/Clock/Realtime/Millisecond.hs
--- a/src/FRP/Rhine/Clock/Realtime/Millisecond.hs
+++ b/src/FRP/Rhine/Clock/Realtime/Millisecond.hs
@@ -2,9 +2,7 @@
 Provides a clock that ticks at every multiple of a fixed number of milliseconds.
 -}
 
-{-# LANGUAGE Arrows #-}
 {-# LANGUAGE DataKinds #-}
-{-# LANGUAGE KindSignatures #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}
@@ -56,12 +54,12 @@
 
 --   Note that this clock internally uses 'threadDelay' which can block
 --   for quite a lot longer than the requested time, which can cause
---   the clock to miss one or more ticks when using low values of 'n'. 
---   When using 'threadDelay', the difference between the real wait time 
---   and the requested wait time will be larger when using 
+--   the clock to miss one or more ticks when using low values of 'n'.
+--   When using 'threadDelay', the difference between the real wait time
+--   and the requested wait time will be larger when using
 --   the '-threaded' ghc option (around 800 microseconds) than when not using
 --   this option (around 100 microseconds). For low values of @n@ it is recommended
---   that '-threaded' not be used in order to miss less ticks. The clock will adjust 
+--   that '-threaded' not be used in order to miss less ticks. The clock will adjust
 --   the wait time, up to no wait time at all, to catch up when a tick is missed.
 
 waitClock :: KnownNat n => Millisecond n
diff --git a/src/FRP/Rhine/Reactimation.hs b/src/FRP/Rhine/Reactimation.hs
--- a/src/FRP/Rhine/Reactimation.hs
+++ b/src/FRP/Rhine/Reactimation.hs
@@ -3,9 +3,7 @@
 as main loops.
 -}
 
-{-# LANGUAGE Arrows #-}
 {-# LANGUAGE GADTs #-}
-{-# LANGUAGE RecordWildCards #-}
 module FRP.Rhine.Reactimation where
 
 -- dunai
diff --git a/src/FRP/Rhine/Reactimation/ClockErasure.hs b/src/FRP/Rhine/Reactimation/ClockErasure.hs
--- a/src/FRP/Rhine/Reactimation/ClockErasure.hs
+++ b/src/FRP/Rhine/Reactimation/ClockErasure.hs
@@ -7,7 +7,6 @@
 {-# LANGUAGE Arrows #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE GADTs #-}
-{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TupleSections #-}
 module FRP.Rhine.Reactimation.ClockErasure where
 
@@ -71,7 +70,7 @@
       maybeB <- eraseClockSN initialTime sn1 -< (time, tagL, maybeA)
       returnA -< Left <$> ((time, , ) <$> outTag proxy1 tagL <*> maybeB)
     Right tagR -> do
-      returnA -< Right <$> (time, ) <$> inTag proxy2 tagR
+      returnA -< Right . (time, ) <$> inTag proxy2 tagR
   maybeC <- mapMaybeS $ eraseClockResBuf (outProxy proxy1) (inProxy proxy2) initialTime resBuf -< resBufIn
   case tag of
     Left  _    -> do
@@ -97,6 +96,39 @@
   in proc (time, tag, aMaybe) -> do
   bMaybe <- mapMaybeS $ eraseClockClSF (inProxy proxy) initialTime clsf -< (time, , ) <$> inTag proxy tag <*> aMaybe
   eraseClockSN initialTime sn -< (time, tag, bMaybe)
+
+eraseClockSN initialTime (Feedback buf0 sn) =
+  let
+    proxy = toClockProxy sn
+  in feedback buf0 $ proc ((time, tag, aMaybe), buf) -> do
+  (cMaybe, buf') <- case inTag proxy tag of
+    Nothing -> do
+      returnA -< (Nothing, buf)
+    Just tagIn -> do
+      timeInfo <- genTimeInfo (inProxy proxy) initialTime -< (time, tagIn)
+      (c, buf') <- arrM $ uncurry get -< (buf, timeInfo)
+      returnA -< (Just c, buf')
+  bdMaybe <- eraseClockSN initialTime sn -< (time, tag, (, ) <$> aMaybe <*> cMaybe)
+  case (, ) <$> outTag proxy tag <*> bdMaybe of
+    Nothing -> do
+      returnA -< (Nothing, buf')
+    Just (tagOut, (b, d)) -> do
+      timeInfo <- genTimeInfo (outProxy proxy) initialTime -< (time, tagOut)
+      buf'' <- arrM $ uncurry $ uncurry put -< ((buf', timeInfo), d)
+      returnA -< (Just b, buf'')
+
+eraseClockSN initialTime (FirstResampling sn buf) =
+  let
+    proxy = toClockProxy sn
+  in proc (time, tag, acMaybe) -> do
+    bMaybe <- eraseClockSN initialTime sn -< (time, tag, fst <$> acMaybe)
+    let
+      resBufInput = case (inTag proxy tag, outTag proxy tag, snd <$> acMaybe) of
+        (Just tagIn, _, Just c) -> Just $ Left (time, tagIn, c)
+        (_, Just tagOut, _) -> Just $ Right (time, tagOut)
+        _ -> Nothing
+    dMaybe <- mapMaybeS $ eraseClockResBuf (inProxy proxy) (outProxy proxy) initialTime buf -< resBufInput
+    returnA -< (,) <$> bMaybe <*> join dMaybe
 
 -- | Translate a resampling buffer into a monadic stream function.
 --
diff --git a/src/FRP/Rhine/SN.hs b/src/FRP/Rhine/SN.hs
--- a/src/FRP/Rhine/SN.hs
+++ b/src/FRP/Rhine/SN.hs
@@ -69,6 +69,15 @@
     => SN m                  cl1      a b
     -> SN m                      cl2  a b
     -> SN m (ParallelClock m cl1 cl2) a b
+  -- | Bypass the signal network by forwarding data in parallel through a 'ResamplingBuffer'.
+  FirstResampling
+    :: ( Clock m (In cl), Clock m (Out cl)
+       , Time cl ~ Time (Out cl)
+       , Time cl ~ Time (In cl)
+       )
+    => SN               m cl               a      b
+    -> ResamplingBuffer m (In cl) (Out cl)    c      d
+    -> SN               m cl              (a, c) (b, d)
   -- | A 'ClSF' can always be postcomposed onto an 'SN' if the clocks match on the output.
   Postcompose
     :: ( Clock m (Out cl)
@@ -85,6 +94,16 @@
     => ClSF m (In cl) a b
     -> SN   m     cl    b c
     -> SN   m     cl  a   c
+  -- | Data can be looped back to the beginning of an 'SN',
+  --   but it must be resampled since the 'Out' and 'In' clocks are generally different.
+  Feedback
+    :: ( Clock m (In cl),  Clock m (Out cl)
+       , Time (In cl) ~ Time cl
+       , Time (Out cl) ~ Time cl
+       )
+    => ResBuf m (Out cl) (In cl) d c
+    -> SN     m cl (a, c) (b, d)
+    -> SN     m cl  a      b
 
 instance GetClockProxy cl => ToClockProxy (SN m cl a b) where
   type Cl (SN m cl a b) = cl
diff --git a/src/FRP/Rhine/SN/Combinators.hs b/src/FRP/Rhine/SN/Combinators.hs
--- a/src/FRP/Rhine/SN/Combinators.hs
+++ b/src/FRP/Rhine/SN/Combinators.hs
@@ -26,6 +26,8 @@
 Parallel   sn1    sn2 >>>^ f = Parallel  (sn1 >>>^ f) (sn2 >>>^ f)
 Postcompose sn clsf >>>^ f = Postcompose sn $ clsf >>^ f
 Precompose clsf sn >>>^ f = Precompose clsf $ sn >>>^ f
+Feedback buf sn >>>^ f = Feedback buf $ sn >>>^ first f
+firstResampling@(FirstResampling _ _) >>>^ f = Postcompose firstResampling $ arr f
 
 -- | Precompose a signal network with a pure function.
 (^>>>)
@@ -38,6 +40,8 @@
 f ^>>> Parallel   sn1    sn2 = Parallel   (f ^>>> sn1) (f ^>>> sn2)
 f ^>>> Postcompose sn clsf = Postcompose (f ^>>> sn) clsf
 f ^>>> Precompose clsf sn = Precompose (f ^>> clsf) sn
+f ^>>> Feedback buf sn = Feedback buf $ first f ^>>> sn
+f ^>>> firstResampling@(FirstResampling _ _) = Precompose (arr f) firstResampling
 
 -- | Postcompose a signal network with a 'ClSF'.
 (>--^)
@@ -74,9 +78,24 @@
     rb  = rb1 *-* rb2
 Parallel sn11 sn12 **** Parallel sn21 sn22
   = Parallel (sn11 **** sn21) (sn12 **** sn22)
+
+Precompose clsf sn1 **** sn2 = Precompose (first clsf) $ sn1 **** sn2
+sn1 **** Precompose clsf sn2 = Precompose (second clsf) $ sn1 **** sn2
+Postcompose sn1 clsf **** sn2 = Postcompose (sn1 **** sn2) (first clsf)
+sn1 **** Postcompose sn2 clsf = Postcompose (sn1 **** sn2) (second clsf)
+
+Feedback buf sn1 **** sn2 = Feedback buf $ (\((a, c), c1) -> ((a, c1), c)) ^>>> (sn1 **** sn2) >>>^ (\((b, d1), d) -> ((b, d), d1))
+sn1 **** Feedback buf sn2 = Feedback buf $ (\((a, c), c1) -> (a, (c, c1))) ^>>> (sn1 **** sn2) >>>^ (\(b, (d, d1)) -> ((b, d), d1))
+
+FirstResampling sn1 buf **** sn2 = (\((a1, c1), c) -> ((a1, c), c1)) ^>>> FirstResampling (sn1 **** sn2) buf >>>^ (\((b1, d), d1) -> ((b1, d1), d))
+sn1 **** FirstResampling sn2 buf = (\(a, (a1, c1)) -> ((a, a1), c1)) ^>>> FirstResampling (sn1 **** sn2) buf >>>^ (\((b, b1), d1) -> (b, (b1, d1)))
+
 -- Note that the patterns above are the only ones that can occur.
 -- This is ensured by the clock constraints in the SF constructors.
-_ **** _ = error "Impossible pattern in ****"
+Synchronous _ **** Parallel _ _ = error "Impossible pattern: Synchronous _ **** Parallel _ _"
+Parallel _ _ **** Synchronous _ = error "Impossible pattern: Parallel _ _ **** Synchronous _"
+Synchronous _ **** Sequential {} = error "Impossible pattern: Synchronous _ **** Sequential {}"
+Sequential {} **** Synchronous _ = error "Impossible pattern: Sequential {} **** Synchronous _"
 
 -- | Compose two signal networks on different clocks in clock-parallel.
 --   At one tick of @ParClock m cl1 cl2@, one of the networks is stepped,
diff --git a/src/FRP/Rhine/Schedule.hs b/src/FRP/Rhine/Schedule.hs
--- a/src/FRP/Rhine/Schedule.hs
+++ b/src/FRP/Rhine/Schedule.hs
@@ -81,7 +81,7 @@
   :: Monad m
   => Schedule m cl1 cl2
   -> Schedule m (RescaledClock cl1 time) (RescaledClock cl2 time)
-rescaledSchedule schedule = Schedule $ initSchedule'
+rescaledSchedule schedule = Schedule initSchedule'
   where
     initSchedule' cl1 cl2 = initSchedule (rescaledScheduleS schedule) (rescaledClockToS cl1) (rescaledClockToS cl2)
 
diff --git a/src/FRP/Rhine/Type.hs b/src/FRP/Rhine/Type.hs
--- a/src/FRP/Rhine/Type.hs
+++ b/src/FRP/Rhine/Type.hs
@@ -6,6 +6,8 @@
 {-# LANGUAGE Arrows #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE FlexibleContexts #-}
 module FRP.Rhine.Type where
 
 -- dunai
@@ -16,6 +18,8 @@
 import FRP.Rhine.Clock
 import FRP.Rhine.Clock.Proxy
 import FRP.Rhine.SN
+import FRP.Rhine.ResamplingBuffer (ResamplingBuffer)
+import FRP.Rhine.Schedule (Out, In)
 
 {- |
 A 'Rhine' consists of a 'SN' together with a clock of matching type 'cl'.
@@ -55,3 +59,22 @@
   return $ proc a -> do
     (time, tag) <- runningClock -< ()
     eraseClockSN initTime sn -< (time, tag, a <$ inTag (toClockProxy sn) tag)
+
+{- |
+Loop back data from the output to the input.
+
+Since output and input will generally tick at different clocks,
+the data needs to be resampled.
+-}
+feedbackRhine
+  :: ( Clock m (In cl),  Clock m (Out cl)
+     , Time (In cl) ~ Time cl
+     , Time (Out cl) ~ Time cl
+     )
+  => ResamplingBuffer m (Out cl) (In cl) d c
+  -> Rhine            m cl (a, c) (b, d)
+  -> Rhine            m cl  a      b
+feedbackRhine buf Rhine { .. } = Rhine
+  { sn = Feedback buf sn
+  , clock
+  }
