Keelan Lightfoot 7 years ago
parent
commit
f737e06975
6 changed files with 66 additions and 624 deletions
  1. 0
    89
      chopper.go
  2. 11
    3
      common.go
  3. 15
    3
      decoder.go
  4. 9
    38
      demodulator.go
  5. 0
    480
      examples/rttymodem/main.go
  6. 31
    11
      modulator.go

+ 0
- 89
chopper.go View File

@@ -1,89 +0,0 @@
1
-// Copyright (C) 2017 Keelan Lightfoot
2
-// Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin
3
-//             System (Univ. of Wisconsin-Madison, Trace R&D Center)
4
-// Copyright (C) 2007-2008 Omnitor AB
5
-// Copyright (C) 2007-2008 Voiceriver Inc
6
-//
7
-// This library is free software; you can redistribute it and/or modify it
8
-// under the terms of the GNU Lesser General Public License as published by
9
-// the Free Software Foundation; either version 2.1 of the License, or (at
10
-// your option) any later version.
11
-
12
-package gobaudot
13
-
14
-import (
15
-	"math"
16
-)
17
-
18
-
19
-
20
-type TimeSlice struct {
21
-	Marking		bool
22
-	Carrier		bool
23
-	LpfVal		float64
24
-	ThreshVal		float64
25
-}
26
-
27
-// Chopper is fun
28
-type Chopper struct {
29
-	input         chan int16
30
-	output        chan TimeSlice  
31
-	dsp           *dsp
32
-	settings      *ModemSettings
33
-	sigOut        bool
34
-	samplerate    int
35
-	div           int
36
-}
37
-
38
-// NewChopper is fun
39
-func NewChopper(s *ModemSettings, input chan int16, output chan TimeSlice) *Chopper {
40
-
41
-	d := new(Chopper)
42
-	d.input = input
43
-	d.output = output
44
-	
45
-	d.settings = s
46
-
47
-	// the bandpass filter is picky about the sample rate, and the output is perfectly
48
-	// balanced when the sample rate is 3x the center frequency, so lets divide the sample
49
-	// rate down to as close to that as we can get.
50
-
51
-	centerFrequency := math.Abs(float64(d.settings.OneFreq)+float64(d.settings.ZeroFreq)) / 2 * 3
52
-
53
-	d.div = int(math.Floor(float64(d.settings.SampleRate) / float64(centerFrequency)))
54
-	if d.div == 0 {
55
-		d.div = 1
56
-	}
57
-
58
-	d.samplerate = d.settings.SampleRate / d.div
59
-	
60
-	d.dsp = newDsp(d.settings.OneFreq, d.settings.ZeroFreq, d.samplerate)
61
-
62
-	go d.run()
63
-	return d
64
-}
65
-
66
-func (d *Chopper) run() {
67
-
68
-	count := 0
69
-	for sam := range d.input {
70
-	
71
-		count++
72
-		// can't remember what this is for
73
-		if count%d.div > 0 {
74
-			continue
75
-		}
76
-
77
-		d.dsp.demod(float64(sam))
78
-
79
-		d.output <- TimeSlice{
80
-			Marking: d.dsp.mark(),
81
-			Carrier: d.dsp.carrier(),
82
-			LpfVal: d.dsp.lpfVal(),
83
-			ThreshVal: d.dsp.threshVal(),
84
-		}
85
-		
86
-		
87
-	}
88
-
89
-}

+ 11
- 3
common.go View File

@@ -22,10 +22,18 @@ const (
22 22
 	EventDemodAbort
23 23
 	// EventDemodCase inform application regarding a demodulated LETRS/Figures case character
24 24
 	EventDemodCase
25
-	// EventTransmitState data value is 1 for transmitting or 0 for not transmitting, and 2 for timeout (following transmit)
26
-	EventTransmitState
27
-	// EventIdle is fun
25
+
26
+	// EventActive is triggered when the modulator exits out of an idle period to begin modulating
27
+	EventActive
28
+	// EventIdle is triggered after idle
28 29
 	EventIdle
30
+	
31
+	
32
+	// EventStartBit is triggered with the falling edge of the start bit when transmitting a character
33
+	EventStartBit
34
+	// EventStopBit is triggered with rising edge of the stop bit when transmitting a character
35
+	EventStopBit
36
+	
29 37
 	// EventEncodeStringComplete is fun
30 38
 	EventEncodeStringComplete
31 39
 )

+ 15
- 3
decoder.go View File

@@ -17,6 +17,13 @@ type Decoder struct {
17 17
 	input   chan byte
18 18
 	output  chan rune
19 19
 	charset *CodeSet
20
+	usos bool
21
+}
22
+
23
+// UnshiftOnSpace sets how the decoder behaves when a space is received while in figures
24
+// mode
25
+func (d *Decoder) UnshiftOnSpace(usos bool) {
26
+	d.usos = usos
20 27
 }
21 28
 
22 29
 // NewDecoder creates a new decoder.
@@ -36,13 +43,18 @@ func (d *Decoder) run() {
36 43
 		switch c {
37 44
 		case d.charset.UnshiftCode:
38 45
 			shift = shiftLetter
39
-			//obl.callback(EventDemodCase, Letters)
46
+			//TODO: Trigger EventDemodCase
40 47
 		case d.charset.ShiftCode:
41 48
 			shift = shiftFigure
42
-		//	obl.callback(EventDemodCase, Figures)
49
+			//TODO: Trigger EventDemodCase
43 50
 		default:
51
+			r := d.charset.toRune(shift, c)
52
+			if d.usos && shift == shiftFigure && r == ' ' {
53
+				shift = shiftLetter
54
+				//TODO: Trigger EventUnshiftOnSpace
55
+			}
44 56
 			d.output <- d.charset.toRune(shift, c)
45
-			//	obl.callback(obl,EventDemodChar, ascii);
57
+			//TODO: Trigger EventDemodChar
46 58
 		}
47 59
 	}
48 60
 }

+ 9
- 38
demodulator.go View File

@@ -39,45 +39,13 @@ type Demodulator struct {
39 39
 	sigOut        bool
40 40
 	samplerate    int
41 41
 	div           int
42
+	inhibitOutput bool
42 43
 	autobaud      struct {
43 44
 		enabled  bool    // audio baud detection enable/disable flag
44 45
 		estimate float64 // autobaud estimate
45 46
 	}
46 47
 }
47 48
 
48
-// NewDumbDemodulator is fun
49
-func NewDumbDemodulator(s *ModemSettings, input chan int16, sigOutput chan [3]float64) *Demodulator {
50
-
51
-	d := new(Demodulator)
52
-	d.input = input
53
-	d.sigOutput = sigOutput
54
-
55
-	d.settings = s
56
-
57
-	// the bandpass filter is picky about the sample rate, and the output is perfectly
58
-	// balanced when the sample rate is 3x the center frequency, so lets divide the sample
59
-	// rate down to as close to that as we can get.
60
-
61
-	centerFrequency := math.Abs(float64(d.settings.OneFreq)+float64(d.settings.ZeroFreq)) / 2 * 3
62
-
63
-	d.div = int(math.Floor(float64(d.settings.SampleRate) / float64(centerFrequency)))
64
-	if d.div == 0 {
65
-		d.div = 1
66
-	}
67
-
68
-	d.samplerate = d.settings.SampleRate / d.div
69
-	//d.div = div
70
-	d.samplesPerBit = int(float64(d.samplerate) / d.settings.Baud)
71
-	d.autobaud.enabled = false
72
-
73
-	d.dsp = newDsp(d.settings.OneFreq, d.settings.ZeroFreq, d.samplerate)
74
-
75
-	d.autobaud.estimate = d.settings.Baud
76
-	d.sigOut = true
77
-
78
-	go d.run()
79
-	return d
80
-}
81 49
 
82 50
 // NewDemodulator is fun
83 51
 func NewDemodulator(s *ModemSettings, input chan int16, output chan byte) *Demodulator {
@@ -108,6 +76,10 @@ func NewDemodulator(s *ModemSettings, input chan int16, output chan byte) *Demod
108 76
 
109 77
 }
110 78
 
79
+func (d *Demodulator) Inhibit(i bool) {
80
+	d.inhibitOutput = i
81
+}
82
+
111 83
 func (d *Demodulator) run() {
112 84
 
113 85
 	var nextState int
@@ -168,13 +140,12 @@ func (d *Demodulator) run() {
168 140
 					}
169 141
 					bit++
170 142
 					if bit == d.settings.DataBits {
171
-
172
-						/* complete baudot char has been received */
173
-
143
+						// complete baudot char has been received
174 144
 						sample = int(float64(d.samplerate) / d.settings.Baud)
175 145
 						nextState = demodStateWaitStop
176
-						//	fmt.Println("demodStateWaitStop", baudot)
177
-						d.output <- baudot
146
+						if !d.inhibitOutput {
147
+							d.output <- baudot
148
+						}
178 149
 					} else {
179 150
 						/* sample next bit 1 bit-period later */
180 151
 						sample = int(float64(d.samplerate) / d.settings.Baud)

+ 0
- 480
examples/rttymodem/main.go View File

@@ -1,480 +0,0 @@
1
-package main
2
-
3
-import (
4
-	"fmt"
5
-
6
-	"bufio"
7
-	"bytes"
8
-	"github.com/gordonklaus/portaudio"
9
-	"github.com/naleek/gortty"
10
-	"io"
11
-	"net"
12
-	"os"
13
-	"regexp"
14
-	"strings"
15
-	"unicode/utf8"
16
-	"time"
17
-)
18
-
19
-func msain() {
20
-
21
-	CLI(os.Stdin, os.Stdout, nil)
22
-
23
-}
24
-
25
-func main() {
26
-	fmt.Println("hi")
27
-	s := &gortty.ModemSettings{
28
-		Baud:       45,
29
-		StopBits:   2,
30
-		DataBits:   5,
31
-		SampleRate: 8000,
32
-		OneFreq:    2125,
33
-		ZeroFreq:   2295,
34
-	}
35
-
36
-	portaudio.Initialize()
37
-	defer portaudio.Terminate()
38
-
39
-	in := make([]int16, 64)
40
-	out := make([]int16, 64)
41
-	stream, err := portaudio.OpenDefaultStream(1, 1, float64(s.SampleRate), len(in), in, out)
42
-	if err != nil {
43
-		panic(err)
44
-	}
45
-	defer stream.Close()
46
-	charset := gortty.LoadCharset(&gortty.USTTY)
47
-
48
-	encoderIn := make(chan rune, 100)
49
-	modulatorIn := make(chan byte)
50
-
51
-	demodulatorIn := make(chan int16)
52
-	modulatorOut := make(chan int16)
53
-
54
-	decoderIn := make(chan byte)
55
-	decoderOut := make(chan rune)
56
-
57
-	modemReader := NewModemReader(decoderOut)
58
-	modemWriter := NewModemWriter(encoderIn)
59
-
60
-	
61
-	gortty.NewEncoder(encoderIn, modulatorIn, charset)
62
-	modulator := gortty.NewModulator(s, modulatorIn, modulatorOut)
63
-
64
-	gortty.NewDemodulator(s, demodulatorIn, decoderIn)
65
-	gortty.NewDecoder(decoderIn, decoderOut, charset)
66
-
67
-	//go CLI(modemReader, modemWriter)
68
-	modulator.SetAmplitude(0)
69
-	
70
-	go CLI(modemReader, modemWriter, modulator) // os.Stdin
71
-
72
-	stream.Start()
73
-	go func() {
74
-		l := 0
75
-		for v := range modulatorOut {
76
-			out[l] = v
77
-			l++
78
-			if l == 64 {
79
-				l = 0
80
-				stream.Write()
81
-			}
82
-		}
83
-	}()
84
-	for {
85
-		stream.Read()
86
-		for i := range in {
87
-			demodulatorIn <- in[i]
88
-		}
89
-	}
90
-	//stream.Stop()
91
-
92
-	// go func() {
93
-	// 	encoder.EncodeString("hello world")
94
-	// 	time.Sleep(2000 * time.Millisecond)
95
-	// 	done <- struct{}{}
96
-	// }()
97
-
98
-}
99
-
100
-type ModemReader struct {
101
-	input chan rune
102
-}
103
-
104
-func NewModemReader(c chan rune) *ModemReader {
105
-	m := &ModemReader{}
106
-	m.input = c
107
-	return m
108
-}
109
-
110
-func (m *ModemReader) Read(p []byte) (n int, err error) {
111
-	r := <-m.input
112
-
113
-	fmt.Print(string(r))
114
-
115
-	l := utf8.EncodeRune(p, r)
116
-	return l, nil
117
-}
118
-
119
-func (m *ModemReader) Add(b rune) {
120
-	m.input <- b
121
-}
122
-
123
-type ModemWriter struct {
124
-	output chan rune
125
-}
126
-
127
-func NewModemWriter(c chan rune) *ModemWriter {
128
-	m := &ModemWriter{}
129
-	m.output = c
130
-	return m
131
-}
132
-
133
-func (m *ModemWriter) Write(p []byte) (n int, err error) {
134
-
135
-	runes := bytes.Runes(p)
136
-
137
-	fmt.Print(string(runes))
138
-	
139
-	for _, r := range runes {
140
-		m.output <- r
141
-	}
142
-	return len(p), nil
143
-}
144
-
145
-func (m *ModemWriter) Get() rune {
146
-	return <-m.output
147
-}
148
-
149
-func CLI(ttyReader io.Reader, ttyWriter io.Writer, modulator *gortty.Modulator) {
150
-
151
-	fmt.Fprintln(ttyWriter, "RTTY TCP Modem 1.0")
152
-
153
-	localEcho := false
154
-	autoAnswer := false
155
-	escapeRune := '?'	
156
-	modulatorOn := true
157
-	ttyBufferedReader := bufio.NewReader(ttyReader)
158
-	
159
-	var netConn *NetConn
160
-	
161
-	for { 
162
-
163
-		s, _ := ttyBufferedReader.ReadString('\n')
164
-
165
-		//fmt.Println(s) 
166
-		s = strings.ToUpper(s)
167
-		regex, _ := regexp.Compile(`^AT([SHOED])([0-9])?(\?)?(?:=([\S]*))?(?: (.*))?`)
168
-
169
-		params := regex.FindStringSubmatch(s)
170
-
171
-		if params == nil {
172
-			continue
173
-		}
174
-
175
-		command := params[1]
176
-
177
-		var register string
178
-		if len(params) > 2 {
179
-			register = params[2]
180
-		}
181
-
182
-		var registerQuery string
183
-		if len(params) > 3 {
184
-			registerQuery = params[3]
185
-		}
186
-
187
-		var value string
188
-		if len(params) > 4 {
189
-			value = params[4]
190
-		}
191
-
192
-		var parameter string
193
-		if len(params) > 5 {
194
-			parameter = params[5]
195
-		}
196
-
197
-		//fmt.Fprintf(ttyWriter, "command: '%s' register: '%s' query: '%s' value: '%s' parameter: '%s'\n", command, register, registerQuery, value, parameter)
198
-		switch command {
199
-		case "O": // online
200
-			if !netConn.Connected() {
201
-				fmt.Fprintln(ttyWriter, "ERROR No active session")
202
-				break
203
-			}
204
-			fmt.Fprintln(ttyWriter, "OK")
205
-			dataMode(netConn, ttyBufferedReader, ttyWriter, escapeRune)
206
-		case "D": // dial
207
-			if netConn.Connected() {
208
-				fmt.Fprintln(ttyWriter, "ERROR Session in progress")
209
-				break
210
-			}
211
-			addr := strings.TrimSpace(parameter)
212
-			
213
-			var err error
214
-			netConn, err = NewNetConn(addr)
215
-			if err != nil {
216
-				fmt.Fprintln(ttyWriter, "ERROR", err)
217
-				break
218
-			} else {
219
-				fmt.Fprintln(ttyWriter, "CONNECTED")
220
-				dataMode(netConn, ttyBufferedReader, ttyWriter, escapeRune)
221
-			}
222
-		case "H": // hangup
223
-			if !netConn.Connected() {
224
-				fmt.Fprintln(ttyWriter, "ERROR No active session")
225
-				break
226
-			}
227
-			netConn.Hangup()
228
-			fmt.Fprintln(ttyWriter, "OK")
229
-		case "E": // local echo
230
-			localEcho = (register == "1")
231
-			fmt.Fprintln(ttyWriter, "local echo", localEcho)
232
-		case "S": // local echo
233
-			switch register {
234
-			case "0":
235
-				if registerQuery == "?" {
236
-					if autoAnswer {
237
-						fmt.Fprintln(ttyWriter, "1")
238
-					} else {
239
-						fmt.Fprintln(ttyWriter, "0")
240
-					}
241
-				} else if value != "" {
242
-					autoAnswer = (value == "1")
243
-					fmt.Fprintln(ttyWriter, "OK")
244
-				}
245
-			case "2":
246
-				if registerQuery == "?" {
247
-					fmt.Fprintln(ttyWriter, string(escapeRune))
248
-				} else if value != "" {
249
-					runes := []rune(value)
250
-					escapeRune = runes[0]
251
-					fmt.Fprintln(ttyWriter, "OK")
252
-				}
253
-			case "3":
254
-				if registerQuery == "?" {
255
-					if modulatorOn {
256
-						fmt.Fprintln(ttyWriter, "1")
257
-					} else {
258
-						fmt.Fprintln(ttyWriter, "0")
259
-					}
260
-				} else if value != "" {
261
-					modulatorOn = (value == "1")
262
-					fmt.Fprintln(ttyWriter, "OK")
263
-					if modulatorOn {
264
-						modulator.SetAmplitude(16384)
265
-					} else {	
266
-						modulator.SetAmplitude(0)
267
-					}
268
-				}
269
-			}
270
-
271
-		
272
-		}
273
-		
274
-		
275
-	}
276
-
277
-}
278
-
279
-
280
-
281
-func dataMode(netConn *NetConn, ttyBufferedReader *bufio.Reader, ttyWriter io.Writer, escapeRune rune) {
282
-	
283
-	ttyCancelReader := make(chan struct{})
284
-	ttyReadChan := make(chan rune)
285
-	ttyReadPending := make(chan struct{})
286
-	
287
-	var t *time.Timer
288
-	escapeRunes := 0
289
-
290
-
291
-	go func() {
292
-		for {
293
-			r, l, err := ttyBufferedReader.ReadRune()
294
-			if err != nil && err != io.EOF {
295
-				return
296
-			}
297
-			if l > 0 {
298
-				select {
299
-				case <-ttyCancelReader:
300
-					ttyBufferedReader.UnreadRune() // oops this wasn't ours. let's put it back.
301
-					close(ttyReadPending)
302
-					return
303
-				default:
304
-					ttyReadChan <- r
305
-				}
306
-			}
307
-		}
308
-	}()
309
-	
310
-	netReadChan := netConn.OutputChannel()
311
-	netConn.Resume()
312
-pipeLoop:	
313
-	for {
314
-		var r rune	
315
-		if t == nil {
316
-			select {
317
-				case r = <-ttyReadChan:
318
-				case q := <-netReadChan:
319
-					ttyWriter.Write([]byte(string(q)))
320
-					continue pipeLoop
321
-			}
322
-		} else {
323
-			select {
324
-				case r = <-ttyReadChan:
325
-				case q := <-netReadChan:
326
-					ttyWriter.Write([]byte(string(q)))
327
-					continue pipeLoop
328
-				case <-t.C:
329
-					fmt.Fprintln(ttyWriter, "\nOK")
330
-					close(ttyCancelReader)
331
-					netConn.Pause()
332
-					break pipeLoop
333
-
334
-			}
335
-		}
336
-		if r == escapeRune && escapeRunes < 3 {
337
-			escapeRunes++
338
-		} else {
339
-			if t != nil {
340
-				t.Stop()
341
-			}
342
-			escapeRunes = 0
343
-		}
344
-		if escapeRunes == 3 {
345
-			t = time.NewTimer(1*time.Second)
346
-		}
347
-		err := netConn.Write(r)
348
-		if err != nil {
349
-			fmt.Fprintln(ttyWriter, "NO CARRIER")
350
-			close(ttyCancelReader)
351
-			break pipeLoop
352
-		}
353
-	}
354
-	<- ttyReadPending
355
-	
356
-}
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-type NetConn struct {
366
-	netReader *bufio.Reader
367
-	conn net.Conn
368
-	netReadChan chan rune
369
-	hangup chan struct{}
370
-	paused  bool
371
-	connected bool
372
-}
373
-
374
-func NewNetConn(addr string) (*NetConn, error) {
375
-	c := &NetConn{}
376
-	var err error
377
-	c.conn, err = net.Dial("tcp", addr)
378
-	if err != nil {
379
-		return nil, err
380
-	}
381
-	c.connected = true
382
-
383
-	c.netReader = bufio.NewReader(c.conn)
384
-	
385
-	c.netReadChan = make(chan rune)
386
-	c.hangup = make(chan struct{})
387
-	c.paused = false
388
-	
389
-	go func() {
390
-		for {
391
-			r, l, err := c.netReader.ReadRune()
392
-			if err != nil && err != io.EOF {
393
-				c.cleanup()
394
-				return
395
-			}
396
-			if c.paused || l == 0 {
397
-				select {
398
-				case <- c.hangup:
399
-					c.cleanup()
400
-					return
401
-				default: // discard
402
-				}
403
-			} else {
404
-				select {
405
-				case c.netReadChan <- r:
406
-				case <- c.hangup:
407
-					c.cleanup()
408
-					return
409
-				}
410
-			}
411
-		}
412
-		
413
-	}()
414
-	
415
-	
416
-	return c, nil
417
-}
418
-
419
-func (c *NetConn) Write(r rune) error {
420
-	if c == nil {
421
-		return fmt.Errorf("NetConn not initialized")
422
-	}
423
-	_, err := c.conn.Write([]byte(string(r)))
424
-	if err != nil {	
425
-		close(c.hangup)
426
-	}
427
-	return err
428
-}
429
-
430
-func (c *NetConn) Hangup() {
431
-	if c == nil {
432
-		return
433
-	}
434
-	c.conn.Close()
435
-	close(c.hangup)
436
-}
437
-
438
-func (c *NetConn) cleanup() {
439
-	close(c.netReadChan)
440
-	c.connected = false 
441
-}
442
-
443
-func (c *NetConn) Pause() {
444
-	if c == nil {
445
-		return
446
-	}
447
-	c.paused = true
448
-}
449
-
450
-func (c *NetConn) Resume() {
451
-	if c == nil {
452
-		return
453
-	}
454
-	c.paused = false
455
-}
456
-
457
-func (c *NetConn) OutputChannel() chan rune {
458
-	if c == nil {
459
-		return nil
460
-	}
461
-	return c.netReadChan
462
-}
463
-
464
-func (c *NetConn) Connected() bool {
465
-	if c == nil {
466
-		return false
467
-	}
468
-	return c.connected
469
-}
470
-
471
-
472
-
473
-
474
-
475
-
476
-
477
-
478
-
479
-
480
-

+ 31
- 11
modulator.go View File

@@ -15,6 +15,7 @@ import "math"
15 15
 
16 16
 const (
17 17
 	modStateIdle = iota
18
+	modStateReady
18 19
 	modStateStart
19 20
 	modStateBit
20 21
 	modStateStop
@@ -37,6 +38,8 @@ type Modulator struct {
37 38
 	wZeroQ16        uint16  // scaled zero freq 0..2math.Pi -> 0..65536
38 39
 	sinLutQ15       []int16 // sine lookup table
39 40
 	callback        Callback
41
+	
42
+	idleDelay int
40 43
 }
41 44
 
42 45
 // NewModulator is fun
@@ -53,7 +56,7 @@ func NewModulator(s *ModemSettings, input chan byte, output chan int16) *Modulat
53 56
 	m.numStopHalfBits = int(math.Floor(m.settings.StopBits / .5))
54 57
 	m.numStopSamples = m.numStopHalfBits * m.samplesPerBit / 2
55 58
 	m.amplitude = 16384
56
-
59
+	m.idleDelay = m.settings.SampleRate
57 60
 	wOne, wZero := calculateW(m.settings.OneFreq, m.settings.ZeroFreq, m.settings.SampleRate)
58 61
 
59 62
 	m.wOneQ16 = uint16((65536 / (2.0 * math.Pi)) * wOne)
@@ -72,6 +75,12 @@ func (m *Modulator) SetCallback(callback Callback) {
72 75
 	m.callback = callback
73 76
 }
74 77
 
78
+// SetIdleDelay sets how long (in samples) that the modulator must be in the ready state 
79
+// before switching to the idle state
80
+func (m *Modulator) SetIdleDelay(d int) {
81
+	m.idleDelay = d
82
+}
83
+
75 84
 func (m *Modulator) modulate(bit int) { //short *buffer, int samples)
76 85
 	/* use of unsigned short in Q16 for format for phase
77 86
 	   accumulator leads to natural wrap-around at 2PI
@@ -109,24 +118,33 @@ func (m *Modulator) run() {
109 118
 	for {
110 119
 		nextState = modState
111 120
 		switch modState {
121
+		case modStateReady:
122
+			fallthrough
112 123
 		case modStateIdle:
113 124
 			select {
114 125
 			case c = <-m.input:
115 126
 				modSample = 0
116 127
 				nextState = modStateStart
117
-				//fmt.Println("modStateStart")
118
-				//obl.callback(EventTransmitState, TransmitStart)
119 128
 				idleSamples = 0
129
+				if m.callback != nil {
130
+					m.callback(EventStartBit, 0)
131
+					if (modState == modStateIdle) {
132
+						m.callback(EventActive, 0)
133
+					}
134
+				}
120 135
 			default:
121 136
 				m.modulate(1)
122 137
 
123
-				if idleSamples < m.settings.SampleRate {
124
-					idleSamples++
125
-				} else if idleSamples == m.settings.SampleRate {
126
-					if m.callback != nil {
127
-						m.callback(EventIdle, 0)
138
+				if modState == modStateReady {
139
+					if idleSamples < m.idleDelay {
140
+						idleSamples++
141
+					} else {
142
+						nextState = modStateIdle
143
+						if m.callback != nil {
144
+							m.callback(EventIdle, 0)
145
+						}
146
+						idleSamples++
128 147
 					}
129
-					idleSamples++
130 148
 				}
131 149
 			}
132 150
 		case modStateStart:
@@ -138,7 +156,6 @@ func (m *Modulator) run() {
138 156
 				modBit = int(c & 0x1)
139 157
 				modSample = 0
140 158
 				nextState = modStateBit
141
-				//fmt.Println("modStateBit")
142 159
 
143 160
 			}
144 161
 		case modStateBit:
@@ -161,7 +178,10 @@ func (m *Modulator) run() {
161 178
 			// when stop bit complete start transmission of next char, or enter idle state
162 179
 			if modSample == m.numStopSamples {
163 180
 				modSample = 0
164
-				nextState = modStateIdle
181
+				nextState = modStateReady
182
+				if m.callback != nil {
183
+					m.callback(EventStopBit, 0)
184
+				}
165 185
 			}
166 186
 			// 	(obl.callback)(obl,OBL_EVENT_TX_STATE, OBL_TRANSMIT_STOP);
167 187
 		}