add multiple features

1. dpcm (temporary boilerplate needed, sorry)
2. noise
3. volume
4. multiline comments
This commit is contained in:
Maksymilian Wilczyński committed 2025-04-05 18:34:56 +02:00
1 parent 164d2a37d9
commit 9bc043a159
5 files changed
+205 -108

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
.vs/
x64/
*.wav
output.wav
*.vcxproj.user
BIN
View File
Binary file not shown.
+191 -47
View File
@@ -1,5 +1,6 @@
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <sstream>
@@ -7,13 +8,17 @@
const int SAMPLE_RATE = 44100;
const double M_TAU = 6.283185307179586476925286766559;
uint16_t lfsr = 0xACE1u;
unsigned bit;
// Enum for waveform types
enum Waveform {
SINE,
TRIANGLE,
SAWTOOTH,
SQUARE
SQUARE,
NOISE,
DPCM
};
// Structure to hold the waveform parameters
@@ -22,9 +27,30 @@ struct Wave {
int start_ms;
int duration_ms;
int frequency;
double volume; // New field for volume control (0.0 to 1.0)
double volume;
double dutyCycle; // For SQUARE
double sweepDepth; // Vibrato or slide
double sweepRate;
double attack;
double decay;
std::string samplePath;
std::vector<int16_t> sampleData;
};
bool loadWavFile(const std::string& path, std::vector<int16_t>& outSamples) {
std::ifstream file(path, std::ios::binary);
if (!file.is_open()) return false;
file.seekg(44); // skip header
outSamples.clear();
int16_t sample;
while (file.read(reinterpret_cast<char*>(&sample), sizeof(sample))) {
outSamples.push_back(sample);
}
return true;
}
// Read the parameters from the text file
bool loadWaveParams(const std::string& filename, std::vector<Wave>& waves, int& totalDuration) {
std::ifstream file(filename);
@@ -34,41 +60,97 @@ bool loadWaveParams(const std::string& filename, std::vector<Wave>& waves, int&
}
std::string line;
bool inCommentBlock = false;
while (std::getline(file, line)) {
// Remove C-style comments
if (inCommentBlock) {
size_t endComment = line.find("*/");
if (endComment != std::string::npos) {
inCommentBlock = false;
line = line.substr(endComment + 2); // Remaining part of the line after the comment
}
else {
continue; // Skip entire line if still in a comment block
}
}
size_t startComment = line.find("/*");
while (startComment != std::string::npos) {
size_t endComment = line.find("*/", startComment + 2);
if (endComment != std::string::npos) {
line.erase(startComment, endComment - startComment + 2); // Remove the comment
startComment = line.find("/*"); // Look for another comment
}
else {
inCommentBlock = true;
line = line.substr(0, startComment); // Remove everything after /* till end of line
break;
}
}
// Strip whitespace
line.erase(0, line.find_first_not_of(" \t\r\n"));
if (line.empty() || line[0] == '#') continue;
// Check if the line defines the total duration
if (line.find("TOTAL_DURATION") != std::string::npos) {
size_t startPos = line.find("(") + 1;
size_t endPos = line.find(")");
totalDuration = std::stoi(line.substr(startPos, endPos - startPos));
continue;
}
else if (line.find("SINE") != std::string::npos || line.find("SAWTOOTH") != std::string::npos ||
line.find("SQUARE") != std::string::npos || line.find("TRIANGLE") != std::string::npos) {
std::istringstream ss(line);
std::string waveType;
int start_ms, duration_ms, frequency;
double volume;
ss >> waveType >> start_ms >> duration_ms >> frequency >> volume;
Wave wave;
wave.start_ms = start_ms;
wave.duration_ms = duration_ms;
wave.frequency = frequency;
wave.volume = volume;
// Initialize variables for waveform properties
std::istringstream ss(line);
std::string waveType;
int start_ms, duration_ms, frequency;
double volume = 1.0;
double duty = 0.5;
double sweepDepth = 0.0, sweepRate = 0.0;
double attack = 0.01, decay = 0.2;
if (waveType == "SINE") {
wave.type = SINE;
}
else if (waveType == "SAWTOOTH") {
wave.type = SAWTOOTH;
}
else if (waveType == "SQUARE") {
wave.type = SQUARE;
}
else if (waveType == "TRIANGLE") {
wave.type = TRIANGLE;
}
// Read the basic wave parameters
ss >> waveType >> start_ms >> duration_ms >> frequency >> volume;
waves.push_back(wave);
// Set default values if optional parameters are missing
if (!(ss >> duty)) duty = 0.5;
if (!(ss >> sweepDepth)) sweepDepth = 0.0;
if (!(ss >> sweepRate)) sweepRate = 0.0;
if (!(ss >> attack)) attack = 0.01;
if (!(ss >> decay)) decay = 0.2;
// Create a new Wave object and set its properties
Wave wave;
wave.start_ms = start_ms;
wave.duration_ms = duration_ms;
wave.frequency = frequency;
wave.volume = volume;
wave.dutyCycle = duty;
wave.sweepDepth = sweepDepth;
wave.sweepRate = sweepRate;
wave.attack = attack;
wave.decay = decay;
// Determine wave type and set corresponding enum
if (waveType == "SINE") wave.type = SINE;
else if (waveType == "SAWTOOTH") wave.type = SAWTOOTH;
else if (waveType == "SQUARE") wave.type = SQUARE;
else if (waveType == "TRIANGLE") wave.type = TRIANGLE;
else if (waveType == "NOISE") wave.type = NOISE;
else if (waveType == "DPCM") {
wave.type = DPCM;
ss >> wave.samplePath; // Read sample file path for DPCM waves
if (!loadWavFile(wave.samplePath, wave.sampleData)) {
std::cout << "Failed to load sample: " << wave.samplePath << std::endl;
}
}
else {
std::cout << "Unknown waveform type: " << waveType << std::endl;
continue; // Skip unknown waveform types
}
// Add the wave to the vector
waves.push_back(wave);
}
return true;
@@ -96,43 +178,105 @@ double sawtoothWave(double frequency, double time) {
return 2 * phase - 1; // Ramp from -1 to 1
}
double squareWave(double frequency, double time) {
double squareWave(double frequency, double time, double duty = 0.5) {
double period = 1.0 / frequency;
double phase = fmod(time, period) / period; // Normalized phase (0 to 1)
return phase < 0.5 ? 1.0 : -1.0; // 1 for the first half, -1 for the second half
double phase = fmod(time, period) / period; // 0 to 1
return phase < duty ? 1.0 : -1.0;
}
// Mix the waveforms with volume control
double generateWave(Waveform waveform, double frequency, double time) {
double generateWave(Waveform waveform, double frequency, double time, double duty = 0.5) {
switch (waveform) {
case SINE:
return sineWave(frequency, time);
case TRIANGLE:
return triangleWave(frequency, time);
case SAWTOOTH:
return sawtoothWave(frequency, time);
case SQUARE:
return squareWave(frequency, time);
default:
return sineWave(frequency, time); // Default to sine wave
case SINE: return sineWave(frequency, time);
case TRIANGLE: return triangleWave(frequency, time);
case SAWTOOTH: return sawtoothWave(frequency, time);
case SQUARE: return squareWave(frequency, time, duty);
default: return sineWave(frequency, time);
}
}
double applyVibrato(double baseFreq, double time, double depth = 5.0, double rate = 5.0) {
return baseFreq + depth * std::sin(M_TAU * rate * time);
}
double applyEnvelope(double time, double startTime, double duration, double volume, double attack, double decay) {
double elapsed = time - startTime;
if (elapsed < 0.0) return 0.0;
if (elapsed < attack) {
// Attack phase: ramp up from 0 to volume
return volume * (elapsed / attack);
}
if (elapsed < (attack + decay)) {
// Decay phase: ramp down to 0
return volume * (1.0 - ((elapsed - attack) / decay));
}
// After envelope ends
return 0.0;
}
double noiseWave() {
// 16-bit Galois LFSR
bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1;
lfsr = (lfsr >> 1) | (bit << 15);
return ((lfsr & 1) ? 1.0 : -1.0); // White-ish noise
}
std::vector<int16_t> generateAudio(const std::vector<Wave>& waves, int totalDuration) {
std::vector<int16_t> samples(SAMPLE_RATE * totalDuration / 1000, 0); // Initialize with zeros
// Process each wave
for (const auto& wave : waves) {
int startSample = wave.start_ms * SAMPLE_RATE / 1000;
int endSample = (wave.start_ms + wave.duration_ms) * SAMPLE_RATE / 1000;
// Generate the waveform for the specified duration
if (wave.type == DPCM) {
// DPCM Sample Playback (raw PCM sample, assumed 16-bit mono)
for (int i = 0; i < wave.sampleData.size(); ++i) {
int t = startSample + i;
if (t >= samples.size()) break;
double envelopeVol = applyEnvelope(
t / (double)SAMPLE_RATE,
wave.start_ms / 1000.0,
wave.duration_ms / 1000.0,
1.0, wave.attack, wave.decay
);
samples[t] += static_cast<int16_t>(wave.sampleData[i] * envelopeVol);
}
continue; // Skip standard waveform generation for DPCM
}
for (int t = startSample; t < endSample; ++t) {
if (t >= samples.size()) break; // Prevent out-of-bounds access
if (t >= samples.size()) break;
double time = static_cast<double>(t) / SAMPLE_RATE;
double sample = generateWave(wave.type, wave.frequency, time);
sample *= wave.volume; // Apply volume
samples[t] += static_cast<int16_t>(sample * 32767); // Linear mixing
double startTime = wave.start_ms / 1000.0;
double relTime = time - startTime;
if (relTime < 0) continue;
// Frequency sweep / vibrato
double modFreq = wave.frequency + wave.sweepDepth * std::sin(M_TAU * wave.sweepRate * time);
// Generate waveform
double rawSample = 0.0;
if (wave.type == NOISE) {
rawSample = noiseWave(); // LFSR noise
}
else {
rawSample = generateWave(wave.type, modFreq, time, wave.dutyCycle);
}
// Envelope application
double envelopeVolume = applyEnvelope(time, startTime, wave.duration_ms / 1000.0, wave.volume, wave.attack, wave.decay);
// Final sample
double finalSample = rawSample * envelopeVolume;
// Mix into audio buffer
samples[t] += static_cast<int16_t>(std::clamp(finalSample * 32767.0, -32768.0, 32767.0));
}
}
+8
View File
@@ -76,6 +76,8 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -90,6 +92,8 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -104,6 +108,8 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -118,6 +124,8 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
+5 -60
View File
@@ -1,61 +1,6 @@
TOTAL_DURATION(10000)
TOTAL_DURATION(3000) // 3 seconds total
SQUARE 0 400 392 0.7
SAWTOOTH 0 400 392 0.4
SINE 0 400 392 0.3
TRIANGLE 0 400 392 0.2
SQUARE 450 400 392 0.7
SAWTOOTH 450 400 392 0.4
SINE 450 400 392 0.3
TRIANGLE 450 400 392 0.2
SQUARE 900 800 440 0.7
SAWTOOTH 900 800 440 0.4
SINE 900 800 440 0.3
TRIANGLE 900 800 440 0.2
SQUARE 1800 800 392 0.7
SAWTOOTH 1800 800 392 0.4
SINE 1800 800 392 0.3
TRIANGLE 1800 800 392 0.2
SQUARE 2700 800 523 0.7
SAWTOOTH 2700 800 523 0.4
SINE 2700 800 523 0.3
TRIANGLE 2700 800 523 0.2
SQUARE 3600 1600 494 0.7
SAWTOOTH 3600 1600 494 0.4
SINE 3600 1600 494 0.3
TRIANGLE 3600 1600 494 0.2
SQUARE 5400 400 392 0.7
SAWTOOTH 5400 400 392 0.4
SINE 5400 400 392 0.3
TRIANGLE 5400 400 392 0.2
SQUARE 5850 400 392 0.7
SAWTOOTH 5850 400 392 0.4
SINE 5850 400 392 0.3
TRIANGLE 5850 400 392 0.2
SQUARE 6300 800 440 0.7
SAWTOOTH 6300 800 440 0.4
SINE 6300 800 440 0.3
TRIANGLE 6300 800 440 0.2
SQUARE 7200 800 392 0.7
SAWTOOTH 7200 800 392 0.4
SINE 7200 800 392 0.3
TRIANGLE 7200 800 392 0.2
SQUARE 8100 800 587 0.7
SAWTOOTH 8100 800 587 0.4
SINE 8100 800 587 0.3
TRIANGLE 8100 800 587 0.2
SQUARE 9000 1600 523 0.7
SAWTOOTH 9000 1600 523 0.4
SINE 9000 1600 523 0.3
TRIANGLE 9000 1600 523 0.2
SINE 0 1000 440 1.0 0 3 5 0.05 0.3 // sine with slow vibrato and long decay
SQUARE 500 1000 330 0.7 0.25 6 4 0.01 0.2 // square with duty cycle and sweep
NOISE 1500 400 0 0.8 0 0 0 0.02 0.1 // noise burst
DPCM 2000 800 /* boilerplate */ 0 0 0 0 0 0 1.0 snare.wav