Last active
November 10, 2015 14:54
-
-
Save Foaly/31c2176b439ffc4bb6fc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _USE_MATH_DEFINES | |
#include <SFML/Graphics.hpp> | |
#include <SFML/Audio.hpp> | |
#include <iostream> | |
#include <iomanip> | |
#include <vector> | |
#include <cmath> | |
#include <iterator> | |
#include <algorithm> | |
float toGrayscale(const sf::Color& color); | |
void addSineWave(std::vector<float>& samples, float amplitude, float frequency, int sampleRate); | |
int main() { | |
sf::Clock processingClock; | |
// load the image | |
sf::Image image; | |
if (!image.loadFromFile("data/eye.png")) | |
{ | |
std::cerr << "Error loading image" << std::endl; | |
return -1; | |
} | |
auto imageSize = image.getSize(); | |
const int maxFrequency = 20000; | |
const int minFrequency = 200; | |
const int samplingRate = 44100; | |
const int pxPerSec = 30; | |
std::cout << "Frequency range: " << minFrequency << " - " << maxFrequency << std::endl; | |
std::cout << "Pixel per second: " << pxPerSec << std::endl; | |
std::cout << "Samplingrate: " << samplingRate << std::endl; | |
const int freqencyRange = maxFrequency - minFrequency; | |
const int interval = freqencyRange / imageSize.y; // TODO: what is this? frequency band (height) per pixel? | |
const unsigned int numberOfSamplesPerPixel = samplingRate / pxPerSec; | |
std::vector<sf::Int16> samples; | |
std::vector<float> samplePerPixel(numberOfSamplesPerPixel, 0.f); | |
for (unsigned int x = 0; x < imageSize.x; ++x) | |
{ | |
// reset the vector with 0 | |
std::fill(samplePerPixel.begin(), samplePerPixel.end(), 0.f); | |
for (unsigned int y = 0; y < imageSize.y; ++y) | |
{ | |
const float grayValue = toGrayscale(image.getPixel(x, y)) / 255.f; | |
if (grayValue > 0.f) | |
{ | |
const int yInverted = (imageSize.y - 1) - y; | |
const float frequency = yInverted * interval + minFrequency; | |
addSineWave(samplePerPixel, grayValue, frequency, samplingRate); | |
} | |
} | |
// find the absolute maximum | |
auto minmaxElement = std::minmax_element(samplePerPixel.begin(), samplePerPixel.end()); | |
float absMax = std::max(std::abs(*(minmaxElement.first)), *(minmaxElement.second)); | |
// normalize vector | |
std::transform(samplePerPixel.begin(), samplePerPixel.end(), samplePerPixel.begin(), | |
std::bind2nd(std::divides<float>(), absMax)); | |
//std::transform(samplePerPixel.begin(), samplePerPixel.end(), samplePerPixel.begin(), | |
// [&absMax](auto x) { return x * absMax } ); | |
// scale the float values (-1, 1) to signed int values (-32768, 32767) | |
std::transform(samplePerPixel.begin(), samplePerPixel.end(), samplePerPixel.begin(), | |
std::bind2nd(std::multiplies<float>(), 32767)); | |
// append to the buffers samples | |
std::copy(samplePerPixel.begin(), samplePerPixel.end(), back_inserter(samples)); | |
// progress counter | |
std::cout << std::setprecision(2) << "Generating sound: " << static_cast<float>(x) / imageSize.x * 100.f << "% \r"; | |
std::cout.flush(); | |
} | |
std::cout << "Generating sound complete" << std::endl; | |
std::cout << "Calculation time: " << processingClock.getElapsedTime().asSeconds() << " seconds" << std::endl; | |
sf::SoundBuffer soundBuffer; | |
if (!soundBuffer.loadFromSamples(&samples[0], samples.size(), 1, samplingRate)) { | |
std::cerr << "Loading sound from buffer failed!" << std::endl; | |
return -1; | |
} | |
// play the generated sound until it's over or escape was pressed | |
sf::Sound sound(soundBuffer); | |
sound.play(); | |
while (sound.getStatus() == sf::Sound::Playing) | |
{ | |
sf::sleep(sf::milliseconds(100)); | |
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)) | |
break; | |
} | |
// save sound to file | |
if (!soundBuffer.saveToFile("out.wav")) | |
{ | |
std::cerr << "Saving sound buffer to file failed!" << std::endl; | |
return -1; | |
} | |
return 0; | |
} | |
/** | |
* \brief | |
* | |
* \param samples The sample vector into which the sounds are added | |
* \param amplitude A float value between 0.f and 1.f | |
* \param frequency The frequency the tone should have | |
* \param sampleRate The sampling rate of the tone | |
*/ | |
void addSineWave(std::vector<float>& samples, float amplitude, float frequency, int sampleRate) | |
{ | |
const float twoPi = 2.f * static_cast<float>(M_PI); | |
const float phase = 0.f; | |
for (int i = 0; i < samples.size(); ++i) | |
{ | |
samples[i] += std::sin(twoPi * frequency / sampleRate * i + phase) * amplitude; | |
} | |
} | |
/** | |
* @brief This function converts a sf::Color to a grayscale value using the NTSC | |
* conversion weights. (see https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems) | |
* | |
* @param color The color to convert | |
* @return The grayscale value of the given color in range [0.f, 255.f] | |
*/ | |
float toGrayscale(const sf::Color& color) | |
{ | |
return 0.299f * color.r + 0.587f * color.g + 0.114f * color.b; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment