Experiências de software em RTTY
Link do vídeo....
https://youtu.be/cGU99-iFAto?si=Pa35OpVo4roKsBXT
Foi utilizado o VSCODE.
Base do projeto....
Forms1.cs --------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Numerics;
using System.Windows.Forms;
using NAudio.Wave;
namespace RTTY_Waterfall;
public class Form1 : Form
{
// ==============================
// AUDIO & DADOS
// ==============================
WaveOutEvent output;
BufferedWaveProvider provider;
System.Windows.Forms.Timer audioTimer = new();
System.Windows.Forms.Timer waterfallTimer = new();
float[] samples;
int sampleIndex = 0;
List<(int startSample, char character)> charMap = new();
// ==============================
// UI
// ==============================
Button btnOpen, btnPlay, btnStop, btnSave;
TrackBar zoomBar;
PictureBox waterfallBox;
Label lblCurrentChar, lblFullText;
string currentText = "";
Bitmap waterfallBmp;
public Form1()
{
Text = "RTTY Generator + Waterfall - Loop Mode";
Width = 1000;
Height = 650;
InitUI();
InitAudio();
}
void InitUI()
{
btnOpen = new Button() { Text = "Abrir TXT", Left = 10, Top = 10, Width = 100 };
btnPlay = new Button() { Text = "Play Loop", Left = 120, Top = 10, Width = 80 };
btnStop = new Button() { Text = "Stop", Left = 210, Top = 10, Width = 80 };
btnSave = new Button() { Text = "Salvar WAV", Left = 300, Top = 10, Width = 100 };
zoomBar = new TrackBar() { Left = 420, Top = 5, Width = 200, Minimum = 1, Maximum = 8, Value = 2 };
lblCurrentChar = new Label()
{
Left = 650,
Top = 5,
Width = 50,
Height = 40,
Font = new Font("Consolas", 24, FontStyle.Bold),
ForeColor = Color.Red,
TextAlign = ContentAlignment.MiddleCenter
};
lblFullText = new Label()
{
Left = 10,
Top = 535,
Width = 960,
Height = 60,
Font = new Font("Consolas", 12),
BackColor = Color.Black,
ForeColor = Color.Lime,
BorderStyle = BorderStyle.Fixed3D
};
waterfallBox = new PictureBox() { Left = 10, Top = 50, Width = 960, Height = 480, BorderStyle = BorderStyle.FixedSingle };
Controls.AddRange(new Control[] { btnOpen, btnPlay, btnStop, btnSave, zoomBar, waterfallBox, lblCurrentChar, lblFullText });
btnOpen.Click += BtnOpen_Click;
btnPlay.Click += BtnPlay_Click;
btnStop.Click += BtnStop_Click;
btnSave.Click += BtnSave_Click;
waterfallBmp = new Bitmap(waterfallBox.Width, waterfallBox.Height);
}
void InitAudio()
{
provider = new BufferedWaveProvider(new WaveFormat(RTTY.SampleRate, 16, 1));
provider.BufferDuration = TimeSpan.FromSeconds(20);
provider.DiscardOnBufferOverflow = true;
output = new WaveOutEvent();
output.Init(provider);
audioTimer.Interval = 20;
audioTimer.Tick += AudioTimer_Tick;
waterfallTimer.Interval = 30;
waterfallTimer.Tick += WaterfallTimer_Tick;
}
void BtnPlay_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(currentText)) return;
charMap.Clear();
lblFullText.Text = "";
// Geração inicial
samples = RTTY.GenerateSamples(currentText);
// Mapeamento de tempo por caractere para o display
double samplesPerChar = 8 * (RTTY.SampleRate / RTTY.Baud);
for (int i = 0; i < currentText.Length; i++)
charMap.Add(((int)(i * samplesPerChar), currentText[i]));
provider.ClearBuffer();
sampleIndex = 0;
output.Play();
audioTimer.Start();
waterfallTimer.Start();
}
void AudioTimer_Tick(object sender, EventArgs e)
{
if (samples == null) return;
// --- LÓGICA DE LOOP ---
if (sampleIndex >= samples.Length)
{
sampleIndex = 0; // Volta para o início do array de áudio
}
int targetBufferedBytes = provider.WaveFormat.AverageBytesPerSecond / 2;
int bytesToFill = targetBufferedBytes - provider.BufferedBytes;
if (bytesToFill > 0)
{
int samplesToCopy = bytesToFill / 2;
// Se o que falta para acabar o array for menor que o necessário,
// pegamos apenas até o fim (o próximo Tick cuidará do reinício)
samplesToCopy = Math.Min(samplesToCopy, samples.Length - sampleIndex);
if (samplesToCopy <= 0) return;
byte[] buffer = new byte[samplesToCopy * 2];
for (int i = 0; i < samplesToCopy; i++)
{
short val = (short)(samples[sampleIndex++] * short.MaxValue);
buffer[i * 2] = (byte)(val & 0xff);
buffer[i * 2 + 1] = (byte)(val >> 8);
}
provider.AddSamples(buffer, 0, buffer.Length);
}
UpdateTextDisplay();
}
void UpdateTextDisplay()
{
char found = ' ';
string history = "";
foreach (var item in charMap)
{
if (sampleIndex >= item.startSample)
{
found = item.character;
history += item.character;
}
else break;
}
lblCurrentChar.Text = found.ToString();
if (history.Length > 80) history = history.Substring(history.Length - 80);
lblFullText.Text = history;
}
void WaterfallTimer_Tick(object sender, EventArgs e)
{
if (samples == null) return;
int fftSize = 1024;
int zoom = zoomBar.Value;
Complex[] fft = new Complex[fftSize];
int start = Math.Max(0, sampleIndex - fftSize);
for (int i = 0; i < fftSize; i++)
{
float s = (start + i < samples.Length) ? samples[start + i] : 0;
fft[i] = new Complex(s, 0);
}
FFT(fft);
ScrollBitmap(waterfallBmp);
for (int x = 0; x < waterfallBmp.Width; x++)
{
int bin = x * zoom;
if (bin >= fftSize / 2) break;
double mag = fft[bin].Magnitude;
int c = Math.Clamp((int)(Math.Log10(mag + 1) * 150), 0, 255);
waterfallBmp.SetPixel(x, waterfallBmp.Height - 1, ColorFromValue(c));
}
waterfallBox.Image = waterfallBmp;
}
static void FFT(Complex[] buffer)
{
int n = buffer.Length;
for (int j = 1, i = 0; j < n; j++)
{
int bit = n >> 1;
for (; (i & bit) != 0; bit >>= 1) i &= ~bit;
i |= bit;
if (j < i) (buffer[j], buffer[i]) = (buffer[i], buffer[j]);
}
for (int len = 2; len <= n; len <<= 1)
{
double ang = -2 * Math.PI / len;
Complex wlen = new(Math.Cos(ang), Math.Sin(ang));
for (int i = 0; i < n; i += len)
{
Complex w = Complex.One;
for (int j = 0; j < len / 2; j++)
{
var u = buffer[i + j];
var v = buffer[i + j + len / 2] * w;
buffer[i + j] = u + v;
buffer[i + j + len / 2] = u - v;
w *= wlen;
}
}
}
}
static void ScrollBitmap(Bitmap bmp)
{
using Graphics g = Graphics.FromImage(bmp);
g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height - 1), new Rectangle(0, 1, bmp.Width, bmp.Height - 1), GraphicsUnit.Pixel);
}
static Color ColorFromValue(int v) => Color.FromArgb(v, v / 2, 255 - v);
void BtnOpen_Click(object sender, EventArgs e)
{
using OpenFileDialog ofd = new();
ofd.Filter = "Text|*.txt";
if (ofd.ShowDialog() == DialogResult.OK) { currentText = File.ReadAllText(ofd.FileName); MessageBox.Show("Texto carregado!"); }
}
void BtnStop_Click(object sender, EventArgs e)
{
audioTimer.Stop();
waterfallTimer.Stop();
output.Stop();
provider.ClearBuffer();
sampleIndex = 0;
lblCurrentChar.Text = "";
}
void BtnSave_Click(object sender, EventArgs e)
{
if (samples == null) return;
using SaveFileDialog sfd = new();
sfd.Filter = "WAV|*.wav";
if (sfd.ShowDialog() == DialogResult.OK) RTTY.SaveWav16(sfd.FileName, samples);
}
}
----------------------------------------------------------------------------------------------------------------
Program.cs -------------------------------------------------------------------------------------------------
using System;
using System.Windows.Forms;
namespace RTTY_Waterfall;
internal static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}


Comentários
Postar um comentário