wearyhacker
Well-known member
I am working on some software for an electronic concertina being constructed by a friend of mine. The instrument will look like and play like a real concertina.
He has already captured a number of samples of real concertinas of various types.
Our current workflow for producing soundfonts is as follows.
1. Record an instrument.
2. Decide which samples to use.
3. Normalise the selected samples using DAW such as Audacity.
4. Produce a soundfont using Polyphone.
5. Produce c++ source code using the soundfont decoder from
https://github.com/PaulStoffregen/Wavetable-Synthesis.git.
6. Compile and link these files into the firmware.
The following is my crazy idea.
I am thinking it would not be too difficult to write a small utility to
serialise the the static data that is defined in these files so that it
could be loaded dynamically from the sd card and then used by the
currently running firmware.
For a proof of concept we could basically link a wavetable.cpp and a wavetable.h into a simple c++ program that does the serialisation to a file. A more complex approach would be to use something like flex and bison to parse the supplied files, and then do the serialisation.
What do you think?
The structures are quite simple and we can fix the pointers at load
time.
The interface to the wavetable is passed in the following structure
This is what this structure looks like for irish2
The sample_count field is the number of samples(4). The
sample_note_ranges is a pointer to an array of four 8 bit numbers. I
would have to look further into the code to work out these are handled
I would assume these are treated two 4 bit numbers. The sample data
field is pointer to an array of 4 structures.
This is what this structure looks like for irish2
The sample_count field is the number of samples(4). The
sample_note_ranges is a pointer to an array of four 8 bit numbers. I
would have to look further into the code to work out these are handled
I. would assume these are treated two 4 bit numbers. The sample data
field is pointer to an array of 4 structures.
The sample_data struct in irish2 looks like this.
You can see that each one point to a different block of bit bit audio
samples.
It should not be to difficult to load the dumps of all this from a file
and modify the fields in a instrument_data structure to point to the the
relevant arrays of structures. Then modify the sample pointer in each of
sample_data structure to point to the correct block of raw sample data.
What do you guys think?
Am I being completely stupid?
There is a similar approach suggested here.
He has already captured a number of samples of real concertinas of various types.
Our current workflow for producing soundfonts is as follows.
1. Record an instrument.
2. Decide which samples to use.
3. Normalise the selected samples using DAW such as Audacity.
4. Produce a soundfont using Polyphone.
5. Produce c++ source code using the soundfont decoder from
https://github.com/PaulStoffregen/Wavetable-Synthesis.git.
6. Compile and link these files into the firmware.
The following is my crazy idea.
I am thinking it would not be too difficult to write a small utility to
serialise the the static data that is defined in these files so that it
could be loaded dynamically from the sd card and then used by the
currently running firmware.
For a proof of concept we could basically link a wavetable.cpp and a wavetable.h into a simple c++ program that does the serialisation to a file. A more complex approach would be to use something like flex and bison to parse the supplied files, and then do the serialisation.
What do you think?
The structures are quite simple and we can fix the pointers at load
time.
The interface to the wavetable is passed in the following structure
Code:
struct instrument_data {
const uint8_t sample_count;
const uint8_t* sample_note_ranges;
const sample_data* samples;
};
Code:
const AudioSynthWavetable::instrument_data irish2 = {4, irish2_ranges, irish2_samples };
sample_note_ranges is a pointer to an array of four 8 bit numbers. I
would have to look further into the code to work out these are handled
I would assume these are treated two 4 bit numbers. The sample data
field is pointer to an array of 4 structures.
This is what this structure looks like for irish2
Code:
const AudioSynthWavetable::instrument_data irish2 = {4, irish2_ranges, irish2_samples };
sample_note_ranges is a pointer to an array of four 8 bit numbers. I
would have to look further into the code to work out these are handled
I. would assume these are treated two 4 bit numbers. The sample data
field is pointer to an array of 4 structures.
Code:
struct sample_data {
// SAMPLE VALUES
const int16_t* sample;
const bool LOOP;
const int INDEX_BITS;
const float PER_HERTZ_PHASE_INCREMENT;
const uint32_t MAX_PHASE;
const uint32_t LOOP_PHASE_END;
const uint32_t LOOP_PHASE_LENGTH;
const uint16_t INITIAL_ATTENUATION_SCALAR;
// VOLUME ENVELOPE VALUES
const uint32_t DELAY_COUNT;
const uint32_t ATTACK_COUNT;
const uint32_t HOLD_COUNT;
const uint32_t DECAY_COUNT;
const uint32_t RELEASE_COUNT;
const int32_t SUSTAIN_MULT;
// VIRBRATO VALUES
const uint32_t VIBRATO_DELAY;
const uint32_t VIBRATO_INCREMENT;
const float VIBRATO_PITCH_COEFFICIENT_INITIAL;
const float VIBRATO_PITCH_COEFFICIENT_SECOND;
// MODULATION VALUES
const uint32_t MODULATION_DELAY;
const uint32_t MODULATION_INCREMENT;
const float MODULATION_PITCH_COEFFICIENT_INITIAL;
const float MODULATION_PITCH_COEFFICIENT_SECOND;
const int32_t MODULATION_AMPLITUDE_INITIAL_GAIN;
const int32_t MODULATION_AMPLITUDE_SECOND_GAIN;
};
Code:
static const AudioSynthWavetable::sample_data irish2_samples[4] = {
{
(int16_t*)sample_0_irish2_gfffG55, // sample
true, // LOOP
12, // LENGTH_BITS
(1 << (32 - 12)) * WAVETABLE_CENTS_SHIFT(0) * 8000.0 / WAVETABLE_NOTE_TO_FREQUENCY(55) / AUDIO_SAMPLE_RATE_EXACT + 0.5, // PER_HERTZ_PHASE_INCREMENT
((uint32_t)2563 - 1) << (32 - 12), // MAX_PHASE
((uint32_t)2562 - 1) << (32 - 12), // LOOP_PHASE_END
(((uint32_t)2562 - 1) << (32 - 12)) - (((uint32_t)1746 - 1) << (32 - 12)), // LOOP_PHASE_LENGTH
uint16_t(UINT16_MAX * WAVETABLE_DECIBEL_SHIFT(0)), // INITIAL_ATTENUATION_SCALAR
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DELAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // ATTACK_COUNT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // HOLD_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DECAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // RELEASE_COUNT
int32_t((1.0 - WAVETABLE_DECIBEL_SHIFT(0.0)) * AudioSynthWavetable::UNITY_GAIN), // SUSTAIN_MULT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // VIBRATO_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // VIBRATO_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // VIBRATO_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // VIBRATO_COEFFICIENT_SECONDARY
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // MODULATION_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // MODULATION_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // MODULATION_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // MODULATION_PITCH_COEFFICIENT_SECOND
int32_t(UINT16_MAX * (WAVETABLE_DECIBEL_SHIFT(0) - 1.0)) * 4, // MODULATION_AMPLITUDE_INITIAL_GAIN
int32_t(UINT16_MAX * (1.0 - WAVETABLE_DECIBEL_SHIFT(0))) * 4, // MODULATION_AMPLITUDE_FINAL_GAIN
},
{
(int16_t*)sample_1_irish2_gfffF66, // sample
true, // LOOP
10, // LENGTH_BITS
(1 << (32 - 10)) * WAVETABLE_CENTS_SHIFT(0) * 8000.0 / WAVETABLE_NOTE_TO_FREQUENCY(66) / AUDIO_SAMPLE_RATE_EXACT + 0.5, // PER_HERTZ_PHASE_INCREMENT
((uint32_t)761 - 1) << (32 - 10), // MAX_PHASE
((uint32_t)760 - 1) << (32 - 10), // LOOP_PHASE_END
(((uint32_t)760 - 1) << (32 - 10)) - (((uint32_t)565 - 1) << (32 - 10)), // LOOP_PHASE_LENGTH
uint16_t(UINT16_MAX * WAVETABLE_DECIBEL_SHIFT(0)), // INITIAL_ATTENUATION_SCALAR
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DELAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // ATTACK_COUNT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // HOLD_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DECAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // RELEASE_COUNT
int32_t((1.0 - WAVETABLE_DECIBEL_SHIFT(0.0)) * AudioSynthWavetable::UNITY_GAIN), // SUSTAIN_MULT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // VIBRATO_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // VIBRATO_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // VIBRATO_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // VIBRATO_COEFFICIENT_SECONDARY
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // MODULATION_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // MODULATION_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // MODULATION_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // MODULATION_PITCH_COEFFICIENT_SECOND
int32_t(UINT16_MAX * (WAVETABLE_DECIBEL_SHIFT(0) - 1.0)) * 4, // MODULATION_AMPLITUDE_INITIAL_GAIN
int32_t(UINT16_MAX * (1.0 - WAVETABLE_DECIBEL_SHIFT(0))) * 4, // MODULATION_AMPLITUDE_FINAL_GAIN
},
{
(int16_t*)sample_2_irish2_gfffF78, // sample
true, // LOOP
10, // LENGTH_BITS
(1 << (32 - 10)) * WAVETABLE_CENTS_SHIFT(0) * 8000.0 / WAVETABLE_NOTE_TO_FREQUENCY(78) / AUDIO_SAMPLE_RATE_EXACT + 0.5, // PER_HERTZ_PHASE_INCREMENT
((uint32_t)837 - 1) << (32 - 10), // MAX_PHASE
((uint32_t)836 - 1) << (32 - 10), // LOOP_PHASE_END
(((uint32_t)836 - 1) << (32 - 10)) - (((uint32_t)544 - 1) << (32 - 10)), // LOOP_PHASE_LENGTH
uint16_t(UINT16_MAX * WAVETABLE_DECIBEL_SHIFT(0)), // INITIAL_ATTENUATION_SCALAR
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DELAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // ATTACK_COUNT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // HOLD_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DECAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // RELEASE_COUNT
int32_t((1.0 - WAVETABLE_DECIBEL_SHIFT(0.0)) * AudioSynthWavetable::UNITY_GAIN), // SUSTAIN_MULT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // VIBRATO_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // VIBRATO_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // VIBRATO_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // VIBRATO_COEFFICIENT_SECONDARY
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // MODULATION_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // MODULATION_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // MODULATION_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // MODULATION_PITCH_COEFFICIENT_SECOND
int32_t(UINT16_MAX * (WAVETABLE_DECIBEL_SHIFT(0) - 1.0)) * 4, // MODULATION_AMPLITUDE_INITIAL_GAIN
int32_t(UINT16_MAX * (1.0 - WAVETABLE_DECIBEL_SHIFT(0))) * 4, // MODULATION_AMPLITUDE_FINAL_GAIN
},
{
(int16_t*)sample_3_irish2_gfffF90, // sample
true, // LOOP
9, // LENGTH_BITS
(1 << (32 - 9)) * WAVETABLE_CENTS_SHIFT(0) * 8000.0 / WAVETABLE_NOTE_TO_FREQUENCY(90) / AUDIO_SAMPLE_RATE_EXACT + 0.5, // PER_HERTZ_PHASE_INCREMENT
((uint32_t)460 - 1) << (32 - 9), // MAX_PHASE
((uint32_t)459 - 1) << (32 - 9), // LOOP_PHASE_END
(((uint32_t)459 - 1) << (32 - 9)) - (((uint32_t)367 - 1) << (32 - 9)), // LOOP_PHASE_LENGTH
uint16_t(UINT16_MAX * WAVETABLE_DECIBEL_SHIFT(0)), // INITIAL_ATTENUATION_SCALAR
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DELAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // ATTACK_COUNT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // HOLD_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // DECAY_COUNT
uint32_t(1.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / AudioSynthWavetable::ENVELOPE_PERIOD + 0.5), // RELEASE_COUNT
int32_t((1.0 - WAVETABLE_DECIBEL_SHIFT(0.0)) * AudioSynthWavetable::UNITY_GAIN), // SUSTAIN_MULT
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // VIBRATO_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // VIBRATO_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // VIBRATO_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // VIBRATO_COEFFICIENT_SECONDARY
uint32_t(0.00 * AudioSynthWavetable::SAMPLES_PER_MSEC / (2 * AudioSynthWavetable::LFO_PERIOD)), // MODULATION_DELAY
uint32_t(8.2 * AudioSynthWavetable::LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT)), // MODULATION_INCREMENT
(WAVETABLE_CENTS_SHIFT(0) - 1.0) * 4, // MODULATION_PITCH_COEFFICIENT_INITIAL
(1.0 - WAVETABLE_CENTS_SHIFT(0)) * 4, // MODULATION_PITCH_COEFFICIENT_SECOND
int32_t(UINT16_MAX * (WAVETABLE_DECIBEL_SHIFT(0) - 1.0)) * 4, // MODULATION_AMPLITUDE_INITIAL_GAIN
int32_t(UINT16_MAX * (1.0 - WAVETABLE_DECIBEL_SHIFT(0))) * 4, // MODULATION_AMPLITUDE_FINAL_GAIN
},
};
samples.
It should not be to difficult to load the dumps of all this from a file
and modify the fields in a instrument_data structure to point to the the
relevant arrays of structures. Then modify the sample pointer in each of
sample_data structure to point to the correct block of raw sample data.
What do you guys think?
Am I being completely stupid?
There is a similar approach suggested here.