add multiple features
1. dpcm (temporary boilerplate needed, sorry) 2. noise 3. volume 4. multiline comments
This commit is contained in:
1 parent
164d2a37d9
commit
9bc043a159
5 files changed
+205
-108
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
.vs/
|
||||
x64/
|
||||
*.wav
|
||||
output.wav
|
||||
*.vcxproj.user
|
||||
+191
-47
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user