|  |  |  | @ -1,26 +1,26 @@ | 
		
	
		
			
				|  |  |  |  | package main | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | import ( | 
		
	
		
			
				|  |  |  |  | 	"crypto/ed25519" | 
		
	
		
			
				|  |  |  |  | 	"crypto/rand" | 
		
	
		
			
				|  |  |  |  | 	"crypto/x509" | 
		
	
		
			
				|  |  |  |  | 	"encoding/binary" | 
		
	
		
			
				|  |  |  |  | 	"encoding/pem" | 
		
	
		
			
				|  |  |  |  | 	"flag" | 
		
	
		
			
				|  |  |  |  | 	"fmt" | 
		
	
		
			
				|  |  |  |  | 	"os" | 
		
	
		
			
				|  |  |  |  | 	"os/signal" | 
		
	
		
			
				|  |  |  |  | 	"syscall" | 
		
	
		
			
				|  |  |  |  | 	"time" | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	"git.metznet.ca/MetzNet/go-ncurses" | 
		
	
		
			
				|  |  |  |  | 	"git.metznet.ca/MetzNet/pnyx" | 
		
	
		
			
				|  |  |  |  | 	"github.com/gen2brain/malgo" | 
		
	
		
			
				|  |  |  |  | 	"github.com/google/uuid" | 
		
	
		
			
				|  |  |  |  | 	"github.com/hraban/opus" | 
		
	
		
			
				|  |  |  |  |   "crypto/ed25519" | 
		
	
		
			
				|  |  |  |  |   "crypto/rand" | 
		
	
		
			
				|  |  |  |  |   "crypto/x509" | 
		
	
		
			
				|  |  |  |  |   "encoding/binary" | 
		
	
		
			
				|  |  |  |  |   "encoding/pem" | 
		
	
		
			
				|  |  |  |  |   "flag" | 
		
	
		
			
				|  |  |  |  |   "fmt" | 
		
	
		
			
				|  |  |  |  |   "os" | 
		
	
		
			
				|  |  |  |  |   "os/signal" | 
		
	
		
			
				|  |  |  |  |   "syscall" | 
		
	
		
			
				|  |  |  |  |   "time" | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   "git.metznet.ca/MetzNet/go-ncurses" | 
		
	
		
			
				|  |  |  |  |   "git.metznet.ca/MetzNet/pnyx" | 
		
	
		
			
				|  |  |  |  |   "github.com/gen2brain/malgo" | 
		
	
		
			
				|  |  |  |  |   "github.com/google/uuid" | 
		
	
		
			
				|  |  |  |  |   "github.com/hraban/opus" | 
		
	
		
			
				|  |  |  |  | ) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | var decoders = map[pnyx.PeerID]chan[]byte{} | 
		
	
		
			
				|  |  |  |  | var decoders = map[pnyx.PeerID]chan []byte{} | 
		
	
		
			
				|  |  |  |  | var encoder *opus.Encoder | 
		
	
		
			
				|  |  |  |  | var sample_rate int = 0 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -33,18 +33,18 @@ func set_sample_rate(audio_data chan []int16, new_sample_rate int) error { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for peer_id, decoder_chan := range(decoders) { | 
		
	
		
			
				|  |  |  |  |   for peer_id, decoder_chan := range decoders { | 
		
	
		
			
				|  |  |  |  |     if decoder_chan != nil { | 
		
	
		
			
				|  |  |  |  |       decoder_chan <- nil | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     new_chan := make(chan[]byte, 1000) | 
		
	
		
			
				|  |  |  |  |     new_chan := make(chan []byte, 1000) | 
		
	
		
			
				|  |  |  |  |     decoders[peer_id] = new_chan | 
		
	
		
			
				|  |  |  |  |     go handle_peer_decode(audio_data, peer_id, decoders[peer_id], sample_rate) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_chan chan[]byte, sample_rate int){ | 
		
	
		
			
				|  |  |  |  | func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_chan chan []byte, sample_rate int) { | 
		
	
		
			
				|  |  |  |  |   decoder, err := opus.NewDecoder(sample_rate, 1) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
	
		
			
				
					|  |  |  | @ -54,11 +54,11 @@ func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_cha | 
		
	
		
			
				|  |  |  |  |   missed := 0 | 
		
	
		
			
				|  |  |  |  |   for running { | 
		
	
		
			
				|  |  |  |  |     select { | 
		
	
		
			
				|  |  |  |  |     case <-time.After(20*time.Millisecond): | 
		
	
		
			
				|  |  |  |  |     case <-time.After(20 * time.Millisecond): | 
		
	
		
			
				|  |  |  |  |       missed += 1 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       if missed > 100 { | 
		
	
		
			
				|  |  |  |  |         decode_chan <- <- decode_chan | 
		
	
		
			
				|  |  |  |  |         decode_chan <- <-decode_chan | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       pcm := make([]int16, sample_rate/50) | 
		
	
	
		
			
				
					|  |  |  | @ -88,11 +88,11 @@ func mixer(data_chan chan []int16, speaker_chan chan []int16) { | 
		
	
		
			
				|  |  |  |  |   var samples []int16 = nil | 
		
	
		
			
				|  |  |  |  |   for true { | 
		
	
		
			
				|  |  |  |  |     if samples == nil { | 
		
	
		
			
				|  |  |  |  |       samples = <- data_chan | 
		
	
		
			
				|  |  |  |  |       samples = <-data_chan | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       select { | 
		
	
		
			
				|  |  |  |  |       case new_samples := <- data_chan: | 
		
	
		
			
				|  |  |  |  |         for i, sample := range(new_samples) { | 
		
	
		
			
				|  |  |  |  |       case new_samples := <-data_chan: | 
		
	
		
			
				|  |  |  |  |         for i, sample := range new_samples { | 
		
	
		
			
				|  |  |  |  |           samples[i] += sample | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       case speaker_chan <- samples: | 
		
	
	
		
			
				
					|  |  |  | @ -108,13 +108,13 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	infos, err := ctx.Devices(malgo.Playback) | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  |   infos, err := ctx.Devices(malgo.Playback) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   var playback_device *malgo.DeviceInfo = nil | 
		
	
		
			
				|  |  |  |  | 	for _, info := range infos { | 
		
	
		
			
				|  |  |  |  |   for _, info := range infos { | 
		
	
		
			
				|  |  |  |  |     if info.IsDefault != 0 { | 
		
	
		
			
				|  |  |  |  |       full, err := ctx.DeviceInfo(malgo.Playback, info.ID, malgo.Shared) | 
		
	
		
			
				|  |  |  |  |       if err != nil { | 
		
	
	
		
			
				
					|  |  |  | @ -122,19 +122,19 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       playback_device = &full | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if playback_device == nil { | 
		
	
		
			
				|  |  |  |  |     panic("No default playback device") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	infos, err = ctx.Devices(malgo.Capture) | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  |   infos, err = ctx.Devices(malgo.Capture) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   var capture_device *malgo.DeviceInfo = nil | 
		
	
		
			
				|  |  |  |  | 	for _, info := range infos { | 
		
	
		
			
				|  |  |  |  |   for _, info := range infos { | 
		
	
		
			
				|  |  |  |  |     if info.IsDefault != 0 { | 
		
	
		
			
				|  |  |  |  |       full, err := ctx.DeviceInfo(malgo.Capture, info.ID, malgo.Shared) | 
		
	
		
			
				|  |  |  |  |       if err != nil { | 
		
	
	
		
			
				
					|  |  |  | @ -142,33 +142,33 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       capture_device = &full | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if capture_device == nil { | 
		
	
		
			
				|  |  |  |  |     panic("No default capture device") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig := malgo.DefaultDeviceConfig(malgo.Capture) | 
		
	
		
			
				|  |  |  |  | 	inDeviceConfig.Capture.Format = malgo.FormatS16 | 
		
	
		
			
				|  |  |  |  | 	inDeviceConfig.Capture.Channels = 1 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.Capture.Format = malgo.FormatS16 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.Capture.Channels = 1 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.Capture.DeviceID = capture_device.ID.Pointer() | 
		
	
		
			
				|  |  |  |  | 	inDeviceConfig.SampleRate = 48000 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.SampleRate = 48000 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.PeriodSizeInFrames = 960 | 
		
	
		
			
				|  |  |  |  | 	inDeviceConfig.Alsa.NoMMap = 1 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.Alsa.NoMMap = 1 | 
		
	
		
			
				|  |  |  |  |   inDeviceConfig.Capture.ShareMode = malgo.Shared | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig := malgo.DefaultDeviceConfig(malgo.Playback) | 
		
	
		
			
				|  |  |  |  | 	outDeviceConfig.Playback.Format = malgo.FormatS16 | 
		
	
		
			
				|  |  |  |  | 	outDeviceConfig.Playback.Channels = 1 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.Playback.Format = malgo.FormatS16 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.Playback.Channels = 1 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.Playback.DeviceID = playback_device.ID.Pointer() | 
		
	
		
			
				|  |  |  |  | 	outDeviceConfig.SampleRate = 48000 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.SampleRate = 48000 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.PeriodSizeInFrames = 960 | 
		
	
		
			
				|  |  |  |  | 	outDeviceConfig.Alsa.NoMMap = 1 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.Alsa.NoMMap = 1 | 
		
	
		
			
				|  |  |  |  |   outDeviceConfig.Playback.ShareMode = malgo.Shared | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   onSendFrames := func(output_samples []byte, input_samples []byte, framecount uint32) { | 
		
	
		
			
				|  |  |  |  |     select { | 
		
	
		
			
				|  |  |  |  |     case pcm := <- speaker: | 
		
	
		
			
				|  |  |  |  |     case pcm := <-speaker: | 
		
	
		
			
				|  |  |  |  |       for i := 0; i < sample_rate/50; i++ { | 
		
	
		
			
				|  |  |  |  |         binary.LittleEndian.PutUint16(output_samples[i*2:], uint16(pcm[i])) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
	
		
			
				
					|  |  |  | @ -176,9 +176,9 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	playbackCallbacks := malgo.DeviceCallbacks{ | 
		
	
		
			
				|  |  |  |  | 		Data: onSendFrames, | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   playbackCallbacks := malgo.DeviceCallbacks{ | 
		
	
		
			
				|  |  |  |  |     Data: onSendFrames, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   outDevice, err := malgo.InitDevice(ctx.Context, outDeviceConfig, playbackCallbacks) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
	
		
			
				
					|  |  |  | @ -190,7 +190,7 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) { | 
		
	
		
			
				|  |  |  |  |   onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) { | 
		
	
		
			
				|  |  |  |  |     if encoder != nil { | 
		
	
		
			
				|  |  |  |  |       pcm := make([]int16, len(input_samples)/2) | 
		
	
		
			
				|  |  |  |  |       for i := 0; i < len(input_samples)/2; i++ { | 
		
	
	
		
			
				
					|  |  |  | @ -207,21 +207,21 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext | 
		
	
		
			
				|  |  |  |  |       default: | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	captureCallbacks := malgo.DeviceCallbacks{ | 
		
	
		
			
				|  |  |  |  | 		Data: onRecvFrames, | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   captureCallbacks := malgo.DeviceCallbacks{ | 
		
	
		
			
				|  |  |  |  |     Data: onRecvFrames, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks) | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  |   inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	err = inDevice.Start() | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  |   err = inDevice.Start() | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return ctx, outDevice, inDevice | 
		
	
		
			
				|  |  |  |  | } | 
		
	
	
		
			
				
					|  |  |  | @ -257,7 +257,7 @@ func get_private_key(path string, generate bool) ed25519.PrivateKey { | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     key_pem := pem.EncodeToMemory(&pem.Block{ | 
		
	
		
			
				|  |  |  |  |       Type: "PRIVATE KEY", | 
		
	
		
			
				|  |  |  |  |       Type:  "PRIVATE KEY", | 
		
	
		
			
				|  |  |  |  |       Bytes: key_pkcs8, | 
		
	
		
			
				|  |  |  |  |     }) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -271,21 +271,21 @@ func get_private_key(path string, generate bool) ed25519.PrivateKey { | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func print_decoration(window, title, channels, body ncurses.Window, top_left string) {  | 
		
	
		
			
				|  |  |  |  | func print_decoration(window, title, channels, body ncurses.Window, top_left string) { | 
		
	
		
			
				|  |  |  |  |   max_y := ncurses.GetMaxY(*ncurses.CurScr) | 
		
	
		
			
				|  |  |  |  |   max_x := ncurses.GetMaxX(*ncurses.CurScr) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.WResize(window, max_y, max_x) | 
		
	
		
			
				|  |  |  |  |   ncurses.WResize(title, 1, max_x/4-2) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWin(title, 1, 1) | 
		
	
		
			
				|  |  |  |  |   ncurses.WResize(channels, max_y - 4, (max_x / 4) - 2) | 
		
	
		
			
				|  |  |  |  |   ncurses.WResize(channels, max_y-4, (max_x/4)-1) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWin(channels, 3, 1) | 
		
	
		
			
				|  |  |  |  |   ncurses.WResize(body, max_y - 2, max_x - (max_x / 4) - 2) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWin(body, 1, (max_x / 4) + 1) | 
		
	
		
			
				|  |  |  |  |   ncurses.WResize(body, max_y-2, max_x-(max_x/4)-2) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWin(body, 1, (max_x/4)+1) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWAddStr(title, 0, 0, top_left) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for i := 1; i < max_x - 1; i++ { | 
		
	
		
			
				|  |  |  |  |   for i := 1; i < max_x-1; i++ { | 
		
	
		
			
				|  |  |  |  |     ncurses.MvWAddStr(window, 0, i, "═") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -306,7 +306,7 @@ func print_decoration(window, title, channels, body ncurses.Window, top_left str | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for i := 1; i < max_x-1; i++ { | 
		
	
		
			
				|  |  |  |  |     ncurses.MvWAddStr(window, max_y - 1, i, "═") | 
		
	
		
			
				|  |  |  |  |     ncurses.MvWAddStr(window, max_y-1, i, "═") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWAddStr(window, 0, 0, "╔") | 
		
	
	
		
			
				
					|  |  |  | @ -324,12 +324,231 @@ func print_decoration(window, title, channels, body ncurses.Window, top_left str | 
		
	
		
			
				|  |  |  |  |   ncurses.WRefresh(body) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ChannelState struct { | 
		
	
		
			
				|  |  |  |  |   ID pnyx.ChannelID | 
		
	
		
			
				|  |  |  |  |   Name string | 
		
	
		
			
				|  |  |  |  |   Members []pnyx.PeerID | 
		
	
		
			
				|  |  |  |  |   Modes []pnyx.ModeID | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type PeerState struct { | 
		
	
		
			
				|  |  |  |  |   Name string | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type UIState struct { | 
		
	
		
			
				|  |  |  |  |   SelectedBody    int | 
		
	
		
			
				|  |  |  |  |   SelectedPeer    int | 
		
	
		
			
				|  |  |  |  |   SelectedChannel int | 
		
	
		
			
				|  |  |  |  |   SelectedArea    int | 
		
	
		
			
				|  |  |  |  |   Channels        []ChannelState | 
		
	
		
			
				|  |  |  |  |   Peers           map[pnyx.PeerID]PeerState | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | const ( | 
		
	
		
			
				|  |  |  |  |   AREA_CHANNELS = 0 | 
		
	
		
			
				|  |  |  |  |   AREA_BODY = 1 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   BODY_CHANNEL = 0 | 
		
	
		
			
				|  |  |  |  |   BODY_DETAIL = 1 | 
		
	
		
			
				|  |  |  |  |   BODY_QUIT = 2 | 
		
	
		
			
				|  |  |  |  | ) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_back(state *UIState) { | 
		
	
		
			
				|  |  |  |  |   if state.SelectedArea == AREA_BODY { | 
		
	
		
			
				|  |  |  |  |     if state.SelectedBody == BODY_QUIT { | 
		
	
		
			
				|  |  |  |  |       state.SelectedBody = BODY_CHANNEL | 
		
	
		
			
				|  |  |  |  |     } else if state.SelectedBody == BODY_DETAIL { | 
		
	
		
			
				|  |  |  |  |       state.SelectedBody = BODY_CHANNEL | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     state.SelectedArea = AREA_CHANNELS | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_up(state *UIState) { | 
		
	
		
			
				|  |  |  |  |   if state.SelectedArea == AREA_CHANNELS { | 
		
	
		
			
				|  |  |  |  |     if state.SelectedPeer > 0 { | 
		
	
		
			
				|  |  |  |  |       state.SelectedPeer -= 1 | 
		
	
		
			
				|  |  |  |  |     } else if state.SelectedChannel > 0 { | 
		
	
		
			
				|  |  |  |  |       state.SelectedChannel -= 1 | 
		
	
		
			
				|  |  |  |  |       state.SelectedPeer = len(state.Channels[state.SelectedChannel].Members) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_down(state *UIState) { | 
		
	
		
			
				|  |  |  |  |   if state.SelectedArea == AREA_CHANNELS { | 
		
	
		
			
				|  |  |  |  |     if state.SelectedPeer < len(state.Channels[state.SelectedChannel].Members) { | 
		
	
		
			
				|  |  |  |  |       state.SelectedPeer += 1 | 
		
	
		
			
				|  |  |  |  |     } else if state.SelectedChannel < len(state.Channels) - 1 { | 
		
	
		
			
				|  |  |  |  |       state.SelectedChannel += 1 | 
		
	
		
			
				|  |  |  |  |       state.SelectedPeer = 0 | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_select(state *UIState) { | 
		
	
		
			
				|  |  |  |  |   if state.SelectedArea == AREA_CHANNELS { | 
		
	
		
			
				|  |  |  |  |     state.SelectedArea = AREA_BODY | 
		
	
		
			
				|  |  |  |  |     state.SelectedBody = BODY_CHANNEL | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_edit(state *UIState) { | 
		
	
		
			
				|  |  |  |  |   if state.SelectedArea == AREA_CHANNELS { | 
		
	
		
			
				|  |  |  |  |     state.SelectedArea = AREA_BODY | 
		
	
		
			
				|  |  |  |  |     state.SelectedBody = BODY_DETAIL | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_quit(state *UIState) { | 
		
	
		
			
				|  |  |  |  |   state.SelectedArea = AREA_BODY | 
		
	
		
			
				|  |  |  |  |   state.SelectedBody = BODY_QUIT | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func handle_input(state *UIState, char []byte) { | 
		
	
		
			
				|  |  |  |  |   switch char[0] { | 
		
	
		
			
				|  |  |  |  |   case 'q': | 
		
	
		
			
				|  |  |  |  |     handle_quit(state) | 
		
	
		
			
				|  |  |  |  |   case 'e': | 
		
	
		
			
				|  |  |  |  |     handle_edit(state) | 
		
	
		
			
				|  |  |  |  |   case '\r': | 
		
	
		
			
				|  |  |  |  |     handle_select(state) | 
		
	
		
			
				|  |  |  |  |   case 0x1B: | 
		
	
		
			
				|  |  |  |  |     if len(char) == 1 { | 
		
	
		
			
				|  |  |  |  |       handle_back(state) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       switch char[1] { | 
		
	
		
			
				|  |  |  |  |       case '[': | 
		
	
		
			
				|  |  |  |  |         if len(char) == 2 { | 
		
	
		
			
				|  |  |  |  |         } else { | 
		
	
		
			
				|  |  |  |  |           switch char[2] { | 
		
	
		
			
				|  |  |  |  |             case 'A': // Up
 | 
		
	
		
			
				|  |  |  |  |               handle_up(state) | 
		
	
		
			
				|  |  |  |  |             case 'B': // Down
 | 
		
	
		
			
				|  |  |  |  |               handle_down(state) | 
		
	
		
			
				|  |  |  |  |             case 'C': // Right
 | 
		
	
		
			
				|  |  |  |  |               handle_select(state) | 
		
	
		
			
				|  |  |  |  |             case 'D': // Left
 | 
		
	
		
			
				|  |  |  |  |               handle_back(state) | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func draw_channels(state *UIState, channels ncurses.Window) { | 
		
	
		
			
				|  |  |  |  |   channel_highlight_color := 2 | 
		
	
		
			
				|  |  |  |  |   if state.SelectedArea == AREA_CHANNELS { | 
		
	
		
			
				|  |  |  |  |     channel_highlight_color = 1 | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.WErase(channels) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.WMove(channels, 0, 0) | 
		
	
		
			
				|  |  |  |  |   for i, channel_state := range(state.Channels) { | 
		
	
		
			
				|  |  |  |  |     if i == state.SelectedChannel && state.SelectedPeer == 0 { | 
		
	
		
			
				|  |  |  |  |       ncurses.WAttrOn(channels, ncurses.COLOR_PAIR(channel_highlight_color)) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     ncurses.WAddStr(channels, channel_state.Name) | 
		
	
		
			
				|  |  |  |  |     ncurses.WAddCh(channels, '\n') | 
		
	
		
			
				|  |  |  |  |     for j, peer_id := range(channel_state.Members) { | 
		
	
		
			
				|  |  |  |  |       ncurses.WAddStr(channels, "  ↳") | 
		
	
		
			
				|  |  |  |  |       peer_state, found := state.Peers[peer_id] | 
		
	
		
			
				|  |  |  |  |       if state.SelectedChannel == i && state.SelectedPeer == (j+1) { | 
		
	
		
			
				|  |  |  |  |         ncurses.WAttrOn(channels, ncurses.COLOR_PAIR(channel_highlight_color)) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       if found { | 
		
	
		
			
				|  |  |  |  |         ncurses.WAddStr(channels, peer_state.Name) | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         ncurses.WAddStr(channels, fmt.Sprintf("%s", peer_id)) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       if state.SelectedChannel == i && state.SelectedPeer == (j+1) { | 
		
	
		
			
				|  |  |  |  |         ncurses.WAttrOff(channels, ncurses.COLOR_PAIR(channel_highlight_color)) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       ncurses.WAddCh(channels, '\n') | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     if i == state.SelectedChannel && state.SelectedPeer == 0 { | 
		
	
		
			
				|  |  |  |  |       ncurses.WAttrOff(channels, ncurses.COLOR_PAIR(channel_highlight_color)) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.WRefresh(channels) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func draw_body_channel(state *UIState, body ncurses.Window) { | 
		
	
		
			
				|  |  |  |  |   ncurses.WErase(body) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWAddStr(body, 0, 0, "channel") | 
		
	
		
			
				|  |  |  |  |   ncurses.WRefresh(body) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func draw_body_detail(state *UIState, body ncurses.Window) { | 
		
	
		
			
				|  |  |  |  |   ncurses.WErase(body) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWAddStr(body, 0, 0, "detail") | 
		
	
		
			
				|  |  |  |  |   ncurses.WRefresh(body) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func draw_body_quit(state *UIState, body ncurses.Window) { | 
		
	
		
			
				|  |  |  |  |   ncurses.WErase(body) | 
		
	
		
			
				|  |  |  |  |   ncurses.MvWAddStr(body, 0, 0, "quit") | 
		
	
		
			
				|  |  |  |  |   ncurses.WRefresh(body) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func draw_gui(state *UIState, channels, body ncurses.Window) { | 
		
	
		
			
				|  |  |  |  |   draw_channels(state, channels) | 
		
	
		
			
				|  |  |  |  |   switch state.SelectedBody { | 
		
	
		
			
				|  |  |  |  |   case BODY_CHANNEL: | 
		
	
		
			
				|  |  |  |  |     draw_body_channel(state, body) | 
		
	
		
			
				|  |  |  |  |   case BODY_DETAIL: | 
		
	
		
			
				|  |  |  |  |     draw_body_detail(state, body) | 
		
	
		
			
				|  |  |  |  |   case BODY_QUIT: | 
		
	
		
			
				|  |  |  |  |     draw_body_quit(state, body) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Window, packet_chan chan pnyx.Payload, user_chan chan []byte, sigwinch_channel chan os.Signal) { | 
		
	
		
			
				|  |  |  |  |   channels := ncurses.NewWin(0, 0, 0, 0) | 
		
	
		
			
				|  |  |  |  |   body := ncurses.NewWin(0, 0, 0, 0) | 
		
	
		
			
				|  |  |  |  |   title := ncurses.NewWin(0, 0, 0, 0) | 
		
	
		
			
				|  |  |  |  |   state := &UIState{ | 
		
	
		
			
				|  |  |  |  |     Peers: map[pnyx.PeerID]PeerState{ | 
		
	
		
			
				|  |  |  |  |       [32]byte{}: { | 
		
	
		
			
				|  |  |  |  |         Name: "Test User 0", | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |       [32]byte{0x01}: { | 
		
	
		
			
				|  |  |  |  |         Name: "Test User 1", | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |       [32]byte{0x02}: { | 
		
	
		
			
				|  |  |  |  |         Name: "Test User 2", | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |     }, | 
		
	
		
			
				|  |  |  |  |     Channels: []ChannelState{ | 
		
	
		
			
				|  |  |  |  |       { | 
		
	
		
			
				|  |  |  |  |         ID: 0, | 
		
	
		
			
				|  |  |  |  |         Name: "Channel 0", | 
		
	
		
			
				|  |  |  |  |         Members: []pnyx.PeerID{[32]byte{}, [32]byte{0x01}}, | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |       { | 
		
	
		
			
				|  |  |  |  |         ID: 1, | 
		
	
		
			
				|  |  |  |  |         Name: "Channel 1", | 
		
	
		
			
				|  |  |  |  |         Members: []pnyx.PeerID{[32]byte{0x02}}, | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |     }, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ncurses.InitColor(1, 1000, 1000, 1000) | 
		
	
		
			
				|  |  |  |  |   ncurses.InitColor(2, 300, 300, 300) | 
		
	
		
			
				|  |  |  |  |   ncurses.InitColor(3, 150, 150, 150) | 
		
	
		
			
				|  |  |  |  |   ncurses.InitColor(4, 150, 150, 300) | 
		
	
		
			
				|  |  |  |  |   ncurses.InitPair(1, 1, 2) | 
		
	
		
			
				|  |  |  |  |   ncurses.InitPair(2, 1, 3) | 
		
	
		
			
				|  |  |  |  |   ncurses.InitPair(3, 1, 4) | 
		
	
		
			
				|  |  |  |  |   ncurses.ScrollOk(body, true) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   print_decoration(window, title, channels, body, client.Remote()) | 
		
	
		
			
				|  |  |  |  |   draw_gui(state, channels, body) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for client.Active() { | 
		
	
		
			
				|  |  |  |  |     select { | 
		
	
	
		
			
				
					|  |  |  | @ -338,6 +557,7 @@ func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Wind | 
		
	
		
			
				|  |  |  |  |       ncurses.WRefresh(window) | 
		
	
		
			
				|  |  |  |  |       ncurses.WClear(window) | 
		
	
		
			
				|  |  |  |  |       print_decoration(window, title, channels, body, client.Remote()) | 
		
	
		
			
				|  |  |  |  |       draw_gui(state, channels, body) | 
		
	
		
			
				|  |  |  |  |     case packet := <-packet_chan: | 
		
	
		
			
				|  |  |  |  |       switch packet := packet.(type) { | 
		
	
		
			
				|  |  |  |  |       case pnyx.ChannelCommandPacket: | 
		
	
	
		
			
				
					|  |  |  | @ -366,7 +586,7 @@ func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Wind | 
		
	
		
			
				|  |  |  |  |           decode_chan, exists := decoders[packet.Peer] | 
		
	
		
			
				|  |  |  |  |           if exists == false { | 
		
	
		
			
				|  |  |  |  |             if sample_rate != 0 { | 
		
	
		
			
				|  |  |  |  |               decode_chan = make(chan[]byte, 1000) | 
		
	
		
			
				|  |  |  |  |               decode_chan = make(chan []byte, 1000) | 
		
	
		
			
				|  |  |  |  |               decoders[packet.Peer] = decode_chan | 
		
	
		
			
				|  |  |  |  |               go handle_peer_decode(audio_data, packet.Peer, decoders[packet.Peer], sample_rate) | 
		
	
		
			
				|  |  |  |  |               decode_chan <- packet.Data | 
		
	
	
		
			
				
					|  |  |  | @ -379,15 +599,15 @@ func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Wind | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     case char := <-user_chan: | 
		
	
		
			
				|  |  |  |  |       ncurses.WAddStr(body,  string(char)) | 
		
	
		
			
				|  |  |  |  |       ncurses.WRefresh(body) | 
		
	
		
			
				|  |  |  |  |       handle_input(state, char) | 
		
	
		
			
				|  |  |  |  |       draw_gui(state, channels, body) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func process_mic(client *pnyx.Client, mic chan []byte) { | 
		
	
		
			
				|  |  |  |  |   for true { | 
		
	
		
			
				|  |  |  |  |     data := <- mic | 
		
	
		
			
				|  |  |  |  |     data := <-mic | 
		
	
		
			
				|  |  |  |  |     err := client.Send(pnyx.NewDataPacket(pnyx.ChannelID(0), pnyx.MODE_AUDIO, data)) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       panic(err) | 
		
	
	
		
			
				
					|  |  |  | @ -415,7 +635,6 @@ func main() { | 
		
	
		
			
				|  |  |  |  |   defer inDevice.Uninit() | 
		
	
		
			
				|  |  |  |  |   defer inDevice.Stop() | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   user_chan := make(chan []byte, 1024) | 
		
	
		
			
				|  |  |  |  |   packet_chan := make(chan pnyx.Payload, 1024) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -431,7 +650,7 @@ func main() { | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     panic(err) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |    | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   join_packet := pnyx.NewChannelCommandPacket(uuid.New(), pnyx.ChannelID(0), pnyx.MODE_CHANNEL, pnyx.CHANNEL_COMMAND_JOIN, nil) | 
		
	
		
			
				|  |  |  |  |   err = client.Send(join_packet) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
	
		
			
				
					|  |  |  | @ -446,11 +665,11 @@ func main() { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   go process_mic(client, mic) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   locale := ncurses.SetLocale(0, "") | 
		
	
		
			
				|  |  |  |  |   fmt.Printf("locale: %s\n", locale) | 
		
	
		
			
				|  |  |  |  |    | 
		
	
		
			
				|  |  |  |  |   ncurses.SetLocale(0, "") | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   user_chan, stdin_active := ncurses.UTF8Listener(100, os.Stdin) | 
		
	
		
			
				|  |  |  |  |   window := ncurses.InitScr() | 
		
	
		
			
				|  |  |  |  |   ncurses.CursSet(0) | 
		
	
		
			
				|  |  |  |  |   ret := ncurses.StartColor() | 
		
	
		
			
				|  |  |  |  |   if ret != 0 { | 
		
	
		
			
				|  |  |  |  |     panic(ret) | 
		
	
	
		
			
				
					|  |  |  | 
 |