Merge pull request #649 from MihailRis/generated-pcm-stream
Generated PCM stream (part 1)
This commit is contained in:
+201
-37
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <vector>
|
||||
#include <cwctype>
|
||||
|
||||
#include "../lua_custom_types.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
static int l_tobytes(lua::State* L) {
|
||||
|
||||
@@ -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>());
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>());
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user