midi播放测试
This commit is contained in:
parent
e0539cb4e1
commit
e77638960c
@ -4,7 +4,7 @@
|
||||
class clip {
|
||||
public:
|
||||
virtual ~clip() = default;
|
||||
virtual auto get_length() -> midi_tick = 0;
|
||||
virtual void process(midi_tick tick, midi_tick length) = 0;
|
||||
virtual auto get_length() -> uint64_t = 0;
|
||||
virtual void process(uint64_t tick, uint64_t length) = 0;
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
@ -1 +1,31 @@
|
||||
#include "clip_instance.h"
|
||||
|
||||
#include "clip.h"
|
||||
|
||||
clip_instance::clip_instance(clip* clip): clip_(clip) {
|
||||
clip_end_ = clip->get_length();
|
||||
}
|
||||
|
||||
void clip_instance::process(int64_t sample_pos, int32_t length) {
|
||||
const int64_t clip_end = clip_end_;
|
||||
if (sample_pos >= clip_end) {
|
||||
return;
|
||||
}
|
||||
const int64_t clip_begin = clip_begin_;
|
||||
if (sample_pos + length <= clip_begin) {
|
||||
return;
|
||||
}
|
||||
const int64_t clip_length = clip_end - clip_begin;
|
||||
const int64_t clip_pos = sample_pos - clip_begin;
|
||||
const int64_t clip_length_remaining = clip_length - clip_pos;
|
||||
const int64_t length_remaining = length - clip_length_remaining;
|
||||
if (clip_length_remaining < length) {
|
||||
clip_->process(clip_pos, clip_length_remaining);
|
||||
clip_begin_ = clip_end;
|
||||
if (length_remaining > 0) {
|
||||
process(sample_pos + clip_length_remaining, length_remaining);
|
||||
}
|
||||
} else {
|
||||
clip_->process(clip_pos, length);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,15 @@
|
||||
#pragma once
|
||||
#include "misc/mempool.h"
|
||||
|
||||
class clip_instance : public pool_obj<clip_instance> {
|
||||
class clip;
|
||||
|
||||
class clip_instance : public pool_obj<clip_instance> {
|
||||
public:
|
||||
clip_instance(clip* clip);
|
||||
|
||||
void process(int64_t sample_pos, int32_t length);
|
||||
private:
|
||||
clip* clip_;
|
||||
int64_t clip_begin_ = 0;
|
||||
int64_t clip_end_ = 0;
|
||||
};
|
||||
|
@ -1,16 +1,30 @@
|
||||
#include "midi_clip.h"
|
||||
|
||||
void midi_track::process(midi_tick tick, midi_tick length) {
|
||||
#include "audio/plugin_host/plugin_host.h"
|
||||
|
||||
void midi_track::process(uint64_t tick, uint64_t length) {
|
||||
const uint64_t end_tick = tick + length;
|
||||
for (; last_index_ < events_.size(); ++last_index_) {
|
||||
auto& event = events_[last_index_];
|
||||
if (event.tick >= end_tick) {
|
||||
break;
|
||||
}
|
||||
host_->
|
||||
host_->push_midi(event, event.tick - tick);
|
||||
}
|
||||
}
|
||||
|
||||
void midi_clip::process(midi_tick tick, midi_tick length) {
|
||||
auto end_tick = tick += length;
|
||||
void midi_clip::update_length() {
|
||||
length_ = 0;
|
||||
for (const auto [instrument, track] : tracks_) {
|
||||
const smf::MidiEventList& list = track->get_track();
|
||||
if (!list.size())
|
||||
continue;
|
||||
length_ = std::max<uint64_t>(length_, list.back().tick);
|
||||
}
|
||||
}
|
||||
|
||||
void midi_clip::process(uint64_t tick, uint64_t length) {
|
||||
for (const auto [instrument, track] : tracks_) {
|
||||
track->process(tick, length);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include <unordered_map>
|
||||
|
||||
#include "clip.h"
|
||||
#include "misc/mempool.h"
|
||||
#include "MidiEventList.h"
|
||||
@ -9,10 +11,17 @@ class plugin_host;
|
||||
class midi_track : public pool_obj<midi_track> {
|
||||
public:
|
||||
midi_track(plugin_host* host) : host_(host) {}
|
||||
void process(midi_tick tick, midi_tick length);
|
||||
void process(uint64_t tick, uint64_t length);
|
||||
void reset() {
|
||||
last_index_ = 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_host() const -> plugin_host* {
|
||||
return host_;
|
||||
}
|
||||
[[nodiscard]] auto get_track() -> smf::MidiEventList& {
|
||||
return events_;
|
||||
}
|
||||
private:
|
||||
plugin_host* host_;
|
||||
smf::MidiEventList events_;
|
||||
@ -21,16 +30,25 @@ private:
|
||||
|
||||
class midi_clip : public clip, public pool_obj<midi_clip> {
|
||||
public:
|
||||
auto get_length() -> midi_tick override {
|
||||
void update_length();
|
||||
auto get_length() -> uint64_t override {
|
||||
return length_;
|
||||
}
|
||||
void process(midi_tick tick, midi_tick length) override;
|
||||
void process(uint64_t tick, uint64_t length) override;
|
||||
void reset() override {
|
||||
for (auto track : tracks_) {
|
||||
for (const auto [instrument, track] : tracks_) {
|
||||
track->reset();
|
||||
}
|
||||
}
|
||||
auto get_instrument_track(plugin_host* instrument) -> midi_track* {
|
||||
if (tracks_.contains(instrument)) {
|
||||
return tracks_[instrument];
|
||||
}
|
||||
auto new_track = new midi_track(instrument);
|
||||
tracks_[instrument] = new_track;
|
||||
return new_track;
|
||||
}
|
||||
private:
|
||||
midi_tick length_;
|
||||
std::vector<midi_track*> tracks_;
|
||||
uint64_t length_ = 0;
|
||||
std::unordered_map<plugin_host*, midi_track*> tracks_;
|
||||
};
|
||||
|
@ -1,34 +1,82 @@
|
||||
#include "midi_sequencer.h"
|
||||
|
||||
#include "MidiFile.h"
|
||||
#include "audio/device/audio_device_manager.h"
|
||||
#include "audio/plugin_host/plugin_host_manager.h"
|
||||
#include "misc/tick.h"
|
||||
#include "audio/plugin_host/vst2/vst2_plugin_host.h"
|
||||
#include "clip/clip_instance.h"
|
||||
#include "clip/midi_clip.h"
|
||||
#include "thread_message/thread_message_hubs.h"
|
||||
|
||||
IMPL_SINGLETON_INSTANCE(midi_sequencer)
|
||||
|
||||
|
||||
void midi_sequencer::test() {
|
||||
const char* path = R"(F:\VST\VST64\4Front Piano x64.dll)";
|
||||
// host->try_open_editor();
|
||||
|
||||
smf::MidiFile midifile("E:/Projects/AronaStudio/Arona/resources/1.mid");
|
||||
if (!midifile.status())
|
||||
spdlog::critical("Error reading MIDI file");
|
||||
midifile.doTimeAnalysis();
|
||||
midifile.linkNotePairs();
|
||||
midifile.joinTracks();
|
||||
auto c = get_midi_sequencer()->create_clip<midi_clip>();
|
||||
auto track_count = midifile.getTrackCount();
|
||||
for (int i = 0; i < track_count; ++i) {
|
||||
plugin_host* host = get_plugin_host_manager()->create_instrument_plugin_host(path);
|
||||
auto& t = midifile[i];
|
||||
for (int j = 0; j < t.size(); ++j) {
|
||||
auto& e = t[j];
|
||||
midi_tick tick(e.tick);
|
||||
e.tick = tick.to_audio_frame(midifile.getTPQ());
|
||||
}
|
||||
const auto track = c->get_instrument_track(host);
|
||||
track->get_track() = midifile[i];
|
||||
}
|
||||
c->update_length();
|
||||
g_audio_thread_hub.push_message([=, this]() {
|
||||
create_clip_instance(c);
|
||||
});
|
||||
}
|
||||
|
||||
void midi_sequencer::init(singleton_initliazer& initliazer) {
|
||||
singleton_t<midi_sequencer>::init(initliazer);
|
||||
singleton_t::init(initliazer);
|
||||
auto adm = initliazer.require<audio_device_manager>();
|
||||
adm->on_sample_rate_changed.add_raw(this, &midi_sequencer::on_sample_rate_changed);
|
||||
set_bpm(140.0);
|
||||
set_tpq(960);
|
||||
}
|
||||
|
||||
void midi_sequencer::process(double sample_rate, uint32_t delta_frames) {
|
||||
if (!playing_) {
|
||||
return;
|
||||
}
|
||||
for (auto clip_ins : clip_instances_) {
|
||||
clip_ins->process(sample_pos_, delta_frames);
|
||||
}
|
||||
update_time(sample_rate, delta_frames);
|
||||
update_vst2_time_info();
|
||||
}
|
||||
|
||||
auto midi_sequencer::create_clip_instance(clip* clip) -> clip_instance* {
|
||||
auto instance = new clip_instance(clip);
|
||||
clip_instances_.push_back(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void midi_sequencer::delete_clip(clip* clip) {
|
||||
auto it = std::find(clips_.begin(), clips_.end(), clip);
|
||||
if (it != clips_.end()) {
|
||||
clips_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void midi_sequencer::set_bpm(double in_bpm) {
|
||||
bpm_ = std::max(in_bpm, 1.0);
|
||||
vst2_time.tempo = bpm_;
|
||||
}
|
||||
|
||||
void midi_sequencer::set_tpq(int32_t in_tpq) {
|
||||
tpq_ = in_tpq;
|
||||
}
|
||||
|
||||
void midi_sequencer::on_sample_rate_changed(uint32_t sample_rate) {
|
||||
vst2_time.sampleRate = sample_rate;
|
||||
}
|
||||
@ -37,8 +85,9 @@ void midi_sequencer::update_time(double sample_rate, uint32_t delta_frames) {
|
||||
auto clock = std::chrono::system_clock::now();
|
||||
sample_pos_ += delta_frames;
|
||||
nano_seconds_ = std::chrono::duration_cast<std::chrono::nanoseconds>(clock.time_since_epoch()).count();
|
||||
tpq_pos_ = sample_pos_ * tpq_ / sample_rate;
|
||||
tpq_pos_ = sample_pos_ * TPQ / sample_rate;
|
||||
|
||||
vst2_time.sampleRate = sample_rate;
|
||||
update_vst2_time_info();
|
||||
}
|
||||
|
||||
|
@ -4,23 +4,38 @@
|
||||
#include "misc/tick.h"
|
||||
#include "misc/singleton/singleton.h"
|
||||
|
||||
class midi_sequencer : public singleton_t<midi_sequencer> {
|
||||
class clip;
|
||||
class clip_instance;
|
||||
|
||||
class CORE_API midi_sequencer : public singleton_t<midi_sequencer> {
|
||||
public:
|
||||
static constexpr int32_t TPQ = 960;
|
||||
void test();
|
||||
|
||||
void init(singleton_initliazer& initliazer) override;
|
||||
void process(double sample_rate, uint32_t delta_frames);
|
||||
auto toggle_play() -> bool {
|
||||
playing_ = !playing_;
|
||||
return playing_;
|
||||
}
|
||||
|
||||
const char* get_name() override { return "midi_sequencer"; }
|
||||
template<class T>
|
||||
auto create_clip() -> T* {
|
||||
auto clip = new T();
|
||||
clips_.push_back(clip);
|
||||
return clip;
|
||||
}
|
||||
auto create_clip_instance(clip* clip) -> clip_instance*;
|
||||
|
||||
void delete_clip(clip* clip);
|
||||
|
||||
auto get_name() -> const char* override { return "midi_sequencer"; }
|
||||
void set_bpm(double in_bpm);
|
||||
void set_tpq(int32_t in_tpq);
|
||||
[[nodiscard]] double get_bpm() const {
|
||||
[[nodiscard]] auto get_bpm() const -> double {
|
||||
return bpm_;
|
||||
}
|
||||
[[nodiscard]] int32_t get_tpq() const {
|
||||
return tpq_;
|
||||
}
|
||||
|
||||
uint64_t get_sample_pos() const {
|
||||
[[nodiscard]] auto get_sample_pos() const -> double {
|
||||
return sample_pos_;
|
||||
}
|
||||
protected:
|
||||
@ -29,11 +44,13 @@ protected:
|
||||
private:
|
||||
void update_vst2_time_info();
|
||||
|
||||
bool playing_ = false;
|
||||
double bpm_ = 0;
|
||||
int32_t tpq_ = 0; // ticks per quarter note
|
||||
audio_frame sample_pos_ = 0;
|
||||
double sample_pos_ = 0;
|
||||
int32_t tpq_pos_ = 0;
|
||||
uint64_t nano_seconds_ = 0;
|
||||
std::vector<clip*> clips_;
|
||||
std::vector<clip_instance*> clip_instances_;
|
||||
};
|
||||
|
||||
DEFINE_SINGLETON_INSTANCE(midi_sequencer)
|
||||
|
@ -13,7 +13,6 @@ void audio_clock::set_midi_tick(uint64_t tick) {
|
||||
|
||||
void audio_clock::update_midi_tick() {
|
||||
auto sample_rate = get_audio_device_manager()->get_sample_rate();
|
||||
auto tpq = get_midi_sequencer()->get_tpq();
|
||||
double second = (double)sample_tick_ / sample_rate;
|
||||
midi_tick_ = (uint64_t)(second * tpq);
|
||||
midi_tick_ = (uint64_t)(second * midi_sequencer::TPQ);
|
||||
}
|
||||
|
@ -4,16 +4,9 @@
|
||||
#include <atomic>
|
||||
|
||||
#include "extern.h"
|
||||
#include "misc/core_assert.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#if defined(_DEBUG) || defined(DEBUG)
|
||||
#define checkf(expr, msg, ...) if (!(expr)) { spdlog::error(msg, __VA_ARGS__); throw std::runtime_error(fmt::format(msg, __VA_ARGS__)); }
|
||||
#define check(expr) if (!(expr)) { spdlog::error("Check failed: {}", #expr); throw std::runtime_error(fmt::format("Check failed: {}", #expr)); }
|
||||
#else
|
||||
#define checkf(...)
|
||||
#define check(...)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Basic implementation of a circular buffer built for pushing and popping arbitrary amounts of data at once.
|
||||
* Designed to be thread safe for SPSC; However, if Push() and Pop() are both trying to access an overlapping area of the buffer,
|
||||
@ -234,7 +227,7 @@ public:
|
||||
// Will assert if the buffer is empty. Please check Num() > 0 before calling.
|
||||
SampleType pop() {
|
||||
// Calling this when the buffer is empty is considered a fatal error.
|
||||
check(Num() > 0);
|
||||
check(num() > 0);
|
||||
|
||||
SampleType* src_buffer = internal_buffer_.data();
|
||||
const uint32_t read_index = read_counter_;
|
||||
@ -246,7 +239,7 @@ public:
|
||||
|
||||
SampleType* get_header() {
|
||||
// Calling this when the buffer is empty is considered a fatal error.
|
||||
check(Num() > 0);
|
||||
check(num() > 0);
|
||||
|
||||
// 根据read_counter_获取当前的header
|
||||
return &internal_buffer_[read_counter_];
|
||||
@ -298,6 +291,3 @@ public:
|
||||
|
||||
using circular_buffer_type = circular_audio_buffer<sample_t>;
|
||||
using circular_buffer_vector_type = std::vector<circular_buffer_type>;
|
||||
|
||||
#undef checkf
|
||||
#undef check
|
@ -15,7 +15,7 @@
|
||||
IMPL_SINGLETON_INSTANCE(plugin_host_manager)
|
||||
|
||||
void plugin_host_manager::init(singleton_initliazer& initliazer) {
|
||||
singleton_t<plugin_host_manager>::init(initliazer);
|
||||
singleton_t::init(initliazer);
|
||||
auto* mixer_ptr = initliazer.require<mixer>();
|
||||
mixer_ptr->on_remove_track.add_raw(this, &plugin_host_manager::on_mixer_track_removed);
|
||||
}
|
||||
|
@ -7,7 +7,9 @@
|
||||
|
||||
std::unordered_map<std::string, std::weak_ptr<dynamic_library>> vst2_library_map;
|
||||
std::unordered_map<void*, uint32_t> vst2_latency_map;
|
||||
VstTimeInfo vst2_plugin_host::vst_time_info{};
|
||||
VstTimeInfo vst2_plugin_host::vst_time_info{
|
||||
.flags = kVstNanosValid | kVstPpqPosValid | kVstTempoValid | kVstBarsValid | kVstCyclePosValid | kVstTimeSigValid
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, bool> can_do_map = {
|
||||
{"sendVstEvents", true}, {"sendVstMidiEvent", true}, {"sendVstTimeInfo", true}, {"receiveVstEvents", false},
|
||||
@ -27,8 +29,9 @@ std::shared_ptr<dynamic_library> get_vst2_library(const std::string& path) {
|
||||
}
|
||||
|
||||
bool vst_host_can_do(const char* can_do) {
|
||||
if (can_do_map.contains(can_do)) return can_do_map[can_do];
|
||||
spdlog::warn("Unknown can do: {}", can_do);
|
||||
if (can_do_map.contains(can_do))
|
||||
return can_do_map[can_do];
|
||||
ensuref(can_do_map.contains(can_do), "Unknown can do: {}", can_do);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -67,7 +70,7 @@ VstIntPtr vst_master_callback(AEffect* effect, VstInt32 opcode, VstInt32 index,
|
||||
return 1;
|
||||
}
|
||||
case audioMasterGetTime :
|
||||
return (VstIntPtr) &vst2_time;
|
||||
return (VstIntPtr)&vst2_time;
|
||||
case audioMasterIdle :
|
||||
// UE_LOG(LogTemp, Log, TEXT("Plugin Idle"));
|
||||
return 1;
|
||||
@ -123,25 +126,39 @@ VstIntPtr vst_master_callback(AEffect* effect, VstInt32 opcode, VstInt32 index,
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef AEffect*(*vst_plugin_entry_proc)(audioMasterCallback AudioMaster);
|
||||
using vst_plugin_entry_proc = AEffect*(audioMasterCallback);
|
||||
|
||||
vst2_events::vst_events_handle::vst_events_handle(const std::vector<VstMidiEvent>& events) {
|
||||
const auto num_events = events.size();
|
||||
const auto event_size = num_events * sizeof(VstEvent*) + sizeof(VstEvents);
|
||||
const auto buffer = static_cast<VstEvents*>(std::malloc(event_size));
|
||||
std::memset(buffer, 0, event_size);
|
||||
|
||||
buffer->numEvents = num_events;
|
||||
for (int i = 0; i < num_events; ++i) {
|
||||
buffer->events[i] = (VstEvent*)&events[i];
|
||||
}
|
||||
data_ = buffer;
|
||||
}
|
||||
|
||||
void vst2_events::push(const smf::MidiEvent& event, uint32_t frame_delta) {
|
||||
// if (!event.isNote())
|
||||
// return;
|
||||
VstMidiEvent midi_event{};
|
||||
memset(&midi_event, 0, sizeof(VstMidiEvent));
|
||||
midi_event.type = kVstMidiType;
|
||||
midi_event.byteSize = sizeof(VstMidiEvent);
|
||||
midi_event.deltaFrames = frame_delta;
|
||||
midi_event.midiData[0] = event[0];
|
||||
midi_event.midiData[1] = event[1];
|
||||
midi_event.midiData[2] = event[2];
|
||||
midi_events_.push_back(midi_event);
|
||||
midi_event.midiData[0] = event.getP0();
|
||||
midi_event.midiData[1] = event.getP1();
|
||||
midi_event.midiData[2] = event.getP2();
|
||||
midi_event.noteOffVelocity = event.isNoteOff() ? event.getVelocity() : 0;
|
||||
|
||||
vst_events_.numEvents = static_cast<VstInt32>(midi_events_.size());
|
||||
vst_events_.events[0] = (VstEvent*)midi_events_.data();
|
||||
midi_events_.push_back(midi_event);
|
||||
}
|
||||
|
||||
void vst2_events::clear() {
|
||||
midi_events_.clear();
|
||||
vst_events_.numEvents = 0;
|
||||
}
|
||||
|
||||
vst2_plugin_host::vst2_plugin_host() : plugin_host(type) {
|
||||
@ -161,7 +178,7 @@ bool vst2_plugin_host::load_plugin(const char* path) {
|
||||
// load vst
|
||||
library_ = get_vst2_library(path);
|
||||
// get main function
|
||||
const auto entry_proc = (vst_plugin_entry_proc) (library_->get_function("VSTPluginMain"));
|
||||
const auto entry_proc = (vst_plugin_entry_proc*)library_->get_function("VSTPluginMain");
|
||||
if (!entry_proc) {
|
||||
spdlog::error("Failed to get entry point from {}", path);
|
||||
return false;
|
||||
@ -224,7 +241,11 @@ void vst2_plugin_host::update_channel_node_name() {
|
||||
}
|
||||
|
||||
void vst2_plugin_host::process(uint32_t frame_num) {
|
||||
// TODO send midi
|
||||
if (!events_.empty()) {
|
||||
auto event_handle = events_.flush();
|
||||
dispatch(effProcessEvents, 0, 0, event_handle);
|
||||
}
|
||||
|
||||
#if USE_DOUBLE_SAMPLE
|
||||
effect_->processDoubleReplacing(effect_, channel->get_input_headers(), channel->get_output_headers(), static_cast<VstInt32>(frame_num));
|
||||
#else
|
||||
@ -240,7 +261,6 @@ void vst2_plugin_host::process(uint32_t frame_num) {
|
||||
|
||||
void vst2_plugin_host::push_midi(const smf::MidiEvent& event, uint32_t frame_delta) {
|
||||
events_.push(event, frame_delta);
|
||||
dispatch(effProcessEvents, 0, 0, events_.get());
|
||||
}
|
||||
|
||||
void vst2_plugin_host::idle_editor() {
|
||||
|
@ -6,13 +6,35 @@
|
||||
|
||||
class vst2_events {
|
||||
public:
|
||||
struct vst_events_handle {
|
||||
public:
|
||||
vst_events_handle(const std::vector<VstMidiEvent>& events);
|
||||
vst_events_handle(const vst_events_handle&) = delete;
|
||||
vst_events_handle(vst_events_handle&& other) noexcept : data_(other.data_) {
|
||||
other.data_ = nullptr;
|
||||
}
|
||||
|
||||
~vst_events_handle() {
|
||||
free(data_);
|
||||
}
|
||||
operator VstEvents*() {
|
||||
return data_;
|
||||
}
|
||||
private:
|
||||
VstEvents* data_;
|
||||
};
|
||||
|
||||
void push(const smf::MidiEvent& event, uint32_t frame_delta);
|
||||
void clear();
|
||||
[[nodiscard]] auto get() -> VstEvents* {
|
||||
return &vst_events_;
|
||||
[[nodiscard]] auto flush() -> vst_events_handle {
|
||||
vst_events_handle out { midi_events_ };
|
||||
return out;
|
||||
}
|
||||
[[nodiscard]] auto empty() const -> bool {
|
||||
return midi_events_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
VstEvents vst_events_;
|
||||
std::vector<VstMidiEvent> midi_events_;
|
||||
};
|
||||
|
||||
|
57
core/misc/core_assert.h
Normal file
57
core/misc/core_assert.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <cassert>
|
||||
|
||||
// 定义 check 宏
|
||||
#define check(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
spdlog::critical("Check failed: {} in file {} at line {}", #expr, __FILE__, __LINE__); \
|
||||
assert(expr); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// 定义 checkf 宏
|
||||
#define checkf(expr, msg, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
spdlog::critical("Check failed: {} in file {} at line {}. " msg, #expr, __FILE__, __LINE__, __VA_ARGS__); \
|
||||
assert(expr); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// 定义 ensure 宏
|
||||
#if BUILD_DEBUG
|
||||
#define ensure(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
spdlog::error("Ensure failed: {} in file {} at line {}", #expr, __FILE__, __LINE__); \
|
||||
assert(expr); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define ensure(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
spdlog::error("Ensure condition failed: {} in file {} at line {}", #expr, __FILE__, __LINE__); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// 定义 ensuref 宏
|
||||
#if BUILD_DEBUG
|
||||
#define ensuref(expr, msg, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
spdlog::error("Ensure failed: {} in file {} at line {}. " msg, #expr, __FILE__, __LINE__, __VA_ARGS__); \
|
||||
assert(expr); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define ensuref(expr, msg, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
spdlog::error("Ensure condition failed: {} in file {} at line {}. " msg, #expr, __FILE__, __LINE__, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
54
core/misc/core_math.h
Normal file
54
core/misc/core_math.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
template <typename Type, size_t N>
|
||||
constexpr int numElementsInArray(Type (&)[N]) noexcept { return N; }
|
||||
|
||||
/** Remaps a value from a source range to a target range. */
|
||||
template <typename Type>
|
||||
auto jmap(Type sourceValue, Type sourceRangeMin, Type sourceRangeMax, Type targetRangeMin, Type targetRangeMax) -> Type
|
||||
{
|
||||
check(sourceRangeMax != sourceRangeMin); // mapping from a range of zero will produce NaN!
|
||||
return targetRangeMin + ((targetRangeMax - targetRangeMin) * (sourceValue - sourceRangeMin)) / (sourceRangeMax -
|
||||
sourceRangeMin);
|
||||
}
|
||||
|
||||
template <typename FloatType>
|
||||
auto RoundToInt(const FloatType value) noexcept -> int
|
||||
{
|
||||
#ifdef __INTEL_COMPILER
|
||||
#pragma float_control (precise, on, push)
|
||||
#endif
|
||||
|
||||
union
|
||||
{
|
||||
int asInt[2];
|
||||
double asDouble;
|
||||
} n;
|
||||
n.asDouble = static_cast<double>(value) + 6755399441055744.0;
|
||||
|
||||
#if JUCE_BIG_ENDIAN
|
||||
return n.asInt [1];
|
||||
#else
|
||||
return n.asInt[0];
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Returns true if a value is at least zero, and also below a specified upper limit.
|
||||
This is basically a quicker way to write:
|
||||
@code valueToTest >= 0 && valueToTest < upperLimit
|
||||
@endcode
|
||||
*/
|
||||
template <typename Type1, typename Type2>
|
||||
auto IsPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept -> bool
|
||||
{
|
||||
check(Type1() <= static_cast<Type1> (upperLimit));
|
||||
// makes no sense to call this if the upper limit is itself below zero..
|
||||
return Type1() <= valueToTest && valueToTest < static_cast<Type1>(upperLimit);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
auto IsPositiveAndBelow(int valueToTest, Type upperLimit) noexcept -> bool
|
||||
{
|
||||
check(upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero..
|
||||
return static_cast<unsigned int>(valueToTest) < static_cast<unsigned int>(upperLimit);
|
||||
}
|
36
core/misc/core_str.h
Normal file
36
core/misc/core_str.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
// 定义 TCHAR 类型
|
||||
#ifdef UNICODE
|
||||
using char_t = wchar_t;
|
||||
#define TEXT(x) L##x
|
||||
#else
|
||||
using char_t = char;
|
||||
#define TEXT(x) x
|
||||
#endif
|
||||
|
||||
inline auto ANSI_TO_TCHAR(const char* ansi_str) -> const char_t* {
|
||||
#ifdef UNICODE
|
||||
// 如果 TCHAR 是 wchar_t,需要进行转换
|
||||
size_t len = strlen(ansiStr) + 1;
|
||||
std::vector<wchar_t> wstr(len);
|
||||
mbstowcs(wstr.data(), ansiStr, len);
|
||||
return wstr.data();
|
||||
#else
|
||||
// 如果 TCHAR 是 char,直接返回原始字符串
|
||||
return ansi_str;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline auto TCHAR_TO_ANSI(const char_t* tchar_str) -> const char* {
|
||||
#ifdef UNICODE
|
||||
// 如果 TCHAR 是 wchar_t,需要进行转换
|
||||
size_t len = wcslen(tcharStr) + 1;
|
||||
std::vector<char> ansiStr(len);
|
||||
wcstombs(ansiStr.data(), tcharStr, len);
|
||||
return ansiStr.data();
|
||||
#else
|
||||
// 如果 TCHAR 是 char,直接返回原始字符串
|
||||
return tchar_str;
|
||||
#endif
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <bits/ranges_algo.h>
|
||||
|
||||
class memory_pool {
|
||||
public:
|
||||
@ -66,22 +67,19 @@ template<class T>
|
||||
class obj_mempool {
|
||||
public:
|
||||
template<typename ...Args>
|
||||
static auto alloc(Args&&... args) -> T* {
|
||||
auto obj = static_cast<T*>(pool_.allocate());
|
||||
static auto construct(Args&&... args) -> T* {
|
||||
auto obj = alloc();
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
objs_.push_back(obj);
|
||||
return obj;
|
||||
}
|
||||
static auto alloc() -> T* {
|
||||
auto obj = static_cast<T*>(pool_.allocate());
|
||||
static auto construct() -> T* {
|
||||
auto obj = alloc();
|
||||
new (obj) T();
|
||||
objs_.push_back(obj);
|
||||
return obj;
|
||||
}
|
||||
static void free(T* p) {
|
||||
p->~T();
|
||||
pool_.deallocate(p);
|
||||
objs_.erase(std::ranges::remove(objs, p));
|
||||
deallocate(p);
|
||||
}
|
||||
static void free_all() {
|
||||
for (auto obj : objs_) {
|
||||
@ -103,6 +101,17 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto alloc() -> T* {
|
||||
T* p = static_cast<T*>(pool_.allocate());
|
||||
objs_.push_back(p);
|
||||
return p;
|
||||
}
|
||||
static void deallocate(T* p) {
|
||||
pool_.deallocate(p);
|
||||
auto e = std::ranges::remove(objs_, p);
|
||||
objs_.erase(e.begin(), e.end());
|
||||
}
|
||||
private:
|
||||
inline static auto pool_ = memory_pool(sizeof(T), 64);
|
||||
inline static std::vector<T*> objs_;
|
||||
@ -110,12 +119,11 @@ private:
|
||||
|
||||
template<class T>
|
||||
class pool_obj {
|
||||
inline static auto pool = memory_pool(sizeof(T), 64);
|
||||
public:
|
||||
auto operator new(size_t size) -> void* {
|
||||
return pool.allocate();
|
||||
return obj_mempool<T>::alloc();
|
||||
}
|
||||
void operator delete(void* p) {
|
||||
pool.deallocate(p);
|
||||
obj_mempool<T>::deallocate(static_cast<T*>(p));
|
||||
}
|
||||
};
|
||||
|
@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include <cassert>
|
||||
|
||||
/**
|
||||
* A smart pointer to an object which implements AddRef/Release.
|
||||
*/
|
||||
template<typename ReferencedType>
|
||||
class ref_count_ptr {
|
||||
typedef ReferencedType* reference_type;
|
||||
using reference_type = ReferencedType *;
|
||||
|
||||
public:
|
||||
ref_count_ptr(): reference_(nullptr) {
|
||||
|
@ -1,5 +1,3 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace scope_exit_support {
|
||||
|
@ -4,26 +4,25 @@
|
||||
|
||||
midi_tick::midi_tick(const audio_frame& in_frame) {
|
||||
auto bpm = g_midi_sequencer.get_bpm();
|
||||
auto tpq = g_midi_sequencer.get_tpq();
|
||||
auto tpq = midi_sequencer::TPQ;
|
||||
auto sample_rate = g_audio_device_manager.get_sample_rate();
|
||||
|
||||
double time_in_seconds = (double)in_frame.get_frames() / sample_rate;
|
||||
ticks = time_in_seconds * bpm * tpq / 60.0;
|
||||
}
|
||||
|
||||
audio_frame midi_tick::to_audio_frame() const {
|
||||
return audio_frame(*this);
|
||||
auto midi_tick::to_audio_frame(int32_t tpq) const -> audio_frame {
|
||||
return audio_frame(*this, tpq);
|
||||
}
|
||||
|
||||
audio_frame::audio_frame(const midi_tick& in_tick) {
|
||||
audio_frame::audio_frame(const midi_tick& in_tick, int32_t tpq) {
|
||||
auto bpm = g_midi_sequencer.get_bpm();
|
||||
auto tpq = g_midi_sequencer.get_tpq();
|
||||
auto sample_rate = g_audio_device_manager.get_sample_rate();
|
||||
double tick_duration = 60.0 / (bpm * tpq);
|
||||
double tick_in_seconds = in_tick.get_ticks() * tick_duration;
|
||||
frames = tick_in_seconds * sample_rate;
|
||||
}
|
||||
|
||||
midi_tick audio_frame::to_midi_tick() const {
|
||||
midi_tick audio_frame::to_midi_tick(int32_t tpq) const {
|
||||
return midi_tick(*this);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ public:
|
||||
}
|
||||
explicit midi_tick(const audio_frame& in_frame);
|
||||
|
||||
[[nodiscard]] auto to_audio_frame() const -> audio_frame;
|
||||
[[nodiscard]] auto to_audio_frame(int32_t tpq) const -> audio_frame;
|
||||
[[nodiscard]] auto get_ticks() const -> int64_t {
|
||||
return ticks;
|
||||
}
|
||||
@ -37,9 +37,9 @@ public:
|
||||
audio_frame(int64_t in_frames) : frames(in_frames) {
|
||||
|
||||
}
|
||||
explicit audio_frame(const midi_tick& in_tick);
|
||||
explicit audio_frame(const midi_tick& in_tick, int32_t tpq);
|
||||
|
||||
[[nodiscard]] auto to_midi_tick() const -> midi_tick;
|
||||
[[nodiscard]] auto to_midi_tick(int32_t tpq) const -> midi_tick;
|
||||
[[nodiscard]] auto get_frames() const -> int64_t {
|
||||
return frames;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include "extern.h"
|
||||
#include "misc/mempool.h"
|
||||
|
||||
class CORE_API thread_message {
|
||||
public:
|
||||
@ -9,7 +10,7 @@ public:
|
||||
virtual void execute() = 0;
|
||||
};
|
||||
|
||||
class CORE_API lamba_thread_message : public thread_message {
|
||||
class CORE_API lamba_thread_message : public thread_message, public pool_obj<lamba_thread_message> {
|
||||
public:
|
||||
lamba_thread_message(const std::function<void()>& in_func) : function(in_func) {
|
||||
|
||||
|
@ -6,7 +6,7 @@ void thread_message_hub::process_messages() {
|
||||
while (!messages_.empty()) {
|
||||
lamba_thread_message* message = messages_.front();
|
||||
message->execute();
|
||||
obj_mempool<lamba_thread_message>::free(message);
|
||||
delete message;
|
||||
messages_.pop();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "../misc/mempool.h"
|
||||
#include "thread_message.h"
|
||||
|
||||
class CORE_API thread_message_hub {
|
||||
@ -18,7 +17,7 @@ public:
|
||||
// push_message(message);
|
||||
// }
|
||||
void push_message(const std::function<void()>& func) {
|
||||
auto* message = obj_mempool<lamba_thread_message>::alloc(func);
|
||||
auto* message = new lamba_thread_message(func);
|
||||
push_message(message);
|
||||
}
|
||||
void process_messages();
|
||||
|
Loading…
x
Reference in New Issue
Block a user