Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions ImageSonificationPlugin.jucer
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,32 @@
<MAINGROUP id="tIElxY" name="ImageSonificationPlugin">
<GROUP id="{FD711F5C-72BD-F3A2-E139-1339E676D35A}" name="Source">
<GROUP id="{76012BD6-BCB9-B2DF-3807-C613F131C857}" name="Algorithms">
<FILE id="BL7Zxc" name="EECS351WN22algorithm.cpp" compile="1" resource="0"
file="Source/EECS351WN22algorithm.cpp"/>
<FILE id="ctuvXT" name="EECS351WN22algorithm.h" compile="0" resource="0"
file="Source/EECS351WN22algorithm.h"/>
<FILE id="wEzKu7" name="ImageAsNoiseAlgorithm.cpp" compile="1" resource="0"
file="Source/ImageAsNoiseAlgorithm.cpp"/>
<FILE id="zgzE9K" name="ImageAsNoiseAlgorithm.h" compile="0" resource="0"
file="Source/ImageAsNoiseAlgorithm.h"/>
<FILE id="muccrV" name="LandscapeAlgorithm.cpp" compile="1" resource="0"
file="Source/LandscapeAlgorithm.cpp"/>
<FILE id="xaCWo3" name="LandscapeAlgorithm.h" compile="0" resource="0"
file="Source/LandscapeAlgorithm.h"/>
<FILE id="jjLfdO" name="AlgorithmBase.cpp" compile="1" resource="0"
file="Source/Algorithms/AlgorithmBase.cpp"/>
<FILE id="eVR5Kb" name="AlgorithmBase.h" compile="0" resource="0" file="Source/Algorithms/AlgorithmBase.h"/>
<GROUP id="{12D3855B-4AB5-C682-3743-106E483C1ACF}" name="RowByRow">
<FILE id="Hl8zXS" name="LandscapeAlgorithm.cpp" compile="1" resource="0"
file="Source/Algorithms/RowByRow/LandscapeAlgorithm.cpp"/>
<FILE id="NCXwPJ" name="LandscapeAlgorithm.h" compile="0" resource="0"
file="Source/Algorithms/RowByRow/LandscapeAlgorithm.h"/>
<FILE id="VLpdUY" name="RowByRowBase.cpp" compile="1" resource="0"
file="Source/Algorithms/RowByRow/RowByRowBase.cpp"/>
<FILE id="QQeQob" name="RowByRowBase.h" compile="0" resource="0" file="Source/Algorithms/RowByRow/RowByRowBase.h"/>
</GROUP>
<GROUP id="{B67AF9CF-4A1C-E278-A6E7-E581442EE855}" name="PixelByPixel">
<FILE id="fvp6Vp" name="EECS351WN22algorithm.cpp" compile="1" resource="0"
file="Source/Algorithms/PixelByPixel/EECS351WN22algorithm.cpp"/>
<FILE id="c4ihBN" name="EECS351WN22algorithm.h" compile="0" resource="0"
file="Source/Algorithms/PixelByPixel/EECS351WN22algorithm.h"/>
<FILE id="hIzmG1" name="ImageAsNoiseAlgorithm.cpp" compile="1" resource="0"
file="Source/Algorithms/PixelByPixel/ImageAsNoiseAlgorithm.cpp"/>
<FILE id="X2Z30C" name="ImageAsNoiseAlgorithm.h" compile="0" resource="0"
file="Source/Algorithms/PixelByPixel/ImageAsNoiseAlgorithm.h"/>
<FILE id="VfFBf4" name="PixelByPixelBase.cpp" compile="1" resource="0"
file="Source/Algorithms/PixelByPixel/PixelByPixelBase.cpp"/>
<FILE id="y4ZNh5" name="PixelByPixelBase.h" compile="0" resource="0"
file="Source/Algorithms/PixelByPixel/PixelByPixelBase.h"/>
</GROUP>
<FILE id="SpPVP5" name="WindowingAlgorithm.cpp" compile="1" resource="0"
file="Source/WindowingAlgorithm.cpp"/>
<FILE id="mLfhNE" name="WindowingAlgorithm.h" compile="0" resource="0"
Expand Down
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
# ImageSonificationPlugin
VST3 plugin that turns your image into music. (Or noise, depending on the algorithm!)

## Implemented algorithms:
* <b>Pixel By Pixel</b> - group of algorithms that analyze image pixel-by-pixel, turning single pixel into one or more samples of sound.
* Common options:
* Direction of play - Direction, in which image is analyzed (left to right, top to bottom or random)
* Window size - Size of group of pixels (window) that are averaged to one pixel. (Making image de facto more blurry.)
* Pixel play length [Not yet Implemented] - Length, how many samples of sound are generated from single pixel.
* <b>Simple Image as Noise</b> algorithm - RGBs values are averaged and played as a sound samples.
* Color Predominance to Music Note - Depending which color is the most predominant in the pixel (or window), given note is played. Inspired by [project sonify by EECS351 researchers](https://sites.google.com/umich.edu/eecs351-project-sonify/how-we-sonify).
* Note Complexity [To be implemented] - how many sines are creating single note?
* Envelope function [To be implemented] - What is the function that decide about share of each sine in the final sound?
* Color Threshold [To Be implemented] - Limit, at which RGB pixel is treated as black or white, instead of pixel with actual color.
* <b>Row By Row</b> - Group of algorithms, where one note is created from the whole collumn (or row) of pixels.
* Common Options:
* Row or Collumn [To Be implemented] - Whether image is scanned row-by-row or collumn-by-collumn
* <b>Landscape</b> algorithm - Algorithm finds "break point" between darker pixels (ground) and lighter pixels (sky) and treats that breakpoint as new sound intensity
* TODO - add subalgorithm, how breakpoint is to be treated
* <b>Spectrogram</b> algorithm [To Be Implemented] - whole collumn is treated as spectrogram - height of the pixel in the collumn is treated as note height, pixel intensity is treated as an envelope (share if this particular note in final sound).
* Musical effects - Image is treated as an musical effect
* TODO - merge first one - Reverb in the next PR.

## Works to be analyzed and implemented:
* [The Sonification Handbook](https://sonification.de/handbook/download/TheSonificationHandbook-HermannHuntNeuhoff-2011.pdf)
* Bardziej przegląd literatury
* Jakiś śmieszny algorytm na str. 443
* More or less a literature overview
* Funny alg on page 443
* [Along The Line](http://web.archive.org/web/20161212144920if_/http://grond.at:80/index.htm?html/projects/along_the_line/along_the_line.htm&html/submenues/submenu_projects.htm)
* [The sound of photographic image](https://research.gold.ac.uk/id/eprint/14651/1/sound_photographic.pdf)
* [A FRAMEWORK FOR DESIGNING IMAGE SONIFICATION METHODS](https://ccrma.stanford.edu/~woony/publications/Yeo_Berger-ICAD05.pdf) <- to się dobre wydaje
* [A FRAMEWORK FOR DESIGNING IMAGE SONIFICATION METHODS](https://ccrma.stanford.edu/~woony/publications/Yeo_Berger-ICAD05.pdf) <- First one to be checked!
* [An Experimental System for Auditory Image Representations](https://www.seeingwithsound.com/voicebme.html)
14 changes: 14 additions & 0 deletions Source/Algorithms/AlgorithmBase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
==============================================================================

AlgorithmBase.cpp
Created: 26 Jul 2024 4:17:04pm
Author: OEM

==============================================================================
*/

#include "AlgorithmBase.h"

juce::Image::BitmapData* AlgorithmBase::imageBitmapPtr = nullptr;

19 changes: 19 additions & 0 deletions Source/Algorithms/AlgorithmBase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
==============================================================================

AlgorithmBase.h
Created: 26 Jul 2024 2:22:01pm
Author: OEM

==============================================================================
*/

#include <JuceHeader.h>
#pragma once

class AlgorithmBase {
public:
virtual void generateNextSamples(float* output_buffer, unsigned int buffer_length) = 0;

static juce::Image::BitmapData* imageBitmapPtr;
};
74 changes: 74 additions & 0 deletions Source/Algorithms/PixelByPixel/EECS351WN22algorithm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
==============================================================================

EECS351WN22algorithm.cpp
Created: 11 Dec 2023 7:31:49pm
Author: OEM

==============================================================================
*/

#include "EECS351WN22algorithm.h"


EECS351WN22algorithm::EECS351WN22algorithm(PixelByPixelDirection& directionOfPlay, int& windowSize) : PixelByPixelBase(directionOfPlay, windowSize)
{
SamplesPerPixel = 1500;
}

float EECS351WN22algorithm::getSampleFromPixel(juce::Colour)
{
float currentSample = 0;
for (short unsigned int j = 0; j < 3; j++) {
currentSample += std::sinf(currentAngle[j]);
currentAngle[j] += angleDelta[j];
}

return currentSample / 3;
}

void EECS351WN22algorithm::iteratePixelAlgorithmSpecific(juce::Colour pixel)
{
juce::uint8 r = pixel.getRed();
juce::uint8 g = pixel.getGreen();
juce::uint8 b = pixel.getBlue();

// TODO - make chord length (currently 3) changeable from GUI

if (r < EECS_limit && g < EECS_limit && b < EECS_limit) {
chords[0] = 64;
chords[1] = 67;
chords[2] = 71;
}
else if (r > 255 - EECS_limit && g > 255 - EECS_limit && b > 255 - EECS_limit) {
chords[0] = 62;
chords[1] = 65;
chords[2] = 69;
}
else if (g >= b && g >= r) {
chords[0] = 69;
chords[1] = 72;
chords[2] = 66;
}
else if (b >= g && b >= r) {
chords[0] = 65;
chords[1] = 68;
chords[2] = 72;
}
else if (r >= g && r >= b) {
chords[0] = 60;
chords[1] = 64;
chords[2] = 67;
}
else {
chords[0] = 60;
chords[1] = 64;
chords[2] = 67;
}

for (unsigned short int j = 0; j < 3; j++) {
float chords_hertz = (float)juce::MidiMessage::getMidiNoteInHertz(chords[j]);
float chords_cyclesPerSample = chords_hertz / SampleRate;
angleDelta[j] = chords_cyclesPerSample * 2.0 * juce::MathConstants<float>::pi;
}
}
28 changes: 28 additions & 0 deletions Source/Algorithms/PixelByPixel/EECS351WN22algorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
==============================================================================

EECS351WN22algorithm.h
Created: 11 Dec 2023 7:31:49pm
Author: OEM

==============================================================================
*/
#include <JuceHeader.h>
#include "PixelByPixelBase.h"

#pragma once

class EECS351WN22algorithm : public PixelByPixelBase {
public:
EECS351WN22algorithm(PixelByPixelDirection& directionOfPlay, int& windowSize);

float getSampleFromPixel(juce::Colour pixel) override;
void iteratePixelAlgorithmSpecific(juce::Colour pixel) override;

private:
short unsigned int chords[3] = { 36, 40, 43 };
float angleDelta[3] = { 0, 0, 0 };
float currentAngle[3] = { 0, 0, 0 };

short unsigned int EECS_limit = 35;
};
20 changes: 20 additions & 0 deletions Source/Algorithms/PixelByPixel/ImageAsNoiseAlgorithm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
==============================================================================

ImageAsNoiseAlgorithm.cpp
Created: 11 Dec 2023 5:59:17pm
Author: OEM

==============================================================================
*/

#include "ImageAsNoiseAlgorithm.h"

ImageAsNoiseAlgorithm::ImageAsNoiseAlgorithm(PixelByPixelDirection& directionOfPlay, int& windowSize): PixelByPixelBase(directionOfPlay, windowSize)
{
}

float ImageAsNoiseAlgorithm::getSampleFromPixel(juce::Colour pixel)
{
return (pixel.getFloatRed() + pixel.getFloatGreen() + pixel.getFloatBlue()) / 3.f;
}
21 changes: 21 additions & 0 deletions Source/Algorithms/PixelByPixel/ImageAsNoiseAlgorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
==============================================================================

ImageAsNoiseAlgorithm.h
Created: 11 Dec 2023 5:59:17pm
Author: OEM

==============================================================================
*/

#include <JuceHeader.h>
#include "PixelByPixelBase.h"
#pragma once


class ImageAsNoiseAlgorithm: public PixelByPixelBase {
public:
ImageAsNoiseAlgorithm(PixelByPixelDirection& directionOfPlay, int& windowSize);

float getSampleFromPixel(juce::Colour pixel) override;
};
122 changes: 122 additions & 0 deletions Source/Algorithms/PixelByPixel/PixelByPixelBase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
==============================================================================

PixelByPixelBase.cpp
Created: 15 Jan 2024 3:37:37pm
Author: j.mazur, k.P³aneta

==============================================================================
*/

#include "PixelByPixelBase.h"
//#include <juce_MathsFunctions.h>

#define ACC2RGB(acc_val, div) ((juce::uint8)(acc_val / div))

PixelByPixelBase::PixelByPixelBase(PixelByPixelDirection& directionOfPlay, int& windowSize): AlgorithmBase(), directionOfPlay(directionOfPlay), WindowSize(windowSize), SampleRate(0u)
{
// TODO - make windowSize type juce::uint8 and cut away slider at value 255!
}

void PixelByPixelBase::generateNextSamples(float* output_buffer, unsigned int buffer_length)
{
for (float* i = output_buffer; i < output_buffer + buffer_length; ++i) {
auto pixel = Window();
*i = this->getSampleFromPixel(pixel);
SamplesPerPixelIterator++;
if (SamplesPerPixel == SamplesPerPixelIterator)
{
this->iteratePixelCommon();
this->iteratePixelAlgorithmSpecific(pixel);
SamplesPerPixelIterator = 0;
}
}
}

void PixelByPixelBase::prepareToPlay(double sampleRate, int)
{
SampleRate = (unsigned int)sampleRate;
}

void PixelByPixelBase::iteratePixelCommon()
{
if (directionOfPlay == LeftToRight) {
WidthIt += 1;
if (WidthIt >= imageBitmapPtr->width) {
WidthIt = 0;
HeightIt += 1;

if (HeightIt >= imageBitmapPtr->height) {
HeightIt = 0;
}
}
}
else if (directionOfPlay == Random) {
short unsigned int randomInt = (short unsigned int) juce::Random::getSystemRandom().nextInt(4);
if (WidthIt < imageBitmapPtr->width - 1 && randomInt == 0) {
WidthIt += 1;
}
if (HeightIt < imageBitmapPtr->height - 1 && randomInt == 1) {
HeightIt += 1;
}
if (WidthIt > 1 && randomInt == 2) {
WidthIt -= 1;
}
if (HeightIt > 1 && randomInt == 3) {
HeightIt -= 1;
}
}
else if (directionOfPlay == UpToDown) {
HeightIt += 1;
if (HeightIt >= imageBitmapPtr->height) {
HeightIt = 0;
WidthIt += 1;

if (WidthIt >= imageBitmapPtr->width) {
WidthIt = 0;
}
}
}
}

juce::Colour PixelByPixelBase::Window()
{
if (WindowSize == 1)
{
return imageBitmapPtr->getPixelColour(WidthIt, HeightIt);
}

unsigned int half_win_size = WindowSize / 2;

unsigned int lower_bound_width = std::max((WidthIt - half_win_size), 0u);
unsigned int higher_bound_width = std::min((unsigned int)(imageBitmapPtr->width), (WidthIt + half_win_size));

unsigned int lower_bound_height = std::max((HeightIt - half_win_size), 0u);
unsigned int higher_bound_height = std::min((unsigned int)(imageBitmapPtr->height), (HeightIt + half_win_size));

// max window size is 255 (uint8), so accumulators can contain values up to 255*255*255 - uint32 is needed!
juce::uint32 R = 0u;
juce::uint32 G = 0u;
juce::uint32 B = 0u;

juce::Colour temp_colour;

// TODO - cache correct part of the sample for the calculation of the next sample!
for (unsigned int i = lower_bound_width; i < higher_bound_width; ++i)
{
for (unsigned int j = lower_bound_height; j < higher_bound_height; ++j)
{
temp_colour = imageBitmapPtr->getPixelColour(i, j);
R += temp_colour.getRed();
G += temp_colour.getGreen();
B += temp_colour.getBlue();
}
}

unsigned int win_size = (higher_bound_width - lower_bound_width) * (higher_bound_height - lower_bound_height);

return juce::Colour(ACC2RGB(R, win_size), ACC2RGB(G, win_size), ACC2RGB(B, win_size));
}

int PixelByPixelBase::WidthIt = 0;
int PixelByPixelBase::HeightIt = 0;
Loading