FPGA design: Noise - activated musical square waves
I've tried a variety of FPGA "designs." The one I'm sharing now produces musical tones similar to the white keys on a piano for six octaves.
The tones are simple square waves which probably sound most like 8-bit video games from the 90's. The spacing of the tones use something called the just intonation temperament. Instead of powers of (1/12), just intonation uses simple fractions that are actually more in tune with each other. The downside of just intonation is that you can't easily change scales.
Each tone is activated when a noise source running at 50 million bits per second sends out by chance 26 1's in a row. It sounds pretty unlikely, but given the number of bits per second and the number of tones (N = 42), it happens enough to produce music. Here's a sample:
Here's what the spectrogram of this sample looks like. Square waves produce a fundamental sine wave and odd harmonics (3x, 5x, 7x, etc.)
The noise source is perhaps the most critical. In this particular project I'm using XOR'ed ring oscillators. These are described in the scientific literature. The big challenge is that no two ring oscillators are exactly alike. Despite having the same "length" of 101 delay gates, the idiosyncracies of chip transistors means the actual delay times will vary between each RO. Also, their susceptibility to noise will each be different. I can only hope that 16x RO's per tone is enough randomness to average out the variations so that each tone will trigger the same number of times. If you look carefully at the spectrogram, you will see certain tones are little more triggered than others.
Here are some voice samples after the signal is translated via machine learning:
"confident signal"
"it mirrors wonderful"
"given the opposite"
"now that we're talking"
Finally, here's the Verilog code, for some future brave FPGA developer:
//
// Author: Michael S. Lee
// Noise source-activate musical square wave tones
// Started: 12/2021
//
module XOR_loop_gate
#(parameter N = 149) // Length of each RO
(input wire clki,
output wire gate0);
wire [N:0] loop[M-1:0] /* synthesis keep */;
reg [0:0] gate;
reg [M-1:0] lpb;
reg [L-1:0] buffer = 0;
wire hit;
reg [0:0] check;
integer ctr;
parameter period = 65536 * 48; // duration of tone (in 50 Mhz samples)
parameter M = 16; // # of ring oscillators
parameter L = 26; // length of seq. of 1s to turn on gate
genvar i, k;
integer j;
generate
for (i = 0; i < M; i = i + 1)
begin: loopnum
assign loop[i][N] = ~ (loop[i][0]);
for (k = 0; k < N; k = k + 1)
begin: loops
assign loop[i][k] = loop[i][k+1];
end
end
endgenerate
assign hit = ^(lpb);
assign gate0 = gate;
always @(posedge clki)
begin
for (j = 0 ; j < M; j = j + 1) begin
lpb[j] <= loop[j][0];
end
buffer = (buffer << 1) + hit;
check = &(buffer);
if (check == 1) begin
ctr <= period;
gate <= 1;
end
else if (ctr > 0) begin
ctr <= ctr - 1;
end
else begin
gate <= 0;
end
end
endmodule
module Direct_Voice(clk,out);
input clk;
output wire out;
parameter N = 42; // # of musical tones
parameter bits = 7;
reg [bits+1:0] PWM = 0;
genvar k;
integer i;
wire [N-1:0] outw /* synthesis keep */;
reg[N-1:0] outr;
reg[18:0] outp[N];
integer sum, suma[24];
reg[22:0] clk2 = 0, clk3 = 0, clk5 = 0,
clk7 = 0, clk9 = 0, clk15 = 0;
generate
for (k = 0; k < N; k = k + 1)
begin: prep
XOR_loop_gate #(101) test(clk, outw[k]);
end
endgenerate
assign out = PWM[bits+1];
always @(posedge clk)
begin
// Clocks for different musical tones
clk2 = clk2 + 1;
clk3 = clk3 + 3;
clk5 = clk5 + 5;
clk7 = clk7 + 7;
clk9 = clk9 + 9;
clk15 = clk15 + 15;
// Convert wire gates to registers
for (i = 0 ; i < N; i = i + 1) begin
outr[i] <= outw[i];
end
suma[0] = (outr[0] & clk2[19]) + (outr[1] & clk3[19]);
suma[1] = (outr[2] & clk5[20]) + (outr[3] & clk7[20]);
suma[2] = (outr[4] & clk9[21]) + (outr[5] & clk15[21]);
suma[3] = (outr[6] & clk2[18]) + (outr[7] & clk3[18]);
suma[4] = (outr[8] & clk5[19]) + (outr[9] & clk7[19]);
suma[5] = (outr[10] & clk9[20]) + (outr[11] & clk15[20]);
suma[6] = (outr[12] & clk2[17]) + (outr[13] & clk3[17]);
suma[7] = (outr[14] & clk5[18]) + (outr[15] & clk7[18]);
suma[8] = (outr[16] & clk9[19]) + (outr[17] & clk15[19]);
suma[9] = (outr[18] & clk2[16]) + (outr[19] & clk3[16]);
suma[10] = (outr[20] & clk5[17]) + (outr[21] & clk7[17]);
suma[11] = (outr[22] & clk9[18]) + (outr[23] & clk15[18]);
suma[12] = (outr[24] & clk2[15]) + (outr[25] & clk3[15]);
suma[13] = (outr[26] & clk5[16]) + (outr[27] & clk7[16]);
suma[14] = (outr[28] & clk9[17]) + (outr[29] & clk15[17]);
suma[15] = (outr[30] & clk2[14]) + (outr[31] & clk3[14]);
suma[16] = (outr[32] & clk5[15]) + (outr[33] & clk7[15]);
suma[17] = (outr[34] & clk9[16]) + (outr[35] & clk15[16]);
suma[18] = (outr[36] & clk2[13]) + (outr[37] & clk3[13]);
suma[19] = (outr[38] & clk5[14]) + (outr[39] & clk7[14]);
suma[20] = (outr[40] & clk9[15]) + (outr[41] & clk15[15]);
// suma[21] = (outr[36] & clk2[20]) + (outr[37] & clk3[20]);
// suma[22] = (outr[38] & clk5[21]) + (outr[39] & clk7[21]);
// suma[23] = (outr[40] & clk9[22]) + (outr[41] & clk15[22]);
sum = 0;
for (i = 0 ; i < 21; i = i + 1) begin
sum = sum + suma[i];
end
PWM = PWM[bits:0] + sum;
end
endmodule
14 Comments
Recommended Comments