mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 16:47:36 +00:00
Piano: Add sampler
This commit adds basic support for importing, viewing and playing WAV samples at different pitches. Naming issues: - We are using the Sample struct from Music.h, but also the Sample struct from LibAudio (Audio::Sample). This is a little confusing. set_recorded_sample() finds the peak sample and then divides all the samples by that peak to get a guaranteed min/max of -1/1. This is nice because our other waves are also bound between these values and we can just do the same stuff. This is why we're using Audio::Sample, because it uses floats, whereas Music.h's Sample uses i16s. It's a little annoying that we have to use a mixture of floats and doubles though. For playback at lower frequencies, we're calculating in-between samples, rather than just playing samples multiple times. Basically, you get the current sample and add the difference between the current sample and the next sample multiplied by the distance from the current sample. This is like drawing the hypotenuse of a right-angled triangle.
This commit is contained in:
parent
591870c7b4
commit
9997b0dbf5
9 changed files with 317 additions and 35 deletions
|
@ -26,6 +26,7 @@
|
|||
*/
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include <LibAudio/WavLoader.h>
|
||||
#include <limits>
|
||||
#include <math.h>
|
||||
|
||||
|
@ -94,6 +95,9 @@ void AudioEngine::fill_buffer(FixedArray<Sample>& buffer)
|
|||
case Wave::Noise:
|
||||
val = (volume * m_power[note]) * noise();
|
||||
break;
|
||||
case Wave::RecordedSample:
|
||||
val = (volume * m_power[note]) * recorded_sample(note);
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
@ -143,6 +147,37 @@ void AudioEngine::reset()
|
|||
m_previous_column = horizontal_notes - 1;
|
||||
}
|
||||
|
||||
String AudioEngine::set_recorded_sample(const StringView& path)
|
||||
{
|
||||
Audio::WavLoader wav_loader(path);
|
||||
if (wav_loader.has_error())
|
||||
return String(wav_loader.error_string());
|
||||
auto wav_buffer = wav_loader.get_more_samples(60 * sample_rate * sizeof(Sample)); // 1 minute maximum
|
||||
|
||||
if (!m_recorded_sample.is_empty())
|
||||
m_recorded_sample.clear();
|
||||
m_recorded_sample.resize(wav_buffer->sample_count());
|
||||
|
||||
float peak = 0;
|
||||
for (int i = 0; i < wav_buffer->sample_count(); ++i) {
|
||||
float left_abs = fabs(wav_buffer->samples()[i].left);
|
||||
float right_abs = fabs(wav_buffer->samples()[i].right);
|
||||
if (left_abs > peak)
|
||||
peak = left_abs;
|
||||
if (right_abs > peak)
|
||||
peak = right_abs;
|
||||
}
|
||||
|
||||
if (peak) {
|
||||
for (int i = 0; i < wav_buffer->sample_count(); ++i) {
|
||||
m_recorded_sample[i].left = wav_buffer->samples()[i].left / peak;
|
||||
m_recorded_sample[i].right = wav_buffer->samples()[i].right / peak;
|
||||
}
|
||||
}
|
||||
|
||||
return String::empty();
|
||||
}
|
||||
|
||||
// All of the information for these waves is on Wikipedia.
|
||||
|
||||
double AudioEngine::sine(size_t note)
|
||||
|
@ -188,6 +223,21 @@ double AudioEngine::noise() const
|
|||
return w;
|
||||
}
|
||||
|
||||
double AudioEngine::recorded_sample(size_t note)
|
||||
{
|
||||
int t = m_pos[note];
|
||||
if (t >= m_recorded_sample.size())
|
||||
return 0;
|
||||
double w = m_recorded_sample[t].left;
|
||||
if (t + 1 < m_recorded_sample.size()) {
|
||||
double t_fraction = m_pos[note] - t;
|
||||
w += (m_recorded_sample[t + 1].left - m_recorded_sample[t].left) * t_fraction;
|
||||
}
|
||||
double recorded_sample_step = note_frequencies[note] / middle_c;
|
||||
m_pos[note] += recorded_sample_step;
|
||||
return w;
|
||||
}
|
||||
|
||||
static inline double calculate_step(double distance, int milliseconds)
|
||||
{
|
||||
if (milliseconds == 0)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue