Best way to incorporate wav audio into a module?

What’s the best way to code audio samples (wav files) into a module?

I’ve taken a look at the Autodafe Drum modules on GitHub, as an example, and it looks like the audio files are converted to some kind of Hex Dump in a .h file:

unsigned char <sample name>[] = {
  0x0f, 0x00, 0x97, 0xff, 0xdf, 0xff, 0xe9, 0xff, 0x31, 0x00, 0x65, 0x00,
  0x4f, 0x00, 0x8e, 0x01, 0x21, 0x00, 0x80, 0x19, 0x89…};
unsigned int <sample name>_len = 11996;

I’m just starting to learn c++ for the purpose of developing a few ideas into modules and was wondering how to extract this hex data from wav files. Is there an app, utility, command line execution, etc to do this for several files at the same time, like a batch process? I’m on MacOS by the way.

Also, is this hex dump representation of the audio sample, the actual audio, and it will play as expected when called properly in a module’s source code?

Thanks to all who offer advice or help to point me in the right direction :slight_smile:

1 Like

Why not give the user the ability to replace samples?

That might be a consideration, as an additional feature, but not really the purpose of the module I’d like to develop.

Could you offer advice about the audio file conversion I mentioned above?

You can use the AudioFile.h by Adam Stark to read audio files into memory. From my perspective, there’s no need to put sample data into a .h file.

1 Like

Thank you @Ahornberg! I will give this a look.

From the sounds of it, you feel there’s a better way to embed sampled audio into a module. What other method might you recommend? Thank you again :slight_smile:

I would not embed audio files. I would load them as resources in the same way other resources as SVG-files and fonts are loaded. This will make your code much more maintainable. Make yourself comfortabel with the rack::asset part of the API.

https://vcvrack.com/docs-v2/namespacerack_1_1asset

1 Like

Thank you again!

This also seems like a plausible solution, and from the looks of it, the below might work to reference a sample, if the sample was stored in a samples folder in the plugin root?

asset::plugin(pluginInstance, "samples/00.wav") // "/<Rack user dir>/plugins/MyPlugin/samples/00.wav"

Then the sample would have to be called properly to be played back by a trig or gate?

The samples would also be “public” with this method, which has its pros and cons.

asset::plugin gives you the path to your samle. In the constructor of your module, use AudioFile.h by Adam Stark to load the sample (see the sample code provided by Adam Stark).

By the way, in my code everything is public. I organize the code for my modules by separating all declarations in .h files and implementations in .cpp files. Maybe take a look at my code:

1 Like

Hello! It’s been a while since I’ve done anything like this, but at one point I was able to embed audio similar to the Autodafe Drum modules. I also did this for drums. Here you can see my encoded audio:

I created a web page with instructions for preparing the samples:

@extends('products/equation_composer/equation_composer')

@section('page_title')
	@parent - Converting Samples
@stop

@section('page_js')
	<script>
		$('document').ready(function(){

			$('#submit').click(function(e) {

				e.preventDefault();
				
				var input_textarea = $('textarea#csv-input');
				var output_textarea = $('textarea#csv-output');

				values_csv = $(input_textarea).val();
				values_array = values_csv.split(/	\n?/);

				$(values_array).each(function(index, value) {

					var int_value = Math.floor(((parseFloat(value) + 1) / 2) * 4096);

					if(output_textarea.val() == "")
					{
						$(output_textarea).val(int_value);
					}
					else
					{

						$(output_textarea).val($(output_textarea).val() + "," + int_value);
					}

				});
			});

		});	
	</script>
@stop

@section('page_content')

	<style>
		textarea {
			width: 1024px;
			height: 200px;
		}	
	</style>

	<h1>Prepping samples</h1>

	<p>Instructions for prepping samples for ModuleSamplePlayer.</p>

	<ol>
		<li>Open sample in Wavosaur</li>
		<li>Convert sample to mono (drop one of the channels, don't mix the two)</li>
		<li>Convert the sample to 22050.  Make sure the sample is 16 bit.</li>
		<li>Export sample as .txt</li>
		<li>Copy exported .txt (which should look like a single column of numbers) into the input textarea below</li>
		<li>Convert, and paste results into GlobalSamples.cpp</li>
	</ol>
	
	<p>Each Kick/Snare/Hihat kit takes up about 10% of program memory</p>
	
	<label>Paste exported sample data here...</label>
	<br>
	<textarea id="csv-input"></textarea>
	<br>
	<input type="submit" id="submit" value="convert">
	<br><br>
	<label>Converted sample data will be placed here.</label>
	<br>
	<textarea id="csv-output"></textarea>	
@stop

The Javascript code (also in that snippet above) converted the RAW .wav output to 16 bit unsigned integers between 0 and 4096.

The code to playback these samples is here: EquationComposer/ModuleSamplePlayer.cpp at master · clone45/EquationComposer · GitHub

You will probably need to rewrite the counter which indexes the sample position and use the sample rate (args.sampleRate) setting in rack to get the correct playback speed. And you probably won’t need to use fixed floating point math, which should greatly simplify your code.

I hope this gets you started!

1 Like

This is great, thank you! I will most definitely take a look at your repo, there’s lots to learn here. I appreciate all your advice :slight_smile:

Thank you Bret :slight_smile: I will also take a look at this!

1 Like

If you get really stuck, let me know and I’ll write some sample code for you in VCV Rack. :bowing_man:

1 Like

That’s awfully generous of you! I still got some ways to go, but might just take you up on that. Thank you :slight_smile:

Between the two methods suggested in this thread so far, which has the ability to be read faster and which takes up less resources?

Ahornberg’s strategy has a lot of advantages and should probably be considered the “proper” way to do it. With his method, you’ll end up with far better audio quality and it will be a lot simpler to implement.

The only drawback to his approach is, as you mentioned, the sample will be made public. You might be able to get crazy and do some sort of encryption/decryption of the audio files if that’s an issue, but that’s beyond me.

I believe that both will take up around the same amount of resources, but I’m not sure. There could be “program space” memory limitations that I’m not aware of.

Both can be lightning fast. Using Ahornberg’s strategy, you would want to pre-load the samples into memory and keep them there. Once loaded from disk, accessing the samples will be near instantaneous since it’s analogues to reading memory.

Some of the drawbacks of the “hard coded” sample strategy are:

  • Maintaining a high quality of audio would require gigantic C++ files. I would be surprised if it worked unless you are using super tiny samples.

  • You would probably need a fairly intensive process to convert normal audio files into the code.

If I were you, I would take Ahornberg’s approach. If you need to protect the files, I’d look into some type of encryption.

You might ask, “If so, why did you take the approach of coding in the samples?” That code is written for a microcontroller that didn’t have external storage, so it was my only option. :speak_no_evil:

Hope this helps! If you need examples of using AudioFile.h, I’m fairly familiar with it!

Cheers, Bret

2 Likes

This is a great response! Thank you so much for taking the time to educate me and provide examples, both you and @Ahornberg have given me a lot to consider and review.

I’ll spend the next little while familiarizing myself with all this, and continuing my journey into C++. I’m currently watching The Cherno’s YouTube series about C++, and am as far in as learning about strings, about 30 videos or so. This is all very new to me, so again, I do appreciate your detailed responses!

One big question I’ve had regarding C++ was the order/flow of the code, and how important it is. After doing some searching it seems it’s not always mandatory, but in some instances it is, as certain declarations and definitions need to be made before we can start calling on them.

The other more important question I’ve been asking myself, is how to pass signal/voltage from one process to another.

For example, if I’m designing a module that has a signal flow where a filter’s output passes through a VCA, and that VCA is also modulated by an envelope. I would need to define the behaviour for each section, in the right order of signal flow, and then call the right output voltages to be present at the intended input. Whether that’s the trig or gate to fire the envelope, or filter output to feed a VCA, the integer or float variable needs to exist before being called, using syntaxes like .getVoltage, or .getInput or .getOutput etc (these are “calls” from what I make of it).

So I’ve been reviewing a lot of repos on GitHub, and the C++ code is making more and more sense the deeper I get into this video series. I also have been looking at the online Rack API as a sort of manual as well, as much as it mostly goes over my head at this point.

<off-topic>For a beginner, I would recommend to organize the source code strictly in header-files that include all declarations, and all implementation goes into the cpp-files.</off-topic>

Back to the topic: As @clone45 said, the approach to put audio-data into code is for systems that lack a dedicated file-system where audio-files could be load from.

1 Like

That is great advice in general, and everywhere I’ve worked had similar “coding conventions”. But it’s not how VCV is written, so maybe not the best beginner advice? afaik VCV modules are often a single CPP file that has module declaration, module implementation, widget declaration and widget implementation all in one file. They always use struct where most would use class, and struct members are always public. I think there are some good reasons for that in VCV, and some of the others… I’m ok with it

If you do load wav files from the file system, make sure to test it on windows with a path that includes non-ascii Unicode characters. It’s super easy to have bugs in that situation.

lastly, I don’t know what the “best” wave loader/parser is. I’ve always used dr_wav and I know some others do.

2 Likes

Maybe I should edit the thread title, as I’ll most likely have other code related questions, and would prefer to keep all this information in one place.

Please forgive my ignorance. I have taken a hard look at your TapeRecorder.cpp and the only part that stuck out to me, with regards to reading a wav file from a file path is this:

void TapeRecorder::onAdd(const AddEvent& e) {
	// std::string path = system::join(createPatchStorageDirectory(), "wavetable.wav");
	// Read file...
	DEBUG("onAdd");
}

I have also looked at Adam Stark’s AudioFile.h, and it seems as though this section is what relates to reading the wav file:

template <class T>
bool AudioFile<T>::load (std::string filePath)
{
    std::ifstream file (filePath, std::ios::binary);
    
    // check the file exists
    if (! file.good())
    {
        reportError ("ERROR: File doesn't exist or otherwise can't load file\n"  + filePath);
        return false;
    }
    
    std::vector<uint8_t> fileData;

	file.unsetf (std::ios::skipws);

	file.seekg (0, std::ios::end);
	size_t length = file.tellg();
	file.seekg (0, std::ios::beg);

	// allocate
	fileData.resize (length);

	file.read(reinterpret_cast<char*> (fileData.data()), length);
	file.close();

	if (file.gcount() != length)
	{
		reportError ("ERROR: Couldn't read entire file\n" + filePath);
		return false;
	}
    
    // get audio file format
    audioFileFormat = determineAudioFileFormat (fileData);
    
    if (audioFileFormat == AudioFileFormat::Wave)
    {
        return decodeWaveFile (fileData);
    }
    else if (audioFileFormat == AudioFileFormat::Aiff)
    {
        return decodeAiffFile (fileData);
    }
    else
    {
        reportError ("Audio File Type: Error");
        return false;
    }
}

Again, forgive me. I can’t find where Adam Stark’s header is included in your code examples, so it’s difficult for me to track where everything is coming from. Chalk this up to growing pains on my end. I could only assume at this point, that you’re referencing namespaces or calls from his file. I just can’t track where just yet.

From the sounds of it, if I’m considering quality of sample playback and other reasons, such as the ability to make adjustments, this does seem like the way to go. What’s got me scratching my head even more so now, is having to rely on the AudioFile.h or like what @Squinky mentioned above (dr wav). From the sounds of it. There doesn’t seem to be a more direct and simplified way to read samples from a file path and just have my module call those samples directly using the Rack API.

I do appreciate all your advice, and assume all responsibility for my own confusion as a result of my inexperience.

That’s true, because actually this and other features on my modules are work in progress and I don’t want to commit a version to my git-repo that doesn’t work well.

Here are the examples provided by Adam Stark himself:

I “simply” grabbed the code in loadAudioFileAndProcessSamples() and modyfied it to my needs.

1 Like

Thank you Bruce! I don’t personally have a non Mac machine to test on, but may be able to get access to one, when I’m actually able to put something together here. Being aware of this potential issue is very helpful :slight_smile:

Regarding sample loading, as I mentioned above. I would have thought there was a more direct way to have a folder of samples, and just call them in the .cpp file. This was why I initially referenced the Autodafe drum modules, as Antonio had his samples in .h files converted into hex code, and just references them in the code when they should be loaded and played.

For some context, I’m looking to rapidly fire off samples and jump between different sets of samples quite quickly. They are very small samples, so maybe converting them to hex .h files would be worth a shot as well. Either way, it’s good to know both methods.

I did take a look at the Fundamental WTVCO module, as I see that’s what’s being used for sample loading there, but am having a similar issue tracking where a file might be read from disk.