public override void RenderFrame(UnsafeColor *ptr) { if (redrawNeeded) { RedrawFrame(ptr); } }
private unsafe void FindHorizMarkers(UnsafeColor *ptr) { long division = horizFreqDivision; double hzPerPixel = (double)bandwidth / SpectrumWidth; long freq = ((centerFreq / 2) / division) * division; //Find the leftmost frequency, aligned to the division set while (true) { //Calculate int px = (int)(freq / hzPerPixel); if (px > SpectrumWidth) { break; } //Render if (px >= 0) { AddHorizontalMarker(ptr, ctx, px, freq); } //Update freq += division; } }
public override unsafe void InitFrame(UnsafeColor *ptr) { //Call main base.InitFrame(ptr); long division = horizFreqDivision; long hzPerPixel = (long)bandwidth / SpectrumWidth; long freqOffset = centerFreq - (bandwidth / 2); long freq = 0; if (freqOffset % division != 0) { freq += division - (freqOffset % division); } while (true) { //Calculate long px = freq / hzPerPixel; if (px > SpectrumWidth) { break; } //Render AddHorizontalMarker(ptr, ctx, (int)px, freq + freqOffset); //Update freq += division; } //Automatically process the points AutoAddVerticalMarkers(ptr, ctx); }
public override unsafe void InitFrame(UnsafeColor *ptr) { //Call main base.InitFrame(ptr); long hzPerPixel = (long)demodulator.MpxSampleRate / 2 / SpectrumWidth; long freq = 0; while (true) { //Calculate long px = freq / hzPerPixel; if (px > SpectrumWidth) { break; } //Render AddHorizontalMarker(ptr, ctx, (int)px, freq); //Update freq += 19000; } //Automatically process the points AutoAddVerticalMarkers(ptr, ctx); }
public unsafe void RenderLine(UnsafeColor *ptr, int canvasWidth, char[] chars, int count, UnsafeColor color) { for (int i = 0; i < Math.Min(chars.Length, count); i++) { RenderCharacter(ptr, canvasWidth, chars[i], color); ptr += width + 1; } }
protected override void DrawableViewReset(int width, int height) { //Fill with black for (int y = 0; y < height; y++) { UnsafeColor *lnDst = pixels + (y * width); for (int x = 0; x < width; x++) { lnDst[x] = new UnsafeColor(0, 0, 0); } } }
public unsafe void RenderCharacter(UnsafeColor *ptr, int canvasWidth, char character, UnsafeColor color) { //Try to find character data if (character == (char)0x00 || character == '\n' || character == '\r') { //Ignore. This is a null or invisible character } else if (characters.ContainsKey(character)) { //Copy pixel data var cha = characters[character]; for (int i = 0; i < width * fullHeight; i++) { //Check if this is black to save CPU if (cha.payload[i] == 0) { continue; } //Get location in memory UnsafeColor *pxl = ptr + (canvasWidth * (i / width)) + (i % width); //Update if (cha.payload[i] == byte.MaxValue) { //Full brightness. Cheat *pxl = color; } else { //Mix float scale = (float)cha.payload[i] / 256; float scaleR = 1 - scale; * pxl = new UnsafeColor( (byte)(((*pxl).r * scaleR) + (color.r * scale)), (byte)(((*pxl).g * scaleR) + (color.g * scale)), (byte)(((*pxl).b * scaleR) + (color.b * scale)) ); } } } else { //Write a blank spot, as this character isn't found } }
public override unsafe void InitFrame(UnsafeColor *ptr) { //Render background for RDS FillArea(ptr, 0, Height - RDS_HEIGHT - PADDING - RDS_MARGIN_TOP, Width, PADDING + RDS_MARGIN_TOP + RDS_HEIGHT, new UnsafeColor(RDS_RT_BACKGROUND_BRIGHTNESS, RDS_RT_BACKGROUND_BRIGHTNESS, RDS_RT_BACKGROUND_BRIGHTNESS)); FillArea(ptr, 0, Height - RDS_HEIGHT - PADDING - RDS_MARGIN_TOP, PS_OFFSET - PADDING, PADDING + RDS_MARGIN_TOP + RDS_HEIGHT, new UnsafeColor(RDS_PS_BACKGROUND_BRIGHTNESS, RDS_PS_BACKGROUND_BRIGHTNESS, RDS_PS_BACKGROUND_BRIGHTNESS)); //Render border FillArea(ptr, 0, 0, Width, BORDER_WIDTH, UnsafeColor.WHITE); FillArea(ptr, 0, Height - BORDER_WIDTH, Width, BORDER_WIDTH, UnsafeColor.WHITE); FillArea(ptr, 0, 0, BORDER_WIDTH, Height, UnsafeColor.WHITE); FillArea(ptr, Width - BORDER_WIDTH, 0, BORDER_WIDTH, Height, UnsafeColor.WHITE); //Render title ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, PADDING, PADDING), new char[][] { label.ToCharArray() }, FontStore.SYSTEM_BOLD_20, FontAlignHorizontal.Left, FontAlignVertical.Center, Width - PADDING - PADDING, TITLE_HEIGHT, new FontColor(1), new FontColor(0) ); //Get left offset int offsetLeft = PADDING + FontStore.SYSTEM_BOLD_20.MeasureWidth(label.Length) + TITLE_MARGIN_RIGHT; //Render sub texts ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, offsetLeft, PADDING), new char[][] { subTextA.ToCharArray(), subTextB.ToCharArray() }, FontStore.SYSTEM_REGULAR_15, FontAlignHorizontal.Left, FontAlignVertical.Center, Width - offsetLeft - PADDING, TITLE_HEIGHT, new FontColor(0.8f), new FontColor(0) ); }
public override unsafe void RenderFrame(UnsafeColor *ptr) { //If not invalidated, ignore if (!invalidated) { return; } //Move the pointer down to the beginning of the RDS bar ptr += (Width * (RDS_LABEL_OFFSET + RDS_LABEL_HEIGHT)); //Render PS name ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, PADDING, 0), new char[][] { RdsDecoder.psBuffer }, FontStore.SYSTEM_REGULAR_15, FontAlignHorizontal.Left, FontAlignVertical.Center, FontStore.SYSTEM_REGULAR_15.MeasureWidth(8), RDS_HEIGHT, new FontColor(1), RDS_PS_TEXT_BACKGROUND ); //Render RT ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, PS_OFFSET, 0), new char[][] { RdsDecoder.rtBuffer }, FontStore.SYSTEM_NARROW_15, FontAlignHorizontal.Left, FontAlignVertical.Center, Width - PS_OFFSET - PADDING, RDS_HEIGHT, new FontColor(0.85f), RDS_RT_TEXT_BACKGROUND ); //Set invalidated flag invalidated = false; }
private void RedrawFrame(UnsafeColor *ptr) { //Render indicators int offsetRight = PADDING_STATUS_HORIZ; offsetRight += DrawStatusIndicator(ptr, offsetRight, demodulator.StereoDetected, 'S', 'T', 'E', 'R', 'E', 'O'); offsetRight += DrawStatusIndicator(ptr, offsetRight, rds.IsRdsSynced, 'R', 'D', 'S'); //Render PS name int offsetLeft = FontStore.SYSTEM_REGULAR_15.MeasureWidth(8) + PADDING_PS + PADDING_PS; ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, 0, 0), new char[][] { rds.psBuffer }, FontStore.SYSTEM_REGULAR_15, FontAlignHorizontal.Center, FontAlignVertical.Center, offsetLeft, HEIGHT, new FontColor(1), new FontColor(0.12f) ); //Render RT text ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, offsetLeft + PADDING_RT, 0), new char[][] { rds.rtBuffer }, FontStore.SYSTEM_NARROW_15, FontAlignHorizontal.Left, FontAlignVertical.Center, Width - offsetLeft - offsetRight - PADDING_RT - PADDING_RT + PADDING_STATUS_HORIZ, HEIGHT, new FontColor(1), new FontColor(0) ); //Update state redrawNeeded = false; }
private int DrawStatusIndicator(UnsafeColor *ptr, int offset, bool active, params char[] name) { //Layout int statusWidth = FontStore.SYSTEM_NARROW_BOLD_15.MeasureWidth(name.Length) + STATUS_PADDING + STATUS_PADDING; //Render FontColor color = active ? new FontColor(1) : new FontColor(0.4f); ctx.TextRenderer.RenderRawBox( ctx.TextRenderer.GetOffsetPixel(ptr, Width - offset - statusWidth, STATUS_MARGIN_VERT), new char[][] { name }, FontStore.SYSTEM_NARROW_BOLD_15, FontAlignHorizontal.Center, FontAlignVertical.Center, statusWidth, HEIGHT - STATUS_MARGIN_VERT - STATUS_MARGIN_VERT, new FontColor(0), color ); return(statusWidth + STATUS_MARGIN_HORIZ); }
private unsafe void RunWorker() { //Open pixel buffer byte[] pixelBuffer = new byte[generator.Height * generator.Width * sizeof(UnsafeColor)]; GCHandle pixelBufferHandle = GCHandle.Alloc(pixelBuffer, GCHandleType.Pinned); UnsafeColor *pixelBufferPtr = (UnsafeColor *)pixelBufferHandle.AddrOfPinnedObject(); //Create IQ buffer UnsafeBuffer iqBuffer = UnsafeBuffer.Create(generator.BufferSize, sizeof(Complex)); Complex * iqBufferPtr = (Complex *)iqBuffer; //Create audio buffers byte[] audioBuffer = new byte[generator.BufferSize * 2 * sizeof(float)]; GCHandle audioBufferHandle = GCHandle.Alloc(audioBuffer, GCHandleType.Pinned); float * audioBufferPtr = (float *)audioBufferHandle.AddrOfPinnedObject(); float * audioLBufferPtr = audioBufferPtr; float * audioRBufferPtr = (audioLBufferPtr + generator.BufferSize); //Create resampler ArbitraryFloatResampler resamplerA = new ArbitraryFloatResampler(generator.OutputAudioRate, 48000, generator.BufferSize); ArbitraryFloatResampler resamplerB = new ArbitraryFloatResampler(generator.OutputAudioRate, 48000, generator.BufferSize); //Create pipes for FFMPEG string videoPipeName = $"SpectrumVideoRenderer{Process.GetCurrentProcess().Id}Video"; string audioPipeName = $"SpectrumVideoRenderer{Process.GetCurrentProcess().Id}Audio"; NamedPipeServerStream videoPipe = new NamedPipeServerStream(videoPipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 10000, 10000); NamedPipeServerStream audioPipe = new NamedPipeServerStream(audioPipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 10000, 10000); //Start FFMPEG Process encoder = Process.Start(new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-y -f rawvideo -pix_fmt bgra -s {generator.Width}x{generator.Height} -r 30 -i \\\\.\\pipe\\{videoPipeName} -f f32le -ar 48000 -ac 2 -i \\\\.\\pipe\\{audioPipeName} E:\\test_video.mp4", RedirectStandardInput = true, UseShellExecute = false }); //Make sure WAV is at the beginning wav.PositionSamples = 0; //Process DateTime startTime = DateTime.UtcNow; DateTime lastProgressUpdate = DateTime.MinValue; int read; do { //Read read = wav.Read(iqBufferPtr, generator.BufferSize); //Process int audioRead = generator.ProcessFrame(iqBufferPtr, pixelBufferPtr, audioLBufferPtr, audioRBufferPtr, read); //Write pixels if (!videoPipe.IsConnected) { videoPipe.WaitForConnection(); } videoPipe.WriteAsync(pixelBuffer, 0, pixelBuffer.Length); //Add samples to resamplers resamplerA.Input(audioLBufferPtr, audioRead, 1); resamplerB.Input(audioRBufferPtr, audioRead, 1); //Read audio back and merge it into one stream do { //Read resamplerA.Output(audioBufferPtr, generator.BufferSize, 2); audioRead = resamplerB.Output(audioBufferPtr + 1, generator.BufferSize, 2); //Write to stream if (!audioPipe.IsConnected) { audioPipe.WaitForConnection(); } audioPipe.WriteAsync(audioBuffer, 0, audioRead * 2 * sizeof(float)); } while (audioRead != 0); //Update status if needed if ((DateTime.UtcNow - lastProgressUpdate).TotalMilliseconds > 100) { float progress = (float)wav.PositionSamples / wav.LengthSamples; long totalSeconds = (long)((DateTime.UtcNow - startTime).TotalSeconds / progress); long remainingSeconds = (long)(totalSeconds - (DateTime.UtcNow - startTime).TotalSeconds); string statusText = $"Rendering... {(remainingSeconds / 60 / 60).ToString().PadLeft(2, '0')}:{((remainingSeconds / 60) % 60).ToString().PadLeft(2, '0')}:{(remainingSeconds % 60).ToString().PadLeft(2, '0')} remaining, {Math.Round(progress*100, 2)}%"; Invoke((MethodInvoker) delegate { status.Text = statusText; progressBar.Value = (int)(progress * 1000); }); lastProgressUpdate = DateTime.UtcNow; } } while (read != 0 && !requestClose); //Update status Invoke((MethodInvoker) delegate { status.Text = "Finishing up rendering..."; btnCancel.Enabled = false; }); //Tell FFMPEG to stop and wait for it to do so audioPipe.Close(); videoPipe.Close(); encoder.WaitForExit(); //Clean up audioBufferHandle.Free(); pixelBufferHandle.Free(); iqBuffer.Dispose(); resamplerA.Dispose(); resamplerB.Dispose(); //Close hasCompleted = true; Invoke((MethodInvoker) delegate { Close(); }); }
public override void InitFrame(UnsafeColor *ptr) { }
public unsafe void RenderLine(UnsafeColor *ptr, int canvasWidth, int x, int y, char[] chars, int count) { RenderLine(ptr, canvasWidth, x, y, chars, count, UnsafeColor.WHITE); }
public unsafe void RenderLine(UnsafeColor *ptr, int canvasWidth, int x, int y, char[] chars, int count, UnsafeColor color) { RenderLine(ptr + (y * canvasWidth) + x, canvasWidth, chars, count, color); }
public override unsafe void InitFrame(UnsafeColor *ptr) { Fill(ptr, color); }
public override unsafe void RenderFrame(UnsafeColor *ptr) { }