diff --git a/on-host/arpist-traject/src/syntax.rs b/on-host/arpist-traject/src/syntax.rs
index 8f6a1cf2f2d9d2b16d9d99509bebf6debc64036e..a48bf781ef365841f67feb3735afb782d1e8b633 100644
--- a/on-host/arpist-traject/src/syntax.rs
+++ b/on-host/arpist-traject/src/syntax.rs
@@ -1,38 +1,54 @@
-use std::ops::Range;
+use std::ops::{Deref, Range};
 
 mod parser;
 
 pub use parser::CachedLexer;
 
 #[derive(Debug, Clone)]
-pub struct Program(Vec<Blocks>);
+pub struct Program(pub Vec<Blocks>);
 
 #[derive(Debug, Clone)]
 pub enum Blocks {
-    TimeDefinition(TimeRef),
+    TimeDefinition(TimeAnchorBlock),
     Generator(GeneratorBlock),
     Setting(SettingBlock),
 }
 
+#[derive(Debug, Clone)]
+pub struct TimeAnchorBlock {
+    span: Range<usize>,
+    pub timeref: TimeRef,
+    pub freq: Option<f64>,
+}
+
 #[derive(Debug, Clone)]
 pub struct SettingBlock {
     span: Range<usize>,
-    name: Setting,
-    settings: Vec<NumberParameter>,
+    pub kind: SettingKind,
+    pub settings: Vec<NumberParameter>,
+}
+
+impl SettingBlock {
+    pub fn get(&self, name: &str) -> Option<&Number> {
+        self.settings
+            .iter()
+            .find(|p| p.name == name)
+            .map(|p| &p.value)
+    }
 }
 
 #[derive(Debug, Clone)]
 pub struct GeneratorBlock {
     span: Range<usize>,
-    generator: Generator,
-    parameters: Vec<ExprParameter>,
+    pub kind: GeneratorKind,
+    pub parameters: Vec<ExprParameter>,
 }
 
 #[derive(Debug, Clone)]
 pub struct NumberParameter {
     span: Range<usize>,
-    name: String,
-    value: Number,
+    pub name: String,
+    pub value: Number,
 }
 
 #[derive(Debug, Clone)]
@@ -90,6 +106,14 @@ pub struct Number {
     value: f64,
 }
 
+impl Deref for Number {
+    type Target = f64;
+
+    fn deref(&self) -> &Self::Target {
+        &self.value
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct Generator {
     span: Range<usize>,
@@ -118,6 +142,20 @@ pub struct TimeRef {
     offset: f64,
 }
 
+#[derive(Debug, Clone)]
+pub struct TimeStepFreq {
+    span: Range<usize>,
+    freq: f64,
+}
+
+impl Deref for TimeRef {
+    type Target = f64;
+
+    fn deref(&self) -> &Self::Target {
+        &self.offset
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct TimeVar {
     span: Range<usize>,
diff --git a/on-host/arpist-traject/src/syntax/parser.rs b/on-host/arpist-traject/src/syntax/parser.rs
index a6a8309c0f6887bcb9123b8b483831bd0ab710c6..2ef12618cecbb15edad0eeedfbfa0edca7da9226 100644
--- a/on-host/arpist-traject/src/syntax/parser.rs
+++ b/on-host/arpist-traject/src/syntax/parser.rs
@@ -130,7 +130,7 @@ fn parse_program(mut view: LexerView<'_>) -> Option<Program> {
                 blocks.push(Blocks::Setting(parse_setting_block(view.view())?));
             }
             Token::AtTimeLine => {
-                blocks.push(Blocks::TimeDefinition(parse_timedef_block(view.view())?));
+                blocks.push(Blocks::TimeDefinition(parse_timeanchor_block(view.view())?));
             }
             Token::FillerLine => {
                 consume_filler_lines(view.view());
@@ -142,16 +142,39 @@ fn parse_program(mut view: LexerView<'_>) -> Option<Program> {
     Some(Program(blocks))
 }
 
-fn parse_timedef_block(mut view: LexerView<'_>) -> Option<TimeRef> {
+fn parse_timeanchor_block(mut view: LexerView<'_>) -> Option<TimeAnchorBlock> {
     let (Token::AtTimeLine, s) = view.next()? else {
         return None;
     };
     let timeref = parse_time_ref(view.view())?;
+    let freq = if let (Token::TimeStepFreq(f), _) = view.peek()? {
+        view.next();
+        // f is of format "1Hz" or "1kHz"
+        let scale = f.chars().skip_while(|c| c.is_numeric()).collect::<String>();
+        let freq = f
+            .chars()
+            .take_while(|c| c.is_numeric())
+            .collect::<String>()
+            .parse::<f64>()
+            .ok()?;
+        let freq = match scale.as_str() {
+            "Hz" => freq,
+            "kHz" => freq * 1e3,
+            "MHz" => freq * 1e6,
+            "GHz" => freq * 1e9,
+            "THz" => freq * 1e12,
+            _ => return None,
+        };
+        Some(freq)
+    } else {
+        None
+    };
     consume_new_line(view.view());
     view.commit();
-    Some(TimeRef {
-        span: s.start..timeref.span.end,
-        offset: timeref.offset,
+    Some(TimeAnchorBlock {
+        span: s.start..s.end,
+        timeref,
+        freq,
     })
 }
 
@@ -170,7 +193,7 @@ fn parse_generator_block(mut view: LexerView<'_>) -> Option<GeneratorBlock> {
     view.commit();
     Some(GeneratorBlock {
         span: s.start..parameters.last().unwrap().span.end,
-        generator,
+        kind: generator.kind,
         parameters,
     })
 }
@@ -190,7 +213,7 @@ fn parse_setting_block(mut view: LexerView<'_>) -> Option<SettingBlock> {
     view.commit();
     Some(SettingBlock {
         span: s.start..settings.last().unwrap().span.end,
-        name: setting,
+        kind: setting.name,
         settings,
     })
 }
diff --git a/on-host/arpist-traject/src/tokens.rs b/on-host/arpist-traject/src/tokens.rs
index 927535043f06019d02bc1c3da1f372f09b2c6282..bc8e39d164c6130424443eed2be9a641448b4ccb 100644
--- a/on-host/arpist-traject/src/tokens.rs
+++ b/on-host/arpist-traject/src/tokens.rs
@@ -38,6 +38,9 @@ pub enum Token {
     #[regex("T[+-]?[0-9]+(\\.[0-9]+)?", |lex| lex.slice()[1..].parse::<f64>().ok())]
     TimeRef(f64),
 
+    #[regex(" ~ [0-9]+(\\.[0-9]+)?(Hz|kHz|MHz|GHz|THz)", |lex| lex.slice()[3..].to_string())]
+    TimeStepFreq(String),
+
     #[regex("[a-z]+ *: *", |lex| lex.slice().trim().trim_end_matches(':').trim().to_string())]
     Parameter(String),
 
diff --git a/on-host/test.txt b/on-host/test.txt
index a1532724cf30d21d1f4b6d9eb496be5d7b66dc0b..d2e9fa60490bf2bf72a1975ad9ca7848a4a453a0 100644
--- a/on-host/test.txt
+++ b/on-host/test.txt
@@ -10,4 +10,4 @@
 \ y : 0.02*t
 \ z : 3*t^2
 |
-@ T+10
+@ T+10 ~ 1Hz