1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-07-09 22:25:12 +00:00

Add volume, panning & waveform support for SOUND

This commit is contained in:
Samuel Gomes 2023-06-10 02:24:05 +05:30
parent 947ebdca20
commit 3f16e3c9d1
4 changed files with 114 additions and 125 deletions

View file

@ -25,11 +25,11 @@
AUDIO_DEBUG_PRINT("Condition (%s) failed", #_exp_) AUDIO_DEBUG_PRINT("Condition (%s) failed", #_exp_)
#else #else
# ifdef _MSC_VER # ifdef _MSC_VER
# define AUDIO_DEBUG_PRINT(_fmt_, ...) // Don't do anything in release builds # define AUDIO_DEBUG_PRINT(_fmt_, ...) // Don't do anything in release builds
# else # else
# define AUDIO_DEBUG_PRINT(_fmt_, _args_...) // Don't do anything in release builds # define AUDIO_DEBUG_PRINT(_fmt_, _args_...) // Don't do anything in release builds
# endif # endif
# define AUDIO_DEBUG_CHECK(_exp_) // Don't do anything in release builds # define AUDIO_DEBUG_CHECK(_exp_) // Don't do anything in release builds
#endif #endif
// We always use 48000 // We always use 48000
@ -38,7 +38,7 @@
struct qbs; struct qbs;
struct mem_block; struct mem_block;
void sub_sound(double frequency, double lengthInClockTicks); void sub_sound(double frequency, double lengthInClockTicks, double volume, double panning, int32_t waveform, int32_t passed);
void sub_beep(); void sub_beep();
void sub_play(const qbs *str); void sub_play(const qbs *str);
int32_t func_play(int32_t ignore); int32_t func_play(int32_t ignore);

View file

@ -60,10 +60,10 @@ int32 func_instr(int32 start, qbs *str, qbs *substr, int32 passed); // Did not f
void new_mem_lock(); // This is required for MemSound() void new_mem_lock(); // This is required for MemSound()
void free_mem_lock(mem_lock *lock); // Same as above void free_mem_lock(mem_lock *lock); // Same as above
extern ptrszint dblock; // Required for Play(). Did not find this declared anywhere extern ptrszint dblock; // Required for Play(). Did not find this declared anywhere
extern uint64 mem_lock_id; // Another one that we need for the mem stuff extern uint64 mem_lock_id; // Another one that we need for the mem stuff
extern mem_lock *mem_lock_base; // Same as above extern mem_lock *mem_lock_base; // Same as above
extern mem_lock *mem_lock_tmp; // Same as above extern mem_lock *mem_lock_tmp; // Same as above
/// @brief A simple FP32 stereo sample frame /// @brief A simple FP32 stereo sample frame
struct SampleFrame { struct SampleFrame {
@ -132,10 +132,10 @@ struct RawStream {
/// @brief This pushes a whole buffer of mono sample frames to the queue. This is mutex protected and called by the main thread /// @brief This pushes a whole buffer of mono sample frames to the queue. This is mutex protected and called by the main thread
/// @param buffer The buffer containing the sample frames. This cannot be NULL /// @param buffer The buffer containing the sample frames. This cannot be NULL
/// @param frames The total number of frames in the buffer /// @param frames The total number of frames in the buffer
void PushMonoSampleFrames(float *buffer, ma_uint64 frames) { void PushMonoSampleFrames(float *buffer, ma_uint64 frames, float panning = 0.0f) {
libqb_mutex_guard lock(m); // lock the mutex before accessing the vectors libqb_mutex_guard lock(m); // lock the mutex before accessing the vectors
for (ma_uint64 i = 0; i < frames; i++) { for (ma_uint64 i = 0; i < frames; i++) {
producer->data.push_back({buffer[i], buffer[i]}); producer->data.push_back({(buffer[i] * (1.0f - panning)) / 2.0f, (buffer[i] * (1.0f + panning)) / 2.0f});
} }
} }
@ -428,6 +428,12 @@ class PSG {
/// @brief Various types of waveform that can be generated /// @brief Various types of waveform that can be generated
enum class WaveformType { NONE, SQUARE, SAWTOOTH, TRIANGLE, SINE, NOISE, COUNT }; enum class WaveformType { NONE, SQUARE, SAWTOOTH, TRIANGLE, SINE, NOISE, COUNT };
static constexpr auto PAN_LEFT = -1.0f;
static constexpr auto PAN_RIGHT = 1.0f;
static constexpr auto PAN_CENTER = PAN_LEFT + PAN_RIGHT;
static constexpr auto MIN_VOLUME = 0.0;
static constexpr auto MAX_VOLUME = 1.0;
private: private:
/// @brief This struct to used to hold the state of the MML player and also used for the state stack (i.e. when VARPTR$ substrings are used) /// @brief This struct to used to hold the state of the MML player and also used for the state stack (i.e. when VARPTR$ substrings are used)
struct State { struct State {
@ -447,13 +453,13 @@ class PSG {
WaveformType waveformType; // the currently selected waveform type (applies to MML and sound) WaveformType waveformType; // the currently selected waveform type (applies to MML and sound)
float volumeRampDuration; // the volume ramping duration (this can be changed by the user) float volumeRampDuration; // the volume ramping duration (this can be changed by the user)
bool background; // if this is true, then control will be returned back to the caller as soon as the sound / MML is rendered bool background; // if this is true, then control will be returned back to the caller as soon as the sound / MML is rendered
float panning; // stereo pan setting for SOUND (-1.0f - 0.0f - 1.0f)
std::stack<State> stateStack; // this maintains the state stack if we need to process substrings (VARPTR$) std::stack<State> stateStack; // this maintains the state stack if we need to process substrings (VARPTR$)
State currentState; // this is the current state. See State struct State currentState; // this is the current state. See State struct
int tempo; // the tempo of the MML tune (this impacts all lengths) int tempo; // the tempo of the MML tune (this impacts all lengths)
int octave; // the current octave that we'll use for MML notes int octave; // the current octave that we'll use for MML notes
double length; // the length of each MML note (1 = full, 4 = quarter etc.) double length; // the length of each MML note (1 = full, 4 = quarter etc.)
double pause; // the duration of silence after an MML note (this eats away from the note length) double pause; // the duration of silence after an MML note (this eats away from the note length)
double volume; // the current volume (applies to MML and sound)
double duration; // the duration of a sound / MML note / silence (in seconds) double duration; // the duration of a sound / MML note / silence (in seconds)
int dots; // the dots after a note or a pause that increases the duration int dots; // the dots after a note or a pause that increases the duration
bool playIt; // flag that is set when the buffer can be played bool playIt; // flag that is set when the buffer can be played
@ -462,19 +468,15 @@ class PSG {
// These mostly conform to the QBasic and QB64 spec. // These mostly conform to the QBasic and QB64 spec.
static const auto DEFAULT_WAVEFORM_TYPE = WaveformType::TRIANGLE; static const auto DEFAULT_WAVEFORM_TYPE = WaveformType::TRIANGLE;
static constexpr auto DEFAULT_FREQUENCY = 440.0; static constexpr auto DEFAULT_FREQUENCY = 440.0;
static constexpr auto MIN_VOLUME = 0.0; static constexpr auto MAX_MML_VOLUME = 100.0;
static constexpr auto MAX_VOLUME = 100.0; static constexpr auto DEFAULT_MML_VOLUME = MAX_MML_VOLUME / 2;
static constexpr auto DEFAULT_VOLUME = MAX_VOLUME / 2;
static const auto MIN_TEMPO = 32; static const auto MIN_TEMPO = 32;
static const auto MAX_TEMPO = 255; static const auto MAX_TEMPO = 255;
static const auto DEFAULT_TEMPO = 120; static const auto DEFAULT_TEMPO = 120;
static const auto MIN_OCTAVE = 0;
static const auto MAX_OCTAVE = 7; static const auto MAX_OCTAVE = 7;
static const auto DEFAULT_OCTAVE = 4; static const auto DEFAULT_OCTAVE = 4;
static const auto MIN_NOTE = 0; static const auto MIN_LENGTH = 1;
static const auto MAX_NOTE = 12 * (1 + MAX_OCTAVE); static const auto MAX_LENGTH = 64;
static constexpr auto MIN_LENGTH = 1.0;
static constexpr auto MAX_LENGTH = 64.0;
static constexpr auto DEFAULT_LENGTH = 4.0; static constexpr auto DEFAULT_LENGTH = 4.0;
static constexpr auto DEFAULT_PAUSE = 1.0 / 8.0; static constexpr auto DEFAULT_PAUSE = 1.0 / 8.0;
static constexpr auto DEFAULT_VOLUME_RAMP_DURATION = 0.01f; static constexpr auto DEFAULT_VOLUME_RAMP_DURATION = 0.01f;
@ -489,11 +491,14 @@ class PSG {
/// So it makes sense for the calling function to do the resize before calling this /// So it makes sense for the calling function to do the resize before calling this
/// @param waveDuration The duration of the waveform in seconds /// @param waveDuration The duration of the waveform in seconds
/// @param mix Mixes the generated waveform to the buffer instead of overwriting it /// @param mix Mixes the generated waveform to the buffer instead of overwriting it
/// @return True if successful, false otherwise void GenerateWaveform(double waveDuration, bool mix = false) {
bool GenerateWaveform(double waveDuration, bool mix = false) {
auto neededFrames = (ma_uint64)(waveDuration * rawStream->sampleRate); auto neededFrames = (ma_uint64)(waveDuration * rawStream->sampleRate);
if (!neededFrames || mixCursor + neededFrames > waveBuffer.size())
return false; // nothing to do if (!neededFrames || maWaveform.config.frequency >= 20000 || mixCursor + neededFrames > waveBuffer.size()) {
AUDIO_DEBUG_PRINT("Not generating any wavefrom. Frames = %llu, frequency = %lf, cursor = %llu", neededFrames, maWaveform.config.frequency,
mixCursor);
return; // nothing to do
}
maResult = MA_SUCCESS; maResult = MA_SUCCESS;
ma_uint64 generatedFrames = neededFrames; ma_uint64 generatedFrames = neededFrames;
@ -513,8 +518,10 @@ class PSG {
break; break;
} }
if (maResult != MA_SUCCESS) if (maResult != MA_SUCCESS) {
return false; // something went wrong AUDIO_DEBUG_PRINT("maResult = %i", maResult);
return; // something went wrong
}
// Apply volume ramping to the generated waveform to remove click and pops // Apply volume ramping to the generated waveform to remove click and pops
auto rampFrames = volumeRampDuration * rawStream->sampleRate; auto rampFrames = volumeRampDuration * rawStream->sampleRate;
@ -551,50 +558,34 @@ class PSG {
AUDIO_DEBUG_PRINT("Waveform = %i, frames requested = %llu, frames generated = %llu", waveformType, neededFrames, generatedFrames); AUDIO_DEBUG_PRINT("Waveform = %i, frames requested = %llu, frames generated = %llu", waveformType, neededFrames, generatedFrames);
} }
return true;
} }
/// @brief Sets the frequency of the waveform /// @brief Sets the frequency of the waveform
/// @param frequency The frequency of the waveform /// @param frequency The frequency of the waveform
/// @return True if successful void SetFrequency(double frequency) {
bool SetFrequency(double frequency) { maResult = ma_waveform_set_frequency(&maWaveform, frequency);
maResult = MA_SUCCESS;
switch (waveformType) { AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS);
case WaveformType::TRIANGLE:
case WaveformType::SAWTOOTH:
case WaveformType::SINE:
case WaveformType::SQUARE:
maResult = ma_waveform_set_frequency(&maWaveform, frequency);
break;
}
if (maResult != MA_SUCCESS)
return false;
return true;
} }
/// @brief Sends the buffer for playback /// @brief Sends the buffer for playback
/// @return True if successful void PushBufferForPlayback() {
bool PushBufferForPlayback() {
if (!waveBuffer.empty()) { if (!waveBuffer.empty()) {
rawStream->PushMonoSampleFrames(waveBuffer.data(), waveBuffer.size()); rawStream->PushMonoSampleFrames(waveBuffer.data(), waveBuffer.size(), panning);
AUDIO_DEBUG_PRINT("Sent %llu samples for playback", waveBuffer.size()); AUDIO_DEBUG_PRINT("Sent %llu samples for playback", waveBuffer.size());
waveBuffer.clear(); // set the buffer size to zero waveBuffer.clear(); // set the buffer size to zero
mixCursor = 0; // reset the cursor mixCursor = 0; // reset the cursor
return true;
} }
return false;
} }
/// @brief Waits for any playback to complete /// @brief Waits for any playback to complete
void AwaitPlaybackCompletion() { void AwaitPlaybackCompletion() {
auto timeSec = rawStream->GetTimeRemaining(); if (background)
return; // no need to wait
auto timeSec = rawStream->GetTimeRemaining() * 0.95 - 0.25; // per original QB64 behavior
AUDIO_DEBUG_PRINT("Waiting %f seconds for playback to complete", timeSec); AUDIO_DEBUG_PRINT("Waiting %f seconds for playback to complete", timeSec);
@ -618,27 +609,26 @@ class PSG {
PSG(RawStream *pRawStream) { PSG(RawStream *pRawStream) {
rawStream = pRawStream; // save the RawStream object pointer rawStream = pRawStream; // save the RawStream object pointer
mixCursor = 0; mixCursor = 0;
waveformType = DEFAULT_WAVEFORM_TYPE;
volumeRampDuration = DEFAULT_VOLUME_RAMP_DURATION; volumeRampDuration = DEFAULT_VOLUME_RAMP_DURATION;
background = playIt = false; // default to foreground playback background = playIt = false; // default to foreground playback
tempo = DEFAULT_TEMPO; tempo = DEFAULT_TEMPO;
octave = DEFAULT_OCTAVE; octave = DEFAULT_OCTAVE;
length = DEFAULT_LENGTH; length = DEFAULT_LENGTH;
pause = DEFAULT_PAUSE; pause = DEFAULT_PAUSE;
volume = DEFAULT_VOLUME; panning = PAN_CENTER;
duration = 0; duration = 0;
dots = 0; dots = 0;
ZERO_VARIABLE(currentState); ZERO_VARIABLE(currentState);
maWaveformConfig = ma_waveform_config_init(ma_format::ma_format_f32, 1, rawStream->sampleRate, ma_waveform_type::ma_waveform_type_square, maWaveformConfig = ma_waveform_config_init(ma_format::ma_format_f32, 1, rawStream->sampleRate, ma_waveform_type::ma_waveform_type_square,
DEFAULT_VOLUME / MAX_VOLUME, DEFAULT_FREQUENCY); DEFAULT_MML_VOLUME / MAX_MML_VOLUME, DEFAULT_FREQUENCY);
maResult = ma_waveform_init(&maWaveformConfig, &maWaveform); maResult = ma_waveform_init(&maWaveformConfig, &maWaveform);
AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS); AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS);
maNoiseConfig = ma_noise_config_init(ma_format::ma_format_f32, 1, ma_noise_type::ma_noise_type_white, 0, DEFAULT_VOLUME / MAX_VOLUME); maNoiseConfig = ma_noise_config_init(ma_format::ma_format_f32, 1, ma_noise_type::ma_noise_type_white, 0, DEFAULT_MML_VOLUME / MAX_MML_VOLUME);
maResult = ma_noise_init(&maNoiseConfig, NULL, &maNoise); maResult = ma_noise_init(&maNoiseConfig, NULL, &maNoise);
AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS); AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS);
SetWaveformType(waveformType); // this calls the underlying miniaudio API SetWaveformType(DEFAULT_WAVEFORM_TYPE); // this calls the underlying miniaudio API
AUDIO_DEBUG_PRINT("PSG initialized @ %uHz", maWaveform.config.sampleRate); AUDIO_DEBUG_PRINT("PSG initialized @ %uHz", maWaveform.config.sampleRate);
} }
@ -653,10 +643,7 @@ class PSG {
/// @brief Sets the waveform type /// @brief Sets the waveform type
/// @param type The waveform type. See Waveform::Type /// @param type The waveform type. See Waveform::Type
/// @return True if successful void SetWaveformType(WaveformType waveType) {
bool SetWaveformType(WaveformType waveType) {
maResult = MA_SUCCESS;
switch (waveType) { switch (waveType) {
case WaveformType::TRIANGLE: case WaveformType::TRIANGLE:
maResult = ma_waveform_set_type(&maWaveform, ma_waveform_type::ma_waveform_type_triangle); maResult = ma_waveform_set_type(&maWaveform, ma_waveform_type::ma_waveform_type_triangle);
@ -675,41 +662,30 @@ class PSG {
break; break;
} }
if (maResult != MA_SUCCESS) AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS);
return false;
waveformType = waveType; waveformType = waveType;
AUDIO_DEBUG_PRINT("Waveform type set to %i", waveformType); AUDIO_DEBUG_PRINT("Waveform type set to %i", waveformType);
return true;
} }
/// @brief Sets the amplitude of the waveform /// @brief Sets the amplitude of the waveform
/// @param amplitude The amplitude of the waveform /// @param amplitude The amplitude of the waveform
/// @return True if successful void SetAmplitude(double amplitude) {
bool SetAmplitude(double amplitude) { maResult = ma_waveform_set_amplitude(&maWaveform, amplitude);
maResult = MA_SUCCESS; AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS);
maResult = ma_noise_set_amplitude(&maNoise, amplitude);
switch (waveformType) { AUDIO_DEBUG_CHECK(maResult == MA_SUCCESS);
case WaveformType::TRIANGLE:
case WaveformType::SAWTOOTH:
case WaveformType::SINE:
case WaveformType::SQUARE:
maResult = ma_waveform_set_amplitude(&maWaveform, amplitude);
break;
case WaveformType::NOISE:
maResult = ma_noise_set_amplitude(&maNoise, amplitude);
break;
}
if (maResult != MA_SUCCESS)
return false;
AUDIO_DEBUG_PRINT("Amplitude set to %lf", amplitude); AUDIO_DEBUG_PRINT("Amplitude set to %lf", amplitude);
}
return true; /// @brief Set the PSG panning value
/// @param value A number between -1.0 to 1.0. Where 0.0 is center
void SetPanning(float value) {
panning = value;
AUDIO_DEBUG_PRINT("Panning set to %f", panning);
} }
/// @brief Plays a typical retro PC speaker BEEP sound. The volume, waveform and background mode can be changed using PLAY /// @brief Plays a typical retro PC speaker BEEP sound. The volume, waveform and background mode can be changed using PLAY
@ -718,9 +694,7 @@ class PSG {
waveBuffer.assign((size_t)(BEEP_DURATION * rawStream->sampleRate), 0.0f); waveBuffer.assign((size_t)(BEEP_DURATION * rawStream->sampleRate), 0.0f);
GenerateWaveform(BEEP_WAVEFORM_DURATION); GenerateWaveform(BEEP_WAVEFORM_DURATION);
PushBufferForPlayback(); PushBufferForPlayback();
AwaitPlaybackCompletion(); // await playback to complete if we are in MF mode
if (!background)
AwaitPlaybackCompletion(); // await playback to complete if we are in MF mode
} }
/// @brief Emulates a PC speaker sound. The volume, waveform and background mode can be changed using PLAY /// @brief Emulates a PC speaker sound. The volume, waveform and background mode can be changed using PLAY
@ -730,9 +704,7 @@ class PSG {
waveBuffer.assign((size_t)(soundDuration * rawStream->sampleRate), 0.0f); waveBuffer.assign((size_t)(soundDuration * rawStream->sampleRate), 0.0f);
GenerateWaveform(soundDuration); GenerateWaveform(soundDuration);
PushBufferForPlayback(); PushBufferForPlayback();
AwaitPlaybackCompletion(); // await playback to complete if we are in MF mode
if (!background)
AwaitPlaybackCompletion(); // await playback to complete if we are in MF mode
} }
/// @brief This is an MML parser that implements the QB64 MML spec and more /// @brief This is an MML parser that implements the QB64 MML spec and more
@ -793,7 +765,7 @@ class PSG {
processedChar = toupper(currentChar); processedChar = toupper(currentChar);
if (processedChar == 'X') { // substring if (processedChar == 'X') { // "X" + VARPTR$()
// A minimum of 3 bytes is need to read the address // A minimum of 3 bytes is need to read the address
if (currentState.length < 3) { if (currentState.length < 3) {
error(5); error(5);
@ -812,12 +784,11 @@ class PSG {
stateStack.push(currentState); // push the current state to the stack stateStack.push(currentState); // push the current state to the stack
// Set new state // Set new state
auto x = cmem[1280 + offset + 3] * 256 + cmem[1280 + offset + 2]; currentState.byte = &cmem[1280] + (cmem[1280 + offset + 3] * 256 + cmem[1280 + offset + 2]);
currentState.byte = &cmem[1280] + x;
currentState.length = cmem[1280 + offset + 1] * 256 + cmem[1280 + offset + 0]; currentState.length = cmem[1280 + offset + 1] * 256 + cmem[1280 + offset + 0];
continue; continue;
} else if (currentChar == '=') { //= (+VARPTR$) } else if (currentChar == '=') { // "=" + VARPTR$()
if (dots) { if (dots) {
error(5); error(5);
return; return;
@ -984,13 +955,12 @@ class PSG {
numberEntered = 0; numberEntered = 0;
if ((WaveformType)number <= PSG::WaveformType::NONE || (WaveformType)number >= PSG::WaveformType::COUNT) { if ((WaveformType)number <= WaveformType::NONE || (WaveformType)number >= WaveformType::COUNT) {
error(5); error(5);
return; return;
} }
waveformType = (WaveformType)number; SetWaveformType((WaveformType)number);
SetWaveformType(waveformType);
followUp = 0; followUp = 0;
@ -1004,13 +974,12 @@ class PSG {
numberEntered = 0; numberEntered = 0;
if (number > 100) { if (number > MAX_MML_VOLUME) {
error(5); error(5);
return; return;
} }
volume = number; SetAmplitude(number / MAX_MML_VOLUME);
SetAmplitude(volume / 100.0);
followUp = 0; followUp = 0;
@ -1063,7 +1032,7 @@ class PSG {
numberEntered = 0; numberEntered = 0;
if (number < 32 || number > 255) { if (number < MIN_TEMPO || number > MAX_TEMPO) {
number = 120; number = 120;
} }
@ -1080,26 +1049,26 @@ class PSG {
} }
switch (processedChar) { switch (processedChar) {
case 76: case 'L': // legato
pause = 0; pause = 0.0;
break; break;
case 78: case 'N': // normal
pause = 1.0 / 8.0; pause = 1.0 / 8.0;
break; break;
case 83: case 'S': // staccato
pause = 1.0 / 4.0; pause = 1.0 / 4.0;
break; break;
case 66: case 'B': // background
if (!background) { if (!background) {
background = true; if (playIt) { // play pending buffer in foreground before we switch to background
if (playIt) {
playIt = false; playIt = false;
PushBufferForPlayback(); PushBufferForPlayback();
AwaitPlaybackCompletion(); AwaitPlaybackCompletion();
} }
background = true;
} }
break; break;
case 70: case 'F': // foreground
background = false; background = false;
break; break;
default: default:
@ -1134,7 +1103,7 @@ class PSG {
numberEntered = 0; numberEntered = 0;
if (number > 6) { if (number > MAX_OCTAVE) {
error(5); error(5);
return; return;
} }
@ -1153,7 +1122,7 @@ class PSG {
numberEntered = 0; numberEntered = 0;
if (number < 1 || number > 64) { if (number < MIN_LENGTH || number > MAX_LENGTH) {
error(5); error(5);
return; return;
} }
@ -1317,8 +1286,7 @@ class PSG {
if (playIt) { if (playIt) {
PushBufferForPlayback(); PushBufferForPlayback();
if (!background) AwaitPlaybackCompletion();
AwaitPlaybackCompletion();
} }
} }
} }
@ -1581,7 +1549,7 @@ static AudioEngine audioEngine;
/// @brief Initializes the PSG object and it's RawStream object. This only happens once. Subsequent calls to this will return true /// @brief Initializes the PSG object and it's RawStream object. This only happens once. Subsequent calls to this will return true
/// @return Returns true if both objects were successfully created /// @return Returns true if both objects were successfully created
static bool InitPSG() { static bool InitializePSG() {
if (!audioEngine.isInitialized || audioEngine.sndInternal != 0) if (!audioEngine.isInitialized || audioEngine.sndInternal != 0)
return false; return false;
@ -1614,8 +1582,8 @@ static bool InitPSG() {
/// @brief This generates a sound at the specified frequency for the specified amount of time /// @brief This generates a sound at the specified frequency for the specified amount of time
/// @param frequency Sound frequency /// @param frequency Sound frequency
/// @param lengthInClockTicks Duration in clock ticks. There are 18.2 clock ticks per second /// @param lengthInClockTicks Duration in clock ticks. There are 18.2 clock ticks per second
void sub_sound(double frequency, double lengthInClockTicks) { void sub_sound(double frequency, double lengthInClockTicks, double volume, double panning, int32_t waveform, int32_t passed) {
if (new_error || lengthInClockTicks == 0.0) if (new_error || lengthInClockTicks == 0.0 || !InitializePSG())
return; return;
if ((frequency < 37.0 && frequency != 0) || frequency > 32767.0 || lengthInClockTicks < 0.0 || lengthInClockTicks > 65535.0) { if ((frequency < 37.0 && frequency != 0) || frequency > 32767.0 || lengthInClockTicks < 0.0 || lengthInClockTicks > 65535.0) {
@ -1623,13 +1591,36 @@ void sub_sound(double frequency, double lengthInClockTicks) {
return; return;
} }
if (InitPSG()) if (passed & 1) {
audioEngine.psg->Sound(frequency, lengthInClockTicks); if (volume < PSG::MIN_VOLUME || volume > PSG::MAX_VOLUME) {
error(5);
return;
}
audioEngine.psg->SetAmplitude(volume);
}
if (passed & 2) {
if (panning < PSG::PAN_LEFT || panning > PSG::PAN_RIGHT) {
error(5);
return;
}
audioEngine.psg->SetPanning((float)panning);
}
if (passed & 4) {
if ((PSG::WaveformType)waveform <= PSG::WaveformType::NONE || (PSG::WaveformType)waveform >= PSG::WaveformType::COUNT) {
error(5);
return;
}
audioEngine.psg->SetWaveformType((PSG::WaveformType)waveform);
}
audioEngine.psg->Sound(frequency, lengthInClockTicks);
} }
/// @brief This generates a default 'beep' sound /// @brief This generates a default 'beep' sound
void sub_beep() { void sub_beep() {
if (new_error || !InitPSG()) if (new_error || !InitializePSG())
return; return;
audioEngine.psg->Beep(); audioEngine.psg->Beep();
@ -1642,8 +1633,7 @@ void sub_beep() {
int32_t func_play(int32_t ignore) { int32_t func_play(int32_t ignore) {
if (audioEngine.isInitialized && audioEngine.sndInternal == 0 && audioEngine.soundHandles[audioEngine.sndInternal]->rawStream) { if (audioEngine.isInitialized && audioEngine.sndInternal == 0 && audioEngine.soundHandles[audioEngine.sndInternal]->rawStream) {
if (ignore) if (ignore)
return (int32_t)(audioEngine.soundHandles[audioEngine.sndInternal]->rawStream->GetSampleFramesRemaining() / return lround(audioEngine.soundHandles[audioEngine.sndInternal]->rawStream->GetTimeRemaining());
audioEngine.soundHandles[audioEngine.sndInternal]->rawStream->sampleRate);
else else
return (int32_t)audioEngine.soundHandles[audioEngine.sndInternal]->rawStream->GetSampleFramesRemaining(); return (int32_t)audioEngine.soundHandles[audioEngine.sndInternal]->rawStream->GetSampleFramesRemaining();
} }
@ -1654,7 +1644,7 @@ int32_t func_play(int32_t ignore) {
/// @brief Processes and plays the MML specified in the string /// @brief Processes and plays the MML specified in the string
/// @param str The string to play /// @param str The string to play
void sub_play(const qbs *str) { void sub_play(const qbs *str) {
if (new_error || !InitPSG()) if (new_error || !InitializePSG())
return; return;
audioEngine.psg->Play(str); audioEngine.psg->Play(str);

View file

@ -439,7 +439,6 @@ extern long double func_val(qbs *s);
extern void sub_out(int32 port, int32 data); extern void sub_out(int32 port, int32 data);
extern void sub_randomize(double seed, int32 passed); extern void sub_randomize(double seed, int32 passed);
extern float func_rnd(float n, int32 passed); extern float func_rnd(float n, int32 passed);
extern void sub_sound(double frequency, double lengthinclockticks);
// following are declared below to allow for inlining // following are declared below to allow for inlining
// extern double func_abs(double d); // extern double func_abs(double d);
// extern long double func_abs(long double d); // extern long double func_abs(long double d);
@ -537,7 +536,6 @@ extern qbs *func_input(int32 n, int32 i, int32 passed);
extern int32 func__statusCode(int32 handle); extern int32 func__statusCode(int32 handle);
extern double func_sqr(double value); extern double func_sqr(double value);
extern void sub_beep();
extern void snd_check(); extern void snd_check();
extern qbs *func_command(int32 index, int32 passed); extern qbs *func_command(int32 index, int32 passed);
extern int32 func__commandcount(); extern int32 func__commandcount();

View file

@ -3022,9 +3022,10 @@ clearid
id.n = "Sound": id.Dependency = DEPENDENCY_AUDIO_OUT id.n = "Sound": id.Dependency = DEPENDENCY_AUDIO_OUT
id.subfunc = 2 id.subfunc = 2
id.callname = "sub_sound" id.callname = "sub_sound"
id.args = 2 id.args = 5
id.arg = MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) id.arg = MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER)
id.hr_syntax = "SOUND frequency, duration" id.specialformat = "?,?[,[?][,[?][,[?]]]]"
id.hr_syntax = "SOUND frequency#, duration#[, volume#][, panning#][, waveform&]"
regid regid
clearid clearid