Giter Club home page Giter Club logo

augene-ng's Introduction

augene-ng: MML compiler for audio plugins sequencer engine

augene-ng demo 2021-11-18 (links to youtube video)

augene(-ng, next gen) is an experimental compound music authoring toolchain that brings old-fashion MML (music macro language) compiler integrated into modern sequencer that is also used in Tracktion Waveform DAW (so far). It is the tool I (@atsushieno) use it for own music production in 2021 and onwards. Check out the youtube video linked from the sshot above to find out what I have achieved with this tool, as well as the actual project files under samples/mars directory.

You can also have a quick glance at the project by my slides for lightening talk at ADC 2019 for a bit more details, though I went far beyond from there.

The application consists of the following software and libraries behind:

  • The project model implemented in this repository which contains a set of MML sources and associated audio plugin filter graphs, converts MIDI 2.0 UMPs to audio plugin based songs (.tracktionedit)
  • MML compiler mugene-ng - compiles MML into MIDI 2.0 UMPs.
  • JUCE AudioPluginHost for editing audio graph.
  • tracktion_engine - music playback engine.
  • Compose for Desktop, cross-platform desktop port of Jetpack Compose. (The application itself is desktop-only, so far, as it depends on a lot of desktop filesystem idioms.)

Usage

NOTE: before using augene, you most likely have to build things (explained in the next section).

There are two ways to use augene tools: (1) use GUI editor to create a project, compile it to *.tracktionedit on the UI, and play the outcome it with GUI player, or (2) run project compiler and pass an augene project as an argument to generate *.tracktionedit, and play it with GUI player.

For GUI editor, launch augene(-editor) application. It is a cross-platform Kotlin/JVM Compose for Desktop GUI application.

augene editor: configuration tab

By default those lists are actually empty. It's a screenshot of the app that has loaded sample data that makes use of VST3 plugins.

To use this app, there are couple of things to do - Configure the app. Namely paths to two external tools are needed:

  • augene-player (JUCE app in this repository, which is mostly based on PlaybackDemo in tracktion_engine repository)
  • AudioPluginHost (can be found in JUCE extras)

The next step is to build a list of locally installed audio plugins. Begin with "Plugins" button to start the process.

build audio plugin list Once you are done with above, then you're ready to use the app. You can open a *.tracktionedit file and play it. Note that if you don't have the audio plugins specified in the edit file, you are unable to play it.

To compose your own music, create new audiograph and new MML for each list, which can be performed via the buttons on each tab. Then use "Compile" command from the FAB (floating action button).

For console compiler, run java -jar path/to/augene-console.jar path/to/your/project.augene and AugenePlayer respectively.

Building

augene-player

There are two primary steps to build the whole "augene" application. The first step is "augene-player" part, which is a JUCE based C++ application. It is a typical JUCE application project. You can build it with the following steps:

cd augene-player
ln -s ../external/tracktion_engine/modules/juce juce-symlink
mkdir build
cd build
cmake ..
make

Enabling VST2

It is not confirmed in augene-ng era, but if you have VST2 SDK and would like to add support for VST2, edit CMakeLists.txt which follows JUCE convention that I don't explain by myself. And probably CMakeLists.txt for AudioPluginHost too, especially if you try to build it from build-lv2-pluginhost.sh.

kotractive, augene, and augene-editor

Another chunk of the application is the augene project builder (or "editor") which is a Compose for Desktop based GUI app/tool.

Due to current limitation of Kotlin Multiplatform project structure, there are 3 projects to just build one single app... :

  • kotractive, which provides basic "tracktionedit" file data model using ksp, in Kotlin Multiplatform
  • augene, which provides Augene project data model and manipulator API, in Kotlin Multiplatform
  • augene-editor, which is a GUI application project using Compose for Desktop Multiplatform, JVM-only
$ cd kotracktive && ./gradlew publishToMavenLocal && cd ..
$ cd augene && ./gradlew publishToMavenLocal && cd ..
$ cd augene-editor && ./gradlew package && cd ..

Android build

AugenePlayer is being ported to Android as aap-juce-augene. It still does not successfully run, and player only.

Augene project data format

An augene project is a simple set of XML described in a project file which looks like this:

<AugeneProject xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Includes>
    <Include Bank="1" Source="Banks/SfzBanks.augene" />
    <Include Bank="2" Source="Banks/SF2Banks.augene" />
    <Include Bank="3" Source="Banks/SurgeBanks.augene" />
  </Includes>
  <MasterPlugins>
    <MasterPlugin>MasterPlugin1.filtergraph</MasterPlugin>
  </MasterPlugins>
  <AudioGraphs>
    <AudioGraph Id="GrandPiano1" Source="sfizz_city_piano_1.filtergraph" />
  </AudioGraphs>
  <Tracks>
    <AugeneTrack>
      <Id>1</Id>
      <AudioGraph>Unnamed.filtergraph</AudioGraph>
    </AugeneTrack>
  </Tracks>
  <MmlFiles>
    <MmlFile>foobar.mugene</MmlFile>
  </MmlFiles>
  <MmlStrings>
    <MmlString>![CDATA[ 1 @0 V110 v100 o5 l8 cegcegeg  > c1 ]]></MmlString>
  </MmlStrings>
</AugeneProject>

Here is a list of elements:

Element feature
AugeneProject the root element
Includes container of Include elements.
Include include other project files. They can also be a bank list of AudioGraph. See description below.
AudioGraphs container of AudioGraph elements.
AudioGraph gives a filtergraph a name so that it can be referenced by AudioGraph element within AugeneTrack element.
MasterPlugins holds a list of master plugins
MasterPlugin specifies an AudioGraph file that is used as a master plugin
Tracks holds a list of tracks
AugeneTrack a track definition specifier which holds an Id and an AudioGraph file (so far only one plugin is specified. Rooms for improvements.
MmlFiles holds a list of MML files
MmlFile specifies an MML source file to be compiled and converted to the edit file.
MmlStrings holds a list of MML strings
MmlString specifies an MML string to be compiled and converted to the edit file.

An Augene project can include other Augene project files using Include element. It is useful to represent a bank of preset filtergraphs. On an Include element, Bank and BankMsb attributes indicate bank select MSB, BankLsb attribute indicates bank select LSB (Bank is equivalent to BankMsb here). Source attribute indicates the included file path, relative to the including file path.

An AudioGraph can be referenced by its Id attribute, by (1) mugene MIDI track with INSTRUMENTNAME meta event, or (2) AudioGraph attribute on AugeneTrack elements.

All tracks in either MML format (file or string) are converted into tracktionedit. Then audio graphs in the project are interpreted and converted to PLUGIN element in tracktionedit and then for each defined track by Tracks elements, if there is any graph whose Id is identical to the track's AudioGraph then the audio graph is attached to the track.

One thing to note is that while mugene supports track number in double (floating point number) SMF does not have "track numbers" and numbers are counted only by sequential index (0, 1, 2...), the mappings could be totally different. It is always safer to indicate audio graph by INSTRUMENTNAME meta event in mugene MML, or supplementally use AugeneTrack's mappings.

Controlling audio plugin parameters by MML via automation tracks

mugene is designed to generate music as in MIDI files, which has no concept of audio plugins and their parameters. augene (namely its Midi2TracktionEditConverter) has special support for controlling them via Tracktion's "Automation Tracks".

To understand how augene supports them, we should understand how a .tracktionedit XML file represents audio plugin parameter controllers via automation tracks.

<AUTOMATIONTRACK> elements can be created inside a <TRACK> element, and each <AUTOMATIONTRACK> has currentAutoParamPluginID attribute that indicates a <PLUGIN> element by its id (not to be confused with uid or uniqueId), and currentAutoParamTag attribute that indicates the parameter index for the <PLUGIN>. The <AUTOMATIONTRACK> has no actual parameter data controllers though - they are in the <PLUGIN> element. It can contain <AUTOMATIONCURVE> elements, and each of them has a paramID attribute that indicates the parameter index, and a number of <POINT> elements where each of them has t attribute (time) and v attribute (value) (TBD: there is also c attribute whose role is unknown).

Here is an example track (excerpt):

  <TRACK extension:InstrumentName="opn" id="1">
    ...
    <AUTOMATIONTRACK currentAutoParamPluginID="1997591454" currentAutoParamTag="4" id="-1507545692">
      <MACROPARAMETERS id="212771437" />
      <MODIFIERS />
    </AUTOMATIONTRACK>
    <PLUGIN type="vst" uid="a83aaba6" filename="/home/atsushi/.vst3/OPNplug.vst3" name="OPNplug" manufacturer="Jean Pierre Cimalando" id="1997591454" enabled="1" programNum="0" state="...">
      <AUTOMATIONCURVE paramID="4">
        <POINT t="0.0" v="4096.0" c="0.0" />
      </AUTOMATIONCURVE>
    </PLUGIN>
    ...
  </TRACK>

Now, its our (augene's, particularly its Midi2TracktionEditConverter's) role on how to generate them from an SMF. We use system exclusive messages that indicate "we are augene-ng automation controllers". To identify that, system exclusives with 7Dh sysex manufacturer ID (general research / development use), followed by 9 bytes augene-ng characters, is used. Then the next byte is used to distinguish two different controls:

  • 00h to indicate the actual parameter control: the next two bytes represents the parameter ID by LSB and MSB, then the next two bytes for the parameter value LSB and MSB.
  • non-zero byte to indicate a string length, followed by the actual ASCII string that indicates the plugin's unique ID (uid or uniqueID) that is being used from there in the track.

Indicating plugin's unique ID and parameter index is not intuitive for MML authoring. It's better to use some macros that wraps those constants. There is a helper Node.JS script generate-automation-helper.js that parses ~/.config/augene-ng/plugin-metadata.json and generates a bunch of .mugene MML files for each audio plugins that are listed within the JSON, into ~/.config/augene-ng/audio-plugins directory. The generated MML looks like this:

#macro AUDIO_PLUGIN_USE nameLen:number, ident:string {  __MIDI #F0, #7D, "augene-ng", $nameLen, $ident, #F7 }
#macro AUDIO_PLUGIN_PARAMETER parameterID:number, val:number { \
    __MIDI #F0, #7D, "augene-ng", 0, \
    $parameterID % #80, $parameterID / #80, $val % #80, $val / #80 } 

#macro OPNPLUG { AUDIO_PLUGIN_USE 11, "-1472549978" }
#macro OPNPLUG_MASTER_VOLUME val { AUDIO_PLUGIN_PARAMETER 0, $val }
#macro OPNPLUG_EMULATOR val { AUDIO_PLUGIN_PARAMETER 1, $val }
#macro OPNPLUG_CHIP_COUNT val { AUDIO_PLUGIN_PARAMETER 2, $val }
#macro OPNPLUG_CHIP_TYPE val { AUDIO_PLUGIN_PARAMETER 3, $val }
#macro OPNPLUG__PART_\1__OPERATOR_\1_LEVEL val { AUDIO_PLUGIN_PARAMETER 4, $val }
...

Then you can #include this macro, and write your track MML like:

// not quite sure if this 127 works just as 127, you might need some multiplication
1	INSTRUMENTNAME "opn" CH0 @1 OPNPLUG OPNPLUG_PART\1_OPERATOR\1_LEVEL 127

Wherever you have the same set of audio plugins installed on your system, you can generate the same set of macro definitions and therefore the MML should be compatible across environment, whereas the plugin unique IDs might be different.

License

The augene-player part (JUCE application) is released under the GPLv3 license.

The kotlin projects are released under the MIT license.

Dependencies

There are couple of dependencies in this application:

augene-ng's People

Contributors

atsushieno avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

augene-ng's Issues

produce mp3 samples on the CI builds

Since we are the only one who can do it, why not!?

  • we need audio plugins installed at certain location. Maybe studiorack helps it?
  • we need augene-player run headlessly, to achieve (1) audio plugin lookup and cache as JUCE config settings, (2) "export" plugin helper MMLs as part of config files, and (3) "render" tracktionedit files into wav.
  • extend #include processor to make it to load files from the config directory too.
  • ffmpeg or whatever to (a) remove silence tail, and (b) convert to mp3 and/or flac.

split INSTRUMENTNAME and isolate instruments from effectors

Currently when we reference audio graphs using INSTRUMENTNAME, we have to specify ALL the relevant plugins within the *.filtergraph file i.e. it specifies both an instrument and effectors. That means, ALL the tracks that reference the audio graph by INSTRUMENTNAME will share the identical set of effectors as well. For example, sfzz-ui-metal-gtx.filtergraph currently specifies kpp distruction effector, which would be very specific for songs that want to use it. We will end up creating *.filtergraphs for each, only to differentiate effector settings.

That's not ideal. We should be able to indicate the instrument and effects by any combinations to not bloat the filtergraphs.

Idea: use ';' to split instruments into multiple IDs and combine them for each track.

Switch to MIDI 2.0 song format model

Currently in our main branch we use mugene-ng to generate MidiMusic i.e. MIDI 1.0 music data to further feed MidiToTracktionEditConverter. This ends up the input bound to various limitations such as 16 channels and various per-note features (controllers, pitch 7.9, per-note management etc.).

We are going to improve it. In midi2 branch we compile MMLs to Midi2Music. It's just started and not sure when it's verified to work, but it is a first step towards bigger improvements.

kotractive gradle sync fails on IDEA unless we disable native builds.

It has been known when I had been stuck at #5 but while that issue is gone it's still there:

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':commonizeNativeDistribution'.
	at org.gradle.api.internal.tasks.properties.DefaultTaskProperties.resolve(DefaultTaskProperties.java:83)
	at org.gradle.execution.plan.LocalTaskNode.resolveMutations(LocalTaskNode.java:200)
	at org.gradle.execution.plan.DefaultExecutionPlan.getResolvedMutationInfo(DefaultExecutionPlan.java:665)
	at org.gradle.execution.plan.DefaultExecutionPlan.selectNext(DefaultExecutionPlan.java:577)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$executeNextNode$1(DefaultPlanExecutor.java:166)
	at org.gradle.internal.resources.DefaultResourceLockCoordinationService.withStateLock(DefaultResourceLockCoordinationService.java:45)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:155)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:61)
Caused by: java.lang.ClassCastException: class org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension_Decorated cannot be cast to class org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension (org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension_Decorated is in unnamed module of loader 
...
(snip)

To workaround the problem, disable native build again (disable native targets in kotractive/build.gradle.kts).

Import per-note pitchbend etc. as per-note expression (MPE) in .tracktionedit

While Tracktion does not support per-note controller in MIDI 2.0 in general, it supports MPE. It is stored in *.tracktionedit like:

        <NOTE p="39" b="9.999999999999998" l="5.000000000000002" v="100" c="0">
          <PITCHBEND b="0.0" v="-2.500001907348633"/>
          <PITCHBEND b="1.0" v="-2.500001907348633"/>
          <PITCHBEND b="4.0" v="-16.83333206176758"/>
          <PITCHBEND b="3.0" v="-18.33333206176758"/>
          <PITCHBEND b="2.0" v="-20.33333587646484"/>
        </NOTE>

Currently there is no dedicated support for per-note pitchbend (6nh) in mugene-ng default-macro2.mml, but once it is supported we should be able to convert them into <PITCHBEND> elements like above.

(re-)import audio graph settings from tracktionedit file

After compiling project into *.tracktionedit file, what often happens to me is that I check the results on Tracktion Waveform, try some other audio plugin instruments, and set up exactly the same thing on another AudioPluginHost to generate *.filtergraph file as a new input to augene. It is waste of time. If I could directly import back audio plugin settings from *.tracktionedit, then I could just keep manipulating the plugin UIs on Waveform, save the changes, and re-compile.

Having source and destination on the same file is worrisome, but it's not critical if (1) we store *.tacktionedit files on VCS (git etc.), and/or (2) we generate intermediate backup files when we interpret those plugin settings either from *.filtergraph or *.tracktionedit.

support direct SMF imports in the .augene project file?

So far everything in this project assumes that the music source is MML. However it is not mandatory. We do have some tailored sysex and/or meta events that Midi2TracktionEditConverter treats as direct operations to manipulate .tracktionedit, but they can be also done in any SMF generators.

One thing to note though is that we will be switching to MIDI 2.0 UMPs at some stage and the target MML syntaxes (macros) will be of default-macro2.mml. Then SMF importer might become unusable or less useful.

multiple format support for audio graphs and song files

(partly imported from atsushieno/augene#2 and atsushieno/augene#3)

It is possible to support arbitrary audio plugin player backend. Right now it has kotracktive Midi2TracktionEdit and tracktion_engine as the only format, but it is only the converter and sequencer engine player format.

There are some other open song formats such as bitwig/dawproject which might be useful too (Bitwig has no OSS player ability though). dawproject uses some format called vstpresetas a plugin reference.

(Unlike when I created augene, JUCE6 and Waveform11 support VST3 plugins, so there is no gap between those two nowadays.)

native: augene build fails for static linking: undefined reference to ksp-generated classes

When native build is enabled in both kotractive-project (considering that #5 is gone), native build still fails due to:

/sources/ktmidi/augene-ng/kotractive-project/kotractive/build/generated/ksp/nativeMain/kotlin/dev/atsushieno/kotractive/MetaTypeDataTypes.kt:10: error: undefined reference to 'kfun:dev.atsushieno.kotractive.DataTypes#<get-dataType>(){}dev.atsushieno.kotractive.DataType'

Build failure repro on native-issue-2 branch: https://github.com/atsushieno/augene-ng/runs/3775952797?check_suite_focus=true

Kotlin Slack community had no solution to this. https://kotlinlang.slack.com/archives/C013BA8EQSE/p1631002492073100

enable Compose for Desktop package build target

Compose for Desktop supports platform installable package (.msi/.dmg/.deb/.rpm) as package Gradle target, but for now it is impossible. It is due to some complicated situation... package Gradle task works only with JDK 16 or later, so we can run it IF we use JDK 16. However Android build fails if JDK 16 is used. It seems an upstream JetBrains issue.

investigate portable plugin graph

(copied from atsushieno/augene#1)

Right now, when we use this tool to define audio plugin graphs for each track, those plugins are recorded as bare filtergraph generated by AudioPluginHost. When we switch environment across platforms, those plugins become different (they would be AU on Mac, and they would be VST3 on Windows and/or Linux, or LV2 on Linux). Since my secondary purpose of this project is to support AAP, it has to be also portable to Android too.

Since they don't share the same set of plugins, it is song author's responsibility to use the set of common audio plugins. But even if they use only such plugins, those filtergraph files from AudioPluginHost is platform dependent.

Maybe there should be some kind of mapping aliases, or at least per-platform filtergraphs. But the fundamental problem lying around here is, what is an "identical" plugin? Some possible usage scenarios:

  • I use Collective on Linux, Mac, and Windows. They are all VST2, and portable across those desktop. Are they "identical" ? They should be.
  • I use different versions of Collective on those platforms. Are they identical? They probably still should be.
  • I use ADLplug AU on Mac, and ADLplug LV2 on Linux. Are they identical? Argurable.
    • Their behavior would be almost identical. But they have different unique IDs.
    • Also, they wouldn't share the same "state" binary blob.
    • Furthermore, there is no assurance on whether they share the same parameter set or not. At least JUCE on VST3 often generates extraneous parameter set for MIDI CC dispatching (like 2050+ extra).
  • What if I use ADLplug on Linux as LV2 somewhere, and ADLplug via lv2vst i.e. as VST2 elsewhere?

Can we cover some or all of them?


Some relevant web resources (will be updated):


On plugin state portability:

For LV2 plugins, there is LV2_State_Map_Path feature that should be provided by the host of which a plugin can make use to convert absolute and abstract file paths. https://lv2plug.in/doc/html/group__state.html#structLV2__State__Map__Path

It is used by sfizz for example, so that SFZ files are stored in abstract form, not immediate path.

native: kotractive PropertyInfo() is always passed null propertyMetaType

If we enable nativeMain etc. in kotractive/build.gradle and run ./gradlew build, it results in weird failure at nativeTest, particularly at EditModelReaderTest.readTemplate(). The test failure log does not really tell anything (K/N does not give us a stacktrace), and due to #10 we cannot really debug the test on IDEA. I got lost.

I actually went beyond and inserted some debug println()s around, and figured out that PropertyInfo constructor does not receive valid propertyMetaType parameter. They were often passed from typeBoolean, typeInt, typeString etc. (defined in EditModelMetadata.kt). I suspect that those vals are not appropriately initialized on (and only on) Kotlin/Native. The constructor parameter is non-null, and those vals are NOT null, but what is actually passed to PropertyInfo() IS null.

It is most likely a bug in Kotlin/Native runtime, but we may be able to workaround the issue by changing the code structures (place those referent vals elsewhere e.g. inside some object).

loading sampler plugins can take too long time to fully reload

(based on atsushieno/augene#8)

On augene composition workflow, it has to load the entire tracktionedit file every time, and loading a song with sfz using sfizz can take quite long time. Even a single track with UI Standard Guitar along with a few effectors takes 3-4 seconds to load at augene-player. This cannot be waitable for even 10 tracks.

We would need certain shortcuts to avoid full reloading, like hot reloading audio plugin settings parts and compiling MIDI parts respectively. To achieve that however, the entire compilation tool should migrate from JVM augene-editor to C++ augene-player (which is not going to be just a player anymore then).

In the older .NET version it was simply an annoying idea, but now that augene-project is Kotlin MPP, it is possible that we would be able to build it for native. Current blocker is though google/ksp (used by kotracktive-project) that breaks at Kotlin Native builds (issue #5), so it is somewhat future task to achieve.

support automation tracks

(inherited from atsushieno/augene#9)

This time I go forward and drafting specification on how we achieve support for automation tracks.

In *.tracktionedit files, an automation track is represented as an <AUTOMATIONCURVE> element in the target <PLUGIN> element within the <TRACK> element, and the <TRACK> element at the same time contains a corresponding <AUTOMATIONTRACK> element that specifies currentAutoParamPluginID attribute that indicates <PLUGIN>'s id and currentAutoParamTag attribute that indicates the <AUTOMATIONTRACK>'s paramID (which is also the actual parameter ID of the plugin).

The actual AUTOMATIONCURVE looks like:

      <AUTOMATIONCURVE paramID="0">
        <POINT t="1.25" v="0.304347813129425" c="0.0"/>
        <POINT t="1.5" v="0.717391312122345" c="0.0"/>
        <POINT t="5.5" v="0.239130437374115" c="0.0"/>
        <POINT t="6.25" v="0.739130437374115" c="0.0"/>
        <POINT t="8.75" v="0.260869562625885" c="0.0"/>
      </AUTOMATIONCURVE>

... and AUTONATIONTRACK looks like:

    <AUTOMATIONTRACK id="1004" colour="ffff0000" currentAutoParamPluginID="1136" currentAutoParamTag="0">
      <MACROPARAMETERS id="1010"/>
      <MODIFIERS/>
    </AUTOMATIONTRACK>

Since we human beings don't recognize plugin parameter IDs, we'll need some comprehensive mapping from each parameter name to the parameter ID. It is not available on AudioPluginHost, so we will have to create some mapping generator tool that can then be consumed by AugeneProject and AugeneModel in augene-project.

The most probable candidate MIDI messages that are mapped to automation parameters are NRPNs. But since any MIDI messages can be also consumed by the target MIDI plugin, occupying NRPNs is not a very safe idea. It should be at least an optional feature, and desirably there should be other supported mappings e.g. SysEx messages with certain manufacturer IDs.

native builds

The native-issue branch exposes the issue with native build support:

https://github.com/atsushieno/augene-ng/actions/runs/1198125545

e: Could not find "/home/runner/work/augene-ng/augene-ng/kotractive-project/kotractive/build/generated/ksp/nativeMain/classes" in [/home/runner/work/augene-ng/augene-ng/kotractive-project, /home/runner/.konan/klib, /home/runner/.konan/kotlin-native-prebuilt-linux-x86_64-1.5.30/klib/common, /home/runner/.konan/kotlin-native-prebuilt-linux-x86_64-1.5.30/klib/platform/linux_x64]

it's known at Kotlin Slack https://kotlinlang.slack.com/archives/C013BA8EQSE/p1630246449011600

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.