github.com/noriah/catnip@v1.8.5/cmd/catnip/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "os/signal" 9 10 "github.com/noriah/catnip" 11 "github.com/noriah/catnip/dsp" 12 "github.com/noriah/catnip/dsp/window" 13 "github.com/noriah/catnip/graphic" 14 "github.com/noriah/catnip/input" 15 "github.com/noriah/catnip/processor" 16 17 _ "github.com/noriah/catnip/input/all" 18 19 "github.com/integrii/flaggy" 20 ) 21 22 // AppName is the app name 23 const AppName = "catnip" 24 25 // AppDesc is the app description 26 const AppDesc = "Continuous Automatic Terminal Number Interpretation Printer" 27 28 // AppSite is the app website 29 const AppSite = "https://github.com/noriah/catnip" 30 31 var version = "git" 32 33 func main() { 34 log.SetFlags(0) 35 36 cfg := newZeroConfig() 37 38 if doFlags(&cfg) { 39 return 40 } 41 42 chk(cfg.validate(), "invalid config") 43 44 smoother := dsp.NewSmoother(dsp.SmootherConfig{ 45 SampleSize: cfg.sampleSize, 46 SampleRate: cfg.sampleRate, 47 ChannelCount: cfg.channelCount, 48 SmoothingFactor: cfg.smoothFactor, 49 SmoothingMethod: dsp.SmoothingMethod(cfg.smoothingMethod), 50 AverageSize: cfg.smoothingAverageWindowSize, 51 }) 52 53 display := graphic.NewDisplay() 54 55 var output processor.Output 56 57 if !cfg.useNumberWriter { 58 output = display 59 } else { 60 writer := NewWriter() 61 writer.Init(cfg.sampleRate, cfg.sampleSize) 62 writer.SetBinCount(cfg.numberWriterBins) 63 writer.SetInvertDraw(cfg.invertDraw) 64 output = writer 65 } 66 67 display.Smoother = smoother 68 69 catnipCfg := catnip.Config{ 70 Backend: cfg.backend, 71 Device: cfg.device, 72 SampleRate: cfg.sampleRate, 73 SampleSize: cfg.sampleSize, 74 ChannelCount: cfg.channelCount, 75 ProcessRate: cfg.frameRate, 76 Combine: cfg.combine, 77 UseThreaded: cfg.useThreaded, 78 SetupFunc: setupFunc(!cfg.useNumberWriter, &cfg, display), 79 StartFunc: startFunc(!cfg.useNumberWriter, display), 80 CleanupFunc: cleanupFunc(!cfg.useNumberWriter, display), 81 Output: output, 82 Windower: window.Lanczos(), 83 Analyzer: dsp.NewAnalyzer(dsp.AnalyzerConfig{ 84 SampleRate: cfg.sampleRate, 85 SampleSize: cfg.sampleSize, 86 SquashLow: true, 87 SquashLowOld: true, 88 DontNormalize: cfg.dontNormalize, 89 BinMethod: dsp.MaxSampleValue(), 90 }), 91 Smoother: smoother, 92 } 93 94 // Root Context 95 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 96 defer cancel() 97 98 chk(catnip.Run(&catnipCfg, ctx), "failed to run catnip") 99 } 100 101 func setupFunc(isDisplay bool, cfg *config, display *graphic.Display) func() error { 102 if !isDisplay { 103 return func() error { return nil } 104 } 105 106 return func() error { 107 if err := display.Init(cfg.sampleRate, cfg.sampleSize); err != nil { 108 return err 109 } 110 111 display.SetSizes(cfg.barSize, cfg.spaceSize) 112 display.SetBase(cfg.baseSize) 113 display.SetDrawType(graphic.DrawType(cfg.drawType)) 114 display.SetStyles(cfg.styles) 115 display.SetInvertDraw(cfg.invertDraw) 116 117 return nil 118 } 119 } 120 121 func startFunc(isDisplay bool, display *graphic.Display) func(context.Context) (context.Context, error) { 122 if !isDisplay { 123 return func(ctx context.Context) (context.Context, error) { 124 return ctx, nil 125 } 126 } 127 128 return func(ctx context.Context) (context.Context, error) { 129 ctx = display.Start(ctx) 130 return ctx, nil 131 } 132 } 133 134 func cleanupFunc(isDisplay bool, display *graphic.Display) func() error { 135 if !isDisplay { 136 return func() error { return nil } 137 } 138 139 return func() error { 140 display.Stop() 141 display.Close() 142 return nil 143 } 144 } 145 146 func doFlags(cfg *config) bool { 147 148 parser := flaggy.NewParser(AppName) 149 parser.Description = AppDesc 150 parser.AdditionalHelpPrepend = AppSite 151 parser.Version = version 152 153 listBackendsCmd := flaggy.Subcommand{ 154 Name: "list-backends", 155 ShortName: "lb", 156 Description: "list all supported backends", 157 AdditionalHelpAppend: "\nuse the full name after the '-'", 158 } 159 160 parser.AttachSubcommand(&listBackendsCmd, 1) 161 162 listDevicesCmd := flaggy.Subcommand{ 163 Name: "list-devices", 164 ShortName: "ld", 165 Description: "list all devices for a backend", 166 AdditionalHelpAppend: "\nuse the full name after the '-'", 167 } 168 169 parser.AttachSubcommand(&listDevicesCmd, 1) 170 171 parser.String(&cfg.backend, "b", "backend", "backend name") 172 parser.String(&cfg.device, "d", "device", "device name") 173 parser.Float64(&cfg.sampleRate, "r", "rate", "sample rate") 174 parser.Int(&cfg.sampleSize, "n", "samples", "sample size") 175 parser.Int(&cfg.frameRate, "f", "fps", "frame rate (0 to draw on every sample)") 176 parser.Int(&cfg.channelCount, "ch", "channels", "channel count (1 or 2)") 177 parser.Float64(&cfg.smoothFactor, "sf", "smoothing", "smooth factor (0-100)") 178 parser.Int(&cfg.smoothingMethod, "sm", "smooth-method", "smoothing method (0, 1, 2, 3, 4, 5)") 179 parser.Int(&cfg.smoothingAverageWindowSize, "sas", "smooth-average-size", "smoothing window size") 180 parser.Int(&cfg.baseSize, "bt", "base", "base thickness [0, +Inf)") 181 parser.Int(&cfg.barSize, "bw", "bar", "bar width [1, +Inf)") 182 parser.Int(&cfg.spaceSize, "bs", "space", "space width [0, +Inf)") 183 parser.Int(&cfg.drawType, "dt", "draw", "draw type (1, 2, 3, 4, 5, 6, 7, 8, 9)") 184 parser.Bool(&cfg.dontNormalize, "dn", "dont-normalize", "dont normalize analyzer output") 185 parser.Bool(&cfg.useThreaded, "t", "threaded", "use the threaded processor") 186 parser.Bool(&cfg.invertDraw, "i", "invert", "invert the direction of bin drawing") 187 188 parser.Bool(&cfg.useNumberWriter, "nw", "number-writer", "use writer") 189 parser.Int(&cfg.numberWriterBins, "nwb", "number-writer-bins", "number of bins for the number writer per channel.") 190 191 fg, bg, center := graphic.DefaultStyles().AsUInt16s() 192 parser.UInt16(&fg, "fg", "foreground", 193 "foreground color within the 256-color range [0, 255] with attributes") 194 parser.UInt16(&bg, "bg", "background", 195 "background color within the 256-color range [0, 255] with attributes") 196 parser.UInt16(¢er, "ct", "center", 197 "center line color within the 256-color range [0, 255] with attributes") 198 199 chk(parser.Parse(), "failed to parse arguments") 200 201 // Manually set the styles. 202 cfg.styles = graphic.StylesFromUInt16(fg, bg, center) 203 204 switch { 205 case listBackendsCmd.Used: 206 defaultBackend := input.DefaultBackend() 207 208 fmt.Println("all backends. '*' marks default") 209 210 for _, backend := range input.Backends { 211 star := ' ' 212 if defaultBackend == backend.Name { 213 star = '*' 214 } 215 216 fmt.Printf("- %s %c\n", backend.Name, star) 217 } 218 219 return true 220 221 case listDevicesCmd.Used: 222 backend, err := input.InitBackend(cfg.backend) 223 chk(err, "failed to init backend") 224 225 devices, err := backend.Devices() 226 chk(err, "failed to get devices") 227 228 // We don't really need the default device to be indicated. 229 defaultDevice, _ := backend.DefaultDevice() 230 231 fmt.Printf("all devices for %q backend. '*' marks default\n", cfg.backend) 232 233 for idx := range devices { 234 star := ' ' 235 if defaultDevice != nil && devices[idx].String() == defaultDevice.String() { 236 star = '*' 237 } 238 239 fmt.Printf("- %v %c\n", devices[idx], star) 240 } 241 242 return true 243 } 244 245 return false 246 } 247 248 func chk(err error, wrap string) { 249 if err != nil { 250 log.Fatalln(wrap+": ", err) 251 } 252 }