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(&center, "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  }