Metadata-Version: 2.1
Name: graph2sound
Version: 1.1.4
Summary: Read the latest Real Python tutorials
Author: Crossed Omega
License: MIT License
        
        Copyright (c) 2022 Crossed Omega
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://gitlab.com/p8244/graph2sound
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=2.0.0
Requires-Dist: scipy>=1.14.0
Requires-Dist: sounddevice>=0.4.7

# graph2sound

This is a Python library for adding some auditory feedback to your graphs. Alternatively, this library has capabilities such as creating smooth variable-frequency waveforms or playing simple MIDI-tones.

## Installation
Use `pip install graph2sound` to install the module.\
Alternatively, download `graph2sound.py` and save it in your working directory.


## Dependencies
`numpy` for array creation and modification.\
`scipy` for signal modification and interpolation.\
`sounddevice` is required for playback. However, as the wav + samplerate can be extracted with `graph2wav`, it is not strictly needed.

## Usage
Depending on what you want to do, g2s may be called as a single function. These functions actually chain some subroutines together to assemble and play the final tone.\
The general syntax is as follows:
```
import graph2sound as g2s
g2s.graph2sound(y[,x],[t=,r=,s=,l=,h=,w=,f=])
```

### Demo
Test g2s by `g2s.graph2sound()`. Plays a demo sound.

### Single y series
At the very least, g2s requires a single data series consisting of two points or more.
```
y = [i**2 for i in range(10)]
g2s.graph2sound(y)
```
This will play a tone which increases in frequency quadratically. Every point in the series is played for an equal amount of time (linear time mapping).


### Single x y series
If you have an associated time series, for example a result from an ODE solver, you may also map your data points to time. Note: the lengths of the two series must be equal.
```
import numpy as np
t = np.logspace(0.1,1,100)
y = np.sin(t)
g2s.graph2sound(y,t)
```
This will play a sound which steadily increases in frequency, as opposed to a logarithmic increase in frequency.

### Two-dimensional y series
If you have a series of y data which is 2D, g2s will automatically convert all rows to wavs and adds them.
```
y = [[i*j for i in range(5)] for j in range(5)]
g2s.graph2sound(y)
```
This will play the normalized summed waveforms, row major.

### Keyword use and more examples
```
import pandas as pd
d = {'x':[0,0.1,0.2,3],'y':[0,0,0,1]}
df = pd.DataFrame(d)
g2s.graph2sound(df['y'],df['x'],t=1.5,l=440,h=880,w='sawtooth')
```
Plays from a DataFrame. Has a 1.5 second duration, the lowest/highest tones are 440 and 880 and the synth uses a sawtooth wave.

```
from scipy import signal
import numpy as np
x = np.linspace(0,20)
y = signal.square(x)
g2s.graph2sound(y,x,w='square')
```
This is what a square square wave sounds like.

### Keywords
`g2s.graph2sound` takes multiple optional keywords:\
`t = float,int` Default: 2 seconds. Determines the length of the tone played.\
`r = int` Default: 44100. Sample rate. Determines the samples per second which are fed into the audio library.\
`s = int` Default: 100*t. The number of segments the tone is divided in. Essentially: how many frequency changes the tone has.\
`l = float` Default: 130.813 Hz. C3. This is the lowest tone the y series will be mapped to.\
`h = float` Default: 2093.005 Hz. C7. This is the highest tone the y series will be mapped to.

`w =`\
`sine` Outputs a sine waveform (default).\
`square` Outputs a square waveform.\
`sawtooth` Outputs a sawtooth waveform.\
`f =`\
`fifth` Adds a perfect fifth overtone.\
`yaranaika` HARMONY\
`MATLAB` plays the sound like ye olde days.

### Low-level functions
Read the docs (TODO) to use the underlying functions.\
`wave,samplerate = g2s.graph2wave` takes in the arguments mentioned above and converts it to the required waveform. Assembles it into a ready-to-play array. Returns the waveform and the samplerate used.\
`g2s.wave2sound(wave,samplerate)` takes the arguments of the previous function and plays them back.\
`y,x = g2s.argskwargcleanup(*args,**kwargs)` verifies the x/y data format to it can be used in the rest of the functions.\
`f_list = g2s.freqlist(y,x,frequency_low,frequency_high,segments)` creates a frequency table which is required to synthesize the desired tone.\
`wave,samplerate = g2s.synth(time,samplerate,segments,frequency_list,**kwargs)` is the meat and potatoes of this module. Given information on how the samplerate and the segments look like, synthesizes a continuous waveform with the desired samplerate.\
`freq = g2s.midi2freq(mnn)` takes in a [midi note number](https://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf) (not from MIDI.org, needs login) and returns the frequency. A4 = 69 = 440 Hz.\
`mnn = g2s.freq2midi(freq)` takes in a frequency and returns the midi note number as specified above.\
`mnn = g2s.midi2midi(mnn,delta_freq=0)` changes the midi number by a given frequency. Does not output ints or check if the number is a real note or not.\
`freq = g2s.freq2freq(freq,delta_mnn=0)` changes the frequency by a midi note interval.



## Support
Shit's broken? Merge requests are open.


## License
MIT


## Why?
In one of my bachelors' courses (control theory), I was fucking around with MATLAB (hallowed be thy name) and at a certain point, I thought to myself:

> *"This graph needs sound"*\
> \- Crossed Omega, 2018

Obviously, this Lovecraftian horror of an idea took digital form rather quickly. After ample time, the first iteration was done - which sounded pretty horrible<sup>1</sup>. For the next three years, it remained untouched, left stewing, the entity itself anticipating a return. As I learnt Python, one of the first things I did, after giving the IT department a major headache, was rewrite graph2sound to be used as a Python library. Alas, my attempt to do so was not thwarted and thus stands before you an *academic shitpost*<sup>TM</sup> of genuine Dutch quality.\
<sup>1</sup>: You can actually listen to what it sounded like by passing the MATLAB filter keyword `f=MATLAB`.

## TODO
- ~~Add a small amount of filters to modify the waveforms~~
- ~~Rewrite the synth function, some of the arguments are redundant~~
- Add an LFO
- ~~Add a sound library checker~~
- ~~Add proper data sanitation~~
- ~~Add multi-dimensional sounds~~
- (Never ever) Rewrite this as a matplotlib backend

## Known bugs
- The synth function bugs out at some edge cases: for a function with the highest point at t=end, the final frequency is never played. Requires changes to the frequency list function.
- The data validation misses lists such as [1,[1,2,3]] as a check for raggedness. This development is a pain in the bum.
- The raggedness of arrays is still not handled perfectly, as ragged arrays cause all kinds of different errors.
- The returned datatype of the ...2midi functions is a float; this should be an int and checked for if the frequency is actually a midi note.
- The generated wav may start and end with a non-zero loudness. This can cause "popping", which could be fixed by ramping the amplitude up and down by either adding a ramp before and after or multiplying the beginning and ending of the wav with an exponential curve.
