import processing.sound.*;
AudioIn mic;
Waveform waveform;
int numSamples = 1024;
float sampleRate = 44100;
float smoothedFreq = 0;
boolean[][] segments = {
{true, true, true, true, true, true, false},
{false, true, true, false, false, false, false},
{true, true, false, true, true, false, true },
{true, true, true, true, false, false, true },
{false, true, true, false, false, true, true },
{true, false, true, true, false, true, true },
{true, false, true, true, true, true, true },
{true, true, true, false, false, false, false},
{true, true, true, true, true, true, true },
{true, true, true, true, false, true, true }
};
void setup() {
size(displayWidth, displayHeight);
frameRate(30);
mic = new AudioIn(this, 0);
mic.start();
waveform = new Waveform(this, numSamples);
waveform.input(mic);
}
void draw() {
background(10, 20, 10);
waveform.analyze();
float totalPeriodsSamples = 0;
int crossingCount = 0;
float lastCrossingTime = -1;
float maxPeak = 0;
for (int i = 1; i < numSamples; i++) {
float prev = waveform.data[i - 1];
float curr = waveform.data[i];
if (abs(curr) > maxPeak) maxPeak = abs(curr);
if ((prev >= 0 && curr < 0) || (prev < 0 && curr >= 0)) {
float fraction = abs(prev) / (abs(prev) + abs(curr));
float exactCrossingTime = (i - 1) + fraction;
if (lastCrossingTime != -1) {
totalPeriodsSamples += (exactCrossingTime - lastCrossingTime);
crossingCount++;
}
lastCrossingTime = exactCrossingTime;
}
}
float frequencyHz = 0;
if (crossingCount > 0 && totalPeriodsSamples > 0) {
float avgHalfPeriod = totalPeriodsSamples / crossingCount;
frequencyHz = sampleRate / (2.0 * avgHalfPeriod);
}
if (frequencyHz > 20000) frequencyHz = 20000;
if (maxPeak < 0.015 || crossingCount < 2) frequencyHz = 0;
smoothedFreq = lerp(smoothedFreq, frequencyHz, 0.25);
float boxW = width * 0.85;
float boxH = height * 0.35;
float boxX = (width - boxW) / 2;
float boxY = (height - boxH) / 2;
stroke(0, 120, 30);
strokeWeight(5);
fill(5, 15, 5);
rect(boxX, boxY, boxW, boxH, 15);
noStroke();
if ((frameCount / 6) % 2 == 0 && maxPeak > 0.015) {
fill(0, 255, 50);
} else {
fill(5, 40, 10);
}
ellipse(boxX + 40, boxY + 40, 15, 15);
fill(0, 200, 40);
textSize(24);
text("GATE", boxX + 65, boxY + 48);
float w = boxW * 0.11;
float h = w * 1.8;
float spacing = w * 0.22;
float totalDigitsW = (w * 5) + (spacing * 4);
float startX = boxX + (boxW - totalDigitsW) / 2;
float digitsY = boxY + (boxH - h) / 2 + 10;
int displayVal = int(smoothedFreq);
int d5 = (displayVal / 10000) % 10;
int d4 = (displayVal / 1000) % 10;
int d3 = (displayVal / 100) % 10;
int d2 = (displayVal / 10) % 10;
int d1 = displayVal % 10;
drawSevenSegment(startX, digitsY, w, h, d5);
drawSevenSegment(startX + (w + spacing), digitsY, w, h, d4);
drawDecimalDot(startX + (w + spacing) * 2 - (spacing / 2), digitsY + h, w);
drawSevenSegment(startX + (w + spacing) * 2, digitsY, w, h, d3);
drawSevenSegment(startX + (w + spacing) * 3, digitsY, w, h, d2);
drawSevenSegment(startX + (w + spacing) * 4, digitsY, w, h, d1);
fill(0, 255, 50);
textSize(35);
text("kHz", boxX + boxW - 80, boxY + boxH - 35);
fill(0, 100, 25);
textSize(22);
text("SIGNAL LEVEL", boxX + 40, boxY + boxH - 35);
float barMaxW = boxW * 0.45;
float barW = map(maxPeak, 0, 0.5, 0, barMaxW);
barW = constrain(barW, 0, barMaxW);
stroke(0, 60, 15);
strokeWeight(2);
noFill();
rect(boxX + 220, boxY + boxH - 52, barMaxW, 18, 3);
noStroke();
fill(0, 255, 50);
rect(boxX + 222, boxY + boxH - 50, barW, 14, 2);
}
void drawSevenSegment(float x, float y, float w, float h, int num) {
float t = w * 0.16;
float g = 1.5;
color onColor = color(0, 255, 50);
color offColor = color(5, 30, 10);
noStroke();
pushMatrix();
translate(x, y);
shearX(radians(-7));
fill(segments[num][0] ? onColor : offColor);
beginShape(); vertex(t + g, 0); vertex(w - t - g, 0); vertex(w - (t/2) - g, t/2); vertex(w - t - g, t); vertex(t + g, t); vertex((t/2) + g, t/2); endShape(CLOSE);
fill(segments[num][1] ? onColor : offColor);
beginShape(); vertex(w, t + g); vertex(w, (h/2) - (t/2) - g); vertex(w - (t/2), (h/2) - g); vertex(w - t, (h/2) - (t/2) - g); vertex(w - t, t + g); vertex(w - (t/2), (t/2) + g); endShape(CLOSE);
fill(segments[num][2] ? onColor : offColor);
beginShape(); vertex(w, (h/2) + (t/2) + g); vertex(w, h - t - g); vertex(w - (t/2), h - (t/2) - g); vertex(w - t, h - t - g); vertex(w - t, (h/2) + (t/2) + g); vertex(w - (t/2), (h/2) + g); endShape(CLOSE);
fill(segments[num][3] ? onColor : offColor);
beginShape(); vertex(t + g, h - t); vertex(w - t - g, h - t); vertex(w - (t/2) - g, h - (t/2)); vertex(w - t - g, h); vertex(t + g, h); vertex((t/2) + g, h - (t/2)); endShape(CLOSE);
fill(segments[num][4] ? onColor : offColor);
beginShape(); vertex(t, (h/2) + (t/2) + g); vertex(t, h - t - g); vertex(t/2, h - (t/2) - g); vertex(0, h - t - g); vertex(0, (h/2) + (t/2) + g); vertex(t/2, (h/2) + g); endShape(CLOSE);
fill(segments[num][5] ? onColor : offColor);
beginShape(); vertex(t, t + g); vertex(t, (h/2) - (t/2) - g); vertex(t/2, (h/2) - g); vertex(0, (h/2) - (t/2) - g); vertex(0, t + g); vertex(t/2, (t/2) + g); endShape(CLOSE);
fill(segments[num][6] ? onColor : offColor);
beginShape(); vertex(t + g, (h/2) - (t/2)); vertex(w - t - g, (h/2) - (t/2)); vertex(w - (t/2) - g, h/2); vertex(w - t - g, (h/2) + (t/2)); vertex(t + g, (h/2) + (t/2)); vertex((t/2) + g, h/2); endShape(CLOSE);
popMatrix();
}
void drawDecimalDot(float x, float y, float w) {
float dotSize = w * 0.15;
fill(0, 255, 50);
noStroke();
rect(x, y - dotSize, dotSize, dotSize, 1);
}
I am unable to run the demo in Android mode with Processing 4.5.6 on MacOS (M4 Tahoe 26.5) and get this error message:
Build folder: /var/folders/x2/hwxvmv9s6t16_tkw9tgmbp040000gn/T/android737348970227068550sketch
Exception in thread "Thread-54" java.lang.NullPointerException: Cannot read the array length because "list" is null
at processing.app.Library.wrapFiles(Library.java:429)
at processing.app.Library.getApplicationExports(Library.java:439)
at processing.mode.android.AndroidBuild.copyImportedLibs(AndroidBuild.java:839)
at processing.mode.android.AndroidBuild.createAppModule(AndroidBuild.java:481)
at processing.mode.android.AndroidBuild.createProject(AndroidBuild.java:262)
at processing.mode.android.AndroidBuild.build(AndroidBuild.java:223)
at processing.mode.android.AndroidMode.handleRunDevice(AndroidMode.java:294)
at processing.mode.android.AndroidEditor$17.run(AndroidEditor.java:421)
I get the same error message if I run the following reference code in Android mode:
import processing.sound.*;
void setup() {
Sound.list();
}
I am using a Samsung A8 tablet. There is no microphone connected to it. What type of device are you running the code on and does it have a microphone attached to it (or use an internal microphone)?
Addendum:
Runs without error in Java mode. Numbers change if I add a microphone.
Cool beans!
A440 (pitch standard) - Wikipedia
I got it to work on my Android with Processing 4.5.6 and Processing 4.3.4 with Sound Library 2.3.1 and applying necessary permissions for microphone.
:)
My tablet android 8 the sketch works without errors on APDE 5.1
@glv As always you are creative
I finally got it to run in Windows 11. Error messages from Processing 4.5.6 were helpful. I was referred to Examples/Libraries/Sound/IO/AudioInputAndroid. Based on these suggestions I added an initialize function and microphone permissions as shown below:
Modified source code:
import processing.sound.*;
AudioIn mic;
Waveform waveform;
AudioIn input;
Amplitude loudness;
int numSamples = 1024;
float sampleRate = 44100;
float smoothedFreq = 0;
boolean[][] segments = {
{true, true, true, true, true, true, false},
{false, true, true, false, false, false, false},
{true, true, false, true, true, false, true },
{true, true, true, true, false, false, true },
{false, true, true, false, false, true, true },
{true, false, true, true, false, true, true },
{true, false, true, true, true, true, true },
{true, true, true, false, false, false, false},
{true, true, true, true, true, true, true },
{true, true, true, true, false, true, true }
};
void setup() {
size(displayWidth, displayHeight);
frameRate(30);
if (hasPermission("android.permission.RECORD_AUDIO")) {
initialize(true);
} else {
requestPermission("android.permission.RECORD_AUDIO", "initialize");
}
// input = new AudioIn(this, 0);
// input.start();
waveform = new Waveform(this, numSamples);
waveform.input(input);
}
void initialize(boolean granted) {
if (!granted) {
return;
}
// Create an Audio input and grab the 1st channel
input = new AudioIn(this, 0);
// Begin capturing the audio input
input.start();
// start() activates audio capture so that you can use it as
// the input to live sound analysis, but it does NOT cause the
// captured audio to be played back to you. if you also want the
// microphone input to be played back to you, call
// input.play();
// instead (be careful with your speaker volume, you might produce
// painful audio feedback. best to first try it out wearing headphones!)
// Create a new Amplitude analyzer
loudness = new Amplitude(this);
// Patch the input to the volume analyzer
loudness.input(input);
}
void draw() {
background(10, 20, 10);
waveform.analyze();
float totalPeriodsSamples = 0;
int crossingCount = 0;
float lastCrossingTime = -1;
float maxPeak = 0;
for (int i = 1; i < numSamples; i++) {
float prev = waveform.data[i - 1];
float curr = waveform.data[i];
if (abs(curr) > maxPeak) maxPeak = abs(curr);
if ((prev >= 0 && curr < 0) || (prev < 0 && curr >= 0)) {
float fraction = abs(prev) / (abs(prev) + abs(curr));
float exactCrossingTime = (i - 1) + fraction;
if (lastCrossingTime != -1) {
totalPeriodsSamples += (exactCrossingTime - lastCrossingTime);
crossingCount++;
}
lastCrossingTime = exactCrossingTime;
}
}
float frequencyHz = 0;
if (crossingCount > 0 && totalPeriodsSamples > 0) {
float avgHalfPeriod = totalPeriodsSamples / crossingCount;
frequencyHz = sampleRate / (2.0 * avgHalfPeriod);
}
if (frequencyHz > 20000) frequencyHz = 20000;
if (maxPeak < 0.015 || crossingCount < 2) frequencyHz = 0;
smoothedFreq = lerp(smoothedFreq, frequencyHz, 0.25);
float boxW = width * 0.85;
float boxH = height * 0.35;
float boxX = (width - boxW) / 2;
float boxY = (height - boxH) / 2;
stroke(0, 120, 30);
strokeWeight(5);
fill(5, 15, 5);
rect(boxX, boxY, boxW, boxH, 15);
noStroke();
if ((frameCount / 6) % 2 == 0 && maxPeak > 0.015) {
fill(0, 255, 50);
} else {
fill(5, 40, 10);
}
ellipse(boxX + 40, boxY + 40, 15, 15);
fill(0, 200, 40);
textSize(24);
text("GATE", boxX + 65, boxY + 48);
float w = boxW * 0.11;
float h = w * 1.8;
float spacing = w * 0.22;
float totalDigitsW = (w * 5) + (spacing * 4);
float startX = boxX + (boxW - totalDigitsW) / 2;
float digitsY = boxY + (boxH - h) / 2 + 10;
int displayVal = int(smoothedFreq);
int d5 = (displayVal / 10000) % 10;
int d4 = (displayVal / 1000) % 10;
int d3 = (displayVal / 100) % 10;
int d2 = (displayVal / 10) % 10;
int d1 = displayVal % 10;
drawSevenSegment(startX, digitsY, w, h, d5);
drawSevenSegment(startX + (w + spacing), digitsY, w, h, d4);
drawDecimalDot(startX + (w + spacing) * 2 - (spacing / 2), digitsY + h, w);
drawSevenSegment(startX + (w + spacing) * 2, digitsY, w, h, d3);
drawSevenSegment(startX + (w + spacing) * 3, digitsY, w, h, d2);
drawSevenSegment(startX + (w + spacing) * 4, digitsY, w, h, d1);
fill(0, 255, 50);
textSize(35);
text("kHz", boxX + boxW - 80, boxY + boxH - 35);
fill(0, 100, 25);
textSize(22);
text("SIGNAL LEVEL", boxX + 40, boxY + boxH - 35);
float barMaxW = boxW * 0.45;
float barW = map(maxPeak, 0, 0.5, 0, barMaxW);
barW = constrain(barW, 0, barMaxW);
stroke(0, 60, 15);
strokeWeight(2);
noFill();
rect(boxX + 220, boxY + boxH - 52, barMaxW, 18, 3);
noStroke();
fill(0, 255, 50);
rect(boxX + 222, boxY + boxH - 50, barW, 14, 2);
}
void drawSevenSegment(float x, float y, float w, float h, int num) {
float t = w * 0.16;
float g = 1.5;
color onColor = color(0, 255, 50);
color offColor = color(5, 30, 10);
noStroke();
pushMatrix();
translate(x, y);
shearX(radians(-7));
fill(segments[num][0] ? onColor : offColor);
beginShape(); vertex(t + g, 0); vertex(w - t - g, 0); vertex(w - (t/2) - g, t/2); vertex(w - t - g, t); vertex(t + g, t); vertex((t/2) + g, t/2); endShape(CLOSE);
fill(segments[num][1] ? onColor : offColor);
beginShape(); vertex(w, t + g); vertex(w, (h/2) - (t/2) - g); vertex(w - (t/2), (h/2) - g); vertex(w - t, (h/2) - (t/2) - g); vertex(w - t, t + g); vertex(w - (t/2), (t/2) + g); endShape(CLOSE);
fill(segments[num][2] ? onColor : offColor);
beginShape(); vertex(w, (h/2) + (t/2) + g); vertex(w, h - t - g); vertex(w - (t/2), h - (t/2) - g); vertex(w - t, h - t - g); vertex(w - t, (h/2) + (t/2) + g); vertex(w - (t/2), (h/2) + g); endShape(CLOSE);
fill(segments[num][3] ? onColor : offColor);
beginShape(); vertex(t + g, h - t); vertex(w - t - g, h - t); vertex(w - (t/2) - g, h - (t/2)); vertex(w - t - g, h); vertex(t + g, h); vertex((t/2) + g, h - (t/2)); endShape(CLOSE);
fill(segments[num][4] ? onColor : offColor);
beginShape(); vertex(t, (h/2) + (t/2) + g); vertex(t, h - t - g); vertex(t/2, h - (t/2) - g); vertex(0, h - t - g); vertex(0, (h/2) + (t/2) + g); vertex(t/2, (h/2) + g); endShape(CLOSE);
fill(segments[num][5] ? onColor : offColor);
beginShape(); vertex(t, t + g); vertex(t, (h/2) - (t/2) - g); vertex(t/2, (h/2) - g); vertex(0, (h/2) - (t/2) - g); vertex(0, t + g); vertex(t/2, (t/2) + g); endShape(CLOSE);
fill(segments[num][6] ? onColor : offColor);
beginShape(); vertex(t + g, (h/2) - (t/2)); vertex(w - t - g, (h/2) - (t/2)); vertex(w - (t/2) - g, h/2); vertex(w - t - g, (h/2) + (t/2)); vertex(t + g, (h/2) + (t/2)); vertex((t/2) + g, h/2); endShape(CLOSE);
popMatrix();
}
void drawDecimalDot(float x, float y, float w) {
float dotSize = w * 0.15;
fill(0, 255, 50);
noStroke();
rect(x, y - dotSize, dotSize, dotSize, 1);
}
Sketch permissions added:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
Excellent work. The idea is not about reading the frequency; the idea is to design a 7-segment display without using special files, font libraries, or character libraries.


