diff --git a/CMakeLists.txt b/CMakeLists.txt index 387ecf6da..3469c8870 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ endif() add_subdirectory(libraries/libgrapheme EXCLUDE_FROM_ALL) add_subdirectory(libraries/miniz EXCLUDE_FROM_ALL) add_subdirectory(libraries/physfs EXCLUDE_FROM_ALL) +add_subdirectory(libraries/steve EXCLUDE_FROM_ALL) project( obsidian @@ -70,6 +71,7 @@ set(OBSIDIAN_SOURCE_FILES source/g_wolf.cc source/lex.yy.c source/lib_argv.cc + source/lib_midi.cc source/lib_tga.cc source/lib_util.cc source/lib_wad.cc @@ -137,6 +139,7 @@ target_include_directories(obsidian SYSTEM PRIVATE libraries/libgrapheme) target_include_directories(obsidian SYSTEM PRIVATE libraries/minilua) target_include_directories(obsidian SYSTEM PRIVATE libraries/miniz) target_include_directories(obsidian SYSTEM PRIVATE libraries/physfs/src) +target_include_directories(obsidian SYSTEM PRIVATE libraries/steve/src) if(UNIX) if(NOT CONSOLE_ONLY @@ -164,6 +167,7 @@ if(UNIX) libgrapheme miniz physfs-static + steve "-framework Cocoa" "-framework CoreGraphics" "-framework CoreText" @@ -171,7 +175,7 @@ if(UNIX) ) else() target_link_libraries( - obsidian PRIVATE libgrapheme miniz physfs-static + obsidian PRIVATE libgrapheme miniz physfs-static steve ) endif() elseif(${CMAKE_SYSTEM} MATCHES "BSD") @@ -183,10 +187,11 @@ if(UNIX) libgrapheme miniz physfs-static + steve ) else() target_link_libraries( - obsidian PRIVATE libgrapheme miniz physfs-static + obsidian PRIVATE libgrapheme miniz physfs-static steve ) endif() else() @@ -200,6 +205,7 @@ if(UNIX) physfs-static fontconfig pthread + steve ) else() target_link_libraries( @@ -208,6 +214,7 @@ if(UNIX) miniz physfs-static pthread + steve ) endif() endif() @@ -220,10 +227,11 @@ else() libgrapheme miniz physfs-static + steve ) else() target_link_libraries( - obsidian PRIVATE libgrapheme miniz physfs-static + obsidian PRIVATE libgrapheme miniz physfs-static steve ) endif() endif() \ No newline at end of file diff --git a/libraries/steve/CMakeLists.txt b/libraries/steve/CMakeLists.txt new file mode 100644 index 000000000..0835d355a --- /dev/null +++ b/libraries/steve/CMakeLists.txt @@ -0,0 +1,5 @@ +project(steve) + +file(GLOB_RECURSE SOURCES src/*.cpp src/*.h) + +add_library(steve ${SOURCES}) \ No newline at end of file diff --git a/libraries/steve/LICENSE b/libraries/steve/LICENSE new file mode 100644 index 000000000..cf1ab25da --- /dev/null +++ b/libraries/steve/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/libraries/steve/README.md b/libraries/steve/README.md new file mode 100644 index 000000000..7cfe9fb1f --- /dev/null +++ b/libraries/steve/README.md @@ -0,0 +1,8 @@ +# Steve + +Steve is a C++ program that composes music and generates MIDI files from scratch. + +## Assumptions +- Harmony is defined such as, at any given time (preferably at a measure granularity), the set of all tones played fits inside a chord +- Melody is defined by a scale (kept throughout an entire music and common to all instruments), all tones must be fit in the scale +- Rhythm is defined by the fact that notes can only have a power of two duration, and a note can only be placed on a position dividable by its duration diff --git a/libraries/steve/src/Chord.cpp b/libraries/steve/src/Chord.cpp new file mode 100644 index 000000000..049e5765c --- /dev/null +++ b/libraries/steve/src/Chord.cpp @@ -0,0 +1,18 @@ +#include "Chord.h" + +using namespace steve; + +uint8_t ChordDescription::get_tone(uint8_t index) const { + uint8_t tone = 0; + while(index > 0) { + tone += 1; + if(((1 << tone) & tones) != 0) { + index -= 1; + } + } + return tone; +} + +std::string Chord::to_short_string() const { + return key_name(key) + desc->suffix; +} diff --git a/libraries/steve/src/Chord.h b/libraries/steve/src/Chord.h new file mode 100644 index 000000000..b8e2b75c7 --- /dev/null +++ b/libraries/steve/src/Chord.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "ItemDescription.h" +#include "Steve.h" + +namespace steve { + struct ChordDescription : public ItemDescription { + std::string suffix; + ToneSet tones = 1; + bool uppercase = false; + + uint8_t get_tone(uint8_t index) const; + + inline uint8_t get_tone_count() const { return tone_set_count(tones); } + }; + + struct Chord { + public: + std::shared_ptr desc; + uint8_t key; + ToneSet tones; + + inline Chord(std::shared_ptr desc, uint8_t key) + : desc(desc), key(key), tones(tone_set_shift(desc->tones, key)) { + } + + std::string to_short_string() const; + inline Chord shifted(uint8_t semitones) const { return Chord(desc, (key + semitones) % 12); } + }; +} diff --git a/libraries/steve/src/ChordChange.h b/libraries/steve/src/ChordChange.h new file mode 100644 index 000000000..20dbde165 --- /dev/null +++ b/libraries/steve/src/ChordChange.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include "Chord.h" +#include "ItemDescription.h" + +namespace steve { + struct ChordChange : ItemDescription { + std::shared_ptr source_chord, target_chord; + uint8_t tone_shift = 0; + }; +} diff --git a/libraries/steve/src/Config.cpp b/libraries/steve/src/Config.cpp new file mode 100644 index 000000000..bd7c59a87 --- /dev/null +++ b/libraries/steve/src/Config.cpp @@ -0,0 +1,122 @@ +#include "Config.h" + +#include + +#include "Music.h" +#include "Rand.h" +#include "creator/Arpeggio.h" +#include "creator/Bass.h" +#include "creator/Chords.h" +#include "creator/Drums.h" +#include "creator/Melody.h" + +using namespace steve; + +std::shared_ptr Config::get_chord_change(const std::shared_ptr& source, const std::shared_ptr& target, uint8_t tone_shift) { + std::shared_ptr chord_change = _chord_changes.get_item(source->name + "->" + target->name + "+" + std::to_string(tone_shift)); + chord_change->source_chord = source; + chord_change->target_chord = target; + chord_change->tone_shift = tone_shift; + return chord_change; +} + +Config::Config() { + _creators.get_item("Arpeggio")->func = [](Music* music) { + return new Arpeggio(music); + }; + _creators.get_item("Bass")->func = [](Music* music) { + return new Bass(music); + }; + _creators.get_item("Chords")->func = [](Music* music) { + return new Chords(music); + }; + _creators.get_item("Drums")->func = [](Music* music) { + return new Drums(music); + }; + _creators.get_item("Melody")->func = [](Music* music) { + return new Melody(music); + }; +} + +void Config::compute_cache() { + // This needs to happen before computing scale chords + _chords.compute_cache(); + for(auto& scale : _scales.get_all()) { + scale->compute_chords(*this); + } + + _scales.compute_cache(); + _instruments.compute_cache(); + _creators.compute_cache(); + _signatures.compute_cache(); + _chord_changes.compute_cache(); +} + +void Config::list_scales(std::ostream& out) const { + for(const auto& scale_desc : _scales.get_allowed()) { + Scale scale(scale_desc, 0); + out << scale.desc->name << ":" << std::endl; + for(const auto& chord : scale.desc->chords) { + out << '\t' << scale.get_degree_string_for_chord(chord) << "\n"; + } + } +} + +uint32_t Config::get_random_tempo() const { + const float tempo_range = max_tempo - min_tempo; + const uint32_t full_precision_tempo = uint32_t(tempo_range * Rand::next_normal()) + min_tempo; + return (full_precision_tempo / 5) * 5; +} + +std::vector Config::get_chords_inside(ToneSet tones) const { + std::vector chords; + for(const auto& desc : _chords.get_allowed()) { + for(int key(0); key < 12; key++) { + const Chord shifted_chord(desc, key); + if((tones | shifted_chord.tones) == tones) { // All chord tones are in the toneset + chords.push_back(shifted_chord); + } + } + } + return chords; +} +std::vector Config::get_chord_progression(const Scale& scale) const { + std::vector chords; + + // Start with first degree chord + chords.push_back(Chord(_chords.get_random_item([scale](std::shared_ptr chord) { + return std::find_if(scale.desc->chords.begin(), scale.desc->chords.end(), [chord](const Chord& scale_chord) { + return scale_chord.desc == chord && scale_chord.key == 0; + }) != scale.desc->chords.end(); + }), + scale.key)); + + // Progress backwards + while(chords.size() < 4) { + const Chord dest_chord = chords.back(); + const std::shared_ptr chord_change = _chord_changes.get_random_item([&](std::shared_ptr chord_change) { + return dest_chord.desc == chord_change->target_chord && tone_set_within(scale.tones, tone_set_shift(chord_change->source_chord->tones, (dest_chord.key - chord_change->tone_shift + 12) % 12)); + }); + chords.push_back(Chord(chord_change->source_chord, (dest_chord.key - chord_change->tone_shift + 12) % 12)); + } + + // Reverse but keep first as start + std::reverse(chords.begin() + 1, chords.end()); + + return chords; +} + +std::vector> Config::get_creators() const { + std::vector> creators; + + while(creators.empty()) { + for(const auto creator : _creators.get_allowed()) { + const uint32_t count = Rand::next(creator->min_count, creator->max_count); + for(uint32_t i = 0; i < count; i++) { + creators.push_back(creator); + } + } + } + + return creators; +} diff --git a/libraries/steve/src/Config.h b/libraries/steve/src/Config.h new file mode 100644 index 000000000..b7836df47 --- /dev/null +++ b/libraries/steve/src/Config.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "Chord.h" +#include "ChordChange.h" +#include "ConfigItemList.h" +#include "Instrument.h" +#include "Scale.h" +#include "TimeSignature.h" +#include "creator/Creator.h" + +namespace steve { + class Config { + protected: + uint32_t min_tempo = 0, max_tempo = 360; + ConfigItemList _signatures; + ConfigItemList _creators; + ConfigItemList _chords; + ConfigItemList _scales; + ConfigItemList _chord_changes; + ConfigItemList _instruments; + + std::shared_ptr get_chord_change(const std::shared_ptr& source, const std::shared_ptr& target, uint8_t tone_shift); + + public: + Config(); + void compute_cache(); + void list_scales(std::ostream&) const; + + uint32_t get_random_tempo() const; + std::vector get_chords_inside(ToneSet tones) const; + std::vector get_chord_progression(const Scale&) const; + std::vector> get_creators() const; + + inline const auto& get_signatures() const { return _signatures; } + inline const auto& get_scales() const { return _scales; } + inline const auto& get_instruments() const { return _instruments; } + }; +} diff --git a/libraries/steve/src/ConfigItemList.h b/libraries/steve/src/ConfigItemList.h new file mode 100644 index 000000000..ec247bb40 --- /dev/null +++ b/libraries/steve/src/ConfigItemList.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "Chord.h" +#include "Instrument.h" +#include "Scale.h" + +namespace steve { + template + class ConfigItemList { + protected: + std::vector> _items; + std::vector> _allowed_items; + float _total_weight = 0.f; + + public: + inline auto get_all() const { return _items; } + inline auto get_allowed() const { return _allowed_items; } + void compute_cache(); + std::shared_ptr get_item(const std::string& name); + std::shared_ptr get_random_item() const; + template + std::shared_ptr get_random_item(F predicate) const; + }; + + template + void ConfigItemList::compute_cache() { + bool use_whitelist = false; + + _allowed_items.clear(); + _total_weight = 0.f; + + for(const auto& item : _items) { + if(item->whitelisted) { + use_whitelist = true; + } + } + + for(const auto& item : _items) { + if(!item->blacklisted && (!use_whitelist || item->whitelisted)) { + _allowed_items.push_back(item); + _total_weight += item->weight; + } + } + } + + template + std::shared_ptr ConfigItemList::get_item(const std::string& name) { + for(auto& candidate : _items) { + if(candidate->name == name) { + return candidate; + } + } + + std::shared_ptr desc(new T); + _items.push_back(desc); + desc->name = name; + return desc; + } + + template + std::shared_ptr ConfigItemList::get_random_item() const { + float weight_index = Rand::next(0.f, _total_weight); + for(const auto& candidate : _allowed_items) { + if(weight_index < candidate->weight) { + return candidate; + } else { + weight_index -= candidate->weight; + } + } + } + template + template + std::shared_ptr ConfigItemList::get_random_item(F predicate) const { + float filtered_weight = 0.f; + std::vector> filtered_items; + for(const auto& candidate : _allowed_items) { + if(predicate(candidate)) { + filtered_items.push_back(candidate); + filtered_weight += candidate->weight; + } + } + float weight_index = Rand::next(0.f, filtered_weight); + for(const auto& candidate : filtered_items) { + if(weight_index < candidate->weight) { + return candidate; + } else { + weight_index -= candidate->weight; + } + } + return std::shared_ptr(); + } +} diff --git a/libraries/steve/src/ConfigJson.cpp b/libraries/steve/src/ConfigJson.cpp new file mode 100644 index 000000000..857bf50b1 --- /dev/null +++ b/libraries/steve/src/ConfigJson.cpp @@ -0,0 +1,299 @@ +#include "ConfigJson.h" + +#include + +using namespace steve; + +template +void parse_number(json_value_s* json_value, T& target) { + if(const json_number_s* json_number = json_value_as_number(json_value)) { + target = atoi(json_number->number); + } else { + } +} + +void parse_number(json_value_s* json_value, float& target) { + if(const json_number_s* json_number = json_value_as_number(json_value)) { + target = atof(json_number->number); + } else { + } +} + +void parse_note(json_value_s* json_value, uint8_t& target) { + if(const json_string_s* json_string = json_value_as_string(json_value)) { + target = get_note_with_name(json_string->string); + } else { + parse_number(json_value, target); + } +} + +void ConfigJson::parse_file(const char* relative_filepath) { + std::string filepath; + if(!_directory_stack.empty()) { + filepath = _directory_stack.back() + relative_filepath; + } else { + filepath = relative_filepath; + } + + { + const size_t last_slash = filepath.find_last_of('/'); + std::string directory; + if(last_slash >= 0) { + directory = filepath.substr(0, last_slash + 1); + } + _directory_stack.push_back(directory); + } + + FILE* file = fopen(filepath.c_str(), "rb"); + fseek(file, 0, SEEK_END); + const size_t file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* file_data = (char*)malloc(file_size); + fread(file_data, 1, file_size, file); + + parse_buffer(file_data, file_size); + + free(file_data); + + _directory_stack.pop_back(); +} + +void ConfigJson::parse_buffer(const char* buffer, size_t size) { + json_parse_result_s parse_result; + json_value_s* root = json_parse_ex(buffer, size, json_parse_flags_allow_json5, nullptr, nullptr, &parse_result); + + if(const json_object_s* root_object = json_value_as_object(root)) { + // Properties like "parents" need to be parsed first + for(const json_object_element_s* root_attribute = root_object->start; root_attribute != nullptr; root_attribute = root_attribute->next) { + if(!strcmp(root_attribute->name->string, "parents")) { + if(const json_array_s* parents = json_value_as_array(root_attribute->value)) { + for(json_array_element_s* parent = parents->start; parent != nullptr; parent = parent->next) { + if(const json_string_s* parent_path = json_value_as_string(parent->value)) { + parse_file(parent_path->string); + } else { + } + } + } else { + } + } + } + + for(const json_object_element_s* element = root_object->start; element != nullptr; element = element->next) { + if(!strcmp(element->name->string, "min_tempo")) { + parse_number(element->value, min_tempo); + } else if(!strcmp(element->name->string, "max_tempo")) { + parse_number(element->value, max_tempo); + } else if(!strcmp(element->name->string, "time_signatures")) { + parse_time_signatures(json_value_as_object(element->value)); + } else if(!strcmp(element->name->string, "creators")) { + parse_creators(json_value_as_object(element->value)); + } else if(!strcmp(element->name->string, "chords")) { + parse_chords(json_value_as_object(element->value)); + } else if(!strcmp(element->name->string, "scales")) { + parse_scales(json_value_as_object(element->value)); + } else if(!strcmp(element->name->string, "chord_changes")) { + parse_chord_changes(json_value_as_object(element->value)); + } else if(!strcmp(element->name->string, "instruments")) { + parse_instruments(json_value_as_object(element->value)); + } else { + } + } + } else { + } + + free(root); +} + +void ConfigJson::parse_time_signatures(const json_object_s* time_signatures_object) { + for(const json_object_element_s* time_signature_element = time_signatures_object->start; time_signature_element != nullptr; time_signature_element = time_signature_element->next) { + std::shared_ptr desc = _signatures.get_item(time_signature_element->name->string); + std::string time_signature = time_signature_element->name->string; + auto slash_pos = time_signature.find('/'); + if(slash_pos != std::string::npos) { + desc->beats_per_bar = atoi(time_signature.substr(0, slash_pos).c_str()); + desc->beat_value = NoteValue(uint32_t(NoteValue::whole) - log2(atoi(time_signature.substr(slash_pos + 1).c_str()))); + } + if(const json_object_s* time_signature_object = json_value_as_object(time_signature_element->value)) { + parse_item(time_signature_object, *desc); + } + } +} + +void ConfigJson::parse_creators(const json_object_s* creators_object) { + for(const json_object_element_s* creator_element = creators_object->start; creator_element != nullptr; creator_element = creator_element->next) { + std::shared_ptr desc = _creators.get_item(creator_element->name->string); + if(const json_object_s* creator_object = json_value_as_object(creator_element->value)) { + parse_creator(creator_object, *desc); + } else { + } + } +} + +void ConfigJson::parse_creator(const json_object_s* creator_object, CreatorDescription& desc) { + parse_item(creator_object, desc); + for(const json_object_element_s* creator_attribute = creator_object->start; creator_attribute != nullptr; creator_attribute = creator_attribute->next) { + const char* creator_attribute_name = creator_attribute->name->string; + if(!strcmp(creator_attribute_name, "min_count")) { + parse_number(creator_attribute->value, desc.min_count); + } else if(!strcmp(creator_attribute_name, "max_count")) { + parse_number(creator_attribute->value, desc.max_count); + } + } +} + +void ConfigJson::parse_chords(const json_object_s* chords_object) { + for(const json_object_element_s* chord_element = chords_object->start; chord_element != nullptr; chord_element = chord_element->next) { + std::shared_ptr desc = _chords.get_item(chord_element->name->string); + if(const json_object_s* chord_object = json_value_as_object(chord_element->value)) { + parse_chord(chord_object, *desc); + } else { + } + } +} + +void ConfigJson::parse_chord(const json_object_s* chord_object, ChordDescription& desc) { + parse_item(chord_object, desc); + for(const json_object_element_s* chord_attribute = chord_object->start; chord_attribute != nullptr; chord_attribute = chord_attribute->next) { + const char* chord_attribute_name = chord_attribute->name->string; + if(!strcmp(chord_attribute_name, "suffix")) { + if(const json_string_s* chord_suffix = json_value_as_string(chord_attribute->value)) { + desc.suffix = chord_suffix->string; + } else { + } + } else if(!strcmp(chord_attribute_name, "tones")) { + if(const json_array_s* chord_tones = json_value_as_array(chord_attribute->value)) { + for(json_array_element_s* element = chord_tones->start; element != nullptr; element = element->next) { + if(const json_number_s* tone = json_value_as_number(element->value)) { + desc.tones |= 1 << atoi(tone->number); + } else { + } + } + } else { + } + } else if(!strcmp(chord_attribute_name, "uppercase")) { + desc.uppercase = json_value_is_true(chord_attribute->value); + } + } +} + +void ConfigJson::parse_scales(const json_object_s* scales_object) { + for(const json_object_element_s* scale_element = scales_object->start; scale_element != nullptr; scale_element = scale_element->next) { + std::shared_ptr desc = _scales.get_item(scale_element->name->string); + if(const json_object_s* scale_object = json_value_as_object(scale_element->value)) { + parse_scale(scale_object, *desc); + } + } +} + +void ConfigJson::parse_scale(const json_object_s* scale_object, ScaleDescription& desc) { + parse_item(scale_object, desc); + for(const json_object_element_s* scale_attribute = scale_object->start; scale_attribute != nullptr; scale_attribute = scale_attribute->next) { + if(!strcmp(scale_attribute->name->string, "tones")) { + if(const json_array_s* scale_tones = json_value_as_array(scale_attribute->value)) { + for(json_array_element_s* element = scale_tones->start; element != nullptr; element = element->next) { + if(const json_number_s* tone = json_value_as_number(element->value)) { + desc.tones |= 1 << atoi(tone->number); + } else { + } + } + } else { + } + } + } +} + +void ConfigJson::parse_chord_changes(const json_object_s* chord_changes_object) { + for(const json_object_element_s* chord_change_source_element = chord_changes_object->start; chord_change_source_element != nullptr; chord_change_source_element = chord_change_source_element->next) { + const std::string source_name = chord_change_source_element->name->string; + if(const json_object_s* chord_change_target_object = json_value_as_object(chord_change_source_element->value)) { + for(const json_object_element_s* chord_change_target_element = chord_change_target_object->start; chord_change_target_element != nullptr; chord_change_target_element = chord_change_target_element->next) { + const std::string target_name = chord_change_target_element->name->string; + if(const json_object_s* chord_change_offset_object = json_value_as_object(chord_change_target_element->value)) { + for(const json_object_element_s* chord_change_offset_element = chord_change_offset_object->start; chord_change_offset_element != nullptr; chord_change_offset_element = chord_change_offset_element->next) { + const std::string offset_string = chord_change_offset_element->name->string; + if(const json_object_s* chord_change_object = json_value_as_object(chord_change_offset_element->value)) { + parse_chord_change(chord_change_object, source_name, target_name, offset_string); + } + } + } + } + } + } +} + +void ConfigJson::parse_chord_change(const json_object_s* chord_change_object, const std::string& source_string, const std::string& target_string, const std::string& offset_string) { + std::vector> source_chords; + std::vector> target_chords; + std::vector offsets; + + if(source_string == "*") { + source_chords = _chords.get_all(); + } else { + std::stringstream ss(source_string); + for(std::string chord_name; std::getline(ss, chord_name, '|');) { + source_chords.push_back(_chords.get_item(chord_name)); + } + } + + if(target_string == "*") { + target_chords = _chords.get_all(); + } else { + std::stringstream ss(target_string); + for(std::string chord_name; std::getline(ss, chord_name, '|');) { + target_chords.push_back(_chords.get_item(chord_name)); + } + } + + if(offset_string == "*") { + for(uint32_t offset = 0; offset < 12; offset++) { + offsets.push_back(offset); + } + } else { + offsets.push_back(atoi(offset_string.c_str())); + } + + for(const auto& source_chord : source_chords) { + for(const auto& target_chord : target_chords) { + for(uint32_t offset : offsets) { + std::shared_ptr chord_change = get_chord_change(source_chord, target_chord, offset); + parse_item(chord_change_object, *chord_change); + } + } + } +} + +void ConfigJson::parse_instruments(const json_object_s* instruments_object) { + for(const json_object_element_s* instrument_element = instruments_object->start; instrument_element != nullptr; instrument_element = instrument_element->next) { + std::shared_ptr desc = _instruments.get_item(instrument_element->name->string); + if(const json_object_s* instrument_object = json_value_as_object(instrument_element->value)) { + parse_instrument(instrument_object, *desc); + } + } +} +void ConfigJson::parse_instrument(const json_object_s* instrument_object, Instrument& desc) { + parse_item(instrument_object, desc); + for(const json_object_element_s* instrument_attribute = instrument_object->start; instrument_attribute != nullptr; instrument_attribute = instrument_attribute->next) { + if(!strcmp(instrument_attribute->name->string, "index")) { + parse_number(instrument_attribute->value, desc.midi_id); + } else if(!strcmp(instrument_attribute->name->string, "min_tone")) { + parse_note(instrument_attribute->value, desc.min_tone); + } else if(!strcmp(instrument_attribute->name->string, "max_tone")) { + parse_note(instrument_attribute->value, desc.max_tone); + } else if(!strcmp(instrument_attribute->name->string, "voices")) { + parse_note(instrument_attribute->value, desc.voices); + } + } +} + +void ConfigJson::parse_item(const json_object_s* item_object, ItemDescription& item) { + for(const json_object_element_s* item_attribute = item_object->start; item_attribute != nullptr; item_attribute = item_attribute->next) { + if(!strcmp(item_attribute->name->string, "whitelist")) { + item.whitelisted = json_value_is_true(item_attribute->value); + } else if(!strcmp(item_attribute->name->string, "blacklist")) { + item.blacklisted = json_value_is_true(item_attribute->value); + } else if(!strcmp(item_attribute->name->string, "weight")) { + parse_number(item_attribute->value, item.weight); + } + } +} diff --git a/libraries/steve/src/ConfigJson.h b/libraries/steve/src/ConfigJson.h new file mode 100644 index 000000000..7e951a684 --- /dev/null +++ b/libraries/steve/src/ConfigJson.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Config.h" +#include "ext/json.h" + +namespace steve { + class ConfigJson : public Config { + protected: + std::vector _directory_stack; + + public: + void parse_file(const char* filepath); + void parse_buffer(const char* buffer, size_t size); + void parse_time_signatures(const json_object_s*); + void parse_creators(const json_object_s*); + void parse_creator(const json_object_s*, CreatorDescription&); + void parse_chords(const json_object_s*); + void parse_chord(const json_object_s*, ChordDescription&); + void parse_scales(const json_object_s*); + void parse_scale(const json_object_s*, ScaleDescription&); + void parse_chord_changes(const json_object_s*); + void parse_chord_change(const json_object_s*, const std::string& src, const std::string& tar, const std::string& off); + void parse_instruments(const json_object_s*); + void parse_instrument(const json_object_s*, Instrument&); + void parse_item(const json_object_s*, ItemDescription&); + }; +} diff --git a/libraries/steve/src/Instrument.h b/libraries/steve/src/Instrument.h new file mode 100644 index 000000000..dbc5b2b68 --- /dev/null +++ b/libraries/steve/src/Instrument.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include "ItemDescription.h" + +namespace steve { + struct Instrument : ItemDescription { + uint8_t midi_id; + uint8_t min_tone = 0, max_tone = 127; + uint8_t voices = 1; + }; +} diff --git a/libraries/steve/src/ItemDescription.h b/libraries/steve/src/ItemDescription.h new file mode 100644 index 000000000..08b7eb106 --- /dev/null +++ b/libraries/steve/src/ItemDescription.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace steve { + struct ItemDescription { + std::string name; + bool blacklisted = false, whitelisted = false; + float weight = 1.f; + }; +} diff --git a/libraries/steve/src/Music.cpp b/libraries/steve/src/Music.cpp new file mode 100644 index 000000000..bdc169a73 --- /dev/null +++ b/libraries/steve/src/Music.cpp @@ -0,0 +1,237 @@ +#include "Music.h" + +#include +#include +#include + +#include "Chord.h" +#include "Config.h" +#include "Rand.h" +#include "Scale.h" +#include "creator/Creator.h" + +using namespace steve; + +Music::Music(const Config& config) + : _config(config), + _scale(Scale(_config.get_scales().get_random_item(), Rand::next(0, 11))), + _tempo(_config.get_random_tempo()), + _signature(_config.get_signatures().get_random_item()) { + do { + _size = uint32_t(40 * Rand::next_normal()) + 26; + } while(_size > 512); // <=512 with 46 average bars + + _size -= _size % 4; // Multiple of 4 bars + _size *= get_bar_ticks(); + + { // Generate chord progression + _chord_progression = _config.get_chord_progression(_scale); + for(uintptr_t i(0); i < bars(); i++) { + for(uintptr_t j(0); j < get_bar_ticks(); j++) { + _tones.push_back(_chord_progression[i % _chord_progression.size()].tones); + } + } + assert(_tones.size() == _size); + } + + { // Generate beats + _beats.resize(get_bar_ticks()); + for(uint32_t i = 0; i < _beats.size(); i++) { + _beats[i] = false; + for(NoteValue j = get_beat_value(); j >= NoteValue(0); j = NoteValue(uint32_t(j) - 1)) { + const auto ticks = ticks_for(j); + if((i / ticks) * ticks == i) { + if(Rand::next(0ul, (uint32_t(get_beat_value()) - uint32_t(j)) * 2) == 0) { + _beats[i] = true; + break; + } + } + } + } + } + + for(const auto& creator : _config.get_creators()) { + add_creator(creator->func(this)); + } + +#ifndef NDEBUG + check(); +#endif +} +void Music::add_creator(Creator* creator) { + creator->init(); + creator->post_init(); + paste(creator->compose(), _notes); + _creators.push_back(std::unique_ptr(creator)); +} +const Chord& Music::chord_at(size_t i) const { + return _chord_progression[(i / get_bar_ticks()) % _chord_progression.size()]; +} +ToneSet Music::tones_at(size_t start, size_t size) const { + ToneSet tones = ~0; + const uintptr_t start_bar = start / get_bar_ticks(); + const uintptr_t end_bar = (start + size - 1) / get_bar_ticks(); + for(uintptr_t i = start_bar; i <= end_bar; i++) { + tones &= _chord_progression[i % _chord_progression.size()].tones; + } + return tones; +} +bool Music::is_beat(uintptr_t i) const { + return _beats[i % _beats.size()]; +} +std::vector Music::beats_inside(uintptr_t min, uintptr_t max) const { + std::vector beats; + for(uintptr_t i = min; i <= max; i++) { + if(is_beat(i)) { + beats.push_back(i); + } + } + return beats; +} +std::string Music::to_short_string() const { + std::string short_string; + short_string += scale().desc->name + "_" + key_name(scale().key); + short_string += "_" + std::to_string(_signature->beats_per_bar); + short_string += std::to_string(1 << (uint32_t(NoteValue::whole) - uint32_t(get_beat_value()))); + short_string += "_" + std::to_string(tempo()); + + std::replace(short_string.begin(), short_string.end(), ' ', '_'); + + return short_string; +} +void Music::check() const { + Tones final_tones(octave_tones(_notes)); + assert(final_tones.size() <= _tones.size()); + for(uintptr_t i(0); i < final_tones.size(); i++) { + assert(tone_set_within(_scale.tones, final_tones[i])); + assert(tone_set_within(_tones[i], final_tones[i])); + } + + for(const auto& note : _notes) { + assert(is_beat(note.first)); + } +} + +static void write_bigendian(std::ostream& s, uint32_t v, uint32_t byteCount) { + if(byteCount>=4) s << uint8_t(v>>24); + if(byteCount>=3) s << uint8_t(v>>16); + if(byteCount>=2) s << uint8_t(v>>8); + if(byteCount>=1) s << uint8_t(v); +} +class VarLength { + uint32_t _value; +public: + inline VarLength(uint32_t value) : _value(value) {} + friend inline std::ostream& operator<<(std::ostream& s, const VarLength& v) { + if(v._value >= 128) { + s << (uint8_t)(((v._value >> 7) & 0x7f) | 0x80); + } + s << (uint8_t)(v._value & 0x7f); + return s; + } +}; +void Music::write_mid(std::ostream& s) const { + // Header chunk + s << "MThd"; // Chunk id + write_bigendian(s, 6, 4); // Chunk size + write_bigendian(s, 0, 2); // Format type (single track) + write_bigendian(s, 1, 2); // Number of tracks + write_bigendian(s, ticks_for(NoteValue::quarter), 2); // Time division (ticks per beat) + + // Track chunk + s << "MTrk"; + const size_t sizeoff(s.tellp()); + write_bigendian(s, 0, 4); // Placeholder for track size + + { // Tempo meta event + s << uint8_t(0) << uint8_t(0xff) << uint8_t(0x51) << uint8_t(3); + write_bigendian(s, 60000000u / _tempo, 3); // Microseconds per quarter note + } + + { // Time signature meta event + s << uint8_t(0) << uint8_t(0xff) << uint8_t(0x58) << uint8_t(4) + << uint8_t(_signature->beats_per_bar) // Numerator + << uint8_t(uint8_t(NoteValue::whole) - uint8_t(get_beat_value())) // Denominator (2^x) + << uint8_t(0x18) // Metronome pulse in clock ticks + << uint8_t(ticks_for(get_beat_value()) / ticks_for(NoteValue::thirtysecond)); // 32nd per beat + } + + for(uint32_t i(0); i < _creators.size(); i++) { + s << uint8_t(0) << uint8_t(0xC0|i) << _creators[i]->instrument()->midi_id; // Program change + } + + uint32_t last = 0; + uint32_t last_chord = -1; + + Notes end_notes; + auto process_end_notes = [&s, &last, &end_notes](uint32_t next_time) { + while(!end_notes.empty()) { + auto next_end = end_notes.begin(); + if(next_end->first <= next_time) { + s << VarLength(next_end->first - last) << uint8_t(0x80 | next_end->second.channel) << uint8_t(next_end->second.tone) << uint8_t(next_end->second.velocity); // Note off + last = next_end->first; + end_notes.erase(next_end); + } else { + break; + } + } + }; + + for(const auto& note : _notes) { + process_end_notes(note.first); + + s << VarLength(note.first - last) << uint8_t(0x90 | note.second.channel) << uint8_t(note.second.tone) << uint8_t(note.second.velocity); // Note on + + end_notes.insert(std::make_pair(note.first + note.second.duration, note.second)); + + last = note.first; + + if(note.first != last_chord && note.first != _size && note.first % get_bar_ticks() == 0) { + // Chord meta-event + const Chord chord = chord_at(note.first); + const uint8_t degree =_scale.get_degree_for_tone(chord.key); + const std::string chord_str = chord_at(note.first).to_short_string() + " (" + std::to_string(degree + 1) + ")"; + s << uint8_t(0) << uint8_t(0xFF) << uint8_t(0x01) << VarLength(chord_str.size()) << chord_str; + last_chord = note.first; + } + } + process_end_notes(last + get_bar_ticks()); + + s << uint8_t(0) << uint8_t(0xFF) << uint8_t(0x2F) << uint8_t(0); // End of track + + // Write chunk size + const size_t endoff(s.tellp()); + s.seekp(sizeoff); + write_bigendian(s, endoff-sizeoff-4, 4); +} +void Music::write_txt(std::ostream& s) const { + s << "Scale: " << key_name(scale().key) << " " << scale().desc->name << std::endl + << "Tempo: " << tempo() << std::endl + << "Signature: " << _signature->beats_per_bar << "/" << (1 << (uint32_t(NoteValue::whole) - uint32_t(get_beat_value()))) << std::endl + << "Duration: " << duration() << std::endl << std::endl; + + { + s << "Chord progression:" << std::endl; + for(const Chord& chord : _chord_progression) { + s << " - " << _scale.get_degree_string_for_chord(chord) << " (" << chord.to_short_string() << ")" << std::endl; + } + s << std::endl; + } + + { + s << "Rhythm:" << std::endl << '\t'; + for(uintptr_t i = 0; i < _beats.size(); i += 2) { + if(i % ticks_for(get_beat_value()) == 0 && i > 0) { + s << ' '; + } + s << (is_beat(i) ? '1' : '0'); + } + s << std::endl << std::endl; + } + + s << "Creators:" << std::endl; + for(const auto& creator : _creators) { + creator->write_txt(s); + s << std::endl; + } +} \ No newline at end of file diff --git a/libraries/steve/src/Music.h b/libraries/steve/src/Music.h new file mode 100644 index 000000000..faf9cbb8c --- /dev/null +++ b/libraries/steve/src/Music.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#include "Chord.h" +#include "Instrument.h" +#include "Scale.h" +#include "Steve.h" +#include "TimeSignature.h" + +namespace steve { + class Config; + class Creator; + class Music { + protected: + const Config& _config; + Notes _notes; + Tones _tones; + std::vector> _creators; + std::vector _chord_progression; + std::vector _beats; + Scale _scale; + uint32_t _tempo, _size; + std::shared_ptr _signature; + + public: + Music(const Config&); + void add_creator(Creator* creator); + const Chord& chord_at(size_t i) const; + ToneSet tones_at(size_t start, size_t size = 1) const; + bool is_beat(uintptr_t i) const; + std::vector beats_inside(uintptr_t min, uintptr_t max) const; + std::string to_short_string() const; + void check() const; + + void write_mid(std::ostream&) const; + void write_txt(std::ostream&) const; + + inline const Config& get_config() const { return _config; } + inline const Notes& notes() const { return _notes; } + inline const Tones& tones() const { return _tones; } + inline const Scale& scale() const { return _scale; } + inline const std::vector chord_progression() const { return _chord_progression; } + inline size_t size() const { return _size; } + inline size_t bars() const { return _size / get_bar_ticks(); } + inline size_t get_bar_ticks() const { return ticks_for(_signature->beat_value) * _signature->beats_per_bar; } + inline NoteValue get_beat_value() const { return _signature->beat_value; } + inline size_t parts() const { return _creators.size(); } + inline const std::vector>& creators() const { return _creators; } + inline size_t tempo() const { return _tempo; } + inline size_t tickTime() const { return 60000 / (_tempo * 32); } + inline size_t duration() const { return (tickTime() * _size) / 1000; } + }; +} diff --git a/libraries/steve/src/Rand.cpp b/libraries/steve/src/Rand.cpp new file mode 100644 index 000000000..d7aab5f22 --- /dev/null +++ b/libraries/steve/src/Rand.cpp @@ -0,0 +1,41 @@ +#include "Rand.h" + +#include + +using namespace steve; + +std::default_random_engine Rand::generator; +static std::uniform_real_distribution nf_dist(0.f, 1.f); +static std::normal_distribution nn_dist(0.5f, 0.1f); + +void Rand::reseed(uint64_t newseed) { + generator.seed(newseed); +} + +float Rand::next_float() { + return nf_dist(generator); +} + +float Rand::next_normal() { + return std::max(0.f, std::min(1.f, nn_dist(generator))); +} + +uint32_t Rand::next(uint32_t min, uint32_t max) { + std::uniform_int_distribution dist(min, max); + return dist(generator); +} +uint64_t Rand::next(uint64_t min, uint64_t max) { + std::uniform_int_distribution dist(min, max); + return dist(generator); +} +int Rand::next(int min, int max) { + std::uniform_int_distribution dist(min, max); + return dist(generator); +} +float Rand::next(float min, float max) { + std::uniform_real_distribution dist(min, max); + return dist(generator); +} +NoteValue Rand::next(NoteValue min, NoteValue max) { + return NoteValue(Rand::next(uint64_t(min), uint64_t(max))); +} \ No newline at end of file diff --git a/libraries/steve/src/Rand.h b/libraries/steve/src/Rand.h new file mode 100644 index 000000000..c9512ee1c --- /dev/null +++ b/libraries/steve/src/Rand.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "Steve.h" + +namespace steve { + class Rand { + public: + static std::default_random_engine generator; + static float next_float(); + static float next_normal(); + + static uint32_t next(uint32_t min, uint32_t max); // Returns a random unsigned integer between min and max + static uint64_t next(uint64_t min, uint64_t max); // Returns a random unsigned integer between min and max + static int next(int min, int max); // Returns a random integer between min and max + static float next(float min, float max); // Returns a random float between min and max + static NoteValue next(NoteValue min, NoteValue max); + + static void reseed(uint64_t newseed); + + // Containers + template + static const T& in(const std::set& s) { + auto it(s.begin()); + std::advance(it, next(0ull, s.size()-1)); + return *it; + } + template + inline static const T& in(const std::vector& v) { + return v[(unsigned int)next(0ull, v.size()-1)]; + } + }; +} diff --git a/libraries/steve/src/Scale.cpp b/libraries/steve/src/Scale.cpp new file mode 100644 index 000000000..997107cd1 --- /dev/null +++ b/libraries/steve/src/Scale.cpp @@ -0,0 +1,36 @@ +#include "Scale.h" + +#include +#include +#include + +#include "Config.h" +#include "Rand.h" + +using namespace steve; + +void ScaleDescription::compute_chords(const Config& instance) { + // Gather all chords inside scale + chords = instance.get_chords_inside(tones); + + std::sort(chords.begin(), chords.end(), [](const Chord& a, const Chord& b) { + return a.key < b.key; + }); + +} + +uint8_t Scale::get_degree_for_tone(uint8_t tone) const { + tone = tone % 12; + uint8_t cmp_tone = key; + uint8_t degree = 0; + while(cmp_tone != tone) { + cmp_tone = (cmp_tone + 1) % 12; + if((tones & (1 << cmp_tone)) != 0) { + degree += 1; + } + } + return degree; +} +std::string Scale::get_degree_string_for_chord(const Chord& chord) const { + return steve::degree_name(get_degree_for_tone(chord.key), chord.desc->uppercase) + chord.desc->suffix; +} diff --git a/libraries/steve/src/Scale.h b/libraries/steve/src/Scale.h new file mode 100644 index 000000000..a17724f54 --- /dev/null +++ b/libraries/steve/src/Scale.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "Chord.h" +#include "ItemDescription.h" +#include "Steve.h" + +namespace steve { + struct ScaleDescription : public ItemDescription { + std::vector chords; + ToneSet tones = 1; + + void compute_chords(const class Config&); + }; + struct Scale { + std::shared_ptr desc; + uint8_t key = 0; + ToneSet tones = 0; + + inline Scale(const std::shared_ptr& d, uint8_t k) + : desc(d), key(k), tones(tone_set_shift(d->tones, k)) {} + + inline std::string full_name() const { return std::string(key_name(key)) + " " + desc->name; } + + // Zero-based + uint8_t get_degree_for_tone(uint8_t tone) const; + std::string get_degree_string_for_chord(const Chord& chord) const; + }; +} diff --git a/libraries/steve/src/Steve.cpp b/libraries/steve/src/Steve.cpp new file mode 100644 index 000000000..ba04d594b --- /dev/null +++ b/libraries/steve/src/Steve.cpp @@ -0,0 +1,118 @@ +#include "Steve.h" + +#include +#include + +#include "Chord.h" + +using namespace std; +using namespace steve; + +const char* steve::key_name(uint8_t key) { + static const char key_names[][3] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; + return key_names[key]; +} +const char* steve::degree_name(uint8_t degree, bool uppercase) { + static const char upper_degrees[][4] = {"I", "II", "III", "IV", "V", "VI", "VII"}; + static const char lower_degrees[][4] = {"i", "ii", "iii", "iv", "v", "vi", "vii"}; + return uppercase ? upper_degrees[degree] : lower_degrees[degree]; +} +const char* steve::note_value_name(NoteValue v) { + switch(v) { + case NoteValue::sixtyfourth: return "sixtyfourth"; + case NoteValue::thirtysecond: return "thirtysecond"; + case NoteValue::sixteenth: return "sixteenth"; + case NoteValue::eighth: return "eighth"; + case NoteValue::quarter: return "quarter"; + case NoteValue::half: return "half"; + case NoteValue::whole: return "whole"; + } + return "N/A"; +} + +static char note_names[128 * 5]; +void steve::note_name_init() { + // Start at C-1 + // Worst case: C#-1\0 (5 bytes) + for(uint8_t note(0); note < 128; note++) { + sprintf(note_names + note * 5, "%s%d", key_name(note % 12), (note / 12) - 1); + } +} +const char* steve::note_name(uint8_t note) { + return note_names + note * 5; +} +uint8_t steve::get_note_with_name(const char* name) { + for(uintptr_t note = 0; note < 128; note++) { + if(!strcmp(note_names + note * 5, name)) { + return uint8_t(note); + } + } + return 0; +} +ToneSet steve::tone_set_shift(const ToneSet& tones, int shifting) { + assert(shifting >= 0 && shifting < 12); + return ((tones << shifting) | (tones >> (12 - shifting))) & 0xfff; +} +bool steve::tone_set_within(const ToneSet& scale, const ToneSet& chord) { + return (scale | chord) == scale; +} + +uint32_t steve::tone_set_count(ToneSet tones) { + uint32_t count = 0; + while(tones) { + count += tones & 1; + tones >>= 1; + } + return count; +} +const char* steve::tone_set_binary(ToneSet tone_set) { + static char str[13] = {}; + for(uint32_t tone(0); tone<12; tone++) { + str[tone] = (tone_set & (1< +#include +#include + +namespace steve { + struct Note { + uint8_t channel, tone, velocity, duration; + }; + enum class NoteValue { + sixtyfourth, + thirtysecond, + sixteenth, + eighth, + quarter, + half, + whole, + }; + enum class Interval { + perfectunison = 0, + minorsecond, + majorsecond, + minorthird, + majorthird, + perfectfourth, + perfectfifth = 7, + minorsixth, + majorsixth, + minorseventh, + majorseventh, + perfectoctave, + }; + typedef std::multimap Notes; + typedef uint16_t ToneSet; + typedef uint64_t NoteSet; + typedef std::vector Tones; + struct Phrase { + Notes notes; + Tones tones; + size_t size; + }; + inline uint32_t ticks_for(NoteValue v) { return 2 << uint32_t(v); } + const char* key_name(uint8_t); + const char* degree_name(uint8_t, bool uppercase); // Zero-based + const char* note_value_name(NoteValue); + void note_name_init(); + const char* note_name(uint8_t); + uint8_t get_note_with_name(const char*); + ToneSet tone_set_shift(const ToneSet& scale, int shifting); + bool tone_set_within(const ToneSet& scale, const ToneSet& chord); + uint32_t tone_set_count(ToneSet); + const char* tone_set_binary(ToneSet); + void add_note(Notes&, uint8_t channel, uint8_t tone, size_t start, size_t length, uint8_t velocity = 127); + + Tones octave_tones(const Notes&); + void paste(const Notes&, Notes&, size_t start = 0); + Notes copy(const Notes&, size_t start = 0, size_t size = -1); + bool harmony(const ToneSet* base, const ToneSet* piece, size_t size); + + template + inline T clamp(T v, T min, T max) { return std::max(min, std::min(max, v)); } +} diff --git a/libraries/steve/src/TimeSignature.h b/libraries/steve/src/TimeSignature.h new file mode 100644 index 000000000..7c2ae5026 --- /dev/null +++ b/libraries/steve/src/TimeSignature.h @@ -0,0 +1,11 @@ +#pragma once + +#include "ItemDescription.h" +#include "Steve.h" + +namespace steve { + struct TimeSignature : public ItemDescription { + uint32_t beats_per_bar = 4; + NoteValue beat_value = NoteValue::quarter; + }; +} diff --git a/libraries/steve/src/creator/Arpeggio.cpp b/libraries/steve/src/creator/Arpeggio.cpp new file mode 100644 index 000000000..3ff413f23 --- /dev/null +++ b/libraries/steve/src/creator/Arpeggio.cpp @@ -0,0 +1,31 @@ +#include "Arpeggio.h" + +#include "../Music.h" +#include "../Rand.h" + +using namespace steve; + +Arpeggio::Arpeggio(Music* music) + : ChordBasedCreator(music) {} +void Arpeggio::init() { + ChordBasedCreator::init(); + _min_time = NoteValue::sixteenth; + _max_time = NoteValue::quarter; +} +Notes Arpeggio::get(size_t start, size_t size) const { + Notes notes; + auto times = generate_times(start, _music->get_bar_ticks()); + uintptr_t i = 0; + while(i < size) { + const uint8_t base_tone = _min_tone + 12 - (_min_tone % 12); + for(uintptr_t j = 0; j < times.size() - 1; j++) { + const Chord& chord = _music->chord_at(start + i); + const auto chord_tone_count = chord.desc->get_tone_count(); + const auto duration = times[j + 1] - times[j]; + const uint8_t tone = base_tone + chord.key + chord.desc->get_tone(j % chord_tone_count); + add_note(notes, _channel, tone, i, duration); + i += duration; + } + } + return notes; +} diff --git a/libraries/steve/src/creator/Arpeggio.h b/libraries/steve/src/creator/Arpeggio.h new file mode 100644 index 000000000..53c07e3f7 --- /dev/null +++ b/libraries/steve/src/creator/Arpeggio.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ChordBasedCreator.h" + +namespace steve { + class Arpeggio : public ChordBasedCreator { + public: + Arpeggio(Music*); + virtual void init() override; + virtual Notes get(size_t start, size_t size) const override; + virtual const char* name() const override { return "Arpeggio"; } + }; +} diff --git a/libraries/steve/src/creator/Bass.cpp b/libraries/steve/src/creator/Bass.cpp new file mode 100644 index 000000000..eb087d97a --- /dev/null +++ b/libraries/steve/src/creator/Bass.cpp @@ -0,0 +1,31 @@ +#include "Bass.h" + +#include "../Music.h" + +using namespace steve; + +Bass::Bass(Music* music) : Creator(music) {} +void Bass::init() { + Creator::init(); + + _min_tone = 36; + _max_tone = _min_tone + 12; +} +Notes Bass::get(size_t start, size_t size) const { + Notes notes; + auto times = generate_times(start, _music->get_bar_ticks()); + uintptr_t i = 0; + while(i < size) { + for(uintptr_t j = 0; j < times.size() - 1; j++) { + const auto duration = times[j + 1] - times[j]; + const Chord& chord = _music->chord_at(start + i); + const uint8_t tone = _min_tone + chord.key; + add_note(notes, _channel, tone, i, duration); + i += duration; + } + } + return notes; +} +bool Bass::is_valid_instrument(const Instrument& instrument) const { + return instrument.min_tone <= 36 && instrument.max_tone >= 48; +} diff --git a/libraries/steve/src/creator/Bass.h b/libraries/steve/src/creator/Bass.h new file mode 100644 index 000000000..2e73df8b1 --- /dev/null +++ b/libraries/steve/src/creator/Bass.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Creator.h" + +namespace steve { + class Bass : public Creator { + public: + Bass(Music*); + virtual void init() override; + virtual Notes get(size_t start, size_t size) const override; + virtual const char* name() const override { return "Bass"; } + virtual bool is_valid_instrument(const Instrument& instrument) const override; + }; +} diff --git a/libraries/steve/src/creator/ChordBasedCreator.cpp b/libraries/steve/src/creator/ChordBasedCreator.cpp new file mode 100644 index 000000000..03b2221d7 --- /dev/null +++ b/libraries/steve/src/creator/ChordBasedCreator.cpp @@ -0,0 +1,26 @@ +#include "ChordBasedCreator.h" + +#include "../Music.h" +#include "../Rand.h" + +using namespace steve; + +ChordBasedCreator::ChordBasedCreator(Music* music) : Creator(music) {} +void ChordBasedCreator::init() { + Creator::init(); + _min_time = Rand::next(NoteValue::quarter, NoteValue::whole); + _max_time = Rand::next(_min_time, NoteValue::whole); +} +std::vector ChordBasedCreator::chord_for(uintptr_t start, size_t size) const { + const ToneSet chord(_music->tones_at(start, size)); + const uint8_t octave(Rand::next(_min_tone / 12, _max_tone / 12) * 12); + + // Get chord tones in a vector + std::vector tones; + for(uint8_t t(0); t < 12; t++) { + if(chord & 1 << t) { + tones.push_back(t + octave); + } + } + return tones; +} \ No newline at end of file diff --git a/libraries/steve/src/creator/ChordBasedCreator.h b/libraries/steve/src/creator/ChordBasedCreator.h new file mode 100644 index 000000000..31d91d36f --- /dev/null +++ b/libraries/steve/src/creator/ChordBasedCreator.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Creator.h" + +namespace steve { + class ChordBasedCreator : public Creator { + protected: + ChordBasedCreator(Music* music); + virtual void init() override; + virtual std::vector chord_for(uintptr_t start, size_t size) const; + }; +} diff --git a/libraries/steve/src/creator/Chords.cpp b/libraries/steve/src/creator/Chords.cpp new file mode 100644 index 000000000..0cca4b3c6 --- /dev/null +++ b/libraries/steve/src/creator/Chords.cpp @@ -0,0 +1,36 @@ +#include "Chords.h" + +#include "../Instrument.h" +#include "../Music.h" + +using namespace steve; + +Chords::Chords(Music* music) + : ChordBasedCreator(music) {} +Notes Chords::get(size_t start, size_t size) const { + Notes notes; + auto times = generate_times(start, _music->get_bar_ticks()); + uintptr_t i = 0; + while(i < size) { + for(uintptr_t j = 0; j < times.size() - 1; j++) { + const auto duration = times[j + 1] - times[j]; + const Chord& chord = _music->chord_at(start + i); + uint8_t voice_count = 0; + for(uint8_t tone = _min_tone; tone < _max_tone && voice_count < _instrument->voices; tone++) { + if(voice_count == 0 && (tone % 12) != chord.key) { + //continue; // Lowest tone need to be key + } + if(((1 << (tone % 12)) & chord.tones) != 0) { + add_note(notes, _channel, tone, i, duration); + voice_count++; + } + } + i += duration; + } + } + return notes; +} + +bool Chords::is_valid_instrument(const Instrument& instrument) const { + return instrument.voices >= 3; +} \ No newline at end of file diff --git a/libraries/steve/src/creator/Chords.h b/libraries/steve/src/creator/Chords.h new file mode 100644 index 000000000..a7e6d1912 --- /dev/null +++ b/libraries/steve/src/creator/Chords.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ChordBasedCreator.h" + +namespace steve { + class Chords : public ChordBasedCreator { + public: + Chords(Music*); + Notes get(size_t start, size_t size) const override; + const char* name() const override { return "Chords"; } + bool is_valid_instrument(const struct Instrument& instrument) const override; + }; +} diff --git a/libraries/steve/src/creator/Creator.cpp b/libraries/steve/src/creator/Creator.cpp new file mode 100644 index 000000000..68c123825 --- /dev/null +++ b/libraries/steve/src/creator/Creator.cpp @@ -0,0 +1,184 @@ +#include "Creator.h" + +#include +#include +#include +#include "../Config.h" +#include "../Music.h" +#include "../Rand.h" + +using namespace steve; + +Creator::Creator(Music* music) { + _music = music; + _channel = music->creators().size(); +} +void Creator::init() { + _phrase_size = 4 * _music->get_bar_ticks(); + + _instrument = _music->get_config().get_instruments().get_random_item( + [this](std::shared_ptr candidate) { + return is_valid_instrument(*candidate); + }); + const uint8_t ambitus_half_range(Rand::next(6, 10)); + const uint8_t instrument_range(_instrument->max_tone - _instrument->min_tone); + + { + const uint8_t mid_tone(_instrument->min_tone + ambitus_half_range + Rand::next_normal()*float(instrument_range - ambitus_half_range * 2)); + _min_tone = std::max(mid_tone - ambitus_half_range, _instrument->min_tone); + _max_tone = std::min(mid_tone + ambitus_half_range, _instrument->max_tone); + assert(_max_tone - _min_tone >= 12); + } + + _min_time = Rand::next(NoteValue::sixteenth, NoteValue::quarter); + _max_time = Rand::next(std::max(_min_time, NoteValue::quarter), NoteValue::whole); + _repetition = Rand::next_float() * 0.5f; +} +void Creator::post_init() { + { // Make sure we can make notes the size of the beat + // Otherwise we may not be able to complete a bar + if(_min_time > _music->get_beat_value()) { + _min_time = _music->get_beat_value(); + } + if(_max_time < _music->get_beat_value()) { + _max_time = _music->get_beat_value(); + } + } +} + +Notes Creator::compose() { + uint32_t i(0); + Notes notes; + while(i < _music->size()) { + bool new_phrase_needed(true); // If it isn't changed it means no fitting phrase was found + for(uintptr_t phrase_index(0); phrase_index < _phrases.size(); phrase_index++) { // Iterate through figures already created + const Phrase& phrase(_phrases[phrase_index]); + if(i%phrase.size == 0 // Its size is good for the place it would be in + && i + phrase.size <= _music->size() // The phrase isn't too long + && Rand::next_float() < _repetition // Add some randomness + && harmony(_music->tones().data() + i, phrase.tones.data(), phrase.tones.size())) { // It's in harmony with the current music + paste(phrase.notes, notes, i); // Paste the phrase on the music + _phrase_list.push_back(phrase_index); // Register that we used this phrase + i += phrase.size; // Go forth + new_phrase_needed = false; // Specify that the program doesn't need to create a new phrase + break; + } + } + if(new_phrase_needed) { // Needs to create a new phrase of music + Phrase phrase; + phrase.size = _phrase_size; + while(i%phrase.size != 0 || i + phrase.size > _music->size()) // Good size and not too long + phrase.size /= 2; + phrase.notes = get(i, phrase.size); // Create the phrase + phrase.tones = octave_tones(phrase.notes); + _phrase_list.push_back(_phrases.size()); // Register that we used this phrase + _phrases.push_back(phrase); // Add it to the collection of phrases + paste(phrase.notes, notes, i); // Paste it on the music + i += phrase.size; // Go forth + } + } + return notes; +} +bool Creator::is_valid_instrument(const Instrument&) const { + return true; +} +void Creator::write_txt(std::ostream& s) const { + s << "\t" << name() << " (" << _instrument->name << ")" << std::endl + << "\t\t[" << note_value_name(_min_time) << ":" << note_value_name(_max_time) << "]" << std::endl + << "\t\t[" << note_name(_min_tone) << ":" << note_name(_max_tone) << "]" << std::endl + << "\t\tRepetition factor: " << _repetition << std::endl; + + { + s << "\t\tPhrases:"; + for(uintptr_t phrase_index : _phrase_list) { + s << " "; + if(phrase_index < 10) { + s << phrase_index; + } else { + s << char('a' + phrase_index - 10); + } + const Phrase& phrase(_phrases[phrase_index]); + const uint32_t bar_count(phrase.size / _music->get_bar_ticks()); + for(uint32_t i(1); i < bar_count; i++) { + s << " -"; + } + } + s << std::endl; + } +} +uintptr_t Creator::time(uintptr_t i, size_t size) const { + std::vector candidates = _music->beats_inside( + i + ticks_for(_min_time), + i + std::min(ticks_for(_max_time), size)); + assert(!candidates.empty()); + + // Transform from position to duration + for(uintptr_t& candidate : candidates) { + candidate -= i; + } + + const auto min_ticks = ticks_for(_min_time); + const auto max_ticks = ticks_for(_max_time); + const auto score = [this, i, min_ticks, max_ticks](uintptr_t c) -> uintptr_t { + if(c < min_ticks || c > max_ticks) { + return 0; + } + + if((c / min_ticks) * min_ticks != c) { + return 0; + } + + if(_music->tones_at(i, c) == 0) { + return 0; + } + + uintptr_t score = UINTPTR_MAX; + for(uintptr_t i = 0; i < sizeof(uintptr_t) * 8; i++) { + // Low set bits reduce score greatly + // High set bits reduce score slightly + if(((1ull << i) & c) != 0) { + score -= 1ull << (sizeof(uintptr_t) * 8 - i - 1); + } + } + return score; + }; + + // Remove unwanted candidates + auto previous_candidates = candidates; + candidates.erase(std::remove_if(candidates.begin(), candidates.end(), + [score](uintptr_t c) { + return score(c) == 0; + }), candidates.end()); + if(candidates.empty()) { + return 0; + } + + // Sort by increasing awkwardness + std::sort(candidates.begin(), candidates.end(), + [score](uintptr_t a, uintptr_t b) { + return score(a) > score(b); + }); + + std::exponential_distribution dist(0.5f); + const uintptr_t index = std::max(0, std::min(candidates.size() - 1, dist(Rand::generator))); + + const size_t ticks = candidates[index]; + assert(ticks <= size); + return ticks; +} +std::vector Creator::generate_times(uintptr_t start, size_t size) const { + std::vector times; + do { + uintptr_t i = 0; + times = {i}; + while(i < size) { + const uintptr_t d = time(start + i, size - i); + if(d == 0) { + break; + } + i += d; + times.push_back(i); + } + } while(times.back() != size); + return times; +} \ No newline at end of file diff --git a/libraries/steve/src/creator/Creator.h b/libraries/steve/src/creator/Creator.h new file mode 100644 index 000000000..bd4b54df9 --- /dev/null +++ b/libraries/steve/src/creator/Creator.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "../Chord.h" +#include "../ItemDescription.h" +#include "../Steve.h" + +namespace steve { + struct CreatorDescription : public ItemDescription { + std::function func; + uint32_t min_count = 0, max_count = 1; + }; + + class Creator { + protected: + class Music* _music; + std::shared_ptr _instrument; + std::vector _phrases; + std::vector _phrase_list; + size_t _phrase_size; + NoteValue _min_time, _max_time; + uint8_t _min_tone, _max_tone; + float _repetition; + uint8_t _channel; + + public: + Creator(class Music*); + virtual ~Creator() {} + virtual void init(); + virtual void post_init(); + virtual Notes compose(); + virtual Notes get(size_t start, size_t size) const = 0; + virtual const char* name() const = 0; + virtual bool is_valid_instrument(const Instrument& instrument) const; + virtual void write_txt(std::ostream&) const; + + inline std::shared_ptr instrument() const { return _instrument; } + + uintptr_t time(uintptr_t i, size_t size) const; + std::vector generate_times(uintptr_t i, size_t size) const; + }; +} diff --git a/libraries/steve/src/creator/Drums.cpp b/libraries/steve/src/creator/Drums.cpp new file mode 100644 index 000000000..bb7dd8a19 --- /dev/null +++ b/libraries/steve/src/creator/Drums.cpp @@ -0,0 +1,43 @@ +#include "Drums.h" + +#include "../Music.h" +#include "../Rand.h" + +using namespace steve; + +Drums::Drums(Music* music) : Creator(music) {} +void Drums::init() { + Creator::init(); + _channel = 9; + _repetition = 1; +} +Notes Drums::get(size_t, size_t size) const { + Notes notes; + + const auto bar_ticks = _music->get_bar_ticks(); + NoteValue max_period = NoteValue::whole; + while(((bar_ticks / ticks_for(max_period)) * ticks_for(max_period)) != bar_ticks) { + // We use only periods that can divide the bar to avoid weird things + // it would be better to actually have an idea of how drums should sound + // (this was introduced because of time signatures) + max_period = NoteValue(uint32_t(max_period) - 1); + } + + uint32_t layers(Rand::next(2, 5)); + for(uint32_t i(0); i < layers; i++) { + uint8_t tone(Rand::next(35, 59)); + NoteValue period_value = Rand::next(NoteValue::eighth, max_period); + uintptr_t period = ticks_for(period_value); + uintptr_t offset = ticks_for(Rand::next(NoteValue::eighth, period_value)); + if(i == 0 || Rand::next(0, 3) > 0) { + offset = 0; + } + + for(uintptr_t j(offset); j < size; j += period) { + if(_music->is_beat(j)) { + add_note(notes, _channel, tone, j, 1, 100); + } + } + } + return notes; +} diff --git a/libraries/steve/src/creator/Drums.h b/libraries/steve/src/creator/Drums.h new file mode 100644 index 000000000..aec473c58 --- /dev/null +++ b/libraries/steve/src/creator/Drums.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Creator.h" + +namespace steve { + class Drums : public Creator { + public: + Drums(Music* music); + virtual void init() override; + virtual Notes get(size_t start, size_t size) const override; + virtual const char* name() const override { return "Drums"; } + }; +} diff --git a/libraries/steve/src/creator/Melody.cpp b/libraries/steve/src/creator/Melody.cpp new file mode 100644 index 000000000..b957e380a --- /dev/null +++ b/libraries/steve/src/creator/Melody.cpp @@ -0,0 +1,33 @@ +#include "Melody.h" + +#include "../Music.h" +#include "../Rand.h" + +using namespace steve; + +Melody::Melody(Music* music) : Creator(music) {} +Notes Melody::get(size_t start, size_t size) const { + Notes notes; + auto times = generate_times(start, size); + for(uintptr_t i = 0; i < times.size() - 1; i++) { + const auto time = times[i]; + const auto duration = times[i + 1] - time; + const auto tones = choose_note_from_chord(_music->tones_at(start + time, duration)); + const auto tone = Rand::in(tones); + add_note(notes, _channel, tone, time, duration); + } + return notes; +} +std::set Melody::choose_note_from_chord(const ToneSet& tones) const { + std::set notes_in_ambitus; + for(uint8_t tone(0); tone < 12; tone++) { + if(tones & (1 << tone)) { + for(uint8_t t(tone); t <= _max_tone; t += 12) { + if(t >= _min_tone) { + notes_in_ambitus.insert(t); + } + } + } + } + return notes_in_ambitus; +} \ No newline at end of file diff --git a/libraries/steve/src/creator/Melody.h b/libraries/steve/src/creator/Melody.h new file mode 100644 index 000000000..efecc2bde --- /dev/null +++ b/libraries/steve/src/creator/Melody.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Creator.h" + +namespace steve { + class Melody : public Creator { + public: + Melody(Music*); + Notes get(size_t start, size_t size) const override; + const char* name() const override { return "Melody"; } + std::set choose_note_from_chord(const ToneSet& chord) const; + }; +} diff --git a/libraries/steve/src/ext/json.h b/libraries/steve/src/ext/json.h new file mode 100644 index 000000000..bdf3eae0e --- /dev/null +++ b/libraries/steve/src/ext/json.h @@ -0,0 +1,3451 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/json.h. +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to . +*/ + +#ifndef SHEREDOM_JSON_H_INCLUDED +#define SHEREDOM_JSON_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: 'bytes padding added after construct' */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) || defined(__WATCOMC__) +#define json_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define json_weak __attribute__((weak)) +#else +#error Non clang, non gcc, non MSVC, non WATCOM compiler found! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct json_value_s; +struct json_parse_result_s; + +enum json_parse_flags_e { + json_parse_flags_default = 0, + + /* allow trailing commas in objects and arrays. For example, both [true,] and + {"a" : null,} would be allowed with this option on. */ + json_parse_flags_allow_trailing_comma = 0x1, + + /* allow unquoted keys for objects. For example, {a : null} would be allowed + with this option on. */ + json_parse_flags_allow_unquoted_keys = 0x2, + + /* allow a global unbracketed object. For example, a : null, b : true, c : {} + would be allowed with this option on. */ + json_parse_flags_allow_global_object = 0x4, + + /* allow objects to use '=' instead of ':' between key/value pairs. For + example, a = null, b : true would be allowed with this option on. */ + json_parse_flags_allow_equals_in_object = 0x8, + + /* allow that objects don't have to have comma separators between key/value + pairs. */ + json_parse_flags_allow_no_commas = 0x10, + + /* allow c-style comments (either variants) to be ignored in the input JSON + file. */ + json_parse_flags_allow_c_style_comments = 0x20, + + /* deprecated flag, unused. */ + json_parse_flags_deprecated = 0x40, + + /* record location information for each value. */ + json_parse_flags_allow_location_information = 0x80, + + /* allow strings to be 'single quoted'. */ + json_parse_flags_allow_single_quoted_strings = 0x100, + + /* allow numbers to be hexadecimal. */ + json_parse_flags_allow_hexadecimal_numbers = 0x200, + + /* allow numbers like +123 to be parsed. */ + json_parse_flags_allow_leading_plus_sign = 0x400, + + /* allow numbers like .0123 or 123. to be parsed. */ + json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800, + + /* allow Infinity, -Infinity, NaN, -NaN. */ + json_parse_flags_allow_inf_and_nan = 0x1000, + + /* allow multi line string values. */ + json_parse_flags_allow_multi_line_strings = 0x2000, + + /* allow simplified JSON to be parsed. Simplified JSON is an enabling of a set + of other parsing options. */ + json_parse_flags_allow_simplified_json = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_global_object | + json_parse_flags_allow_equals_in_object | + json_parse_flags_allow_no_commas), + + /* allow JSON5 to be parsed. JSON5 is an enabling of a set of other parsing + options. */ + json_parse_flags_allow_json5 = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_c_style_comments | + json_parse_flags_allow_single_quoted_strings | + json_parse_flags_allow_hexadecimal_numbers | + json_parse_flags_allow_leading_plus_sign | + json_parse_flags_allow_leading_or_trailing_decimal_point | + json_parse_flags_allow_inf_and_nan | + json_parse_flags_allow_multi_line_strings) +}; + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to malloc for the entire encoding. + * Returns 0 if an error occurred (malformed JSON input, or malloc failed). */ +json_weak struct json_value_s *json_parse(const void *src, size_t src_size); + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to alloc_func_ptr for the entire + * encoding. Returns 0 if an error occurred (malformed JSON input, or malloc + * failed). If an error occurred, the result struct (if not NULL) will explain + * the type of error, and the location in the input it occurred. If + * alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *, size_t), void *user_data, + struct json_parse_result_s *result); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to malloc for the entire encoding. + */ +json_weak struct json_value_s * +json_extract_value(const struct json_value_s *value); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to alloc_func_ptr for the entire + * encoding. If alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, size_t), void *user_data); + +/* Write out a minified JSON utf-8 string. This string is an encoding of the + * minimal string characters required to still encode the same data. + * json_write_minified performs 1 call to malloc for the entire encoding. Return + * 0 if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_minified(const struct json_value_s *value, + size_t *out_size); + +/* Write out a pretty JSON utf-8 string. This string is encoded such that the + * resultant JSON is pretty in that it is easily human readable. The indent and + * newline parameters allow a user to specify what kind of indentation and + * newline they want (two spaces / three spaces / tabs? \r, \n, \r\n ?). Both + * indent and newline can be NULL, indent defaults to two spaces (" "), and + * newline defaults to linux newlines ('\n' as the newline character). + * json_write_pretty performs 1 call to malloc for the entire encoding. Return 0 + * if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_pretty(const struct json_value_s *value, + const char *indent, const char *newline, + size_t *out_size); + +/* Reinterpret a JSON value as a string. Returns null is the value was not a + * string. */ +json_weak struct json_string_s * +json_value_as_string(struct json_value_s *const value); + +/* Reinterpret a JSON value as a number. Returns null is the value was not a + * number. */ +json_weak struct json_number_s * +json_value_as_number(struct json_value_s *const value); + +/* Reinterpret a JSON value as an object. Returns null is the value was not an + * object. */ +json_weak struct json_object_s * +json_value_as_object(struct json_value_s *const value); + +/* Reinterpret a JSON value as an array. Returns null is the value was not an + * array. */ +json_weak struct json_array_s * +json_value_as_array(struct json_value_s *const value); + +/* Whether the value is true. */ +json_weak int json_value_is_true(const struct json_value_s *const value); + +/* Whether the value is false. */ +json_weak int json_value_is_false(const struct json_value_s *const value); + +/* Whether the value is null. */ +json_weak int json_value_is_null(const struct json_value_s *const value); + +/* The various types JSON values can be. Used to identify what a value is. */ +typedef enum json_type_e { + json_type_string, + json_type_number, + json_type_object, + json_type_array, + json_type_true, + json_type_false, + json_type_null + +} json_type_t; + +/* A JSON string value. */ +typedef struct json_string_s { + /* utf-8 string */ + const char *string; + /* The size (in bytes) of the string */ + size_t string_size; + +} json_string_t; + +/* A JSON string value (extended). */ +typedef struct json_string_ex_s { + /* The JSON string this extends. */ + struct json_string_s string; + + /* The character offset for the value in the JSON input. */ + size_t offset; + + /* The line number for the value in the JSON input. */ + size_t line_no; + + /* The row number for the value in the JSON input, in bytes. */ + size_t row_no; + +} json_string_ex_t; + +/* A JSON number value. */ +typedef struct json_number_s { + /* ASCII string containing representation of the number. */ + const char *number; + /* the size (in bytes) of the number. */ + size_t number_size; + +} json_number_t; + +/* an element of a JSON object. */ +typedef struct json_object_element_s { + /* the name of this element. */ + struct json_string_s *name; + /* the value of this element. */ + struct json_value_s *value; + /* the next object element (can be NULL if the last element in the object). */ + struct json_object_element_s *next; + +} json_object_element_t; + +/* a JSON object value. */ +typedef struct json_object_s { + /* a linked list of the elements in the object. */ + struct json_object_element_s *start; + /* the number of elements in the object. */ + size_t length; + +} json_object_t; + +/* an element of a JSON array. */ +typedef struct json_array_element_s { + /* the value of this element. */ + struct json_value_s *value; + /* the next array element (can be NULL if the last element in the array). */ + struct json_array_element_s *next; + +} json_array_element_t; + +/* a JSON array value. */ +typedef struct json_array_s { + /* a linked list of the elements in the array. */ + struct json_array_element_s *start; + /* the number of elements in the array. */ + size_t length; + +} json_array_t; + +/* a JSON value. */ +typedef struct json_value_s { + /* a pointer to either a json_string_s, json_number_s, json_object_s, or. */ + /* json_array_s. Should be cast to the appropriate struct type based on what. + */ + /* the type of this value is. */ + void *payload; + /* must be one of json_type_e. If type is json_type_true, json_type_false, or. + */ + /* json_type_null, payload will be NULL. */ + size_t type; + +} json_value_t; + +/* a JSON value (extended). */ +typedef struct json_value_ex_s { + /* the JSON value this extends. */ + struct json_value_s value; + + /* the character offset for the value in the JSON input. */ + size_t offset; + + /* the line number for the value in the JSON input. */ + size_t line_no; + + /* the row number for the value in the JSON input, in bytes. */ + size_t row_no; + +} json_value_ex_t; + +/* a parsing error code. */ +enum json_parse_error_e { + /* no error occurred (huzzah!). */ + json_parse_error_none = 0, + + /* expected either a comma or a closing '}' or ']' to close an object or. */ + /* array! */ + json_parse_error_expected_comma_or_closing_bracket, + + /* colon separating name/value pair was missing! */ + json_parse_error_expected_colon, + + /* expected string to begin with '"'! */ + json_parse_error_expected_opening_quote, + + /* invalid escaped sequence in string! */ + json_parse_error_invalid_string_escape_sequence, + + /* invalid number format! */ + json_parse_error_invalid_number_format, + + /* invalid value! */ + json_parse_error_invalid_value, + + /* reached end of buffer before object/array was complete! */ + json_parse_error_premature_end_of_buffer, + + /* string was malformed! */ + json_parse_error_invalid_string, + + /* a call to malloc, or a user provider allocator, failed. */ + json_parse_error_allocator_failed, + + /* the JSON input had unexpected trailing characters that weren't part of the. + JSON value. */ + json_parse_error_unexpected_trailing_characters, + + /* catch-all error for everything else that exploded (real bad chi!). */ + json_parse_error_unknown +}; + +/* error report from json_parse_ex(). */ +typedef struct json_parse_result_s { + /* the error code (one of json_parse_error_e). */ + size_t error; + + /* the character offset for the error in the JSON input. */ + size_t error_offset; + + /* the line number for the error in the JSON input. */ + size_t error_line_no; + + /* the row number for the error, in bytes. */ + size_t error_row_no; + +} json_parse_result_t; + +#ifdef __cplusplus +} /* extern "C". */ +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define json_uintmax_t unsigned __int64 +#else +#include +#define json_uintmax_t uintmax_t +#endif + +#if defined(_MSC_VER) +#define json_strtoumax _strtoui64 +#else +#define json_strtoumax strtoumax +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define json_null nullptr +#else +#define json_null 0 +#endif + +#if defined(__clang__) +#pragma clang diagnostic push + +/* we do one big allocation via malloc, then cast aligned slices of this for. */ +/* our structures - we don't have a way to tell the compiler we know what we. */ +/* are doing, so disable the warning instead! */ +#pragma clang diagnostic ignored "-Wcast-align" + +/* We use C style casts everywhere. */ +#pragma clang diagnostic ignored "-Wold-style-cast" + +/* We need long long for strtoull. */ +#pragma clang diagnostic ignored "-Wc++11-long-long" + +/* Who cares if nullptr doesn't work with C++98, we don't use it there! */ +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#elif defined(_MSC_VER) +#pragma warning(push) + +/* disable 'function selected for inline expansion' warning. */ +#pragma warning(disable : 4711) + +/* disable '#pragma warning: there is no warning number' warning. */ +#pragma warning(disable : 4619) + +/* disable 'warning number not a valid compiler warning' warning. */ +#pragma warning(disable : 4616) + +/* disable 'Compiler will insert Spectre mitigation for memory load if + * /Qspectre. */ +/* switch specified' warning. */ +#pragma warning(disable : 5045) +#endif + +struct json_parse_state_s { + const char *src; + size_t size; + size_t offset; + size_t flags_bitset; + char *data; + char *dom; + size_t dom_size; + size_t data_size; + size_t line_no; /* line counter for error reporting. */ + size_t line_offset; /* (offset-line_offset) is the character number (in + bytes). */ + size_t error; +}; + +json_weak int json_hexadecimal_digit(const char c); +int json_hexadecimal_digit(const char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } + if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + if ('A' <= c && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +json_weak int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result); +int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result) { + const char *p; + int digit; + + if (size > sizeof(unsigned long) * 2) { + return 0; + } + + *result = 0; + for (p = c; (unsigned long)(p - c) < size; ++p) { + *result <<= 4; + digit = json_hexadecimal_digit(*p); + if (digit < 0 || digit > 15) { + return 0; + } + *result |= (unsigned char)digit; + } + return 1; +} + +json_weak int json_skip_whitespace(struct json_parse_state_s *state); +int json_skip_whitespace(struct json_parse_state_s *state) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + + if (offset >= state->size) { + return 0; + } + + /* the only valid whitespace according to ECMA-404 is ' ', '\n', '\r' and + * '\t'. */ + switch (src[offset]) { + default: + return 0; + case ' ': + case '\r': + case '\t': + case '\n': + break; + } + + do { + switch (src[offset]) { + default: + /* Update offset. */ + state->offset = offset; + return 1; + case ' ': + case '\r': + case '\t': + break; + case '\n': + state->line_no++; + state->line_offset = offset; + break; + } + + offset++; + } while (offset < size); + + /* Update offset. */ + state->offset = offset; + return 1; +} + +json_weak int json_skip_c_style_comments(struct json_parse_state_s *state); +int json_skip_c_style_comments(struct json_parse_state_s *state) { + /* to have a C-style comment we need at least 2 characters of space */ + if ((state->offset + 2) > state->size) { + return 0; + } + + /* do we have a comment? */ + if ('/' == state->src[state->offset]) { + if ('/' == state->src[state->offset + 1]) { + /* we had a comment of the form // */ + + /* skip first '/' */ + state->offset++; + + /* skip second '/' */ + state->offset++; + + while (state->offset < state->size) { + switch (state->src[state->offset]) { + default: + /* skip the character in the comment */ + state->offset++; + break; + case '\n': + /* if we have a newline, our comment has ended! Skip the newline */ + state->offset++; + + /* we entered a newline, so move our line info forward */ + state->line_no++; + state->line_offset = state->offset; + return 1; + } + } + + /* we reached the end of the JSON file! */ + return 1; + } else if ('*' == state->src[state->offset + 1]) { + /* we had a comment in the C-style long form */ + + /* skip '/' */ + state->offset++; + + /* skip '*' */ + state->offset++; + + while (state->offset + 1 < state->size) { + if (('*' == state->src[state->offset]) && + ('/' == state->src[state->offset + 1])) { + /* we reached the end of our comment! */ + state->offset += 2; + return 1; + } else if ('\n' == state->src[state->offset]) { + /* we entered a newline, so move our line info forward */ + state->line_no++; + state->line_offset = state->offset; + } + + /* skip character within comment */ + state->offset++; + } + + /* comment wasn't ended correctly which is a failure */ + return 1; + } + } + + /* we didn't have any comment, which is ok too! */ + return 0; +} + +json_weak int json_skip_all_skippables(struct json_parse_state_s *state); +int json_skip_all_skippables(struct json_parse_state_s *state) { + /* skip all whitespace and other skippables until there are none left. note + * that the previous version suffered from read past errors should. the + * stream end on json_skip_c_style_comments eg. '{"a" ' with comments flag. + */ + + int did_consume = 0; + const size_t size = state->size; + + if (json_parse_flags_allow_c_style_comments & state->flags_bitset) { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + + /* This should really be checked on access, not in front of every call. + */ + if (state->offset >= size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume |= json_skip_c_style_comments(state); + } while (0 != did_consume); + } else { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + } while (0 != did_consume); + } + + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); + +json_weak int json_get_string_size(struct json_parse_state_s *state, + size_t is_key); +int json_get_string_size(struct json_parse_state_s *state, size_t is_key) { + size_t offset = state->offset; + const size_t size = state->size; + size_t data_size = 0; + const char *const src = state->src; + const int is_single_quote = '\'' == src[offset]; + const char quote_to_use = is_single_quote ? '\'' : '"'; + const size_t flags_bitset = state->flags_bitset; + unsigned long codepoint; + unsigned long high_surrogate = 0; + + if ((json_parse_flags_allow_location_information & flags_bitset) != 0 && + is_key != 0) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + if ('"' != src[offset]) { + /* if we are allowed single quoted strings check for that too. */ + if (!((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + is_single_quote)) { + state->error = json_parse_error_expected_opening_quote; + state->offset = offset; + return 1; + } + } + + /* skip leading '"' or '\''. */ + offset++; + + while ((offset < size) && (quote_to_use != src[offset])) { + /* add space for the character. */ + data_size++; + + switch (src[offset]) { + default: + break; + case '\0': + case '\t': + state->error = json_parse_error_invalid_string; + state->offset = offset; + return 1; + } + + if ('\\' == src[offset]) { + /* skip reverse solidus character. */ + offset++; + + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset; + return 1; + } + + switch (src[offset]) { + default: + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + /* all valid characters! */ + offset++; + break; + case 'u': + if (!(offset + 5 < size)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + codepoint = 0; + if (!json_hexadecimal_value(&src[offset + 1], 4, &codepoint)) { + /* escaped unicode sequences must contain 4 hexadecimal digits! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + /* Valid sequence! + * see: https://en.wikipedia.org/wiki/UTF-8#Invalid_code_points. + * 1 7 U + 0000 U + 007F 0xxxxxxx. + * 2 11 U + 0080 U + 07FF 110xxxxx + * 10xxxxxx. + * 3 16 U + 0800 U + FFFF 1110xxxx + * 10xxxxxx 10xxxxxx. + * 4 21 U + 10000 U + 10FFFF 11110xxx + * 10xxxxxx 10xxxxxx 10xxxxxx. + * Note: the high and low surrogate halves used by UTF-16 (U+D800 + * through U+DFFF) and code points not encodable by UTF-16 (those after + * U+10FFFF) are not legal Unicode values, and their UTF-8 encoding must + * be treated as an invalid byte sequence. */ + + if (high_surrogate != 0) { + /* we previously read the high half of the \uxxxx\uxxxx pair, so now + * we expect the low half. */ + if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate range. */ + data_size += 3; + high_surrogate = 0; + } else { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + } else if (codepoint <= 0x7f) { + data_size += 0; + } else if (codepoint <= 0x7ff) { + data_size += 1; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate range. */ + /* The codepoint is the first half of a "utf-16 surrogate pair". so we + * need the other half for it to be valid: \uHHHH\uLLLL. */ + if (offset + 11 > size || '\\' != src[offset + 5] || + 'u' != src[offset + 6]) { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + high_surrogate = codepoint; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdfff) { /* low surrogate range. */ + /* we did not read the other half before. */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } else { + data_size += 2; + } + /* escaped codepoints after 0xffff are supported in json through utf-16 + * surrogate pairs: \uD83D\uDD25 for U+1F525. */ + + offset += 5; + break; + } + } else if (('\r' == src[offset]) || ('\n' == src[offset])) { + if (!(json_parse_flags_allow_multi_line_strings & flags_bitset)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + offset++; + } else { + /* skip character (valid part of sequence). */ + offset++; + } + } + + /* If the offset is equal to the size, we had a non-terminated string! */ + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset - 1; + return 1; + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* add enough space to store the string. */ + state->data_size += data_size; + + /* one more byte for null terminator ending the string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int is_valid_unquoted_key_char(const char c); +int is_valid_unquoted_key_char(const char c) { + return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || ('_' == c)); +} + +json_weak int json_get_key_size(struct json_parse_state_s *state); +int json_get_key_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + + if (json_parse_flags_allow_unquoted_keys & flags_bitset) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + size_t data_size = state->data_size; + + /* if we are allowing unquoted keys, first grok for a quote... */ + if ('"' == src[offset]) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else if ((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + ('\'' == src[offset])) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else { + while ((offset < size) && is_valid_unquoted_key_char(src[offset])) { + offset++; + data_size++; + } + + /* one more byte for null terminator ending the string! */ + data_size++; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + /* update offset. */ + state->offset = offset; + + /* update data_size. */ + state->data_size = data_size; + + return 0; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + return json_get_string_size(state, 1); + } +} + +json_weak int json_get_object_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_object_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + int found_closing_brace = 0; + + if (is_global_object) { + /* if we found an opening '{' of an object, we actually have a normal JSON + * object at the root of the DOM... */ + if (!json_skip_all_skippables(state) && '{' == state->src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + if ('{' != src[state->offset]) { + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '{'. */ + state->offset++; + } + + state->dom_size += sizeof(struct json_object_s); + + if ((state->offset == size) && !is_global_object) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + do { + if (!is_global_object) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + found_closing_brace = 1; + + /* finished the object! */ + break; + } + } else { + /* we don't require brackets, so that means the object ends when the input + * stream ends! */ + if (json_skip_all_skippables(state)) { + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (json_parse_flags_allow_no_commas & flags_bitset) { + /* we don't require a comma, and we didn't find one, which is ok! */ + allow_comma = 0; + } else { + /* otherwise we are required to have a comma, and we found none. */ + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_key_size(state)) { + /* key parsing failed! */ + state->error = json_parse_error_invalid_string; + return 1; + } + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + const char current = src[state->offset]; + if ((':' != current) && ('=' != current)) { + state->error = json_parse_error_expected_colon; + return 1; + } + } else { + if (':' != src[state->offset]) { + state->error = json_parse_error_expected_colon; + return 1; + } + } + + /* skip colon. */ + state->offset++; + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + if ((state->offset == size) && !is_global_object && !found_closing_brace) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + state->dom_size += sizeof(struct json_object_element_s) * elements; + + return 0; +} + +json_weak int json_get_array_size(struct json_parse_state_s *state); +int json_get_array_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t elements = 0; + int allow_comma = 0; + const char *const src = state->src; + const size_t size = state->size; + + if ('[' != src[state->offset]) { + /* expected array to begin with leading '['. */ + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '['. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_s); + + while (state->offset < size) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_element_s) * elements; + + /* finished the object! */ + return 0; + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (!(json_parse_flags_allow_no_commas & flags_bitset)) { + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + allow_comma = 0; + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } + + /* we consumed the entire input before finding the closing ']' of the array! + */ + state->error = json_parse_error_premature_end_of_buffer; + return 1; +} + +json_weak int json_get_number_size(struct json_parse_state_s *state); +int json_get_number_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + int had_leading_digits = 0; + const char *const src = state->src; + + state->dom_size += sizeof(struct json_number_s); + + if ((json_parse_flags_allow_hexadecimal_numbers & flags_bitset) && + (offset + 1 < size) && ('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* skip the leading 0x that identifies a hexadecimal number. */ + offset += 2; + + /* consume hexadecimal digits. */ + while ((offset < size) && (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F'))) { + offset++; + } + } else { + int found_sign = 0; + int inf_or_nan = 0; + + if ((offset < size) && + (('-' == src[offset]) || + ((json_parse_flags_allow_leading_plus_sign & flags_bitset) && + ('+' == src[offset])))) { + /* skip valid leading '-' or '+'. */ + offset++; + + found_sign = 1; + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const char inf[9] = "Infinity"; + const size_t inf_strlen = sizeof(inf) - 1; + const char nan[4] = "NaN"; + const size_t nan_strlen = sizeof(nan) - 1; + + if (offset + inf_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < inf_strlen; i++) { + if (inf[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'Infinity' keyword! */ + offset += inf_strlen; + + inf_or_nan = 1; + } + } + + if (offset + nan_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < nan_strlen; i++) { + if (nan[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'NaN' keyword! */ + offset += nan_strlen; + + inf_or_nan = 1; + } + } + + if (inf_or_nan) { + if (offset < size) { + switch (src[offset]) { + default: + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'e': + case 'E': + /* cannot follow an inf or nan with digits! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + } + } + + if (found_sign && !inf_or_nan && (offset < size) && + !('0' <= src[offset] && src[offset] <= '9')) { + /* check if we are allowing leading '.'. */ + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + ('.' != src[offset])) { + /* a leading '-' must be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + if ((offset < size) && ('0' == src[offset])) { + /* skip valid '0'. */ + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + + if ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + /* a leading '0' must not be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* the main digits of our number next. */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + } + + if ((offset < size) && ('.' == src[offset])) { + offset++; + + if ((offset >= size) || !('0' <= src[offset] && src[offset] <= '9')) { + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + !had_leading_digits) { + /* a decimal point must be followed by at least one digit. */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* a decimal point can be followed by more digits of course! */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + } + } + + if ((offset < size) && ('e' == src[offset] || 'E' == src[offset])) { + /* our number has an exponent! Skip 'e' or 'E'. */ + offset++; + + if ((offset < size) && ('-' == src[offset] || '+' == src[offset])) { + /* skip optional '-' or '+'. */ + offset++; + } + + if ((offset < size) && !('0' <= src[offset] && src[offset] <= '9')) { + /* an exponent must have at least one digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + + /* consume exponent digits. */ + do { + offset++; + } while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')); + } + } + + if (offset < size) { + switch (src[offset]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '}': + case ',': + case ']': + /* all of the above are ok. */ + break; + case '=': + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + break; + } + + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + default: + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + state->data_size += offset - state->offset; + + /* one more byte for null terminator ending the number string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_value_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + size_t offset; + const size_t size = state->size; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_value_ex_s); + } else { + state->dom_size += sizeof(struct json_value_s); + } + + if (is_global_object) { + return json_get_object_size(state, /* is_global_object = */ 1); + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + /* can cache offset now. */ + offset = state->offset; + + switch (src[offset]) { + case '"': + return json_get_string_size(state, 0); + case '\'': + if (json_parse_flags_allow_single_quoted_strings & flags_bitset) { + return json_get_string_size(state, 0); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + case '{': + return json_get_object_size(state, /* is_global_object = */ 0); + case '[': + return json_get_array_size(state); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_get_number_size(state); + case '+': + if (json_parse_flags_allow_leading_plus_sign & flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + case '.': + if (json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + state->offset += 5; + return 0; + } else if ((offset + 4) <= size && 'n' == state->src[offset + 0] && + 'u' == state->src[offset + 1] && + 'l' == state->src[offset + 2] && + 'l' == state->src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + return json_get_number_size(state); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + return json_get_number_size(state); + } + + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + } +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); + +json_weak void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string) { + size_t offset = state->offset; + size_t bytes_written = 0; + const char *const src = state->src; + const char quote_to_use = '\'' == src[offset] ? '\'' : '"'; + char *data = state->data; + unsigned long high_surrogate = 0; + unsigned long codepoint; + + string->string = data; + + /* skip leading '"' or '\''. */ + offset++; + + while (quote_to_use != src[offset]) { + if ('\\' == src[offset]) { + /* skip the reverse solidus. */ + offset++; + + switch (src[offset++]) { + default: + return; /* we cannot ever reach here. */ + case 'u': { + codepoint = 0; + if (!json_hexadecimal_value(&src[offset], 4, &codepoint)) { + return; /* this shouldn't happen as the value was already validated. + */ + } + + offset += 4; + + if (codepoint <= 0x7fu) { + data[bytes_written++] = (char)codepoint; /* 0xxxxxxx. */ + } else if (codepoint <= 0x7ffu) { + data[bytes_written++] = + (char)(0xc0u | (codepoint >> 6)); /* 110xxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate. */ + high_surrogate = codepoint; + continue; /* we need the low half to form a complete codepoint. */ + } else if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate. */ + /* combine with the previously read half to obtain the complete + * codepoint. */ + const unsigned long surrogate_offset = + 0x10000u - (0xD800u << 10) - 0xDC00u; + codepoint = (high_surrogate << 10) + codepoint + surrogate_offset; + high_surrogate = 0; + data[bytes_written++] = + (char)(0xF0u | (codepoint >> 18)); /* 11110xxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 12) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else { + /* we assume the value was validated and thus is within the valid + * range. */ + data[bytes_written++] = + (char)(0xe0u | (codepoint >> 12)); /* 1110xxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } + } break; + case '"': + data[bytes_written++] = '"'; + break; + case '\\': + data[bytes_written++] = '\\'; + break; + case '/': + data[bytes_written++] = '/'; + break; + case 'b': + data[bytes_written++] = '\b'; + break; + case 'f': + data[bytes_written++] = '\f'; + break; + case 'n': + data[bytes_written++] = '\n'; + break; + case 'r': + data[bytes_written++] = '\r'; + break; + case 't': + data[bytes_written++] = '\t'; + break; + case '\r': + data[bytes_written++] = '\r'; + + /* check if we have a "\r\n" sequence. */ + if ('\n' == src[offset]) { + data[bytes_written++] = '\n'; + offset++; + } + + break; + case '\n': + data[bytes_written++] = '\n'; + break; + } + } else { + /* copy the character. */ + data[bytes_written++] = src[offset++]; + } + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* record the size of the string. */ + string->string_size = bytes_written; + + /* add null terminator to string. */ + data[bytes_written++] = '\0'; + + /* move data along. */ + state->data += bytes_written; + + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string) { + if (json_parse_flags_allow_unquoted_keys & state->flags_bitset) { + const char *const src = state->src; + char *const data = state->data; + size_t offset = state->offset; + + /* if we are allowing unquoted keys, check for quoted anyway... */ + if (('"' == src[offset]) || ('\'' == src[offset])) { + /* ... if we got a quote, just parse the key as a string as normal. */ + json_parse_string(state, string); + } else { + size_t size = 0; + + string->string = state->data; + + while (is_valid_unquoted_key_char(src[offset])) { + data[size++] = src[offset++]; + } + + /* add null terminator to string. */ + data[size] = '\0'; + + /* record the size of the string. */ + string->string_size = size++; + + /* move data along. */ + state->data += size; + + /* update offset. */ + state->offset = offset; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + json_parse_string(state, string); + } +} + +json_weak void json_parse_object(struct json_parse_state_s *state, + int is_global_object, + struct json_object_s *object); +void json_parse_object(struct json_parse_state_s *state, int is_global_object, + struct json_object_s *object) { + const size_t flags_bitset = state->flags_bitset; + const size_t size = state->size; + const char *const src = state->src; + size_t elements = 0; + int allow_comma = 0; + struct json_object_element_s *previous = json_null; + + if (is_global_object) { + /* if we skipped some whitespace, and then found an opening '{' of an. */ + /* object, we actually have a normal JSON object at the root of the DOM... + */ + if ('{' == src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + /* skip leading '{'. */ + state->offset++; + } + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + while (state->offset < size) { + struct json_object_element_s *element = json_null; + struct json_string_s *string = json_null; + struct json_value_s *value = json_null; + + if (!is_global_object) { + (void)json_skip_all_skippables(state); + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + /* finished the object! */ + break; + } + } else { + if (json_skip_all_skippables(state)) { + /* global object ends when the file ends! */ + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_object_element_s *)state->dom; + + state->dom += sizeof(struct json_object_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our object. */ + object->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_string_ex_s *string_ex = + (struct json_string_ex_s *)state->dom; + state->dom += sizeof(struct json_string_ex_s); + + string_ex->offset = state->offset; + string_ex->line_no = state->line_no; + string_ex->row_no = state->offset - state->line_offset; + + string = &(string_ex->string); + } else { + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + } + + element->name = string; + + (void)json_parse_key(state, string); + + (void)json_skip_all_skippables(state); + + /* skip colon or equals. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } + + /* if we had at least one element, end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + object->start = json_null; + } + + object->length = elements; +} + +json_weak void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array); +void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array) { + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + struct json_array_element_s *previous = json_null; + + /* skip leading '['. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + do { + struct json_array_element_s *element = json_null; + struct json_value_s *value = json_null; + + (void)json_skip_all_skippables(state); + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + /* finished the array! */ + break; + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_array_element_s *)state->dom; + + state->dom += sizeof(struct json_array_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our array. */ + array->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & state->flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + /* end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + array->start = json_null; + } + + array->length = elements; +} + +json_weak void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number); +void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + size_t bytes_written = 0; + const char *const src = state->src; + char *data = state->data; + + number->number = data; + + if (json_parse_flags_allow_hexadecimal_numbers & flags_bitset) { + if (('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* consume hexadecimal digits. */ + while ((offset < size) && + (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F') || + ('x' == src[offset]) || ('X' == src[offset]))) { + data[bytes_written++] = src[offset++]; + } + } + } + + while (offset < size) { + int end = 0; + + switch (src[offset]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'e': + case 'E': + case '+': + case '-': + data[bytes_written++] = src[offset++]; + break; + default: + end = 1; + break; + } + + if (0 != end) { + break; + } + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const size_t inf_strlen = 8; /* = strlen("Infinity");. */ + const size_t nan_strlen = 3; /* = strlen("NaN");. */ + + if (offset + inf_strlen < size) { + if ('I' == src[offset]) { + size_t i; + /* We found our special 'Infinity' keyword! */ + for (i = 0; i < inf_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + + if (offset + nan_strlen < size) { + if ('N' == src[offset]) { + size_t i; + /* We found our special 'NaN' keyword! */ + for (i = 0; i < nan_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + } + + /* record the size of the number. */ + number->number_size = bytes_written; + /* add null terminator to number string. */ + data[bytes_written++] = '\0'; + /* move data along. */ + state->data += bytes_written; + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); +void json_parse_value(struct json_parse_state_s *state, int is_global_object, + struct json_value_s *value) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t offset; + + (void)json_skip_all_skippables(state); + + /* cache offset now. */ + offset = state->offset; + + if (is_global_object) { + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 1, + (struct json_object_s *)value->payload); + } else { + switch (src[offset]) { + case '"': + case '\'': + value->type = json_type_string; + value->payload = state->dom; + state->dom += sizeof(struct json_string_s); + json_parse_string(state, (struct json_string_s *)value->payload); + break; + case '{': + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 0, + (struct json_object_s *)value->payload); + break; + case '[': + value->type = json_type_array; + value->payload = state->dom; + state->dom += sizeof(struct json_array_s); + json_parse_array(state, (struct json_array_s *)value->payload); + break; + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + break; + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + value->type = json_type_true; + value->payload = json_null; + state->offset += 4; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + value->type = json_type_false; + value->payload = json_null; + state->offset += 5; + } else if ((offset + 4) <= size && 'n' == src[offset + 0] && + 'u' == src[offset + 1] && 'l' == src[offset + 2] && + 'l' == src[offset + 3]) { + value->type = json_type_null; + value->payload = json_null; + state->offset += 4; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } + break; + } + } +} + +struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *user_data, size_t size), + void *user_data, struct json_parse_result_s *result) { + struct json_parse_state_s state; + void *allocation; + struct json_value_s *value; + size_t total_size; + int input_error; + + if (result) { + result->error = json_parse_error_none; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + if (json_null == src) { + /* invalid src pointer was null! */ + return json_null; + } + + state.src = (const char *)src; + state.size = src_size; + state.offset = 0; + state.line_no = 1; + state.line_offset = 0; + state.error = json_parse_error_none; + state.dom_size = 0; + state.data_size = 0; + state.flags_bitset = flags_bitset; + + input_error = json_get_value_size( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset)); + + if (0 == input_error) { + json_skip_all_skippables(&state); + + if (state.offset != state.size) { + /* our parsing didn't have an error, but there are characters remaining in + * the input that weren't part of the JSON! */ + + state.error = json_parse_error_unexpected_trailing_characters; + input_error = 1; + } + } + + if (input_error) { + /* parsing value's size failed (most likely an invalid JSON DOM!). */ + if (result) { + result->error = state.error; + result->error_offset = state.offset; + result->error_line_no = state.line_no; + result->error_row_no = state.offset - state.line_offset; + } + return json_null; + } + + /* our total allocation is the combination of the dom and data sizes (we. */ + /* first encode the structure of the JSON, and then the data referenced by. */ + /* the JSON values). */ + total_size = state.dom_size + state.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + if (json_null == allocation) { + /* malloc failed! */ + if (result) { + result->error = json_parse_error_allocator_failed; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + return json_null; + } + + /* reset offset so we can reuse it. */ + state.offset = 0; + + /* reset the line information so we can reuse it. */ + state.line_no = 1; + state.line_offset = 0; + + state.dom = (char *)allocation; + state.data = state.dom + state.dom_size; + + if (json_parse_flags_allow_location_information & state.flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state.dom; + state.dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state.offset; + value_ex->line_no = state.line_no; + value_ex->row_no = state.offset - state.line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state.dom; + state.dom += sizeof(struct json_value_s); + } + + json_parse_value( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset), + value); + + return (struct json_value_s *)allocation; +} + +struct json_value_s *json_parse(const void *src, size_t src_size) { + return json_parse_ex(src, src_size, json_parse_flags_default, json_null, + json_null, json_null); +} + +struct json_extract_result_s { + size_t dom_size; + size_t data_size; +}; + +struct json_value_s *json_extract_value(const struct json_value_s *value) { + return json_extract_value_ex(value, json_null, json_null); +} + +json_weak struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number); +json_weak struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string); +json_weak struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object); +json_weak struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array); +json_weak struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value); + +struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_number_s); + result.data_size = number->number_size; + return result; +} + +struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_string_s); + result.data_size = string->string_size + 1; + return result; +} + +struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object) { + struct json_extract_result_s result; + size_t i; + const struct json_object_element_s *element = object->start; + + result.dom_size = sizeof(struct json_object_s) + + (sizeof(struct json_object_element_s) * object->length); + result.data_size = 0; + + for (i = 0; i < object->length; i++) { + const struct json_extract_result_s string_result = + json_extract_get_string_size(element->name); + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += string_result.dom_size; + result.data_size += string_result.data_size; + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array) { + struct json_extract_result_s result; + size_t i; + const struct json_array_element_s *element = array->start; + + result.dom_size = sizeof(struct json_array_s) + + (sizeof(struct json_array_element_s) * array->length); + result.data_size = 0; + + for (i = 0; i < array->length; i++) { + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value) { + struct json_extract_result_s result = {0, 0}; + + switch (value->type) { + default: + break; + case json_type_object: + result = json_extract_get_object_size( + (const struct json_object_s *)value->payload); + break; + case json_type_array: + result = json_extract_get_array_size( + (const struct json_array_s *)value->payload); + break; + case json_type_number: + result = json_extract_get_number_size( + (const struct json_number_s *)value->payload); + break; + case json_type_string: + result = json_extract_get_string_size( + (const struct json_string_s *)value->payload); + break; + } + + result.dom_size += sizeof(struct json_value_s); + + return result; +} + +struct json_extract_state_s { + char *dom; + char *data; +}; + +json_weak void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value); +void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value) { + struct json_string_s *string; + struct json_number_s *number; + struct json_object_s *object; + struct json_array_s *array; + struct json_value_s *new_value; + + memcpy(state->dom, value, sizeof(struct json_value_s)); + new_value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + new_value->payload = state->dom; + + if (json_type_string == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + } else if (json_type_number == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_number_s)); + number = (struct json_number_s *)state->dom; + state->dom += sizeof(struct json_number_s); + + memcpy(state->data, number->number, number->number_size); + number->number = state->data; + state->data += number->number_size; + } else if (json_type_object == value->type) { + struct json_object_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_object_s)); + object = (struct json_object_s *)state->dom; + state->dom += sizeof(struct json_object_s); + + element = object->start; + object->start = (struct json_object_element_s *)state->dom; + + for (i = 0; i < object->length; i++) { + struct json_value_s *previous_value; + struct json_object_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_object_element_s)); + element = (struct json_object_element_s *)state->dom; + state->dom += sizeof(struct json_object_element_s); + + string = element->name; + memcpy(state->dom, string, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + element->name = string; + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_object_element_s *)state->dom; + } + } + } else if (json_type_array == value->type) { + struct json_array_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_array_s)); + array = (struct json_array_s *)state->dom; + state->dom += sizeof(struct json_array_s); + + element = array->start; + array->start = (struct json_array_element_s *)state->dom; + + for (i = 0; i < array->length; i++) { + struct json_value_s *previous_value; + struct json_array_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_array_element_s)); + element = (struct json_array_element_s *)state->dom; + state->dom += sizeof(struct json_array_element_s); + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_array_element_s *)state->dom; + } + } + } +} + +struct json_value_s *json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, + size_t), + void *user_data) { + void *allocation; + struct json_extract_result_s result; + struct json_extract_state_s state; + size_t total_size; + + if (json_null == value) { + /* invalid value was null! */ + return json_null; + } + + result = json_extract_get_value_size(value); + total_size = result.dom_size + result.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + state.dom = (char *)allocation; + state.data = state.dom + result.dom_size; + + json_extract_copy_value(&state, value); + + return (struct json_value_s *)allocation; +} + +struct json_string_s *json_value_as_string(struct json_value_s *const value) { + if (value->type != json_type_string) { + return json_null; + } + + return (struct json_string_s *)value->payload; +} + +struct json_number_s *json_value_as_number(struct json_value_s *const value) { + if (value->type != json_type_number) { + return json_null; + } + + return (struct json_number_s *)value->payload; +} + +struct json_object_s *json_value_as_object(struct json_value_s *const value) { + if (value->type != json_type_object) { + return json_null; + } + + return (struct json_object_s *)value->payload; +} + +struct json_array_s *json_value_as_array(struct json_value_s *const value) { + if (value->type != json_type_array) { + return json_null; + } + + return (struct json_array_s *)value->payload; +} + +int json_value_is_true(const struct json_value_s *const value) { + return value->type == json_type_true; +} + +int json_value_is_false(const struct json_value_s *const value) { + return value->type == json_type_false; +} + +int json_value_is_null(const struct json_value_s *const value) { + return value->type == json_type_null; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); + +json_weak int json_write_get_number_size(const struct json_number_s *number, + size_t *size); +int json_write_get_number_size(const struct json_number_s *number, + size_t *size) { + json_uintmax_t parsed_number; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* the number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + *size += i; + return 0; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '+' or '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf) { + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + *size += 22; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *size += 1; + } + } + + return 0; + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan) { + /* NaN becomes 1 because JSON can't support it. */ + *size += 1; + + return 0; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a leading decimal point. */ + *size += 1; + goto cleanup; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a trailing decimal point. */ + *size += 1; + goto cleanup; + } + +cleanup: + *size += number->number_size; /* the actual string of the number. */ + + /* if we had a leading '+' we don't record it in the JSON output. */ + if ('+' == number->number[0]) { + *size -= 1; + } + + return 0; +} + +json_weak int json_write_get_string_size(const struct json_string_s *string, + size_t *size); +int json_write_get_string_size(const struct json_string_s *string, + size_t *size) { + size_t i; + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + *size += 2; + break; + default: + *size += 1; + break; + } + } + + *size += 2; /* need to encode the surrounding '"' characters. */ + + return 0; +} + +json_weak int +json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size); +int json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size) { + struct json_array_element_s *element; + + *size += 2; /* '[' and ']'. */ + + if (1 < array->length) { + *size += array->length - 1; /* ','s seperate each element. */ + } + + for (element = array->start; json_null != element; element = element->next) { + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size); +int json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size) { + struct json_object_element_s *element; + + *size += 2; /* '{' and '}'. */ + + *size += object->length; /* ':'s seperate each name/value pair. */ + + if (1 < object->length) { + *size += object->length - 1; /* ','s seperate each element. */ + } + + for (element = object->start; json_null != element; element = element->next) { + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); +int json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_minified_get_array_size( + (struct json_array_s *)value->payload, size); + case json_type_object: + return json_write_minified_get_object_size( + (struct json_object_s *)value->payload, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); + +json_weak char *json_write_number(const struct json_number_s *number, + char *data); +char *json_write_number(const struct json_number_s *number, char *data) { + json_uintmax_t parsed_number, backup; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* The number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + /* We need a copy of parsed number twice, so take a backup of it. */ + backup = parsed_number; + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + /* Restore parsed_number to its original value stored in the backup. */ + parsed_number = backup; + + /* Now use backup to take a copy of i, or the length of the string. */ + backup = i; + + do { + *(data + i - 1) = '0' + (char)(parsed_number % 10); + parsed_number /= 10; + i--; + } while (0 != parsed_number); + + data += backup; + + return data; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf++) { + const char *dbl_max; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *data++ = '-'; + } + + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + for (dbl_max = "1.7976931348623158e308"; '\0' != *dbl_max; dbl_max++) { + *data++ = *dbl_max; + } + + return data; + } + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan++) { + /* NaN becomes 0 because JSON can't support it. */ + *data++ = '0'; + return data; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* insert a '0' to fix the leading decimal point for JSON output. */ + *data++ = '0'; + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + /* insert a '0' to fix the trailing decimal point for JSON output. */ + *data++ = '0'; + + return data; + } + + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; +} + +json_weak char *json_write_string(const struct json_string_s *string, + char *data); +char *json_write_string(const struct json_string_s *string, char *data) { + size_t i; + + *data++ = '"'; /* open the string. */ + + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + *data++ = '\\'; /* escape the control character. */ + *data++ = '"'; + break; + case '\\': + *data++ = '\\'; /* escape the control character. */ + *data++ = '\\'; + break; + case '\b': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'b'; + break; + case '\f': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'f'; + break; + case '\n': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'n'; + break; + case '\r': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'r'; + break; + case '\t': + *data++ = '\\'; /* escape the control character. */ + *data++ = 't'; + break; + default: + *data++ = string->string[i]; + break; + } + } + + *data++ = '"'; /* close the string. */ + + return data; +} + +json_weak char *json_write_minified_array(const struct json_array_s *array, + char *data); +char *json_write_minified_array(const struct json_array_s *array, char *data) { + struct json_array_element_s *element = json_null; + + *data++ = '['; /* open the array. */ + + for (element = array->start; json_null != element; element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_minified_object(const struct json_object_s *object, + char *data); +char *json_write_minified_object(const struct json_object_s *object, + char *data) { + struct json_object_element_s *element = json_null; + + *data++ = '{'; /* open the object. */ + + for (element = object->start; json_null != element; element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + *data++ = ':'; /* ':'s seperate each name/value pair. */ + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); +char *json_write_minified_value(const struct json_value_s *value, char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_minified_array((struct json_array_s *)value->payload, + data); + case json_type_object: + return json_write_minified_object((struct json_object_s *)value->payload, + data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_minified(const struct json_value_s *value, size_t *out_size) { + size_t size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_write_minified_get_value_size(value, &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_minified_value(value, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); + +json_weak int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_array_element_s *element; + + *size += 1; /* '['. */ + + if (0 < array->length) { + /* if we have any elements we need to add a newline after our '['. */ + *size += newline_size; + + *size += array->length - 1; /* ','s seperate each element. */ + + for (element = array->start; json_null != element; + element = element->next) { + /* each element gets an indent. */ + *size += (depth + 1) * indent_size; + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + + /* each element gets a newline too. */ + *size += newline_size; + } + + /* since we wrote out some elements, need to add a newline and indentation. + */ + /* to the trailing ']'. */ + *size += depth * indent_size; + } + + *size += 1; /* ']'. */ + + return 0; +} + +json_weak int +json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size); +int json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_object_element_s *element; + + *size += 1; /* '{'. */ + + if (0 < object->length) { + *size += newline_size; /* need a newline next. */ + + *size += object->length - 1; /* ','s seperate each element. */ + + for (element = object->start; json_null != element; + element = element->next) { + /* each element gets an indent and newline. */ + *size += (depth + 1) * indent_size; + *size += newline_size; + + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + *size += 3; /* seperate each name/value pair with " : ". */ + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + } + + *size += depth * indent_size; + } + + *size += 1; /* '}'. */ + + return 0; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_pretty_get_array_size( + (struct json_array_s *)value->payload, depth, indent_size, newline_size, + size); + case json_type_object: + return json_write_pretty_get_object_size( + (struct json_object_s *)value->payload, depth, indent_size, + newline_size, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); + +json_weak char *json_write_pretty_array(const struct json_array_s *array, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_array(const struct json_array_s *array, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_array_element_s *element; + + *data++ = '['; /* open the array. */ + + if (0 < array->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = array->start; json_null != element; + element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_pretty_object(const struct json_object_s *object, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_object(const struct json_object_s *object, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_object_element_s *element; + + *data++ = '{'; /* open the object. */ + + if (0 < object->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = object->start; json_null != element; + element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + /* " : "s seperate each name/value pair. */ + *data++ = ' '; + *data++ = ':'; + *data++ = ' '; + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_value(const struct json_value_s *value, size_t depth, + const char *indent, const char *newline, + char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_pretty_array((struct json_array_s *)value->payload, depth, + indent, newline, data); + case json_type_object: + return json_write_pretty_object((struct json_object_s *)value->payload, + depth, indent, newline, data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_pretty(const struct json_value_s *value, const char *indent, + const char *newline, size_t *out_size) { + size_t size = 0; + size_t indent_size = 0; + size_t newline_size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_null == indent) { + indent = " "; /* default to two spaces. */ + } + + if (json_null == newline) { + newline = "\n"; /* default to linux newlines. */ + } + + while ('\0' != indent[indent_size]) { + ++indent_size; /* skip non-null terminating characters. */ + } + + while ('\0' != newline[newline_size]) { + ++newline_size; /* skip non-null terminating characters. */ + } + + if (json_write_pretty_get_value_size(value, 0, indent_size, newline_size, + &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_pretty_value(value, 0, indent, newline, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif /* SHEREDOM_JSON_H_INCLUDED. */ diff --git a/scripts/midi/all.steve.json b/scripts/midi/all.steve.json new file mode 100644 index 000000000..73b161f0a --- /dev/null +++ b/scripts/midi/all.steve.json @@ -0,0 +1,687 @@ +{ + "min_tempo": 20, + "max_tempo": 220, + "time_signatures": { + "2/2":{}, "2/4":{}, "3/4":{}, "4/4":{}, "5/4":{}, "6/4":{}, "7/4":{}, "9/8":{} + }, + "chords": { + "Major": { + "suffix": "", + "tones": [4, 7], + "uppercase": true + }, + "Minor": { + "suffix": "min", + "tones": [3, 7] + }, + "Augmented": { + "suffix": "aug", + "tones": [4, 8] + }, + "Diminished": { + "suffix": "dim", + "tones": [3, 6] + }, + + "Major Sixth": { + "suffix": "maj6", + "tones": [4, 7, 9], + "uppercase": true + }, + "Minor Sixth": { + "suffix": "min6", + "tones": [3, 7, 9] + }, + + "Dominant Seventh": { + "suffix": "dom7", + "tones": [4, 7, 10], + "uppercase": true + }, + "Major Seventh": { + "suffix": "maj7", + "tones": [4, 7, 11], + "uppercase": true + }, + "Minor-Major Seventh": { + "suffix": "min/maj7", + "tones": [3, 7, 11] + }, + "Minor Seventh": { + "suffix": "min7", + "tones": [3, 7, 10] + } + }, + "scales": { + "Blues": { + "tones": [3, 5, 6, 7] + }, + "Byzantine": { + "tones": [1, 4, 5, 7, 8, 11] + }, + "Harmonic Minor": { + "tones": [2, 3, 5, 7, 8, 11] + }, + "Kumoi": { + "tones": [2, 3, 7, 9] + }, + "Major": { + "tones": [2, 4, 5, 7, 9, 11] + }, + "Minor": { + "tones": [2, 3, 5, 7, 8, 10] + }, + "Melodic Minor": { + "tones": [2, 3, 5, 7, 9, 11] + }, + "Pentatonic Major": { + "tones": [2, 4, 7, 9] + }, + "Whole Tone": { + "tones": [2, 4, 6, 8, 10] + } + }, + "chord_changes": { + "*": { "*": { "*": {}}} + }, + "instruments": { + "Acoustic Grand Piano": { + "index": 0, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Bright Acoustic Piano": { + "index": 1, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Electric Grand Piano": { + "index": 2, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Honky Tonk Piano": { + "index": 3, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Electric Piano 1": { + "index": 4, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Electric Piano 2": { + "index": 5, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Harpsichord": { + "index": 6, + "min_tone": "F1", + "max_tone": "F6", + "voices": 10 + }, + "Clavinet": { + "index": 7, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Celesta": { + "index": 8, + "min_tone": "C4", + "max_tone": "C8", + "voices": 10 + }, + "Glockenspiel": { + "index": 9, + "min_tone": "G5", + "max_tone": "C8" + }, + "Music Box": { + "index": 10, + "min_tone": "A0", + "max_tone": "C8", + "voices": 10 + }, + "Vibraphone": { + "index": 11, + "min_tone": "F3", + "max_tone": "F6" + }, + "Marimba": { + "index": 12, + "min_tone": "C2", + "max_tone": "C7" + }, + "Xylophone": { + "index": 13, + "min_tone": "F3", + "max_tone": "C8" + }, + "Tubular Bells": { + "index": 14, + "min_tone": "F3", + "max_tone": "G5" + }, + "Dulcimer": { + "index": 15, + "min_tone": "A2", + "max_tone": "A6" + }, + "Drawbar Organ": { + "index": 16, + "min_tone": "C2", + "max_tone": "C7", + "voices": 10 + }, + "Percussive Organ": { + "index": 17, + "min_tone": "C2", + "max_tone": "C7", + "voices": 10 + }, + "Rock Organ": { + "index": 18, + "min_tone": "C2", + "max_tone": "C7", + "voices": 10 + }, + "Church Organ": { + "index": 19, + "min_tone": "C-1", + "max_tone": "G9", + "voices": 10 + }, + "Reed Organ": { + "index": 20, + "min_tone": "C2", + "max_tone": "C7", + "voices": 10 + }, + "Accordion": { + "index": 21, + "min_tone": "D2", + "max_tone": "D8", + "voices": 5 + }, + "Harmonica": { + "index": 22, + "min_tone": "C2", + "max_tone": "C7" + }, + "Tango Accordion": { + "index": 23, + "min_tone": "C2", + "max_tone": "C7", + "voices": 5 + }, + "Acoustic Guitar (Nylon)": { + "index": 24, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Acoustic Guitar (Steel)": { + "index": 25, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Electric Guitar (Jazz)": { + "index": 26, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Electric Guitar (Clean)": { + "index": 27, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Electric Guitar (Muted)": { + "index": 28, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Overdriven Guitar": { + "index": 29, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Distorted Guitar": { + "index": 30, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Guitar Harmonics": { + "index": 31, + "min_tone": "E2", + "max_tone": "C6", + "voices": 6 + }, + "Acoustic Bass": { + "index": 32, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Electric Fingered Bass": { + "index": 33, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Electric Picked Bass": { + "index": 34, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Fretless Bass": { + "index": 35, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Slap Bass 1": { + "index": 36, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Slap Bass 2": { + "index": 37, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Syn Bass 1": { + "index": 38, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Syn Bass 2": { + "index": 39, + "min_tone": "E1", + "max_tone": "G4", + "voices": 4 + }, + "Violin": { + "index": 40, + "min_tone": "G3", + "max_tone": "G7" + }, + "Viola": { + "index": 41, + "min_tone": "C3", + "max_tone": "C7" + }, + "Cello": { + "index": 42, + "min_tone": "C2", + "max_tone": "C6" + }, + "Contrabass": { + "index": 43, + "min_tone": "E1", + "max_tone": "D4" + }, + "Tremolo Strings": { + "index": 44, + "min_tone": "C3", + "max_tone": "C6" + }, + "Pizzicato Strings": { + "index": 45, + "min_tone": "C3", + "max_tone": "C6" + }, + "Orchestral Harp": { + "index": 46, + "min_tone": "B0", + "max_tone": "G7" + }, + "Timpani": { + "index": 47, + "min_tone": "D2", + "max_tone": "A3" + }, + "String Ensemble": { + "index": 48, + "min_tone": "G3", + "max_tone": "C5" + }, + "String Ensemble 2 (Slow)": { + "index": 49, + "min_tone": "G3", + "max_tone": "C5" + }, + "Syn Strings 1": { + "index": 50, + "min_tone": "G3", + "max_tone": "C5" + }, + "Syn Strings 2": { + "index": 51, + "min_tone": "G3", + "max_tone": "C5" + }, + "Choir Aahs": { + "index": 52, + "min_tone": "E2", + "max_tone": "A#5" + }, + "Voice Oohs": { + "index": 53, + "min_tone": "E2", + "max_tone": "A#5" + }, + "Syn Choir": { + "index": 54, + "min_tone": "E2", + "max_tone": "A#5" + }, + "Orchestral Hit": { + "index": 55, + "min_tone": "C3", + "max_tone": "C6" + }, + "Trumpet": { + "index": 56, + "min_tone": "E2", + "max_tone": "C6" + }, + "Trombone": { + "index": 57, + "min_tone": "B1", + "max_tone": "A#4" + }, + "Tuba": { + "index": 58, + "min_tone": "E1", + "max_tone": "F4" + }, + "Muted Trumpet": { + "index": 59, + "min_tone": "E2", + "max_tone": "C6" + }, + "French Horn": { + "index": 60, + "min_tone": "B1", + "max_tone": "F5" + }, + "Brass Section": { + "index": 61, + "min_tone": "E2", + "max_tone": "E4" + }, + "Synth Brass 1": { + "index": 62, + "min_tone": 28, + "max_tone": 67 + }, + "Synth Brass 2": { + "index": 63, + "min_tone": 28, + "max_tone": 67 + }, + "Soprano Sax": { + "index": 64, + "min_tone": "G#3", + "max_tone": "E6" + }, + "Alto Sax": { + "index": 65, + "min_tone": "C#3", + "max_tone": "G#5" + }, + "Tenor Sax": { + "index": 66, + "min_tone": "G#2", + "max_tone": "E5" + }, + "Baritone Sax": { + "index": 67, + "min_tone": "C2", + "max_tone": "A4" + }, + "Oboe": { + "index": 68, + "min_tone": "A#3", + "max_tone": "G6" + }, + "English Horn": { + "index": 69, + "min_tone": "E3", + "max_tone": "C6" + }, + "Bassoon": { + "index": 70, + "min_tone": "A#1", + "max_tone": "E5" + }, + "Clarinet": { + "index": 71, + "min_tone": "C#2", + "max_tone": "G6" + }, + "Piccolo": { + "index": 72, + "min_tone": "D5", + "max_tone": "C8" + }, + "Flute": { + "index": 73, + "min_tone": "C4", + "max_tone": "C7" + }, + "Recorder": { + "index": 74, + "min_tone": "F3", + "max_tone": "G7" + }, + "Pan Flute": { + "index": 75, + "min_tone": "B3", + "max_tone": "G7" + }, + "Bottle Blow": { + "index": 76, + "min_tone": 36, + "max_tone": 95 + }, + "Shakuhachi": { + "index": 77, + "min_tone": "D4", + "max_tone": "E6" + }, + "Whistle": { + "index": 78, + "min_tone": 60, + "max_tone": 84 + }, + "Ocarina": { + "index": 79, + "min_tone": "C4", + "max_tone": "C7" + }, + "Lead 1 (Square)": { + "index": 80 + }, + "Lead 2 (Sawtooth)": { + "index": 81 + }, + "Lead 3 (Calliope)": { + "index": 82 + }, + "Lead 4 (Chiff)": { + "index": 83 + }, + "Lead 5 (Charang)": { + "index": 84 + }, + "Lead 6 (Voice)": { + "index": 85 + }, + "Lead 7 (Fifths)": { + "index": 86 + }, + "Lead 8 (Bass + Lead)": { + "index": 87 + }, + "Pad 1 (New Age)": { + "index": 88 + }, + "Pad 2 (Warm)": { + "index": 89 + }, + "Pad 3 (Polysynth)": { + "index": 90 + }, + "Pad 4 (Choir)": { + "index": 91 + }, + "Pad 5 (Bowed)": { + "index": 92 + }, + "Pad 6 (Metallic)": { + "index": 93 + }, + "Pad 7 (Halo)": { + "index": 94 + }, + "FX 1 (Rain)": { + "index": 96 + }, + "FX 2 (Soundtrack)": { + "index": 97 + }, + "FX 3 (Crystal)": { + "index": 98 + }, + "FX 4 (Atmosphere)": { + "index": 99 + }, + "FX 5 (Brightness)": { + "index": 100 + }, + "FX 6 (Goblins)": { + "index": 101 + }, + "FX 7 (Echoes)": { + "index": 102 + }, + "FX 8 (Sci-Fi)": { + "index": 103 + }, + "Sitar": { + "index": 104, + "min_tone": "C2", + "max_tone": "E5", + "voices": 7 + }, + "Banjo": { + "index": 105, + "min_tone": "C3", + "max_tone": "C6", + "voices": 5 + }, + "Shamisen": { + "index": 106, + "min_tone": "D4", + "max_tone": "F6", + "voices": 3 + }, + "Koto": { + "index": 107, + "min_tone": "A1", + "max_tone": "A7", + "voices": 17 + }, + "Kalimba": { + "index": 108, + "min_tone": "G3", + "max_tone": "D6" + }, + "Bag Pipe": { + "index": 109, + "min_tone": "A4", + "max_tone": "A#5" + }, + "Fiddle": { + "index": 110, + "min_tone": "G3", + "max_tone": "G7" + }, + "Shanai": { + "index": 111, + "min_tone": "A3", + "max_tone": "A5" + }, + "Tinkle Bell": { + "index": 112, + "min_tone": "C#4", + "max_tone": "A6" + }, + "Agogo": { + "index": 113 + }, + "Steel Drums": { + "index": 114, + "min_tone": "F1", + "max_tone": "G6" + }, + "Woodblock": { + "index": 115 + }, + "Taiko Drum": { + "index": 116 + }, + "Melodic Tom": { + "index": 117 + }, + "Synth Drum": { + "index": 118 + }, + "Reverse Cymbal": { + "index": 119 + }, + "Guitar Fret Noise": { + "index": 120 + }, + "Breath Noise": { + "index": 121 + }, + "Seashore": { + "index": 122 + }, + "Bird Tweet": { + "index": 123 + }, + "Telephone Ring": { + "index": 124 + }, + "Helicopter": { + "index": 125 + }, + "Applause": { + "index": 126 + }, + "Gunshot": { + "index": 127 + } + } +} diff --git a/scripts/midi/sane.steve.json b/scripts/midi/sane.steve.json new file mode 100644 index 000000000..88864eae9 --- /dev/null +++ b/scripts/midi/sane.steve.json @@ -0,0 +1,144 @@ +{ + "parents": ["all.steve.json"], + "min_tempo": 80, + "max_tempo": 160, + "time_signatures": { + "2/2": { + "weight": 2 + }, + "2/4": { + "weight": 2 + }, + "4/4": { + "weight": 4 + }, + "7/4": { + "weight": 0.5 + }, + "9/8": { + "weight": 0.1 + } + }, + "creators": { + "Arpeggio": { + "max_count": 0 + }, + "Bass": { + "min_count": 1 + }, + "Melody": { + "min_count": 1, + "max_count": 3 + } + }, + "scales": { + "Major": { + "weight": 5 + }, + "Byzantine": { + "blacklist": true + }, + "Kumoi": { + "blacklist": true + }, + "Whole Tone": { + "blacklist": true + } + }, + "chords": { + "Major Sixth": { + "blacklist": true + }, + "Minor Sixth": { + "blacklist": true + } + }, + "chord_changes": { + // Allow everything but make it less likely + "*": {"*": {"*": {"weight": 0.01}}}, + + // Major chord map + "Minor|Minor Seventh": { // ii/iii + "Minor|Minor Seventh": { // iii/vi + "2": {"weight": 1}, // ii -> iii + "5": {"weight": 1} // iii -> vi + }, + "Major|Dominant Seventh": {"5": {"weight": 1}}, // ii -> V + "Major|Major Sixth|Major Seventh": {"1": {"weight": 1}} // iii -> IV + }, + "Major|Major Sixth|Major Seventh": { // IV + "Major|Major Sixth|Major Seventh": {"7": {"weight": 1}}, // IV -> I + "Minor|Minor Seventh": {"2": {"weight": 1}}, // IV -> ii + "Major|Dominant Seventh": {"2": {"weight": 1}} // IV -> V + }, + "Major|Dominant Seventh": { // V + "Major|Major Sixth|Major Seventh": {"5": {"weight": 1}}, // V -> I + "Minor|Minor Seventh": { + "9": {"weight": 1}, // V -> iii + "2": {"weight": 1} // V -> vi + } + } + }, + "instruments": { + "Acoustic Bass": { + "voices": 1 + }, + "Electric Fingered Bass": { + "voices": 1 + }, + "Electric Picked Bass": { + "voices": 1 + }, + "Fretless Bass": { + "voices": 1 + }, + "Slap Bass 1": { + "voices": 1 + }, + "Slap Bass 2": { + "voices": 1 + }, + "Syn Bass 1": { + "voices": 1 + }, + "Syn Bass 2": { + "voices": 1 + }, + "Guitar Harmonics": { + "blacklist": true + }, + "Bottle Blow": { + "blacklist": true + }, + "Whistle": { + "blacklist": true + }, + "Reverse Cymbal": { + "blacklist": true + }, + "Guitar Fret Noise": { + "blacklist": true + }, + "Breath Noise": { + "blacklist": true + }, + "Seashore": { + "blacklist": true + }, + "Bird Tweet": { + "blacklist": true + }, + "Telephone Ring": { + "blacklist": true + }, + "Helicopter": { + "blacklist": true + }, + "Applause": { + "blacklist": true + }, + "Gunshot": { + "blacklist": true + } + } +} diff --git a/source/g_doom.cc b/source/g_doom.cc index ace0bdf99..ee8743e8e 100644 --- a/source/g_doom.cc +++ b/source/g_doom.cc @@ -46,8 +46,6 @@ extern void CSG_DOOM_Write(); extern std::string BestDirectory(); -// extern void CSG_TestRegions_Doom(); - extern int ef_solid_type; extern int ef_liquid_type; extern int ef_thing_mode; diff --git a/source/lib_midi.cc b/source/lib_midi.cc new file mode 100644 index 000000000..aea5306fc --- /dev/null +++ b/source/lib_midi.cc @@ -0,0 +1,29 @@ +#include + +#include "ConfigJson.h" +#include "Music.h" +#include "Steve.h" + +void steve_generate(const char *config_file, const char *out_file) { + + int num = 1; + + steve::note_name_init(); + + steve::ConfigJson config; + config.parse_file(config_file); + config.compute_cache(); + + for(uint32_t i = 0; i < num; i++) { + std::string music_path; + steve::Music music(config); + if(!out_file) { + music_path = music.to_short_string(); + music_path.append(".mid"); + } + else + music_path = out_file; + std::ofstream fs(music_path, std::ofstream::binary); + music.write_mid(fs); + } +} diff --git a/source/lib_midi.h b/source/lib_midi.h new file mode 100644 index 000000000..b9d91f90b --- /dev/null +++ b/source/lib_midi.h @@ -0,0 +1 @@ +void steve_generate(const char *config_file, const char *out_file); \ No newline at end of file diff --git a/source/main.cc b/source/main.cc index b06a5728f..c245e7aa4 100644 --- a/source/main.cc +++ b/source/main.cc @@ -50,6 +50,8 @@ #include "ui_window.h" #endif +#include "lib_midi.h" // test - Dasho + /** * \brief Ticker time in milliseconds */ @@ -1927,6 +1929,9 @@ softrestart:; old_pixels = new uint8_t[map_size]; memcpy(old_pixels, main_win->build_box->mini_map->pixels, map_size); } + + // test - Dasho + steve_generate(PathAppend(install_dir, "scripts/midi/all.steve.json").c_str(), NULL); } else { diff --git a/source/sys_xoshiro.cc b/source/sys_xoshiro.cc index b651bc220..121ffd4a4 100644 --- a/source/sys_xoshiro.cc +++ b/source/sys_xoshiro.cc @@ -19,12 +19,16 @@ //------------------------------------------------------------------------ #include "fastPRNG.h" +#include "Rand.h" fastPRNG::fastXS64 xoshiro; void xoshiro_Reseed(uint64_t newseed) { xoshiro.seed(newseed); + // proc gen MIDI uses its own RNG, but let's at least + // match the seeds + steve::Rand::reseed(newseed); } uint64_t xoshiro_UInt()