Audio Library

Status
Not open for further replies.
Hi Pensive,

I had 12 crushers, and the teensy would lock when I commented the following out in setup, but it could just be that it's a silly amount of crushers.
The overall sketch was probably a little ambitious which could have also lead to the crash. Hope I didn't sound accusing, the crusher is brilliant and a ton of fun :)
Code:
int current_CrushBits = 16; //this defaults to passthrough.
int current_SampleRate = 44100; // this defaults to passthrough.

  bitcrusher1.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher2.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher3.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher4.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher5.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher6.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher7.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher8.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher9.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher10.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher11.updateCrusher(current_CrushBits,current_SampleRate);
  bitcrusher12.updateCrusher(current_CrushBits,current_SampleRate);

Cheers
Alex
 
Think I've got it.

It's not the values, it's the lack of them.

The crusher will still run even you don't run "updatecrusher".

So I think it's hanging on null values. Easy fix :)
 
Has anyone successfully compiled the Audio Library and used the faster SDfat library?


It says to just add the library and it should be compatible by changing these lines:

A new class, "File", has been added to provide compatibility with the Arduino
SD.h library. To use SdFat with programs written for SD.h replace

#include <SD.h>

#include <SdFat.h> //with these two lines:
SdFat SD;


But, when I try to compile it says
"C:\Arduino-1.06\libraries\Audio/play_sd_raw.h:31:16: fatal error: SD.h: No such file or directory compilation terminated."

So I modified play_sd_raw header file:
//#include "SD.h"
#include <SdFat.h>
SdFat SD;

Then I get this error instead:
Audio\play_sd_raw.cpp.o:(.bss.SD+0x0): multiple definition of `SD'
LTP_V1_04.cpp.o:C:\Arduino-1.06\libraries\Audio/utility/FatFile.h:384: first defined here
collect2.exe: error: ld returned 1 exit status

I can't figure out where the redefinition is, but I sure would love to see how much faster the SD card may perform with the Audio Library if I could get it to compile and test.

I get kind of a squeak sound / digital noise when opening files at high speeds, (like a sound file every 100ms), not sure if it's software or hardware yet honestly, but it only happens when opening over and over quickly. I suspect it has to do with the speed/delay of opening files... maybe file indexing would help if I could figure that out.
 
Has anyone successfully compiled the Audio Library and used the faster SDfat library?


It says to just add the library and it should be compatible by changing these lines:

A new class, "File", has been added to provide compatibility with the Arduino
SD.h library. To use SdFat with programs written for SD.h replace

#include <SD.h>

#include <SdFat.h> //with these two lines:
SdFat SD;


But, when I try to compile it says
"C:\Arduino-1.06\libraries\Audio/play_sd_raw.h:31:16: fatal error: SD.h: No such file or directory compilation terminated."

So I modified play_sd_raw header file:
//#include "SD.h"
#include <SdFat.h>
SdFat SD;

Then I get this error instead:
Audio\play_sd_raw.cpp.o:(.bss.SD+0x0): multiple definition of `SD'
LTP_V1_04.cpp.o:C:\Arduino-1.06\libraries\Audio/utility/FatFile.h:384: first defined here
collect2.exe: error: ld returned 1 exit status

I can't figure out where the redefinition is, but I sure would love to see how much faster the SD card may perform with the Audio Library if I could get it to compile and test.

I get kind of a squeak sound / digital noise when opening files at high speeds, (like a sound file every 100ms), not sure if it's software or hardware yet honestly, but it only happens when opening over and over quickly. I suspect it has to do with the speed/delay of opening files... maybe file indexing would help if I could figure that out.

This will not get you all the way but may help.
Download the latest SdFat beta. https://github.com/greiman/SdFat-beta

Make these changes in SdSpiTeensy3.cpp in the SdFat library.

Code:
 //CORE_PIN11_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2);
  CORE_PIN7_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2);
  CORE_PIN12_CONFIG = PORT_PCR_MUX(2);
  //CORE_PIN13_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2);
  CORE_PIN14_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2);

This code does run with the Audio Card attached.
Code:
//sdinfo.ino
/*
 * This sketch attempts to initialize an SD card and analyze its structure.
 */
#include <SPI.h>
#include <SdFat.h>
/*
 * SD chip select pin.  Common values are:
 *
 * Arduino Ethernet shield, pin 4.
 * SparkFun SD shield, pin 8.
 * Adafruit SD shields and modules, pin 10.
 * Default SD chip select is the SPI SS pin.
 */
const uint8_t SD_CHIP_SELECT = 10;
/*
 * Set DISABLE_CHIP_SELECT to disable a second SPI device.
 * For example, with the Ethernet shield, set DISABLE_CHIP_SELECT
 * to 10 to disable the Ethernet controller.
 */
const int8_t DISABLE_CHIP_SELECT = -1;
SdFat sd;

// serial output steam
ArduinoOutStream cout(Serial);

// global for card size
uint32_t cardSize;

// global for card erase size
uint32_t eraseSize;
//------------------------------------------------------------------------------
// store error strings in flash
#define sdErrorMsg(msg) sdErrorMsg_P(PSTR(msg));
void sdErrorMsg_P(const char* str) {
  cout << pgm(str) << endl;
  if (sd.card()->errorCode()) {
    cout << pstr("SD errorCode: ");
    cout << hex << int(sd.card()->errorCode()) << endl;
    cout << pstr("SD errorData: ");
    cout << int(sd.card()->errorData()) << dec << endl;
  }
}
//------------------------------------------------------------------------------
uint8_t cidDmp() {
  cid_t cid;
  if (!sd.card()->readCID(&cid)) {
    sdErrorMsg("readCID failed");
    return false;
  }
  cout << pstr("\nManufacturer ID: ");
  cout << hex << int(cid.mid) << dec << endl;
  cout << pstr("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
  cout << pstr("Product: ");
  for (uint8_t i = 0; i < 5; i++) {
    cout << cid.pnm[i];
  }
  cout << pstr("\nVersion: ");
  cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl;
  cout << pstr("Serial number: ") << hex << cid.psn << dec << endl;
  cout << pstr("Manufacturing date: ");
  cout << int(cid.mdt_month) << '/';
  cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl;
  cout << endl;
  return true;
}
//------------------------------------------------------------------------------
uint8_t csdDmp() {
  csd_t csd;
  uint8_t eraseSingleBlock;
  if (!sd.card()->readCSD(&csd)) {
    sdErrorMsg("readCSD failed");
    return false;
  }
  if (csd.v1.csd_ver == 0) {
    eraseSingleBlock = csd.v1.erase_blk_en;
    eraseSize = (csd.v1.sector_size_high << 1) | csd.v1.sector_size_low;
  } else if (csd.v2.csd_ver == 1) {
    eraseSingleBlock = csd.v2.erase_blk_en;
    eraseSize = (csd.v2.sector_size_high << 1) | csd.v2.sector_size_low;
  } else {
    cout << pstr("csd version error\n");
    return false;
  }
  eraseSize++;
  cout << pstr("cardSize: ") << 0.000512*cardSize;
  cout << pstr(" MB (MB = 1,000,000 bytes)\n");

  cout << pstr("flashEraseSize: ") << int(eraseSize) << pstr(" blocks\n");
  cout << pstr("eraseSingleBlock: ");
  if (eraseSingleBlock) {
    cout << pstr("true\n");
  } else {
    cout << pstr("false\n");
  }
  return true;
}
//------------------------------------------------------------------------------
// print partition table
uint8_t partDmp() {
  cache_t *p = sd.vol()->cacheClear();
  if (!p) {
    sdErrorMsg("cacheClear failed");
    return false;
  }
  if (!sd.card()->readBlock(0, p->data)) {
      sdErrorMsg("read MBR failed");
      return false;
  }
  for (uint8_t ip = 1; ip < 5; ip++) {
    part_t *pt = &p->mbr.part[ip - 1];
    if ((pt->boot & 0X7F) != 0 || pt->firstSector > cardSize) {
      cout << pstr("\nNo MBR. Assuming Super Floppy format.\n");
      return true;
    }
  }
  cout << pstr("\nSD Partition Table\n");
  cout << pstr("part,boot,type,start,length\n");
  for (uint8_t ip = 1; ip < 5; ip++) {
    part_t *pt = &p->mbr.part[ip - 1];
    cout << int(ip) << ',' << hex << int(pt->boot) << ',' << int(pt->type);
    cout << dec << ',' << pt->firstSector <<',' << pt->totalSectors << endl;
  }
  return true;
}
//------------------------------------------------------------------------------
void volDmp() {
  cout << pstr("\nVolume is FAT") << int(sd.vol()->fatType()) << endl;
  cout << pstr("blocksPerCluster: ") << int(sd.vol()->blocksPerCluster()) << endl;
  cout << pstr("clusterCount: ") << sd.vol()->clusterCount() << endl;
  cout << pstr("freeClusters: "); 
  uint32_t volFree = sd.vol()->freeClusterCount();
  cout <<  volFree << endl;
  float fs = 0.000512*volFree*sd.vol()->blocksPerCluster();
  cout << pstr("freeSpace: ") << fs << pstr(" MB (MB = 1,000,000 bytes)\n");
  cout << pstr("fatStartBlock: ") << sd.vol()->fatStartBlock() << endl;
  cout << pstr("fatCount: ") << int(sd.vol()->fatCount()) << endl;
  cout << pstr("blocksPerFat: ") << sd.vol()->blocksPerFat() << endl;
  cout << pstr("rootDirStart: ") << sd.vol()->rootDirStart() << endl;
  cout << pstr("dataStartBlock: ") << sd.vol()->dataStartBlock() << endl;
  if (sd.vol()->dataStartBlock() % eraseSize) {
    cout << pstr("Data area is not aligned on flash erase boundaries!\n");
    cout << pstr("Download and use formatter from www.sdsd.card()->org/consumer!\n");
  }
}
//------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  SPI.setMOSI(7);
  SPI.setSCK(14);
  while(!Serial) {}  // wait for Leonardo

  // use uppercase in hex and use 0X base prefix
  cout << uppercase << showbase << endl;

  // pstr stores strings in flash to save RAM
  cout << pstr("SdFat version: ") << SD_FAT_VERSION << endl;
  if (DISABLE_CHIP_SELECT < 0) {
    cout << pstr(
      "\nAssuming the SD is the only SPI device.\n"
      "Edit DISABLE_CHIP_SELECT to disable another device.\n");
  } else {
    cout << pstr("\nDisabling SPI device on pin ");
    cout << int(DISABLE_CHIP_SELECT) << endl;
    pinMode(DISABLE_CHIP_SELECT, OUTPUT);
    digitalWrite(DISABLE_CHIP_SELECT, HIGH);
  }
  cout << pstr("\nAssuming the SD chip select pin is: ") <<int(SD_CHIP_SELECT);
  cout << pstr("\nEdit SD_CHIP_SELECT to change the SD chip select pin.\n");
}
//------------------------------------------------------------------------------
void loop() {
  // read any existing Serial data
  while (Serial.read() >= 0) {}

  // pstr stores strings in flash to save RAM
  cout << pstr("\ntype any character to start\n");
  while (Serial.read() <= 0) {}
  delay(400);  // catch Due reset problem
  
  uint32_t t = millis();
  // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
  // breadboards.  use SPI_FULL_SPEED for better performance.
  if (!sd.cardBegin(SD_CHIP_SELECT, SPI_HALF_SPEED)) {
    sdErrorMsg("\ncardBegin failed");
    return;
  }
  t = millis() - t;
  
  cardSize = sd.card()->cardSize();
  if (cardSize == 0) {
    sdErrorMsg("cardSize failed");
    return;
  }
  cout << pstr("\ninit time: ") << t << " ms" << endl;
  cout << pstr("\nCard type: ");
  switch (sd.card()->type()) {
    case SD_CARD_TYPE_SD1:
      cout << pstr("SD1\n");
      break;

    case SD_CARD_TYPE_SD2:
      cout << pstr("SD2\n");
      break;

    case SD_CARD_TYPE_SDHC:
      if (cardSize < 70000000) {
        cout << pstr("SDHC\n");
      } else {
        cout << pstr("SDXC\n");
      }
      break;

    default:
      cout << pstr("Unknown\n");
  }
  if (!cidDmp()) return;
  if (!csdDmp()) return;
  uint32_t ocr;
  if (!sd.card()->readOCR(&ocr)) {
    sdErrorMsg("\nreadOCR failed");
    return;    
  }
  cout << pstr("OCR: ") << hex << ocr << dec << endl;
  if (!partDmp()) return;
  if (!sd.fsBegin()) {
    sdErrorMsg("\nFile System initialization failed.\n");
    return;
  }
  volDmp();
}
 
My long term plan involves SoundFont. That plan may change, but so far, I've looked at a LOT of audio stuff and the SoundFont format seems to have by far the most good quality wavetable sound libraries published. At least a few very good, completely free SoundFont libraries exist covering all the general MIDI sounds. If there are other formats used to publish wavetable synthesis sounds, I'm certainly willing to consider them. However, I'm not willing to consider legacy formats, so if any reading this has a lot of nostalgia for Amiga, please know I'm not considering MOD format (but I wouldn't be opposed to accepting a contribution to play MOD...)

Of course, you're not going to load a SoundFile file directly into Teensy's flash memory, or even the SPI Flash chip. Some sort of conversion utility will load the SoundFile file and let you choose which voice you wish to use. It'll need to have some GUI controls to let you trade-off size vs quality in a variety of ways. Then it'll output raw data, optimized for the library, which you can copy into your sketch, or (someday) put onto the SPI Flash.

SoundFont is complex, but at least it's an open and (fairly) well documented file format. I'll attach the spec.

Chapter 9 is probably the most interesting part to read first. The diagram on page 48 shows most of the components needed. The "Oscillator" part actually plays an attack sample, followed by looping a waveform sample. Both are resampled to the desired note.

Hey Paul,

I am curious if you considered the MIDI-DLS standard.

The specification is here:
http://www.midi.org/techspecs/dls/dls1v11b.pdf

And a high level overview of the two formats in one article:
http://www.emusician.com/gear/1332/downloadable-sounds/31654

Both standards are based on a RIFF file format, and seem to be very similar.

It seems that if your objective is to have many freely available sound packages for whatever format Teensy supports, DLS might be a viable alternative, as there are a few software packages out there which can convert between the two, with one big BUT that comes from the previous article:

DLS sound libraries are more difficult to find, but programs such as Awave and Audio Compositor allow you to open a SoundFont and save it as a DLS Level 1 or DLS Level 2 file. Be aware, however, that when you convert from a SoundFont to a DLS Level 1 file, you lose any filtering or layering programmed into the SoundFont patch.

That being said, the fact that DLS is part of the Open MIDI standard makes me 'feel' a bit better, instead of using something that is created by Creative Labs, but it seems that neither have changed all too much since their inception in the 90s.

The reason the MIDI DLS standard caught my eye was the connection graph. I am still reading through the spec to see exactly how they differ aside from DLS lacking Chrous and Reverb built in (I am not sure if they are the same, but the soundfont graphic being more abstracted), but here they are side by side:
MIDI DLS:
Screen Shot 2015-03-04 at 10.15.33 AM.png

SoundFont:
Screen Shot 2015-03-04 at 10.15.39 AM.png
 
I must admit, this is the first I've heard of MIDI DLS.

Are there sites with free sound files?


Here are a few:

http://www.ixbt.com/multimedia/dlsfury-eng.html
http://www.ronimusic.com/smp_ios_dls_files.htm

This last one has a whole account of trying to convert SF2 to DLS files:
http://www.oocities.org/siliconvalley/peaks/1370/dls/index.html

An apparently there is a file called Gm16.dls which are Roland GS samples licensed by Microsoft, and exist on the Windows 98 CDROM (maybe later versions too? I dunno)

To be honest though, I think you just need one or two sound fonts to start, the most important thing in my opinion is which format is easier to create new wavetable banks for? Could this be wrapped into the web tool? If the format is easy to make content for, people (like me) WILL make new wavetable libraries.

Another thing to consider:

There is a guy Oliver who runs a company called Mutable Instruments who started out by offering an open source synth called the Shruthi-1. That was based on an ATMega644p
http://mutable-instruments.net/shruthi1/build

He did wavetables right. One key feature was building sweeptable tables, which meant being able to shift between two or more tables to blend sounds, the one key factor here though was that the filter section was completely analog, but back then he was limited by processor power.

All the code is up on Github: https://github.com/pichenettes/shruthi-1

And some sound samples of the Shruthi-1 wavetables: https://soundcloud.com/mutable-instruments/some-of-the-shruthi-1-wavetables


These days, Oliver has been building some amazing new synths based on ARM Cortex M1 and M4 chips:
They are also open source:
https://github.com/pichenettes/eurorack

And they include braids which is a wavetable synth:
http://mutable-instruments.net/modules/braids

They have been all the rage in the synth community. I was at NAMM this past January and their gear was all the rage, because even though they are mostly digital in an analog realm (the modular synth realm that is), they sound amazing ;D

Totally worth giving these guys a look.
 
Last edited:
And one other note,

The DLS and SF2 formats are designed to be sound banks for complete sounds that are ready to go, to be implemented as instruments in and of themselves.

As part of a full DSP library, I am not certain they make sense since they will constrict the user into using the filters, panning, envelopes, LFO, CC modulation and pitch bend as specified by the soundfont or DLS file.

Since these are all part of a full DSP library, it seems to me that the wavetable synthesis should be separate from all other components of the synthesis chain, especially in a CPU constrained environment.

Both SF2 and DLS have only low pass filters in their specifications, and each soundfont will include pre-sets for these filters inbuilt. In order to use a high pass, or bandpass, or a different type of filter, you will have to be able to access the digital waveform alone, and patch it out separately, and you might end up having to use two filters in the same code, one which is not even used at all. If that is the case, then all either of these formats are going to get you are pre-sets that make it easy to make sounds.

If instead the wavetable synthesis portion only included the digital oscillator, that would allow users to patch that oscillator into any processing they wish. If readily available sample sets are the goal, then tools or documentation needs to be created to take a WAV sample and convert it into a wavetable sample (with separate start and loop segments). That would result in far more available sample sets in the future, instead of locking us into what is available with the seriously aging SF2 and DLS communities, which don't seem to have anything new that has been created since after 2005.

I would love to work on a library of pre-sets that would allow people to have code ready to go that produces awesome sounds. I was trying to work on this, and posted in another thread when I had issues including the Audio library in another library of my own creation. I found though, through one of your posts earlier in this thread that that would not be possible.
 
Last edited:
There is also a book by Miller Pluckette (the guy who created Max and Pure Data) called The Theory and Technique of Electronic Music which has a ton of theory behind the implementation of wavetable synthesis:

http://msp.ucsd.edu/techniques/latest/book.pdf

It is also an exhaustive reference for all things synth and DSP. I found it through the guy who makes Valhalla DSP plugins https://valhalladsp.com/ which are considered some of the best reverb VSTs available today.
 
I've done a WAV file streaming test, using a hobbytronics standard Transcend 4gb MicroSDHC. No speed rating on the packaging at all im afraid.

Summary
44Khz stereo = 3 voices
44khz mono = 5 voices
22khz stereo = 3 voices
22khz mono = 5 voices

Hi — would it be okay for you to share the code you used to run these tests? I'd like to benchmark some SD cards that we've sourced.

Thanks

Julian
 
Hi — would it be okay for you to share the code you used to run these tests? I'd like to benchmark some SD cards that we've sourced.

Thanks

Julian

I'd be happy to, but it is not clever, i just wired up 8 or 9 buttons then each one added another line playing another file to the "stack" or files to play.

Nevertheless, I will post it up when I get back - I'm on holiday and wont be back to my other pc until next week 14th/15th March
 
Thanks for your suggestion, But I already searched the file at there; but I am not getting any door bell wave file based on 16bit, 16khz. Mostly available are 44Khz and above frequency.
 
Yes, of course almost all published sounds are going to be at 44.1 or 48 kHz sample rate. People don't usually record anything at 16 kHz sample rates!

If you want a slower sample rate, you're going to have to convert (downsample) the data using an audio editing program like Audacity or SOX.
 
Hi

New to the forum and to Teensy.

Before discovering the Audio library and this forum I bought a Teensy LC with the intent of trying to make an Octave down effect (and if unsuccessful use the Teensy for something else). I'm a programmer by trade and an electronics nerd by night (though mostly analog so far).

I have an LC (arrived today), can I still use this library or does it need the 3.1?

Anyone made an Octave down filter for this? My idea was to have a circular buffer (as large as I could fit in memory) and sample at a steady pace (was hoping for something like 20khz) and then use the DAC to play back the sample at half the speed. Then when possible, at "zero" crossings I was thinking, skip a portion of the buffer to play catch up with the input, so to speak.

All this would go in a guitar stompbox (effects pedal that is). Analog octave downs exist, but they suffer from numerous drawbacks, and existing digital ones are expensive and not diy'able for most people. I think something like the Teensy could fill a void there. In the guitar world great audio fidelity is welcome, but we're quite used to quirks, so if the LC could be made to do this it would be perfect even if it wasn't perfect. The thing that would put people off would be if the output was much delayed from the input.
 
The Audio library depends on several features of the Teensy 3.1 - Cortex M4 DSP instuctions, RAM size. It is not compatible with LC and making it compatible would be a substantial rewrite with much lower performance, also severely constrained by the low amount of RAM.

Your lower octave project sounds interesting, and could well be doable on LC. Most low octave circuits seem to just turn the input into a square wave and use a clock divider, which means you lose all the tonality.
 
Its also worth noting that Teensy LC and Teensy 3.1 have closely compatible pinouts, so it is easily possible to start a project with LC, discover you need more speed/RAM/flash and upgrade to a Teensy 3.1, keeping compatibility with your existing circuit board design. (To a certain extent the reverse is also true, unless your design uses the surface mount pads underneeth Teensy 3.1).
 
That much I've understood. Started the coding. I've done some Arduino programming before, but I guess this project would benefit from straight C/C++. Found some examples to start from but I've got to learn more about C programming for these devices (being a C# programmer I'm not used to caring about interrupts and bit shifting :rolleyes: ).
 
Hi Perrow,

Reducing the sample rate by 50% by pushing every other sample received back out of the DAC should result in a quick/easy octave down effect, but it'll sound digital and crushy.

I like the idea of looking for zero crossings and nibbling the stream into grains, but as you say the challenge will be getting the work done in perceivable real-time.

Here is some interesting time stretching code:
http://blogs.zynaptiq.com/bernsee/download/

Good luck with this fun project!
Alex
 
It'd be half the sample rate, but then it'd also be half the frequency. And if we're sampling a guitar for the purpose of emulating a bass guitar, we'll seldom be playing up in the dusty area of the fretboard (i.e. the incoming signal will be fairly low frequency).

Low E string of a guitar is about 80hz, say we keep below four octaves above that, that gives a max input frequency of 1280, say 2560 with some overtones produced. If we can sample at 10 to 15 khz we should be golden. If higher frequencies of the input gives us problems we can put a low pass filter on the incoming signal. The out signal can be low passed too if needed to remove some of the digital sound.

If there's cpu cycles to spare we could interpolate the "between" samples to make the output slightly better.

I'll have to experiment with it to see what the hardware can do.
 
Check out the pitch shifting code of the FV-1 effects chip. http://www.spinsemi.com/knowledge_base/pitch_sft.html and http://www.spinsemi.com/forum/viewtopic.php?t=266&sid=f487c0b3a7389493867619accdf2e75b etc.. I believe there is some good info on explaining how to employ their really clean pitch shifter with delay lines. I was able to get a pitch shifter type thing on the teensy by running two arbitrary wavetables that are updating their table every so often, I think it was like 10 lines to write.. I will try and dig it up :)
 
Status
Not open for further replies.
Back
Top