/clef

Music randomizer and synthezier on Waves blockchain.

Primary LanguageJavaScriptMIT LicenseMIT

clef

Content

  • Smart contracts documentation
  • Ride smart contracts
    • /contracts
    • /scripts
    • /test_ride
  • JS utilities
    • /src_js
    • /test_js
  • Samples: /static/samples

SDK

SDK source code located in ./src_js/sdk.js. You can use this SDK for integration with Clef.

Dependencies

API

const {
  COLORS,
  get_song_name_by_asset_id,
  get_song_colors_by_asset_id,
  set_volume,
  play_song_by_asset_id,
  stop
} = require('sdk.js');
  • COLORS - array of color values which can be returned by get_song_colors_by_asset_id.
    • COLORS.major, COLORS.minor, COLORS.neutral, COLORS.weird
  • get_song_name_by_asset_id(asset_id, options) async - returns string, song name.
    • asset_id argument - string, NFT asset ID.
    • options optional argument - network settings.
    const asset_id = 'CSB5QjKAYeY5BCK4EyLC66fKn5QWX73HAXhn4pNb9HD';
    const name = await get_song_name_by_asset_id(asset_id);
    console.log(`Song ${asset_id} name: ${name}`);
  • get_song_colors_by_asset_id(asset_id, options) async - returns array of song colors.
    • asset_id argument - string, NFT asset ID.
    • options optional argument - network settings.
  • set_volume(volume) async - set audio playback volume.
    • volume argument - number, volume value from 0 to 1.
    await set_volume(0.7);
  • play_song_by_asset_id(Tone, asset_id, ready, options) async - play song.
    • Tone argument - Tone.js interface.
    • asset_id argument - string, NFT asset ID.
    • ready argument - async function, called when song is ready to play.
    • options optional argument - network settings.
    const Tone = require('tone');
    const asset_id = 'CSB5QjKAYeY5BCK4EyLC66fKn5QWX73HAXhn4pNb9HD';
    console.log('Preparing song audio...');
    await play_song_by_asset_id(Tone, asset_id, async () => {
      console.log('Playing...');
    });
    console.log('Playback stopped.');
  • stop() async - stop audio playback.
    await stop();

Default network settings:

const options = {
  fetch:          window.fetch,                         // fetch function
  clef_url:       'https://clef.one/',                  // for audio samples
  clef_cache_url: 'https://cache.clef.one/',            // for cached data
  node_url:       'https://nodes.wavesnodes.com/',      // waves blockchain node
  library:        '3P4m4beJ6p1pMPHqCQMAXEdquUuXJz72CMe' // clef library contract address
};

Dev API

Back-end interface

Back-end implementation for testing located in ./src_js/back_fake.js

Implementation for blockchain interaction located in ./src_js/back_node.js

const { env,
        types,
        authenticate,
        get_resource_by_id,
        get_resource_by_asset_id } = require('back_fake.js');
  • env - list of environment names.

    • env.keeper - use a Keeper Wallet account.
    • env.cloud - use an Email-based Waves.Exchange account.
    • env.web - use a private key- or seed phrase-based Waves.Exchange account.
  • types - list of resource type names.

    • Available for purchase
      • types.chord
      • types.rhythm
      • types.beat
    • Other
      • types.song
      • types.hybrid
      • types.arpeggio
  • get_resource_by_id(id) async - get a resource by id. Returns resource object, or null, if not found.

    const song_id = '1111111A';
    
    const song = await get_resource_by_id(song_id);
    
    if (song != null) {
      console.log(JSON.stringify(song, null, '  '));
    }
  • get_resource_by_asset_id(id) async - get a resource by asset id. Returns resource object, or null, if not found.

    const asset_id = '3foobarfoobar';
    
    const song = await get_resource_by_asset_id(asset_id);
    
    if (song != null) {
      console.log(JSON.stringify(song, null, '  '));
    }
  • authenticate(options) async - Authenticate a user. Returns clearance object with following methods.

    let user = await authenticate({ env: env.keeper });
    • Testing-only methods

      • empty_stock() async - remove all stock resources.
      • add_balance(balance) async - increase user balance.
    • Admin-only methods

      • mint_resources(resources) async - add resources to stock.
        • resources argument should be an array of resources.
        • Each resource should have a quantity field.
        await user.mint_resources([ {
          quantity: 8,
          type: types.chord,
          label: 'Am',
          notes: [ 0, 0, 0, 3, 7 ] } ]);
    • logout() async - sign out.

    • get_balance() async - returns a number, user balance.

    • get_free_mix_balance() async - returns an integer number, user free mix token balance.

    • get_resources(options) async - returns an array of user resources.

      • Each resource contains at least those fields:
        • id - string, unique resource id.
        • type - string, resource type name. One of types.
        • label - string, resource label.
      • options argument may contain following fields.
        • filter - resource type string or an array of resource type strings.
        • size - page size, maximum number of returned resources.
        • after - id of the resource to paginate after.
      /*  Get all user resources. */
      let resources = await user.get_resources();
      
      /*  Get songs only. */
      let songs = await user.get_resources({ filter: types.song });
      
      /*  Get resources by pages. */
      let page0 = await user.get_resources({ filter: types.song, size: 10 });
      let page1 = await user.get_resources({ filter: types.song, size: 10, after: page0[page0.length - 1].id });
    • buy(type) async - buy a resource. type is one of types.

      await user.buy(types.chord);
    • mint_song(resources) async - mint a new song from resources.

      • resources argument is an array of resources. Each resource should have a field id.
      let chords = await user.get_resources({ filter: types.chord });
      await user.mint_song([ chords[0], chords[1] ]);
    • mint_hybrid(songs) async - mint a new song from two other songs.

      • songs argument is an array of 2 songs. Each song should have a field id.
      let songs = await user.get_resources({ filter: types.song });
      await user.mint_hybrid([ songs[0], songs[1] ]);
    • mint_hybrid_with_free_mix_token(songs) async - mint a new song from two other songs. Pay with free mix token.

      • songs argument is an array of 2 songs. Each song should have a field id.
      let songs = await user.get_resources({ filter: types.song });
      await user.mint_hybrid_with_free_mix_token([ songs[0], songs[1] ]);
    • mint_hybrid_and_purge(songs) async - mint a new song from two other songs and purge those songs.

      • songs argument is an array of 2 songs. Each song should have a field id.
      let songs = await user.get_resources({ filter: types.song });
      await user.mint_hybrid_and_purge([ songs[0], songs[1] ]);
    • get_wallet_address() async - get user wallet address. Returns a string. To render an avatar, you can use identity-img like this:

      const address     = await user.get_wallet_address();
      const avatar_size = 64;
      
      const identity_img = require('identity-img');
      
      const avatar_url = identity_img.create(
        address, {
          rows: 8, cells: 8,
          size: avatar_size });
    • get_explorer_url() async - get explorer URL for user wallet. Returns a URL string.

    • can_mint_hybrid() async - returns true if the user has enough balance to mint a hybrid; or false otherwise. Will check for both transaction fee and payment.

    • get_airdrop_info(name) async - returns an object with fields airdrop_exists, user_in_whitelist, allowed_claims and songs_total.

      • name argument is a string, airdrop name.
      • airdrop_exists is true if specified airdrop exists; false otherwise.
      • user_in_whitelist is true if user whitelisted for specified airdrop; false otherwise.
      • allowed_claims is a number of songs user can claim from specified airdrop.
      • songs_total is a total number of songs left in specified airdrop.
      let airdrop = 'test';
      let info = await user.get_airdrop_info(airdrop);
      console.log(`Allowed claims: ${info.allowed_claims}; songs total: ${info.songs_total}`);
    • airdrop_claim(name) async - claim songs from an airdrop. Returns an array with claimed songs ids.

      • name argument is a string, airdrop name.
      let airdrop = 'test';
      let ids = await user.airdrop_claim(airdrop);

Example

const { env, types, authenticate } = require('back_fake.js');

authenticate({ env: env.keeper }).then(user => {
  /* Print user balance. */
  user.get_balance().then(balance => {
    console.log(`Balance: ${balance}`);
  });
  
  /* Buy a chord, then print all bought chords. */
  user.buy(types.chord).then(() => {
    user.get_resources({ filter: types.chord }).then(chords => {
      console.log('Bought chords:');
      console.log(JSON.stringify(chords, null, '  '));
    });
  });
});

NOTE: Don't access resource internal elements directly, they may change in future. Use functions from music.js utility instead.

For use cases see ./test_js/back_fake.test.js

Music

Music utility is located in ./src_js/music.js.

const { diatonic_minor,
        pitch_to_midi,
        beat_to_sec,
        render_sheet
        get_song_label,
        get_song_parents,
        get_song_bpm,
        get_song_meter,
        get_song_tonality,
        colors,
        get_song_colors,
        get_song_chords,
        get_chord_name,
        get_song_chord_names,
        get_song_generation,
        get_song_asset_id,
        get_song_asset_url,
        can_mint_hybrid } = require('music.js');
  • diatonic_minor(key_note) - returns an array of integer numbers representing diatonic minor scale.

  • pitch_to_midi(tonality, pitch) - returns an integer representing a MIDI note from tonality and pitch.

    • tonality argument is an array of MIDI notes to define a tonality.
    • pitch argument is a number of steps from key note in specified tonality.
    let tonality = diatonic_minor(0);
    let midi_key_note = pitch_to_midi(tonality, 0);
    let midi_note     = pitch_to_midi(tonality, 15);
  • beat_to_sec(bpm, beats) - converts beats to seconds according to specified bpm.

    /* duration = 0.5 */
    let duration = beat_to_sec(120, 4);
  • render_sheet(song) - convert song to music sheet.

    • song argument should be a song resource returned from back-end.

    Music sheet is an object with following fields.

    • duration - total duration in seconds.
    • instruments - object with names for each instrument.
    • kick, snare, hihat, bass, back, lead - arrays of notes for each instrument.

    Each array's element is an object with following fields.

    • time - note attack time in seconds.
    • note - MIDI note pitch.
    • duration - note duration.
    • velocity - note velocity. If there is a pause before the first note, the array will have a first element with field duration value equal to zero.
    let songs = await user.get_resources({ filter: types.song });
    let sheet = render_sheet(songs[0]);
  • get_song_id(song) - returns song id: string.

  • get_song_label(song) - returns song label.

  • get_song_parents(song) - returns array of song parents' ids.

  • get_song_bpm(song) - returns song BPM.

  • get_song_meter(song) - returns song meter: array of two integers.

  • get_song_tonality(song) - returns song tonality name: string.

  • get_song_colors(song) - returns song colors: array of integer values. Each value is one of:

    • colors.major,
    • colors.minor,
    • colors.neutral,
    • colors.weird,
  • get_song_chords(song) - returns song chords: array of chords. Each chord is an array of notes. Each note is an integer number of semitone steps from C.

  • get_chord_name(chord) - returns chord name. chord is an array of notes.

  • get_song_chord_names(song) - returns song chord names: array of strings.

  • get_song_generation(song) - returns song generation: integer number.

  • get_song_asset_id(song) - returns song NFT asset id: string.

  • get_song_asset_url(song) - returns song NFT asset url: string.

  • can_mint_hybrid(song) - returns true if a hybrid can be minted from specified song; false otherwise.

NOTE: Resource id and resource asset id are different. Asset id is a hash-identifier of NFT in blockchain.

Example

const { get_resource_by_id } = require('back_fake.js');

const { render_sheet,
        get_song_label,
        get_song_parents,
        get_song_bpm,
        get_song_meter,
        get_song_tonality,
        colors,
        get_song_colors,
        get_song_chords,
        get_song_chord_names,
        get_song_generation,
        get_song_asset_id,
        get_song_asset_url  } = require('music.js');

const song_id = '1111111A';

get_resource_by_id(song_id).then(song => {
  const sheet = render_sheet(song);

  const asset_id  = get_song_asset_id(song);
  const asset_url = get_song_asset_url(song);

  const label       = get_song_label(song);
  const parents     = get_song_parents(song);
  const bpm         = get_song_bpm(song);
  const meter       = get_song_meter(song);
  const tonality    = get_song_tonality(song);
  const colors      = get_song_colors(song);
  const chords      = get_song_chords(song);
  const generation  = get_song_generation(song);

  const chord_names = get_song_chord_names(song);
});

For use cases see ./test_js/music.test.js

Audio

Audio utility is located in ./src_js/audio.js.

const { render_audio,
        render_song,
        play_song,
        stop,
        set_volume } = require('audio.js');

const Tone = require('tone');
  • render_audio(Tone, sheet, log) async - render sheet to a buffer.
    • Tone argument is a Tone.js library interface.
    • song argument should be a sheet data returned by render_sheet(song).
    • log argument is logging interface.
    let buffer = await render_audio(Tone, sheet, (s) => { console.log(s); });
  • render_song(Tone, song, log) async - render song to a buffer.
    • Tone argument is a Tone.js library interface.
    • song argument should be a song resource returned from back-end.
    • log argument is logging interface.
    let buffer = await render_song(Tone, song, (s) => { console.log(s); });
  • play_song(Tone, song, ready, log) async - render song and play. Promise returns when playback is done.
    • Tone argument is a Tone.js library interface.
    • song argument should be a song resource returned from back-end.
    • ready argument is a function called when audio buffer is ready.
    • log argument is logging interface.
    console.log('Play');
    play_song(Tone, song,
        ()      => { console.log('Ready') },
        (s)     => { console.log(s); })
      .then(()  => { console.log('Done'); });
  • stop(Tone) async - stop playing.
    • Tone argument is a Tone.js library interface.
    stop(Tone);
  • set_volume(value) async - set playback volume.
    • value - volume value from 0 to 1, where 0 is silence, 1 is maximum volume.
    set_volume(0.5);

Song genome reference

NOTE: This reference may change.

Chord is defined as a list of notes. Each note is a number of semitone steps from tonality root. First note is bass, second note is lead.

chord {
  label:  string;
  notes:  integer[];
}

Rhythm is defined as a list of note durations. Every other duration defines a pause, so total number of durations is a multiple of 2.

NOTE: In blockchain, rhythm notes are stored as an integer value for scale and a list of integer values for notes. Each note duartion is defined as note / scale.

rhythm {
  label:  string;
  notes:  float[];
}
song {
  label:      string;
  parents:    token_id[];

  bpm:        integer;
  bar_size:   integer;
  beat_size:  integer;
  tonality:   integer;

  instruments {
    kick:     string;
    snare:    string;
    hihat:    string;
    bass:     string;
    back:     string;
    lead:     string;
  }

  chords:     chord[];
  arpeggio:   integer[];

  rhythm {
    kick:     rhythm[];
    snare:    rhythm[];
    hihat:    rhythm[];
    bass:     rhythm[];
    back:     rhythm[];
    lead:     rhythm[];
  }
}

Authors