Merge pull request #649 from MihailRis/generated-pcm-stream

Generated PCM stream (part 1)
This commit is contained in:
MihailRis
2025-11-10 19:12:48 +03:00
committed by GitHub
50 changed files with 2118 additions and 1013 deletions
+201 -37
View File
@@ -5,11 +5,41 @@
#include "debug/Logger.hpp"
#include "alutil.hpp"
#include "../MemoryPCMStream.hpp"
static debug::Logger logger("al-audio");
using namespace audio;
const char* alc_error_to_string(ALCenum error) {
switch (error) {
case ALC_NO_ERROR:
return "no error";
case ALC_INVALID_DEVICE:
return "invalid device handle";
case ALC_INVALID_CONTEXT:
return "invalid context handle";
case ALC_INVALID_ENUM:
return "invalid enum parameter passed to an ALC call";
case ALC_INVALID_VALUE:
return "invalid value parameter passed to an ALC call";
case ALC_OUT_OF_MEMORY:
return "out of memory";
default:
return "unknown ALC error";
}
}
static bool check_alc_errors(ALCdevice* device, const char* context) {
ALCenum error = alcGetError(device);
if (error == ALC_NO_ERROR) {
return false;
}
logger.error() << context << ": " << alc_error_to_string(error) << "("
<< error << ")";
return true;
}
ALSound::ALSound(
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
)
@@ -37,6 +67,70 @@ std::unique_ptr<Speaker> ALSound::newInstance(int priority, int channel) const {
return speaker;
}
ALInputDevice::ALInputDevice(
ALAudio* al,
ALCdevice* device,
uint channels,
uint bitsPerSample,
uint sampleRate
)
: al(al),
device(device),
channels(channels),
bitsPerSample(bitsPerSample),
sampleRate(sampleRate) {
const ALCchar* deviceName = alcGetString(device, ALC_CAPTURE_DEVICE_SPECIFIER);
if (deviceName) {
deviceSpecifier = std::string(deviceName);
} else {
logger.warning() << "could not retrieve input device specifier";
}
}
ALInputDevice::~ALInputDevice() {
alcCaptureCloseDevice(device);
check_alc_errors(device, "alcCaptureCloseDevice");
}
void ALInputDevice::startCapture() {
alcCaptureStart(device);
check_alc_errors(device, "alcCaptureStart");
}
void ALInputDevice::stopCapture() {
alcCaptureStop(device);
check_alc_errors(device, "alcCaptureStop");
}
uint ALInputDevice::getChannels() const {
return channels;
}
uint ALInputDevice::getSampleRate() const {
return sampleRate;
}
uint ALInputDevice::getBitsPerSample() const {
return bitsPerSample;
}
const std::string& ALInputDevice::getDeviceSpecifier() const {
return deviceSpecifier;
}
size_t ALInputDevice::read(char* buffer, size_t bufferSize) {
ALCint samplesCount = 0;
alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount);
check_alc_errors(device, "alcGetIntegerv(ALC_CAPTURE_SAMPLES)");
size_t samplesRead = std::min<ALCsizei>(
samplesCount, bufferSize / channels / (bitsPerSample >> 3)
);
alcCaptureSamples(device, buffer, samplesRead);
check_alc_errors(device, "alcCaptureSamples");
return samplesRead * channels * (bitsPerSample >> 3);
}
ALStream::ALStream(
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
)
@@ -81,9 +175,10 @@ std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
uint free_buffer = al->getFreeBuffer();
if (!preloadBuffer(free_buffer, loop)) {
break;
unusedBuffers.push(free_buffer);
} else {
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
}
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
}
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
}
@@ -130,11 +225,11 @@ void ALStream::unqueueBuffers(uint alsource) {
uint ALStream::enqueueBuffers(uint alsource) {
uint preloaded = 0;
if (!unusedBuffers.empty()) {
uint first_buffer = unusedBuffers.front();
if (preloadBuffer(first_buffer, loop)) {
uint firstBuffer = unusedBuffers.front();
if (preloadBuffer(firstBuffer, loop)) {
preloaded++;
unusedBuffers.pop();
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer));
AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
}
}
return preloaded;
@@ -144,14 +239,14 @@ void ALStream::update(double delta) {
if (this->speaker == 0) {
return;
}
auto p_speaker = audio::get_speaker(this->speaker);
if (p_speaker == nullptr) {
auto speaker = audio::get_speaker(this->speaker);
if (speaker == nullptr) {
this->speaker = 0;
return;
}
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker);
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
assert(alspeaker != nullptr);
if (alspeaker->stopped) {
if (alspeaker->manuallyStopped) {
this->speaker = 0;
return;
}
@@ -162,11 +257,11 @@ void ALStream::update(double delta) {
uint preloaded = enqueueBuffers(alsource);
// alspeaker->stopped is assigned to false at ALSpeaker::play(...)
if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive?
if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive?
if (preloaded) {
p_speaker->play();
} else {
p_speaker->stop();
speaker->play();
} else if (isStopOnEnd()){
speaker->stop();
}
}
}
@@ -207,6 +302,14 @@ void ALStream::setTime(duration_t time) {
}
}
bool ALStream::isStopOnEnd() const {
return stopOnEnd;
}
void ALStream::setStopOnEnd(bool flag) {
stopOnEnd = flag;
}
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
: al(al), priority(priority), channel(channel), source(source) {
}
@@ -273,7 +376,7 @@ void ALSpeaker::setLoop(bool loop) {
void ALSpeaker::play() {
paused = false;
stopped = false;
manuallyStopped = false;
auto p_channel = get_channel(this->channel);
AL_CHECK(alSourcef(
source,
@@ -289,7 +392,7 @@ void ALSpeaker::pause() {
}
void ALSpeaker::stop() {
stopped = true;
manuallyStopped = true;
if (source) {
AL_CHECK(alSourceStop(source));
@@ -353,6 +456,13 @@ int ALSpeaker::getPriority() const {
return priority;
}
bool ALSpeaker::isManuallyStopped() const {
return manuallyStopped;
}
static bool alc_enumeration_ext = false;
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
: device(device), context(context) {
ALCint size;
@@ -365,9 +475,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
maxSources = attrs[i + 1];
}
}
auto devices = getAvailableDevices();
logger.info() << "devices:";
for (auto& name : devices) {
auto outputDevices = getOutputDeviceNames();
logger.info() << "output devices:";
for (auto& name : outputDevices) {
logger.info() << " " << name;
}
auto inputDevices = getInputDeviceNames();
logger.info() << "input devices:";
for (auto& name : inputDevices) {
logger.info() << " " << name;
}
}
@@ -385,8 +501,10 @@ ALAudio::~ALAudio() {
AL_CHECK(alDeleteBuffers(1, &buffer));
}
AL_CHECK(alcMakeContextCurrent(context));
alcMakeContextCurrent(nullptr);
check_alc_errors(device, "alcMakeContextCurrent");
alcDestroyContext(context);
check_alc_errors(device, "alcDestroyContext");
if (!alcCloseDevice(device)) {
logger.error() << "device not closed!";
}
@@ -411,7 +529,71 @@ std::unique_ptr<Stream> ALAudio::openStream(
return std::make_unique<ALStream>(this, stream, keepSource);
}
std::vector<std::string> ALAudio::getInputDeviceNames() {
std::vector<std::string> devices;
if (!alc_enumeration_ext) {
logger.warning() << "enumeration extension is not available";
return devices;
}
auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
if (deviceList == nullptr) {
logger.warning() << "no input devices found";
return devices;
}
while (*deviceList) {
std::string deviceName(deviceList);
devices.push_back(deviceName);
deviceList += deviceName.length() + 1;
}
return devices;
}
std::vector<std::string> ALAudio::getOutputDeviceNames() {
std::vector<std::string> devices;
if (!alc_enumeration_ext) {
logger.warning() << "enumeration extension is not available";
return devices;
}
auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
if (deviceList == nullptr) {
logger.warning() << "no input devices found";
return devices;
}
while (*deviceList) {
std::string deviceName(deviceList);
devices.push_back(deviceName);
deviceList += deviceName.length() + 1;
}
return devices;
}
std::unique_ptr<InputDevice> ALAudio::openInputDevice(
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
) {
uint bps = bitsPerSample >> 3;
ALCdevice* device = alcCaptureOpenDevice(
deviceName.empty() ? nullptr : deviceName.c_str(),
sampleRate,
AL::to_al_format(channels, bitsPerSample),
sampleRate * channels * bps / 8
);
if (check_alc_errors(device, "alcCaptureOpenDevice"))
return nullptr;
return std::make_unique<ALInputDevice>(
this, device, channels, bitsPerSample, sampleRate
);
}
std::unique_ptr<ALAudio> ALAudio::create() {
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
ALCdevice* device = alcOpenDevice(nullptr);
if (device == nullptr) return nullptr;
ALCcontext* context = alcCreateContext(device, nullptr);
@@ -468,24 +650,6 @@ void ALAudio::freeBuffer(uint buffer) {
freebuffers.push_back(buffer);
}
std::vector<std::string> ALAudio::getAvailableDevices() const {
std::vector<std::string> devicesVec;
const ALCchar* devices;
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
if (!AL_GET_ERROR()) {
return devicesVec;
}
const char* ptr = devices;
do {
devicesVec.emplace_back(ptr);
ptr += devicesVec.back().size() + 1;
} while (ptr[0]);
return devicesVec;
}
void ALAudio::setListener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
) {
+48 -3
View File
@@ -58,6 +58,7 @@ namespace audio {
bool keepSource;
char buffer[BUFFER_SIZE];
bool loop = false;
bool stopOnEnd = false;
bool preloadBuffer(uint buffer, bool loop);
void unqueueBuffers(uint alsource);
@@ -80,6 +81,39 @@ namespace audio {
void setTime(duration_t time) override;
static inline constexpr uint STREAM_BUFFERS = 3;
bool isStopOnEnd() const override;
void setStopOnEnd(bool stopOnEnd) override;
};
class ALInputDevice : public InputDevice {
public:
ALInputDevice(
ALAudio* al,
ALCdevice* device,
uint channels,
uint bitsPerSample,
uint sampleRate
);
~ALInputDevice() override;
void startCapture() override;
void stopCapture() override;
uint getChannels() const override;
uint getSampleRate() const override;
uint getBitsPerSample() const override;
const std::string& getDeviceSpecifier() const override;
size_t read(char* buffer, size_t bufferSize) override;
private:
ALAudio* al;
ALCdevice* device;
uint channels;
uint bitsPerSample;
uint sampleRate;
std::string deviceSpecifier;
};
/// @brief AL source adapter
@@ -90,7 +124,7 @@ namespace audio {
float volume = 0.0f;
public:
ALStream* stream = nullptr;
bool stopped = true;
bool manuallyStopped = true;
bool paused = false;
uint source;
duration_t duration = 0.0f;
@@ -130,6 +164,8 @@ namespace audio {
bool isRelative() const override;
int getPriority() const override;
bool isManuallyStopped() const override;
};
class ALAudio : public Backend {
@@ -152,15 +188,24 @@ namespace audio {
void freeSource(uint source);
void freeBuffer(uint buffer);
std::vector<std::string> getAvailableDevices() const;
std::unique_ptr<Sound> createSound(
std::shared_ptr<PCM> pcm, bool keepPCM
) override;
std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource
) override;
std::unique_ptr<InputDevice> openInputDevice(
const std::string& deviceName,
uint sampleRate,
uint channels,
uint bitsPerSample
) override;
std::vector<std::string> getOutputDeviceNames() override;
std::vector<std::string> getInputDeviceNames() override;
void setListener(
glm::vec3 position,
glm::vec3 velocity,
+67
View File
@@ -0,0 +1,67 @@
#include "MemoryPCMStream.hpp"
#include <cstring>
using namespace audio;
MemoryPCMStream::MemoryPCMStream(
uint sampleRate, uint channels, uint bitsPerSample
)
: sampleRate(sampleRate), channels(channels), bitsPerSample(bitsPerSample) {
}
void MemoryPCMStream::feed(util::span<ubyte> bytes) {
buffer.insert(buffer.end(), bytes.begin(), bytes.end());
}
bool MemoryPCMStream::isOpen() const {
return open;
}
void MemoryPCMStream::close() {
open = false;
buffer = {};
}
size_t MemoryPCMStream::read(char* dst, size_t bufferSize) {
if (!open) {
return PCMStream::ERROR;
}
if (buffer.empty()) {
return 0;
}
size_t count = std::min<size_t>(bufferSize, buffer.size());
std::memcpy(dst, buffer.data(), count);
buffer.erase(buffer.begin(), buffer.begin() + count);
return count;
}
size_t MemoryPCMStream::getTotalSamples() const {
return 0;
}
duration_t MemoryPCMStream::getTotalDuration() const {
return 0.0;
}
uint MemoryPCMStream::getChannels() const {
return channels;
}
uint MemoryPCMStream::getSampleRate() const {
return sampleRate;
}
uint MemoryPCMStream::getBitsPerSample() const {
return bitsPerSample;
}
bool MemoryPCMStream::isSeekable() const {
return false;
}
void MemoryPCMStream::seek(size_t position) {}
size_t MemoryPCMStream::available() const {
return buffer.size();
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <vector>
#include "audio.hpp"
#include "util/span.hpp"
namespace audio {
class MemoryPCMStream : public PCMStream {
public:
MemoryPCMStream(uint sampleRate, uint channels, uint bitsPerSample);
void feed(util::span<ubyte> bytes);
bool isOpen() const override;
void close() override;
size_t read(char* buffer, size_t bufferSize) override;
size_t getTotalSamples() const override;
duration_t getTotalDuration() const override;
uint getChannels() const override;
uint getSampleRate() const override;
uint getBitsPerSample() const override;
bool isSeekable() const override;
void seek(size_t position) override;
size_t available() const;
private:
uint sampleRate;
uint channels;
uint bitsPerSample;
bool open = true;
std::vector<ubyte> buffer;
};
}
+21
View File
@@ -61,6 +61,13 @@ namespace audio {
void setTime(duration_t time) override {
}
bool isStopOnEnd() const override {
return false;
}
void setStopOnEnd(bool stopOnEnd) override {
}
};
class NoAudio : public Backend {
@@ -71,10 +78,24 @@ namespace audio {
std::unique_ptr<Sound> createSound(
std::shared_ptr<PCM> pcm, bool keepPCM
) override;
std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource
) override;
std::unique_ptr<InputDevice> openInputDevice(
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
) override {
return nullptr;
}
std::vector<std::string> getInputDeviceNames() override {
return {};
}
std::vector<std::string> getOutputDeviceNames() override {
return {};
}
void setListener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
) override {
+55 -2
View File
@@ -151,6 +151,8 @@ public:
}
};
static std::unique_ptr<InputDevice> input_device = nullptr;
void audio::initialize(bool enabled, AudioSettings& settings) {
enabled = enabled && settings.enabled.get();
if (enabled) {
@@ -180,6 +182,15 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
audio::get_channel(channel.name)->setVolume(value * value);
}, true));
}
input_device = backend->openInputDevice("", 44100, 1, 16);
if (input_device) {
input_device->startCapture();
}
}
InputDevice* audio::get_input_device() {
return input_device.get();
}
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
@@ -242,6 +253,38 @@ std::unique_ptr<Stream> audio::open_stream(
return backend->openStream(std::move(stream), keepSource);
}
std::unique_ptr<InputDevice> audio::open_input_device(
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
) {
return backend->openInputDevice(
deviceName, sampleRate, channels, bitsPerSample
);
}
std::vector<std::string> audio::get_input_devices_names() {
return backend->getInputDeviceNames();
}
std::vector<std::string> audio::get_output_devices_names() {
return backend->getOutputDeviceNames();
}
void audio::set_input_device(const std::string& deviceName) {
auto newDevice = backend->openInputDevice(deviceName, 44100, 1, 16);
if (newDevice == nullptr) {
logger.error() << "could not open input device: " << deviceName;
return;
}
if (input_device) {
input_device->stopCapture();
}
input_device = std::move(newDevice);
if (input_device) {
input_device->startCapture();
}
}
void audio::set_listener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
) {
@@ -421,8 +464,15 @@ void audio::update(double delta) {
speaker->update(channel);
}
if (speaker->isStopped()) {
streams.erase(it->first);
it = speakers.erase(it);
auto foundStream = streams.find(it->first);
if (foundStream == streams.end() ||
(!speaker->isManuallyStopped() &&
foundStream->second->isStopOnEnd())) {
streams.erase(it->first);
it = speakers.erase(it);
} else {
it++;
}
} else {
it++;
}
@@ -458,6 +508,9 @@ void audio::reset_channel(int index) {
}
void audio::close() {
if (input_device) {
input_device->stopCapture();
}
speakers.clear();
delete backend;
backend = nullptr;
+68
View File
@@ -24,6 +24,8 @@ namespace audio {
/// @brief streams and important sounds
constexpr inline int PRIORITY_HIGH = 10;
constexpr inline size_t MAX_INPUT_SAMPLES = 22050;
class Speaker;
/// @brief Audio speaker states
@@ -108,6 +110,31 @@ namespace audio {
}
};
class InputDevice {
public:
virtual ~InputDevice() {};
virtual void startCapture() = 0;
virtual void stopCapture() = 0;
/// @brief Get number of audio channels
/// @return 1 if mono, 2 if stereo
virtual uint getChannels() const = 0;
/// @brief Get audio sampling frequency
/// @return number of mono samples per second
virtual uint getSampleRate() const = 0;
/// @brief Get number of bits per mono sample
/// @return 8 or 16
virtual uint getBitsPerSample() const = 0;
/// @brief Read available data to buffer.
/// @return size of data received or PCMStream::ERROR in case of error
virtual size_t read(char* buffer, size_t bufferSize) = 0;
/// @brief Get device specifier string
virtual const std::string& getDeviceSpecifier() const = 0;
};
/// @brief audio::PCMStream is a data source for audio::Stream
class PCMStream {
public:
@@ -121,6 +148,10 @@ namespace audio {
/// (always equals bufferSize if seekable and looped)
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
/// @brief Read available data to buffer
/// @param buffer destination buffer
/// @param bufferSize destination buffer size
/// @return count of received bytes or PCMStream::ERROR
virtual size_t read(char* buffer, size_t bufferSize) = 0;
/// @brief Close stream
@@ -195,6 +226,9 @@ namespace audio {
/// @brief Set playhead to the selected time
/// @param time selected time
virtual void setTime(duration_t time) = 0;
virtual bool isStopOnEnd() const = 0;
virtual void setStopOnEnd(bool stopOnEnd) = 0;
};
/// @brief Sound is an audio asset that supposed to support many
@@ -329,6 +363,8 @@ namespace audio {
inline bool isStopped() const {
return getState() == State::stopped;
}
virtual bool isManuallyStopped() const = 0;
};
class Backend {
@@ -341,12 +377,20 @@ namespace audio {
virtual std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource
) = 0;
virtual std::unique_ptr<InputDevice> openInputDevice(
const std::string& deviceName,
uint sampleRate,
uint channels,
uint bitsPerSample
) = 0;
virtual void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) = 0;
virtual std::vector<std::string> getInputDeviceNames() = 0;
virtual std::vector<std::string> getOutputDeviceNames() = 0;
virtual void update(double delta) = 0;
/// @brief Check if backend is an abstraction that does not internally
@@ -402,6 +446,28 @@ namespace audio {
std::shared_ptr<PCMStream> stream, bool keepSource
);
/// @brief Open audio input device
/// @param sampleRate sample rate
/// @param channels channels count (1 - mono, 2 - stereo)
/// @param bitsPerSample number of bits per sample (8 or 16)
/// @return new InputDevice instance or nullptr
std::unique_ptr<InputDevice> open_input_device(
const std::string& deviceName,
uint sampleRate,
uint channels,
uint bitsPerSample
);
/// @brief Retrieve names of available audio input devices
/// @return list of device names
std::vector<std::string> get_input_devices_names();
/// @brief Retrieve names of available audio output devices
/// @return list of device names
std::vector<std::string> get_output_devices_names();
void set_input_device(const std::string& deviceName);
/// @brief Configure 3D listener
/// @param position listener position
/// @param velocity listener velocity (used for Doppler effect)
@@ -515,6 +581,8 @@ namespace audio {
/// @brief Stop all playing audio in channel, reset channel state
void reset_channel(int channel);
InputDevice* get_input_device();
/// @brief Finalize audio system
void close();
};
+1 -1
View File
@@ -9,7 +9,7 @@
#include "graphics/core/Texture.hpp"
#include "graphics/core/Atlas.hpp"
#include "util/Buffer.hpp"
#include "../lua_custom_types.hpp"
#include "../usertypes/lua_type_canvas.hpp"
using namespace scripting;
+91 -2
View File
@@ -68,6 +68,26 @@ inline audio::speakerid_t play_stream(
if (channel == -1) {
return 0;
}
if (!scripting::engine->isHeadless()) {
auto assets = scripting::engine->getAssets();
auto stream = assets->getShared<audio::PCMStream>(filename);
if (stream) {
return audio::play(
audio::open_stream(std::move(stream), true),
glm::vec3(
static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)
),
relative,
volume,
pitch,
loop,
channel
);
}
}
io::path file;
if (std::strchr(filename, ':')) {
file = std::string(filename);
@@ -360,16 +380,80 @@ static int l_audio_get_velocity(lua::State* L) {
return 0;
}
// @brief audio.count_speakers() -> integer
/// @brief audio.count_speakers() -> integer
static int l_audio_count_speakers(lua::State* L) {
return lua::pushinteger(L, audio::count_speakers());
}
// @brief audio.count_streams() -> integer
/// @brief audio.count_streams() -> integer
static int l_audio_count_streams(lua::State* L) {
return lua::pushinteger(L, audio::count_streams());
}
/// @brief audio.input.fetch(size) -> Bytearray
static int l_audio_fetch_input(lua::State* L) {
auto device = audio::get_input_device();
if (device == nullptr) {
return 0;
}
size_t size = lua::touinteger(L, 1);
const size_t MAX_BUFFER_SIZE = audio::MAX_INPUT_SAMPLES * 4;
if (size == 0) {
size = MAX_BUFFER_SIZE;
}
size = std::min<size_t>(size, MAX_BUFFER_SIZE);
ubyte buffer[MAX_BUFFER_SIZE];
size = device->read(reinterpret_cast<char*>(buffer), size);
std::vector<ubyte> bytes(buffer, buffer + size);
return lua::create_bytearray(L, std::move(bytes));
}
static int l_audio_get_input_devices_names(lua::State* L) {
auto device_names = audio::get_input_devices_names();
lua::createtable(L, device_names.size(), 0);
int index = 1;
for (const auto& name : device_names) {
lua::pushstring(L, name.c_str());
lua::rawseti(L, index++);
}
return 1;
}
static int l_audio_get_output_devices_names(lua::State* L) {
auto device_names = audio::get_output_devices_names();
lua::createtable(L, device_names.size(), 0);
int index = 1;
for (const auto& name : device_names) {
lua::pushstring(L, name.c_str());
lua::rawseti(L, index++);
}
return 1;
}
static int l_audio_set_input_device(lua::State* L) {
auto device_name = lua::tostring(L, 1);
audio::set_input_device(device_name);
return 0;
}
static int l_audio_get_input_info(lua::State* L) {
auto device = audio::get_input_device();
if (device == nullptr) {
return 0;
}
lua::createtable(L, 0, 4);
lua::pushlstring(L, device->getDeviceSpecifier());
lua::setfield(L, "device_specifier");
lua::pushinteger(L, device->getChannels());
lua::setfield(L, "channels");
lua::pushinteger(L, device->getSampleRate());
lua::setfield(L, "sample_rate");
lua::pushinteger(L, device->getBitsPerSample());
lua::setfield(L, "bits_per_sample");
return 1;
}
const luaL_Reg audiolib[] = {
{"play_sound", lua::wrap<l_audio_play_sound>},
{"play_sound_2d", lua::wrap<l_audio_play_sound_2d>},
@@ -395,5 +479,10 @@ const luaL_Reg audiolib[] = {
{"get_velocity", lua::wrap<l_audio_get_velocity>},
{"count_speakers", lua::wrap<l_audio_count_speakers>},
{"count_streams", lua::wrap<l_audio_count_streams>},
{"__fetch_input", lua::wrap<l_audio_fetch_input>},
{"get_input_devices_names", lua::wrap<l_audio_get_input_devices_names>},
{"get_output_devices_names", lua::wrap<l_audio_get_output_devices_names>},
{"set_input_device", lua::wrap<l_audio_set_input_device>},
{"get_input_info", lua::wrap<l_audio_get_input_info>},
{nullptr, nullptr}
};
@@ -1,7 +1,6 @@
#include "coders/binary_json.hpp"
#include "api_lua.hpp"
#include "util/Buffer.hpp"
#include "../lua_custom_types.hpp"
static int l_tobytes(lua::State* L) {
auto value = lua::tovalue(L, 1);
@@ -10,7 +10,7 @@
#include "content/ContentControl.hpp"
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "../lua_custom_types.hpp"
#include "../usertypes/lua_type_voxelfragment.hpp"
using namespace scripting;
+1
View File
@@ -24,6 +24,7 @@
#include "items/Inventories.hpp"
#include "util/stringutil.hpp"
#include "world/Level.hpp"
#include "../usertypes/lua_type_canvas.hpp"
using namespace gui;
using namespace scripting;
-1
View File
@@ -3,7 +3,6 @@
#include <vector>
#include <cwctype>
#include "../lua_custom_types.hpp"
#include "util/stringutil.hpp"
static int l_tobytes(lua::State* L) {
+5
View File
@@ -48,4 +48,9 @@ namespace lua {
void log_error(const std::string& text);
class Userdata {
public:
virtual ~Userdata() {};
virtual const std::string& getTypeName() const = 0;
};
}
@@ -1,140 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <array>
#include <random>
#include "lua_commons.hpp"
#include "constants.hpp"
#include "maths/UVRegion.hpp"
struct fnl_state;
class Heightmap;
class VoxelFragment;
class Texture;
class ImageData;
namespace lua {
class Userdata {
public:
virtual ~Userdata() {};
virtual const std::string& getTypeName() const = 0;
};
class LuaHeightmap : public Userdata {
std::shared_ptr<Heightmap> map;
std::unique_ptr<fnl_state> noise;
public:
LuaHeightmap(const std::shared_ptr<Heightmap>& map);
LuaHeightmap(uint width, uint height);
virtual ~LuaHeightmap();
uint getWidth() const;
uint getHeight() const;
float* getValues();
const float* getValues() const;
const std::string& getTypeName() const override {
return TYPENAME;
}
const std::shared_ptr<Heightmap>& getHeightmap() const {
return map;
}
fnl_state* getNoise() {
return noise.get();
}
void setSeed(int64_t seed);
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "Heightmap";
};
static_assert(!std::is_abstract<LuaHeightmap>());
class LuaVoxelFragment : public Userdata {
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants;
public:
LuaVoxelFragment(
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants
);
virtual ~LuaVoxelFragment();
std::shared_ptr<VoxelFragment> getFragment(size_t rotation) const {
return fragmentVariants.at(rotation & 0b11);
}
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "VoxelFragment";
};
static_assert(!std::is_abstract<LuaVoxelFragment>());
class LuaCanvas : public Userdata {
public:
explicit LuaCanvas(
std::shared_ptr<Texture> texture,
std::shared_ptr<ImageData> data,
UVRegion region = UVRegion(0, 0, 1, 1)
);
~LuaCanvas() override = default;
const std::string& getTypeName() const override {
return TYPENAME;
}
[[nodiscard]] auto& getTexture() const {
return *texture;
}
[[nodiscard]] auto& getData() const {
return *data;
}
[[nodiscard]] bool hasTexture() const {
return texture != nullptr;
}
auto shareTexture() const {
return texture;
}
void update(int extrusion = ATLAS_EXTRUSION);
void createTexture();
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "Canvas";
private:
std::shared_ptr<Texture> texture; // nullable
std::shared_ptr<ImageData> data;
UVRegion region;
};
static_assert(!std::is_abstract<LuaCanvas>());
class LuaRandom : public Userdata {
public:
std::mt19937 rng;
explicit LuaRandom(uint64_t seed) : rng(seed) {}
virtual ~LuaRandom() override = default;
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "__vc_Random";
};
static_assert(!std::is_abstract<LuaRandom>());
}
+12 -1
View File
@@ -8,7 +8,11 @@
#include "debug/Logger.hpp"
#include "util/stringutil.hpp"
#include "libs/api_lua.hpp"
#include "lua_custom_types.hpp"
#include "usertypes/lua_type_heightmap.hpp"
#include "usertypes/lua_type_voxelfragment.hpp"
#include "usertypes/lua_type_canvas.hpp"
#include "usertypes/lua_type_random.hpp"
#include "usertypes/lua_type_pcmstream.hpp"
#include "engine/Engine.hpp"
static debug::Logger logger("lua-state");
@@ -181,5 +185,12 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
}
pop(L);
}
newusertype<LuaPCMStream>(L);
if (getglobal(L, "audio")) {
if (getglobal(L, "__vc_PCMStream")) {
setfield(L, "PCMStream");
}
pop(L);
}
return L;
}
+5 -5
View File
@@ -155,18 +155,18 @@ static int l_error_handler(lua_State* L) {
}
int lua::call(State* L, int argc, int nresults) {
int handler_pos = gettop(L) - argc;
int handlerPos = gettop(L) - argc;
pushcfunction(L, l_error_handler);
insert(L, handler_pos);
insert(L, handlerPos);
int top = gettop(L);
if (lua_pcall(L, argc, nresults, handler_pos)) {
if (lua_pcall(L, argc, nresults, handlerPos)) {
std::string log = tostring(L, -1);
pop(L);
remove(L, handler_pos);
remove(L, handlerPos);
throw luaerror(log);
}
int added = gettop(L) - (top - argc - 1);
remove(L, handler_pos);
remove(L, handlerPos);
return added;
}
-1
View File
@@ -6,7 +6,6 @@
#include <unordered_map>
#include "data/dv.hpp"
#include "lua_custom_types.hpp"
#include "lua_wrapper.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
@@ -1,12 +1,13 @@
#include <unordered_map>
#include "lua_type_canvas.hpp"
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Texture.hpp"
#include "logic/scripting/lua/lua_custom_types.hpp"
#include "logic/scripting/lua/lua_util.hpp"
#include "engine/Engine.hpp"
#include "assets/Assets.hpp"
#include <unordered_map>
using namespace lua;
LuaCanvas::LuaCanvas(
@@ -0,0 +1,52 @@
#pragma once
#include "../lua_commons.hpp"
#include "maths/UVRegion.hpp"
#include "constants.hpp"
class Texture;
class ImageData;
namespace lua {
class LuaCanvas : public Userdata {
public:
explicit LuaCanvas(
std::shared_ptr<Texture> texture,
std::shared_ptr<ImageData> data,
UVRegion region = UVRegion(0, 0, 1, 1)
);
~LuaCanvas() override = default;
const std::string& getTypeName() const override {
return TYPENAME;
}
[[nodiscard]] auto& getTexture() const {
return *texture;
}
[[nodiscard]] auto& getData() const {
return *data;
}
[[nodiscard]] bool hasTexture() const {
return texture != nullptr;
}
auto shareTexture() const {
return texture;
}
void update(int extrusion = ATLAS_EXTRUSION);
void createTexture();
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "Canvas";
private:
std::shared_ptr<Texture> texture; // nullable
std::shared_ptr<ImageData> data;
UVRegion region;
};
static_assert(!std::is_abstract<LuaCanvas>());
}
@@ -1,9 +1,4 @@
#include "../lua_custom_types.hpp"
#include <cstring>
#include <sstream>
#include <iomanip>
#include <filesystem>
#include "lua_type_heightmap.hpp"
#include "util/functional_util.hpp"
#define FNL_IMPL
@@ -15,6 +10,12 @@
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "../lua_util.hpp"
#include "lua_type_heightmap.hpp"
#include <cstring>
#include <sstream>
#include <iomanip>
#include <filesystem>
using namespace lua;
@@ -0,0 +1,44 @@
#pragma once
#include "../lua_commons.hpp"
struct fnl_state;
class Heightmap;
namespace lua {
class LuaHeightmap : public Userdata {
std::shared_ptr<Heightmap> map;
std::unique_ptr<fnl_state> noise;
public:
LuaHeightmap(const std::shared_ptr<Heightmap>& map);
LuaHeightmap(uint width, uint height);
virtual ~LuaHeightmap();
uint getWidth() const;
uint getHeight() const;
float* getValues();
const float* getValues() const;
const std::string& getTypeName() const override {
return TYPENAME;
}
const std::shared_ptr<Heightmap>& getHeightmap() const {
return map;
}
fnl_state* getNoise() {
return noise.get();
}
void setSeed(int64_t seed);
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "Heightmap";
};
static_assert(!std::is_abstract<LuaHeightmap>());
}
@@ -0,0 +1,117 @@
#include "../lua_util.hpp"
#include "lua_type_pcmstream.hpp"
#include "assets/Assets.hpp"
#include "audio/MemoryPCMStream.hpp"
#include "engine/Engine.hpp"
using namespace lua;
using namespace audio;
using namespace scripting;
LuaPCMStream::LuaPCMStream(std::shared_ptr<audio::MemoryPCMStream>&& stream)
: stream(std::move(stream)) {
}
LuaPCMStream::~LuaPCMStream() = default;
const std::shared_ptr<audio::MemoryPCMStream>& LuaPCMStream::getStream() const {
return stream;
}
static int l_feed(lua::State* L) {
auto stream = touserdata<LuaPCMStream>(L, 1);
if (stream == nullptr) {
return 0;
}
auto bytes = bytearray_as_string(L, 2);
stream->getStream()->feed(
{reinterpret_cast<const ubyte*>(bytes.data()), bytes.size()}
);
return 0;
}
static int l_share(lua::State* L) {
auto stream = touserdata<LuaPCMStream>(L, 1);
if (stream == nullptr) {
return 0;
}
auto alias = require_lstring(L, 2);
if (engine->isHeadless()) {
return 0;
}
auto assets = engine->getAssets();
assets->store<PCMStream>(stream->getStream(), std::string(alias));
return 0;
}
static int l_create_sound(lua::State* L) {
auto stream = touserdata<LuaPCMStream>(L, 1);
if (stream == nullptr) {
return 0;
}
auto alias = require_lstring(L, 2);
auto memoryStream = stream->getStream();
std::vector<char> buffer(memoryStream->available());
memoryStream->readFully(buffer.data(), buffer.size(), true);
auto pcm = std::make_shared<PCM>(
std::move(buffer),
0,
memoryStream->getChannels(),
static_cast<uint8_t>(memoryStream->getBitsPerSample()),
memoryStream->getSampleRate(),
memoryStream->isSeekable()
);
auto sound = audio::create_sound(std::move(pcm), true);
auto assets = engine->getAssets();
assets->store<audio::Sound>(std::move(sound), std::string(alias));
return 0;
}
static std::unordered_map<std::string, lua_CFunction> methods {
{"feed", lua::wrap<l_feed>},
{"share", lua::wrap<l_share>},
{"create_sound", lua::wrap<l_create_sound>},
};
static int l_meta_meta_call(lua::State* L) {
auto sampleRate = touinteger(L, 2);
auto channels = touinteger(L, 3);
auto bitsPerSample = touinteger(L, 4);
auto stream =
std::make_shared<MemoryPCMStream>(sampleRate, channels, bitsPerSample);
return newuserdata<LuaPCMStream>(L, std::move(stream));
}
static int l_meta_tostring(lua::State* L) {
return pushstring(L, "PCMStream");
}
static int l_meta_index(lua::State* L) {
auto stream = touserdata<LuaPCMStream>(L, 1);
if (stream == nullptr) {
return 0;
}
if (isstring(L, 2)) {
auto found = methods.find(tostring(L, 2));
if (found != methods.end()) {
return pushcfunction(L, found->second);
}
}
return 0;
}
int LuaPCMStream::createMetatable(lua::State* L) {
createtable(L, 0, 3);
pushcfunction(L, lua::wrap<l_meta_tostring>);
setfield(L, "__tostring");
pushcfunction(L, lua::wrap<l_meta_index>);
setfield(L, "__index");
createtable(L, 0, 1);
pushcfunction(L, lua::wrap<l_meta_meta_call>);
setfield(L, "__call");
setmetatable(L);
return 1;
}
@@ -0,0 +1,26 @@
#pragma once
#include "../lua_commons.hpp"
namespace audio {
class MemoryPCMStream;
}
namespace lua {
class LuaPCMStream : public Userdata {
public:
explicit LuaPCMStream(std::shared_ptr<audio::MemoryPCMStream>&& stream);
virtual ~LuaPCMStream() override;
const std::shared_ptr<audio::MemoryPCMStream>& getStream() const;
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "__vc_PCMStream";
private:
std::shared_ptr<audio::MemoryPCMStream> stream;
};
static_assert(!std::is_abstract<LuaPCMStream>());
}
@@ -1,5 +1,5 @@
#include "../lua_custom_types.hpp"
#include "../lua_util.hpp"
#include "lua_type_random.hpp"
#include <chrono>
@@ -0,0 +1,23 @@
#pragma once
#include "../lua_commons.hpp"
#include <random>
namespace lua {
class LuaRandom : public Userdata {
public:
std::mt19937 rng;
explicit LuaRandom(uint64_t seed) : rng(seed) {}
virtual ~LuaRandom() override = default;
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "__vc_Random";
};
static_assert(!std::is_abstract<LuaRandom>());
}
@@ -1,7 +1,6 @@
#include "../lua_custom_types.hpp"
#include "lua_type_voxelfragment.hpp"
#include "../lua_util.hpp"
#include "world/generator/VoxelFragment.hpp"
#include "util/stringutil.hpp"
#include "world/Level.hpp"
@@ -0,0 +1,31 @@
#pragma once
#include <array>
#include "../lua_commons.hpp"
class VoxelFragment;
namespace lua {
class LuaVoxelFragment : public Userdata {
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants;
public:
LuaVoxelFragment(
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants
);
virtual ~LuaVoxelFragment();
std::shared_ptr<VoxelFragment> getFragment(size_t rotation) const {
return fragmentVariants.at(rotation & 0b11);
}
const std::string& getTypeName() const override {
return TYPENAME;
}
static int createMetatable(lua::State*);
inline static std::string TYPENAME = "VoxelFragment";
};
static_assert(!std::is_abstract<LuaVoxelFragment>());
}
-1
View File
@@ -17,7 +17,6 @@
#include "logic/BlocksController.hpp"
#include "logic/LevelController.hpp"
#include "lua/lua_engine.hpp"
#include "lua/lua_custom_types.hpp"
#include "maths/Heightmap.hpp"
#include "objects/Player.hpp"
#include "util/stringutil.hpp"
@@ -6,7 +6,7 @@
#include "scripting_commons.hpp"
#include "typedefs.hpp"
#include "lua/lua_engine.hpp"
#include "lua/lua_custom_types.hpp"
#include "lua/usertypes/lua_type_heightmap.hpp"
#include "content/Content.hpp"
#include "voxels/Block.hpp"
#include "voxels/Chunk.hpp"
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <stdexcept>
namespace util {
template <typename T>
class span {
public:
constexpr span(const T* ptr, size_t length)
: ptr(ptr), length(length) {}
const T& operator[](size_t index) const {
return ptr[index];
}
const T& at(size_t index) const {
if (index >= length) {
throw std::out_of_range("index is out of range");
}
return ptr[index];
}
auto begin() const {
return ptr;
}
auto end() const {
return ptr + length;
}
const T* data() const {
return ptr;
}
size_t size() const {
return length;
}
private:
const T* ptr;
size_t length;
};
}