Browse Source

Initial commit

Keelan Lightfoot 7 years ago
commit
0f46b0a7b3
8 changed files with 1323 additions and 0 deletions
  1. 220
    0
      code.go
  2. 142
    0
      common.go
  3. 74
    0
      decoder.go
  4. 317
    0
      demodulator.go
  5. 103
    0
      dsp.go
  6. 176
    0
      encoder.go
  7. 107
    0
      examples/main.go
  8. 184
    0
      modulator.go

+ 220
- 0
code.go View File

@@ -0,0 +1,220 @@
1
+package openbaudot
2
+
3
+import "unicode"
4
+
5
+/*
6
+ * OBL openbaudot Library
7
+ *
8
+ * Copyright (C) 2017 Keelan Lightfoot
9
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
10
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
11
+ * Copyright (C) 2007-2008 Omnitor AB
12
+ * Copyright (C) 2007-2008 Voiceriver Inc
13
+ *
14
+ * This software was developed with support from the National Institute on
15
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
16
+ * # H133E990006 and H133E040014
17
+ *
18
+ * This library is free software; you can redistribute it and/or modify it
19
+ * under the terms of the GNU Lesser General Public License as published by
20
+ * the Free Software Foundation; either version 2.1 of the License, or (at
21
+ * your option) any later version.
22
+ *
23
+ * This library is distributed in the hope that it will be useful, but
24
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
26
+ * License for more details.
27
+ *
28
+ * You should have received a copy of the GNU Lesser General Public License
29
+ * along with this library; if not, write to the Free Software Foundation,
30
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
31
+ *
32
+ * Please send a copy of any improved versions of the library to:
33
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
34
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
35
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
36
+ *
37
+ * file...: obl.go
38
+ * original author.: Keelan Lightfoot
39
+ * Created: 20 May 2017
40
+ *
41
+ */
42
+
43
+const (
44
+	shiftUnknown = iota
45
+	shiftLetter
46
+	shiftFigure
47
+	shiftWhitespace
48
+)
49
+
50
+var (
51
+	defaultSubstitutes = map[rune]rune{
52
+		'{': '(',
53
+		'}': ')',
54
+		'[': '(',
55
+		']': ')',
56
+	}
57
+	// TDD Telecommunications Device for the Deaf standard
58
+	TDD = CodeSet{
59
+		Codes: [2][]rune{
60
+			{'\b', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'},
61
+			{'\b', '3', '\n', '-', ' ', ',', '8', '7', '\r', '$', '4', '\'', ',', '!', ':', '(', '5', '"', ')', '2', '=', '6', '0', '1', '9', '?', '+', '\x0e', '.', '/', ';', '\x0f'},
62
+		},
63
+		Substitutes: defaultSubstitutes,
64
+		Fallback:    '\'',
65
+		ShiftCode:   0x1b,
66
+		UnshiftCode: 0x1f,
67
+	}
68
+	// USTTY US Teletype Corporation
69
+	USTTY = CodeSet{
70
+		Codes: [2][]rune{
71
+			{'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'},
72
+			{'\x00', '3', '\n', '-', ' ', '\a', '8', '7', '\r', '$', '4', '\'', ',', '!', ':', '(', '5', '"', ')', '2', '#', '6', '0', '1', '9', '?', '&', '\x0e', '.', '/', ';', '\x0f'},
73
+		},
74
+		Substitutes: defaultSubstitutes,
75
+		Fallback:    '-',
76
+		ShiftCode:   0x1b,
77
+		UnshiftCode: 0x1f,
78
+	}
79
+	// USITA2 USITA2
80
+	USITA2 = CodeSet{
81
+		Codes: [2][]rune{
82
+			{'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'},
83
+			{'\x00', '3', '\n', '-', ' ', '\'', '8', '7', '\r', '$', '4', '\a', ',', '!', ':', '(', '5', '"', ')', '2', '=', '6', '0', '1', '9', '?', '&', '\x0e', '.', '/', '=', '\x0f'},
84
+		},
85
+		Substitutes: defaultSubstitutes,
86
+		Fallback:    '-',
87
+		ShiftCode:   0x1b,
88
+		UnshiftCode: 0x1f,
89
+	}
90
+	// Weather US Weather
91
+	Weather = CodeSet{
92
+		Codes: [2][]rune{
93
+			{'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'},
94
+			{'\x00', '3', '\n', '↑', ' ', '\a', '8', '7', '\r', '↗', '4', '↙', '⦷', '→', '○', '←', '5', '+', '↖', '2', '↓', '6', '0', '1', '9', '⊕', '↘', '\x0e', '.', '/', '⦶', '\x0f'},
95
+		},
96
+		Substitutes: defaultSubstitutes,
97
+		Fallback:    '-',
98
+		ShiftCode:   0x1b,
99
+		UnshiftCode: 0x1f,
100
+	}
101
+	// Fractions US Fractions
102
+	Fractions = CodeSet{
103
+		Codes: [2][]rune{
104
+			{'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'},
105
+			{'\x00', '3', '\n', '↑', ' ', '\a', '8', '7', '\r', '$', '4', '\'', '⅞', '¼', '⅛', '½', '5', '"', '¾', '2', ' ', '6', '0', '1', '9', '⅝', '&', '\x0e', '.', '/', '⅜', '\x0f'},
106
+		},
107
+		Substitutes: defaultSubstitutes,
108
+		Fallback:    '-',
109
+		ShiftCode:   0x1b,
110
+		UnshiftCode: 0x1f,
111
+	}
112
+)
113
+
114
+// CodeSet represents a teletype character set.
115
+type CodeSet struct {
116
+	// Codes specifies a mapping of runes to codes. There are two tables, 0 for ltrs, 1 for figs
117
+	Codes [2][]rune
118
+	// Substitutes specifies what characters to re-map to existing table entries
119
+	Substitutes map[rune]rune
120
+	// Fallback specifies what rune to use when a table entry or substitution cant be found
121
+	Fallback rune
122
+	// ShiftRune is fun
123
+	ShiftCode byte
124
+	// UnshiftRune is fun
125
+	UnshiftCode byte
126
+
127
+	runeToCodeSet map[rune]code
128
+}
129
+
130
+type code struct {
131
+	shift int
132
+	v     byte
133
+}
134
+
135
+//Initialize is fun
136
+func (cs *CodeSet) Initialize() *CodeSet {
137
+
138
+	cs.runeToCodeSet = make(map[rune]code)
139
+
140
+	for i, runes := range cs.Codes {
141
+
142
+		var shift int
143
+
144
+		switch i {
145
+		case 0:
146
+			shift = shiftLetter
147
+		case 1:
148
+			shift = shiftFigure
149
+		}
150
+
151
+		for v, r := range runes {
152
+			if c, exists := cs.runeToCodeSet[r]; exists {
153
+				c.shift = shiftWhitespace
154
+				cs.runeToCodeSet[r] = c
155
+			} else {
156
+				cs.runeToCodeSet[r] = code{
157
+					shift: shift,
158
+					v:     byte(v),
159
+				}
160
+			}
161
+
162
+		}
163
+	}
164
+
165
+	for in, out := range cs.Substitutes {
166
+		if _, ok := cs.runeToCodeSet[in]; ok {
167
+			// dont load a translation if it overlaps an existing table entry
168
+			continue
169
+		}
170
+		if b, ok := cs.runeToCodeSet[out]; ok {
171
+			// only set the translation if the output exists in table
172
+			cs.runeToCodeSet[in] = b
173
+		}
174
+	}
175
+	return cs
176
+}
177
+
178
+func (cs *CodeSet) toCode(r rune) code {
179
+
180
+	if c, ok := cs.runeToCodeSet[unicode.ToUpper(r)]; ok {
181
+		return c
182
+	}
183
+
184
+	return cs.runeToCodeSet[cs.Fallback]
185
+}
186
+
187
+func (cs *CodeSet) toByte(r rune) byte {
188
+
189
+	if c, ok := cs.runeToCodeSet[unicode.ToUpper(r)]; ok {
190
+		return c.v
191
+	}
192
+
193
+	return cs.runeToCodeSet[cs.Fallback].v
194
+}
195
+
196
+func (c code) toByte() byte {
197
+	return c.v
198
+}
199
+
200
+func (cs *CodeSet) shift() byte {
201
+	return cs.ShiftCode
202
+}
203
+
204
+func (cs *CodeSet) unshift() byte {
205
+	return cs.UnshiftCode
206
+}
207
+
208
+func (cs *CodeSet) toRune(shift int, b byte) rune {
209
+	var i int
210
+	switch shift {
211
+	case shiftLetter:
212
+		i = 0
213
+	case shiftFigure:
214
+		i = 1
215
+	}
216
+	if int(b) < len(cs.Codes[i]) {
217
+		return cs.Codes[i][int(b)]
218
+	}
219
+	return '�'
220
+}

+ 142
- 0
common.go View File

@@ -0,0 +1,142 @@
1
+package openbaudot
2
+
3
+import "math"
4
+
5
+/*
6
+ * OBL openbaudot Library
7
+ *
8
+ * Copyright (C) 2017 Keelan Lightfoot
9
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
10
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
11
+ * Copyright (C) 2007-2008 Omnitor AB
12
+ * Copyright (C) 2007-2008 Voiceriver Inc
13
+ *
14
+ * This software was developed with support from the National Institute on
15
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
16
+ * # H133E990006 and H133E040014
17
+ *
18
+ * This library is free software; you can redistribute it and/or modify it
19
+ * under the terms of the GNU Lesser General Public License as published by
20
+ * the Free Software Foundation; either version 2.1 of the License, or (at
21
+ * your option) any later version.
22
+ *
23
+ * This library is distributed in the hope that it will be useful, but
24
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
26
+ * License for more details.
27
+ *
28
+ * You should have received a copy of the GNU Lesser General Public License
29
+ * along with this library; if not, write to the Free Software Foundation,
30
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
31
+ *
32
+ * Please send a copy of any improved versions of the library to:
33
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
34
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
35
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
36
+ *
37
+ * file...: obl.go
38
+ * original author.: Keelan Lightfoot
39
+ * Created: 20 May 2017
40
+ *
41
+ */
42
+
43
+const (
44
+
45
+	// LETR  Letters Baudot code
46
+	LETR byte = 0x1f
47
+	// FIGR Figures Baudot code
48
+	FIGR byte = 0x1b
49
+	// CRLFThresh start looking for space
50
+	CRLFThresh = 60
51
+	// CRLFForce force CR-LF
52
+	CRLFForce = 72
53
+
54
+	// ModStateIdle no data in mod buffer, idling
55
+	ModStateIdle = 0
56
+	// ModStateStart modulating start bit
57
+	ModStateStart = 1
58
+	// ModStateBit modulating a baudot bit
59
+	ModStateBit = 2
60
+	// ModStateStop modulating stop bit
61
+	ModStateStop = 3
62
+	// ModStateHold modulating hold tone
63
+	ModStateHold = 4
64
+
65
+	// LPFOrder Low pass filter order
66
+	LPFOrder = 20 // 20
67
+
68
+	/* oblDemodulate() state machine states */
69
+
70
+	// DemodStateWaitStart wait for start bit
71
+	DemodStateWaitStart = 0 /**/
72
+	// DemodStateSample sampling demod bits
73
+	DemodStateSample = 1 /**/
74
+	// DemodStateWaitStop wait for stop bit
75
+	DemodStateWaitStop = 2 /**/
76
+
77
+	// MinThresh ratio for vaild signal
78
+	MinThresh = 3
79
+
80
+	/* auto baud detection state machine states */
81
+
82
+	// AutobaudStateWaitStart waiting for start bit
83
+	AutobaudStateWaitStart = 0
84
+	// AutobaudStateWaitZeroX waiting for zero crossing
85
+	AutobaudStateWaitZeroX = 1
86
+	// AutobaudStateWaitStop waiting for stop bit
87
+	AutobaudStateWaitStop = 2
88
+	// AutobaudStateDisabled disabled
89
+	AutobaudStateDisabled = 3
90
+
91
+	/* top level state machines states */
92
+
93
+	// EventDemodChar is fun
94
+	EventDemodChar = iota
95
+	// EventDemodAbort is fun
96
+	EventDemodAbort
97
+	// EventDemodCase inform application regarding a demodulated LETRS/Figures case character
98
+	EventDemodCase
99
+	// EventTransmitState data value is 1 for transmitting or 0 for not transmitting, and 2 for timeout (following transmit)
100
+	EventTransmitState
101
+	// EventIdle is fun
102
+	EventIdle
103
+)
104
+
105
+/* precomputed constants for demodDsp */
106
+
107
+var (
108
+	// sampleRate Sample rate
109
+	sampleRate = 8000
110
+	// Beta filter bandwidth factor
111
+	Beta = 0.95 //.95
112
+)
113
+
114
+func multq(x, y int16) int16 {
115
+	return int16((int(x) * int(y)) >> 15)
116
+
117
+}
118
+
119
+// abs16 returns the absolute value of x.
120
+func abs16(x int16) int16 {
121
+	if x < 0 {
122
+		return -x
123
+	}
124
+	return x
125
+}
126
+
127
+// absint returns the absolute value of x.
128
+func absint(x int) int {
129
+	if x < 0 {
130
+		return -x
131
+	}
132
+	return x
133
+}
134
+
135
+func calculateW(oneF int, zeroF int) (float64, float64) {
136
+	wOne := (2 * math.Pi * float64(oneF) / float64(sampleRate))   //1400 // WOne 1 freq in normalised rads
137
+	wZero := (2 * math.Pi * float64(zeroF) / float64(sampleRate)) //1800 // WZero 0 freq in normazlised rads
138
+	return wOne, wZero
139
+}
140
+
141
+// Callback is func
142
+type Callback func(event int, data interface{})

+ 74
- 0
decoder.go View File

@@ -0,0 +1,74 @@
1
+package openbaudot
2
+
3
+/*
4
+ * OBL openbaudot Library
5
+ *
6
+ * Copyright (C) 2017 Keelan Lightfoot
7
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
8
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
9
+ * Copyright (C) 2007-2008 Omnitor AB
10
+ * Copyright (C) 2007-2008 Voiceriver Inc
11
+ *
12
+ * This software was developed with support from the National Institute on
13
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
14
+ * # H133E990006 and H133E040014
15
+ *
16
+ * This library is free software; you can redistribute it and/or modify it
17
+ * under the terms of the GNU Lesser General Public License as published by
18
+ * the Free Software Foundation; either version 2.1 of the License, or (at
19
+ * your option) any later version.
20
+ *
21
+ * This library is distributed in the hope that it will be useful, but
22
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
24
+ * License for more details.
25
+ *
26
+ * You should have received a copy of the GNU Lesser General Public License
27
+ * along with this library; if not, write to the Free Software Foundation,
28
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29
+ *
30
+ * Please send a copy of any improved versions of the library to:
31
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
32
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
33
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
34
+ *
35
+ * file...: obl.go
36
+ * original author.: Keelan Lightfoot
37
+ * Created: 20 May 2017
38
+ *
39
+ */
40
+
41
+// Decoder is fun
42
+type Decoder struct {
43
+	input   chan byte
44
+	output  chan rune
45
+	charset *CodeSet
46
+}
47
+
48
+// NewDecoder is fun
49
+func NewDecoder(input chan byte, output chan rune, charset *CodeSet) *Decoder {
50
+	d := new(Decoder)
51
+	d.input = input
52
+	d.output = output
53
+	d.charset = charset
54
+
55
+	go d.run()
56
+	return d
57
+}
58
+
59
+func (d *Decoder) run() {
60
+	shift := shiftLetter
61
+	for c := range d.input {
62
+		switch c {
63
+		case LETR:
64
+			shift = shiftLetter
65
+			//obl.callback(EventDemodCase, Letters)
66
+		case FIGR:
67
+			shift = shiftFigure
68
+		//	obl.callback(EventDemodCase, Figures)
69
+		default:
70
+			d.output <- d.charset.toRune(shift, c)
71
+			//	obl.callback(obl,EventDemodChar, ascii);
72
+		}
73
+	}
74
+}

+ 317
- 0
demodulator.go View File

@@ -0,0 +1,317 @@
1
+package openbaudot
2
+
3
+import "math"
4
+
5
+/*
6
+ * OBL openbaudot Library
7
+ *
8
+ * Copyright (C) 2017 Keelan Lightfoot
9
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
10
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
11
+ * Copyright (C) 2007-2008 Omnitor AB
12
+ * Copyright (C) 2007-2008 Voiceriver Inc
13
+ *
14
+ * This software was developed with support from the National Institute on
15
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
16
+ * # H133E990006 and H133E040014
17
+ *
18
+ * This library is free software; you can redistribute it and/or modify it
19
+ * under the terms of the GNU Lesser General Public License as published by
20
+ * the Free Software Foundation; either version 2.1 of the License, or (at
21
+ * your option) any later version.
22
+ *
23
+ * This library is distributed in the hope that it will be useful, but
24
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
26
+ * License for more details.
27
+ *
28
+ * You should have received a copy of the GNU Lesser General Public License
29
+ * along with this library; if not, write to the Free Software Foundation,
30
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
31
+ *
32
+ * Please send a copy of any improved versions of the library to:
33
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
34
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
35
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
36
+ *
37
+ * file...: obl.go
38
+ * original author.: Keelan Lightfoot
39
+ * Created: 20 May 2017
40
+ *
41
+ */
42
+
43
+// Demodulator is fun
44
+type Demodulator struct {
45
+	input         chan int16
46
+	output        chan byte
47
+	state         int     // current demod state machine state
48
+	minSigTimeout int     // minimum signal time out counter
49
+	baud          float64 // demod baud rate
50
+	bit           int     // current bit being received
51
+	sample        int     // down counter for sampling instant
52
+	//	lettersFigures int     // current Letters/Figures mode
53
+	baudot        byte // demodulator baud rate
54
+	samplesPerBit int
55
+	dsp           *dsp
56
+	dataBits      int
57
+	autobaud      struct {
58
+		enabled  bool    // audio baud detection enable/disable flag
59
+		state    int     // auto buad detection state mach state
60
+		zeroX    int     // samples to zero crossing
61
+		estimate float64 // autobaud estimate
62
+	}
63
+}
64
+
65
+// NewDemodulator is fun
66
+func NewDemodulator(baud float64, stopBits float64, dataBits int, input chan int16, output chan byte) *Demodulator {
67
+
68
+	d := new(Demodulator)
69
+	d.input = input
70
+	d.output = output
71
+	d.state = DemodStateWaitStart
72
+	//d.lettersFigures = Letters
73
+	d.baud = baud
74
+	d.dataBits = dataBits
75
+	d.samplesPerBit = int(float64(sampleRate) / d.baud)
76
+
77
+	wOne, wZero := calculateW(2125, 2295)
78
+
79
+	d.dsp = &dsp{
80
+		c1: int16((2.0 * math.Cos(wOne) * Beta) * 32768),
81
+		c2: int16(Beta * Beta * 32768),
82
+		c3: int16((2.0 * math.Cos(wZero) * Beta) * 32768),
83
+	}
84
+
85
+	d.autobaud.estimate = d.baud
86
+	if d.autobaud.enabled {
87
+		d.autobaud.state = AutobaudStateWaitStart
88
+	} else {
89
+		d.autobaud.state = AutobaudStateDisabled
90
+	}
91
+	go d.run()
92
+	return d
93
+
94
+}
95
+
96
+func (d *Demodulator) run() {
97
+
98
+	var nextState int
99
+	var edge, dist int
100
+
101
+	var demDist75 [2]int // previous 75 baud distance measures
102
+	var demDist50 [2]int // previous 50 baud distance measures
103
+	var demDist47 [2]int // previous 47 baud distance measures
104
+	var demDist45 [2]int // previous 45 baud distance measures
105
+
106
+	/* main loop, process sample by sample */
107
+
108
+	for sam := range d.input {
109
+		d.dsp.demod(sam)
110
+
111
+		nextState = d.state
112
+
113
+		switch d.state {
114
+		case DemodStateWaitStart:
115
+			/*
116
+			   The demodulator sits in this state when it
117
+			   is waiting for a start bit.  When a sampleRateK
118
+			   "0" (1800 Hz) start-bit is detected that is
119
+			   above a minimum threshold it transitions
120
+			   out of this state.
121
+			*/
122
+			if d.dsp.signalPresent() {
123
+				d.sample = int(float64(sampleRate)/d.baud + float64(sampleRate)/(2*d.baud))
124
+				d.baudot = 0
125
+				d.bit = 0
126
+				nextState = DemodStateSample
127
+			}
128
+		case DemodStateSample:
129
+
130
+			d.sample--
131
+			if d.sample == 0 {
132
+				if d.dsp.onePresent() {
133
+					d.baudot |= (1 << uint(d.bit))
134
+				}
135
+				d.bit++
136
+
137
+				if d.bit == d.dataBits {
138
+
139
+					/* complete baudot char has been received */
140
+					d.output <- d.baudot
141
+					//fmt.Printf("Demodulator done %d\n", d.baudot)
142
+
143
+					d.sample = int(float64(sampleRate) / d.baud)
144
+					nextState = DemodStateWaitStop
145
+				} else {
146
+					/* sample next bit 1 bit-period later */
147
+					d.sample = int(float64(sampleRate) / d.baud)
148
+				}
149
+			}
150
+
151
+			/* check for sampleRateK signal drop out */
152
+
153
+			//return d.demTotal != 0 && d.demLpf < -1*MinThresh*d.demTotal
154
+
155
+			if d.dsp.dropout() {
156
+				d.minSigTimeout++
157
+
158
+				/* If signal drops out for approx one bit period
159
+				   assume we have lost sampleRateK signal */
160
+				if d.minSigTimeout > d.samplesPerBit {
161
+					nextState = DemodStateWaitStart
162
+				}
163
+			} else {
164
+				d.minSigTimeout = 0
165
+			}
166
+
167
+		case DemodStateWaitStop:
168
+
169
+			/* wait until we are in the middle of stop bit */
170
+
171
+			d.sample--
172
+			if d.sample == 0 {
173
+				nextState = DemodStateWaitStart
174
+			}
175
+
176
+		}
177
+
178
+		d.state = nextState
179
+
180
+		/* auto baud detection state machine ----------------------------*/
181
+
182
+		/*
183
+		   Starts a counter at falling edge that marks the
184
+		   beginning of the start bit.  Measures distance to
185
+		   next rising edge.  Calculates the distance from
186
+		   that rising edge to ideal position of the nearest
187
+		   edge for (i) the 50 baud case and (ii) the 45 baud
188
+		   case.  This gives us a distance metric for each
189
+		   possible baud rate.  The distance measure is
190
+		   smoothed over three samples.  The baud rate with
191
+		   the smallest distance is chosen as the current baud
192
+		   rate estimate.
193
+		*/
194
+
195
+		nextState = d.autobaud.state
196
+
197
+		switch d.autobaud.state {
198
+		case AutobaudStateWaitStart:
199
+			/* start bit kicks off this state machine */
200
+			if d.state == DemodStateSample {
201
+				nextState = AutobaudStateWaitZeroX
202
+				d.autobaud.zeroX = 0
203
+			}
204
+		case AutobaudStateWaitZeroX:
205
+
206
+			d.autobaud.zeroX++
207
+
208
+			if d.dsp.edgeDetected() {
209
+				/* OK, rising edge detected */
210
+
211
+				/* determine distance to closest edge assuming 50 baud */
212
+
213
+				dist75 := sampleRate / 75
214
+				edge = 0
215
+				for i := 0; i < (d.dataBits + 1); i++ {
216
+					dist = absint(edge - d.autobaud.zeroX)
217
+					if dist < dist75 {
218
+						dist75 = dist
219
+					}
220
+					edge += sampleRate / 75
221
+				}
222
+
223
+				/* determine distance to closest edge assuming 50 baud */
224
+
225
+				dist50 := sampleRate / 50
226
+				edge = 0
227
+				for i := 0; i < (d.dataBits + 1); i++ {
228
+					dist = absint(edge - d.autobaud.zeroX)
229
+					if dist < dist50 {
230
+						dist50 = dist
231
+					}
232
+					edge += sampleRate / 50
233
+				}
234
+
235
+				/* determine distance to closest edge assuming 47 baud */
236
+
237
+				dist47 := sampleRate / 47
238
+				edge = 0
239
+				for i := 0; i < (d.dataBits + 1); i++ {
240
+					dist = absint(edge - d.autobaud.zeroX)
241
+					if dist < dist47 {
242
+						dist47 = dist
243
+					}
244
+					edge += sampleRate / 47
245
+				}
246
+
247
+				/* determine distance to closest edge assuming 45 baud */
248
+
249
+				dist45 := sampleRate / 45
250
+				edge = 0
251
+				for i := 0; i < (d.dataBits + 1); i++ {
252
+					dist = absint(edge - d.autobaud.zeroX)
253
+					if dist < dist45 {
254
+						dist45 = dist
255
+					}
256
+					edge += sampleRate / 45
257
+				}
258
+
259
+				/* update 3-point smoothed distance estimates */
260
+
261
+				dist75Smooth := dist75 + demDist75[0] + demDist75[1]
262
+				demDist75[1] = demDist75[0]
263
+				demDist75[0] = dist75
264
+
265
+				dist50Smooth := dist50 + demDist50[0] + demDist50[1]
266
+				demDist50[1] = demDist50[0]
267
+				demDist50[0] = dist50
268
+
269
+				dist47Smooth := dist47 + demDist47[0] + demDist47[1]
270
+				demDist47[1] = demDist47[0]
271
+				demDist47[0] = dist47
272
+
273
+				dist45Smooth := dist45 + demDist45[0] + demDist45[1]
274
+				demDist45[1] = demDist45[0]
275
+				demDist45[0] = dist45
276
+
277
+				/* make a decision on baud rate */
278
+
279
+				if (dist75Smooth < dist47Smooth) && (dist75Smooth < dist45Smooth) && (dist75Smooth < dist50Smooth) {
280
+					d.autobaud.estimate = 75
281
+				}
282
+				if (dist50Smooth < dist47Smooth) && (dist50Smooth < dist45Smooth) && (dist50Smooth < dist75Smooth) {
283
+					d.autobaud.estimate = 50
284
+				}
285
+				if (dist47Smooth < dist50Smooth) && (dist47Smooth < dist45Smooth) && (dist47Smooth < dist75Smooth) {
286
+					d.autobaud.estimate = 47
287
+				}
288
+				if (dist45Smooth < dist50Smooth) && (dist45Smooth < dist47Smooth) && (dist45Smooth < dist75Smooth) {
289
+					d.autobaud.estimate = 45
290
+				}
291
+
292
+				//printf("obl.autobaud_zerox: %d dist50: %02d dist47: %02d dist45: %02d est: %d\n",
293
+				//       obl.autobaud_zerox, dist50Smooth, dist47Smooth, dist45Smooth, obl.dem_baud_est);
294
+				nextState = AutobaudStateWaitStop
295
+			}
296
+
297
+			/* reset autobaud state machine, e.g. if sampleRateK drops out before
298
+			   we get a zero crossing */
299
+
300
+			if d.state != DemodStateSample {
301
+				nextState = AutobaudStateWaitStop
302
+			}
303
+
304
+		case AutobaudStateWaitStop:
305
+			if d.state == DemodStateWaitStop {
306
+				nextState = AutobaudStateWaitStart
307
+			}
308
+
309
+		case AutobaudStateDisabled:
310
+
311
+		default:
312
+
313
+		}
314
+		d.autobaud.state = nextState
315
+	}
316
+
317
+}

+ 103
- 0
dsp.go View File

@@ -0,0 +1,103 @@
1
+package openbaudot
2
+
3
+/*
4
+ * OBL openbaudot Library
5
+ *
6
+ * Copyright (C) 2017 Keelan Lightfoot
7
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
8
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
9
+ * Copyright (C) 2007-2008 Omnitor AB
10
+ * Copyright (C) 2007-2008 Voiceriver Inc
11
+ *
12
+ * This software was developed with support from the National Institute on
13
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
14
+ * # H133E990006 and H133E040014
15
+ *
16
+ * This library is free software; you can redistribute it and/or modify it
17
+ * under the terms of the GNU Lesser General Public License as published by
18
+ * the Free Software Foundation; either version 2.1 of the License, or (at
19
+ * your option) any later version.
20
+ *
21
+ * This library is distributed in the hope that it will be useful, but
22
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
24
+ * License for more details.
25
+ *
26
+ * You should have received a copy of the GNU Lesser General Public License
27
+ * along with this library; if not, write to the Free Software Foundation,
28
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29
+ *
30
+ * Please send a copy of any improved versions of the library to:
31
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
32
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
33
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
34
+ *
35
+ * file...: obl.go
36
+ * original author.: Keelan Lightfoot
37
+ * Created: 20 May 2017
38
+ *
39
+ */
40
+
41
+type dsp struct {
42
+	demOne      [2]float64      // previous 2 output samples of one filter
43
+	demOneQ0    [2]int16        // previous 2 output samples of one filter
44
+	demZero     [2]float64      // previous 2 output samples of zero filter
45
+	demZeroQ0   [2]int16        // previous 2 output samples of one filter
46
+	demLpfIn    [LPFOrder]int16 // previous LPFOrder LPF input samples
47
+	pdemLpfIn   int             // ptr to oldest LPF input sample
48
+	demLpf      int             // current LPF output value
49
+	demTotalIn  [LPFOrder]int16 // previous LPFOrder energy input samples
50
+	pdemTotalIn int             // ptr to oldest energy sample
51
+	demTotal    int             // current energy output value
52
+	c1, c2, c3  int16           // precomputed values for demod_dsp
53
+
54
+}
55
+
56
+func (d *dsp) edgeDetected() bool {
57
+	return d.demLpf > MinThresh*d.demTotal
58
+}
59
+
60
+func (d *dsp) dropout() bool {
61
+	return absint(d.demLpf) < MinThresh*d.demTotal || d.demTotal == 0
62
+}
63
+
64
+func (d *dsp) signalPresent() bool {
65
+	return d.demTotal != 0 && d.demLpf < -1*MinThresh*d.demTotal
66
+}
67
+
68
+func (d *dsp) onePresent() bool {
69
+	return d.demLpf > 0
70
+}
71
+
72
+func (d *dsp) demod(sam int16) { //short *buffer, int samples)
73
+
74
+	sam >>= 5
75
+
76
+	oneQ0 := sam
77
+	oneQ0 += multq(d.c1, d.demOneQ0[0])
78
+	oneQ0 -= multq(d.c2, d.demOneQ0[1])
79
+	d.demOneQ0[1] = d.demOneQ0[0]
80
+	d.demOneQ0[0] = oneQ0
81
+
82
+	zeroQ0 := sam
83
+	zeroQ0 += multq(d.c3, d.demZeroQ0[0])
84
+	zeroQ0 -= multq(d.c2, d.demZeroQ0[1])
85
+	d.demZeroQ0[1] = d.demZeroQ0[0]
86
+	d.demZeroQ0[0] = zeroQ0
87
+
88
+	x := abs16(oneQ0) - abs16(zeroQ0)
89
+	d.demLpf -= int(d.demLpfIn[d.pdemLpfIn])
90
+	d.demLpf += int(x)
91
+	d.demLpfIn[d.pdemLpfIn] = x
92
+	d.pdemLpfIn = (d.pdemLpfIn + 1) % LPFOrder
93
+
94
+	/* now work out total energy estimate, this is used to
95
+	   determine if a valid signal is present by comparing
96
+	   the modem tone to total energy ratio */
97
+
98
+	x = abs16(sam)
99
+	d.demTotal -= int(d.demTotalIn[d.pdemTotalIn])
100
+	d.demTotal += int(x)
101
+	d.demTotalIn[d.pdemTotalIn] = x
102
+	d.pdemTotalIn = (d.pdemTotalIn + 1) % LPFOrder
103
+}

+ 176
- 0
encoder.go View File

@@ -0,0 +1,176 @@
1
+package openbaudot
2
+
3
+/*
4
+ * OBL openbaudot Library
5
+ *
6
+ * Copyright (C) 2017 Keelan Lightfoot
7
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
8
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
9
+ * Copyright (C) 2007-2008 Omnitor AB
10
+ * Copyright (C) 2007-2008 Voiceriver Inc
11
+ *
12
+ * This software was developed with support from the National Institute on
13
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
14
+ * # H133E990006 and H133E040014
15
+ *
16
+ * This library is free software; you can redistribute it and/or modify it
17
+ * under the terms of the GNU Lesser General Public License as published by
18
+ * the Free Software Foundation; either version 2.1 of the License, or (at
19
+ * your option) any later version.
20
+ *
21
+ * This library is distributed in the hope that it will be useful, but
22
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
24
+ * License for more details.
25
+ *
26
+ * You should have received a copy of the GNU Lesser General Public License
27
+ * along with this library; if not, write to the Free Software Foundation,
28
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29
+ *
30
+ * Please send a copy of any improved versions of the library to:
31
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
32
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
33
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
34
+ *
35
+ * file...: obl.go
36
+ * original author.: Keelan Lightfoot
37
+ * Created: 20 May 2017
38
+ *
39
+ */
40
+
41
+// Encoder is fun
42
+type Encoder struct {
43
+	input    chan rune
44
+	output   chan byte
45
+	autoWrap bool // true for auto CRLF mode
46
+	autoCRLF bool
47
+	charset  *CodeSet
48
+}
49
+
50
+// NewEncoder is fun
51
+func NewEncoder(input chan rune, output chan byte, charset *CodeSet) *Encoder {
52
+	e := new(Encoder)
53
+	e.input = input
54
+	e.output = output
55
+	e.charset = charset
56
+
57
+	go e.run()
58
+	return e
59
+}
60
+
61
+// EncodeString is fun
62
+func (t *Encoder) EncodeString(s string) {
63
+	go func() {
64
+		for _, c := range s {
65
+			t.input <- c
66
+		}
67
+	}()
68
+}
69
+
70
+func (t *Encoder) run() {
71
+
72
+	var modCase int           // current Letters/Figures state
73
+	var charsWithoutCRLF int  // number of chars we have handled without inserting a CR-LF
74
+	var charsWithoutShift int // number of chars we have handled without seeing a shift
75
+	for r := range t.input {
76
+		c := t.charset.toCode(r)
77
+
78
+		/*this rune can either be rendered as a Figures character,
79
+		  a Letters character or WHITESPACE which can be rendered by
80
+		  either character set*/
81
+		if r == '\r' || r == '\n' {
82
+			charsWithoutCRLF = 0
83
+		}
84
+
85
+		/*see if we need to first insert a case character*/
86
+		if c.shift == shiftWhitespace {
87
+			/*we have white space, we don't need to insert
88
+			a Letters/Figures case character.  We will insert
89
+			a case character when we transition from white
90
+			space to some other case.  The only exception to this
91
+			is when we are just starting to transmit, in which case
92
+			we must send something, so we'll choose the more common
93
+			Letters case*/
94
+			if modCase == shiftUnknown {
95
+				t.output <- t.charset.unshift()
96
+			}
97
+
98
+			modCase = shiftWhitespace
99
+		} else if c.shift != modCase {
100
+			/*case is either Letters or Figures and doesn't match
101
+			  our modulator's current state, so add a new case char
102
+			  here*/
103
+			if c.shift == shiftLetter {
104
+				t.output <- t.charset.unshift()
105
+			} else {
106
+				t.output <- t.charset.shift()
107
+			}
108
+
109
+			/*and save our current state*/
110
+			modCase = c.shift
111
+
112
+			/*and update our counter*/
113
+			charsWithoutShift = 0
114
+		}
115
+
116
+		/*we need to perform a little logic here to check for some
117
+		 * boundry conditions in the protocol*/
118
+		/* We need to ensure that we re-send the current case character
119
+		   at least every 72 characters. */
120
+		if charsWithoutShift > 70 {
121
+			if modCase == shiftWhitespace {
122
+				t.output <- t.charset.unshift() /*doesn't matter which, so how 'bout LETR*/
123
+			} else if modCase == shiftLetter {
124
+				t.output <- t.charset.unshift()
125
+			} else {
126
+				t.output <- t.charset.shift()
127
+			}
128
+			charsWithoutShift = 0
129
+		}
130
+
131
+		/* we need to ensure that we send a carriage return/linefeed
132
+		 * at least every 72 characters, but we start looking for a
133
+		 * space character starting at 60 characters */
134
+		if !t.autoWrap {
135
+			/*we just insert our regular character*/
136
+			t.output <- c.toByte()
137
+
138
+			if t.autoCRLF {
139
+				if r == '\n' {
140
+					t.output <- t.charset.toByte('\r')
141
+				} else if r == '\r' {
142
+					t.output <- t.charset.toByte('\n')
143
+				}
144
+			}
145
+
146
+		} else if (charsWithoutCRLF > 59) && (modCase == shiftWhitespace) {
147
+			/*insert the cr-lf instead of the space character*/
148
+			t.output <- t.charset.toByte('\r')
149
+			t.output <- t.charset.toByte('\n')
150
+			charsWithoutCRLF = 0
151
+		} else if charsWithoutCRLF > 70 {
152
+			/*insert the cr-lf followed by the character*/
153
+			t.output <- t.charset.toByte('\r')
154
+			t.output <- t.charset.toByte('\n')
155
+			t.output <- c.toByte()
156
+			charsWithoutCRLF = 0
157
+		} else {
158
+			/*we just insert our regular character*/
159
+			t.output <- c.toByte()
160
+
161
+			if t.autoCRLF {
162
+				if r == '\n' {
163
+					t.output <- t.charset.toByte('\r')
164
+				} else if r == '\r' {
165
+					t.output <- t.charset.toByte('\n')
166
+				}
167
+			}
168
+
169
+		}
170
+
171
+		charsWithoutShift++
172
+		charsWithoutCRLF++
173
+
174
+	}
175
+
176
+}

+ 107
- 0
examples/main.go View File

@@ -0,0 +1,107 @@
1
+package main
2
+
3
+import (
4
+	"fmt"
5
+	"time"
6
+
7
+	"github.com/naleek/openbaudot"
8
+)
9
+
10
+func main() {
11
+	baud := 45.45
12
+	stopBits := 2.0
13
+
14
+	charset := openbaudot.USTTY.Initialize()
15
+
16
+	encoderIn := make(chan rune)
17
+	modulatorIn := make(chan byte)
18
+	modulatorOut := make(chan int16)
19
+	demodulatorIn := make(chan int16)
20
+	decoderIn := make(chan byte)
21
+	decoderOut := make(chan rune)
22
+
23
+	done := make(chan struct{})
24
+
25
+	encoder := openbaudot.NewEncoder(encoderIn, modulatorIn, charset)
26
+	modulator := openbaudot.NewModulator(baud, stopBits, 5, modulatorIn, modulatorOut)
27
+
28
+	callback := func(event int, data interface{}) {
29
+		switch event {
30
+		case openbaudot.EventIdle:
31
+			fmt.Println("EventIdle")
32
+			encoder.EncodeString("Hello World")
33
+
34
+		}
35
+
36
+	}
37
+
38
+	modulator.SetCallback(callback)
39
+
40
+	go func() {
41
+		for b := range modulatorOut {
42
+			demodulatorIn <- b
43
+			time.Sleep(125 * time.Microsecond)
44
+		}
45
+	}()
46
+
47
+	openbaudot.NewDemodulator(baud, stopBits, 5, demodulatorIn, decoderIn)
48
+	openbaudot.NewDecoder(decoderIn, decoderOut, charset)
49
+
50
+	running := true
51
+	for running {
52
+		select {
53
+		case r := <-decoderOut:
54
+			fmt.Printf("%c", r)
55
+		case <-done:
56
+			running = false
57
+		}
58
+	}
59
+}
60
+
61
+// // NewOBL is fun
62
+// func NewOBL(baud float64, callback Callback) *OBL {
63
+//
64
+// 	obl := new(OBL)
65
+//
66
+// 	obl.charset = USTTY.initialize()
67
+//
68
+// 	obl.callback = callback
69
+//
70
+// 	obl.mod.sinLutQ15 = make([]int16, 16384)
71
+// 	for i := 0; i < 16384; i++ {
72
+// 		obl.mod.sinLutQ15[i] = int16(32767 * math.Sin(2.0*math.Pi*float64(i)/float64(16384)))
73
+// 	}
74
+//
75
+// 	/* regular reset */
76
+// 	obl.autobaud.enabled = true
77
+// 	obl.Reset(baud)
78
+//
79
+// 	go func() {
80
+// 		portaudio.Initialize()
81
+// 		defer portaudio.Terminate()
82
+//
83
+// 		x := make(chan int16, 300)
84
+//
85
+// 		stream, _ := portaudio.OpenDefaultStream(0, 1, float64(sampleRate), 0, func(out []int16) {
86
+// 			for i := range out {
87
+// 				out[i] = <-x
88
+// 			}
89
+// 		})
90
+//
91
+// 		stream.Start()
92
+//
93
+// 		for {
94
+// 			s := <-obl.mod.output
95
+// 			//go func() {
96
+// 			obl.demod.input <- s
97
+// 			x <- s
98
+// 			//}()
99
+// 			//	binary.Write(f, binary.LittleEndian, s)
100
+// 		}
101
+// 	}()
102
+//
103
+// 	go obl.Modulate()
104
+// 	go obl.Demodulate()
105
+//
106
+// 	return obl
107
+// }

+ 184
- 0
modulator.go View File

@@ -0,0 +1,184 @@
1
+package openbaudot
2
+
3
+import "math"
4
+
5
+/*
6
+ * OBL openbaudot Library
7
+ *
8
+ * Copyright (C) 2017 Keelan Lightfoot
9
+ * Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
10
+ *                   System (Univ. of Wisconsin-Madison, Trace R&D Center)
11
+ * Copyright (C) 2007-2008 Omnitor AB
12
+ * Copyright (C) 2007-2008 Voiceriver Inc
13
+ *
14
+ * This software was developed with support from the National Institute on
15
+ * Disability and Rehabilitation Research, US Dept of Education under Grant
16
+ * # H133E990006 and H133E040014
17
+ *
18
+ * This library is free software; you can redistribute it and/or modify it
19
+ * under the terms of the GNU Lesser General Public License as published by
20
+ * the Free Software Foundation; either version 2.1 of the License, or (at
21
+ * your option) any later version.
22
+ *
23
+ * This library is distributed in the hope that it will be useful, but
24
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
26
+ * License for more details.
27
+ *
28
+ * You should have received a copy of the GNU Lesser General Public License
29
+ * along with this library; if not, write to the Free Software Foundation,
30
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
31
+ *
32
+ * Please send a copy of any improved versions of the library to:
33
+ * Jeff Knighton, Voiceriver Inc, jeff.knighton@voiceriver.com
34
+ * Gunnar Hellstrom, Omnitor AB, Box 92054, 12006 Stockholm, SWEDEN
35
+ * Gregg Vanderheiden, Trace Center, U of Wisconsin, Madison, Wi 53706
36
+ *
37
+ * file...: obl.go
38
+ * original author.: Keelan Lightfoot
39
+ * Created: 20 May 2017
40
+ *
41
+ */
42
+
43
+// Modulator is fun
44
+type Modulator struct {
45
+	input  chan byte
46
+	output chan int16
47
+
48
+	baud           float64 // modulator baud rate
49
+	dataBits       int
50
+	samplesPerBit  int    // number of samples for one bit
51
+	phaseQ16       uint16 // phase acc 0..2math.Pi -> 0..65536
52
+	amplitude      int16  // peak modulator amplitude for one tone
53
+	numStopBits    int    // number of stop bits
54
+	numStopSamples int    // number of samples for stop bit
55
+	wOneQ16        uint16 // scaled one freq 0..2math.Pi -> 0..65536
56
+	wZeroQ16       uint16 // scaled zero freq 0..2math.Pi -> 0..65536
57
+	sinLutQ15      []int16
58
+
59
+	callback Callback
60
+}
61
+
62
+// NewModulator is fun
63
+func NewModulator(baud float64, stopBits float64, dataBits int, input chan byte, output chan int16) *Modulator {
64
+
65
+	m := new(Modulator)
66
+	// mod.input = make(chan code)
67
+	// mod.output = make(chan int16)
68
+
69
+	m.input = input
70
+	m.output = output
71
+
72
+	m.baud = baud
73
+	m.dataBits = dataBits
74
+	m.samplesPerBit = int(float64(sampleRate) / m.baud)
75
+	m.numStopBits = int(math.Floor(stopBits / .5))
76
+	m.numStopSamples = m.numStopBits * m.samplesPerBit / 2
77
+	m.amplitude = 16384
78
+
79
+	wOne, wZero := calculateW(2125, 2295)
80
+
81
+	m.wOneQ16 = uint16((65536 / (2.0 * math.Pi)) * wOne)
82
+	m.wZeroQ16 = uint16((65536 / (2.0 * math.Pi)) * wZero)
83
+
84
+	m.sinLutQ15 = make([]int16, 16384)
85
+	for i := 0; i < 16384; i++ {
86
+		m.sinLutQ15[i] = int16(32767 * math.Sin(2.0*math.Pi*float64(i)/float64(16384)))
87
+	}
88
+	go m.run()
89
+	return m
90
+}
91
+
92
+// SetCallback is fun
93
+func (m *Modulator) SetCallback(callback Callback) {
94
+	m.callback = callback
95
+}
96
+
97
+func (m *Modulator) modulate(bit int) { //short *buffer, int samples)
98
+	/* use of unsigned short in Q16 for format for phase
99
+	   accumulator leads to natural wrap-around at 2PI
100
+	   boundaries
101
+	*/
102
+	/* sin_lut_q15 is in Q15 format, therefore we rightshift 15 to
103
+	   get Q0 at modulator output.  We cast multiply arguments
104
+	   to 32 bit ints to ensure compiler uses a multiply with a 32
105
+	   bit result.
106
+	*/
107
+	if bit != 0 {
108
+		m.phaseQ16 += m.wOneQ16
109
+		m.output <- multq(m.amplitude, m.sinLutQ15[m.phaseQ16>>2])
110
+
111
+	} else {
112
+		m.phaseQ16 += m.wZeroQ16
113
+		m.output <- multq(m.amplitude, m.sinLutQ15[m.phaseQ16>>2])
114
+	}
115
+}
116
+
117
+func (m *Modulator) run() {
118
+
119
+	var modState int  // modulator state machine
120
+	var nextState int // next state of state machine
121
+	var c byte        // current baudot char being modulated
122
+	var modNbit int   // num bits modulated so far in this char
123
+	var modBit int    // current bit in modBaudot being modulated
124
+	var modSample int // current sample in bit being modulated
125
+	var idleSamples int
126
+
127
+	for {
128
+		nextState = modState
129
+		switch modState {
130
+		case ModStateIdle:
131
+			select {
132
+			case c = <-m.input:
133
+				modSample = 0
134
+				nextState = ModStateStart
135
+				//obl.callback(EventTransmitState, TransmitStart)
136
+				idleSamples = 0
137
+			default:
138
+				m.modulate(0)
139
+
140
+				if idleSamples < sampleRate {
141
+					idleSamples++
142
+				} else if idleSamples == sampleRate {
143
+					if m.callback != nil {
144
+						m.callback(EventIdle, 0)
145
+					}
146
+					idleSamples++
147
+				}
148
+			}
149
+		case ModStateStart:
150
+			m.modulate(0)
151
+			modSample++
152
+			// after sending start bit send LSB data bit
153
+			if modSample == m.samplesPerBit {
154
+				modNbit = 0
155
+				modBit = int(c & 0x1)
156
+				modSample = 0
157
+				nextState = ModStateBit
158
+			}
159
+		case ModStateBit:
160
+			m.modulate(modBit)
161
+			modSample++
162
+			// when this data bit complete, send next data bit
163
+			if modSample == m.samplesPerBit {
164
+				modSample = 0
165
+				modNbit++
166
+				modBit = int((c >> uint(modNbit)) & 0x1)
167
+				if modNbit == m.dataBits {
168
+					nextState = ModStateStop
169
+				}
170
+			}
171
+		case ModStateStop:
172
+			m.modulate(1)
173
+			modSample++
174
+			// when stop bit complete start transmission of next char, or enter idle state
175
+			if modSample == m.numStopSamples {
176
+				modSample = 0
177
+				nextState = ModStateIdle
178
+			}
179
+			// 	(obl.callback)(obl,OBL_EVENT_TX_STATE, OBL_TRANSMIT_STOP);
180
+		}
181
+		modState = nextState
182
+	}
183
+
184
+}