From fb6d62f1d2e6f1d3eccf251f7d2e7a5c3f55c6bd Mon Sep 17 00:00:00 2001 From: acheney Date: Wed, 22 Nov 2023 20:05:57 -0600 Subject: [PATCH] add files --- LICENSE | 2 +- README.md | 15 +- scl_to_curve3.pixi | 526 ++++++++++++++++++++++++++++++++++++++++++ scl_to_set_pitch.pixi | 414 +++++++++++++++++++++++++++++++++ 4 files changed, 955 insertions(+), 2 deletions(-) create mode 100644 scl_to_curve3.pixi create mode 100644 scl_to_set_pitch.pixi diff --git a/LICENSE b/LICENSE index 2071b23..4c481fb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2023 A Cheney Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index ec63392..df1e678 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ -# scl_to_sunvox_converters +# abstract + +here are two files for converting `.scl` files to sunvox's own pitch format + +they are: + +- `scl_to_curve3.pixi`: converts `.scl` files to a `.curve16bit` file for the multisynth's curve3. you can also specify a scale to map to 12 notes; details are in the file +- `scl_to_set_pitch.pixi`: converts `.scl` files to a sunvox project with all of the notes in the tuning laid out as SP (set pitch) commands + +to use these, download pixilang and run the `.pixi` file of your choosing. all of the information in the log will be displayed on the screen + +# credits + +the program was written by me, except for the `show_log()` function, which was originally written by nightradio and modified by me diff --git a/scl_to_curve3.pixi b/scl_to_curve3.pixi new file mode 100644 index 0000000..08e903a --- /dev/null +++ b/scl_to_curve3.pixi @@ -0,0 +1,526 @@ +// .scl to sunvox curve3 converter +// by acheney +// under mit license + +// NOTE: BE SURE THERE ARE NO DUPLICATE LINE ENDINGS IN YOUR .SCL FILE, OR ELSE THE SCRIPT WILL FAIL! +// .scl files generated using sevish's scale workshop should work fine, as well as .scl files saved locally on your system +// but if you download an .scl file from anywhere else, be sure to convert the line endings to those of your os +// on linux/osx you can use dos2unix, and on windows unix2dos, or for both, any text editor that lets you convert line endings + +// - BEGIN CONFIG - + +// the frequency of the tuning center in hertz +tuning_center_freq = 440 + +// the midi note to use as the tuning center. 57 = A4 +tuning_center_midi = 57 + +// enables mapping the tuning to 12 notes +// the notes will be mapped according to brensenham's line algorithm +map_to_12_notes = 0 + +// if map_to_12_notes is enabled, change the below declaration to your desired mapping +// the indices of the tuning start at 0 +// the below example is a ten-note tuning mapped to 12 notes + +map_scale = new( 10, 1, INT16 ) + +map_scale[ 0 ] = 0 +map_scale[ 1 ] = 1 +map_scale[ 2 ] = 2 +map_scale[ 3 ] = 3 +map_scale[ 4 ] = 4 +map_scale[ 5 ] = 5 +map_scale[ 6 ] = 6 +map_scale[ 7 ] = 7 +map_scale[ 8 ] = 8 +map_scale[ 9 ] = 9 + +// - END CONFIG - + +scl_filename = file_dialog( "load .scl file", "scl", "file.scl" ) + +scl_name = "" +scl_size = 0 +scl_list = new( 1024, 1, INT8 ) +scl_pitches = new( 128, 1, INT16 ) + +clean( scl_list, 0 ) +clean( scl_pitches, 0 ) + +scl_read( scl_filename ) + +logf( "name: %s\n", scl_name ) +logf( "size: %d\n", scl_size ) + +resize( scl_list, scl_size ) + +if (scl_size > 0 ) { + + scl_write( ) + + if map_to_12_notes + { + scl_pitches = scl_map( map_scale, scl_pitches, scl_size ) + + if scl_pitches != -1 + { + + scl_save( scl_name, scl_pitches ) + + } + + } else { + + scl_save( scl_name, scl_pitches ) + + } + +} + +logf( "\n" ) + +show_log() + +fn scl_read( $scl_filename ) { + + $temp_string = new( 1024, 1, INT8 ) + $length = 0 + $metadata_counter = 0 + $list_counter = 0 + $is_comment = 0 + + logf( "file to load: %s\n", $scl_filename ) + $scl_file = fopen( $scl_filename, "r" ) + + if $scl_file == 0 { ret( -1 ) } + + while 1 { + + $length = fgets( $temp_string, 2048, $scl_file ) + + if ( $length == -1 ) { + + break + + } + + if ( $length >= 0 ) { + + // logf( "read string: %s\n", $temp_string ) + + if ( $temp_string[ 0 ] == '!' ) { + + $is_comment = 1 + + } else { + + $is_comment = 0 + + } + + if ( $is_comment == 0 ) { + + if ( $metadata_counter >= 2 ) { + + scl_list[ $list_counter ] = strip_character( $temp_string, ' ' ) + $list_counter = $list_counter + 1 + + } + + if ( $metadata_counter == 1 ) { + + $metadata_counter = 2 + + if ($length == 0) { + + logf("ERROR: no size specified\n") + + breakall + + } else { + + // logf( "number conversion: %d\n", str_to_num( strip_character( $temp_string, ' ' ) ) ) + scl_size = str_to_num( strip_character( $temp_string, ' ' ) ) + + } + + + + } + + if ( $metadata_counter == 0 ) { + + $metadata_counter = 1 + + if ($length == 0) { + + logf("WARNING: no name found. defaulting to filename...\n") + + strcat( scl_name, basename( $scl_filename, 0 ) ) + + } else { + + strcat( scl_name, $temp_string ) + + } + + + + } + + } else { + + // logf( "comment found, skipping...\n" ) + + } + + + + } + + } + + remove( $temp_string ) + fclose( $scl_file ) +} + +fn strip_character( $input, $c ) { + + $non_char_count = 0 + $dest = clone( $input ) + + $i = 0 while $i < get_xsize( $input ) + 1 { + + if ( $input[ $i ] != $c ) { + + $dest[ $non_char_count ] = $input[ $i ] + $non_char_count = $non_char_count + 1 + + } + + $i = $i + 1 + + } + + ret( $dest ) + +} + +fn scl_write( ) { + + scl_multipliers = new( scl_size, 1, FLOAT ) + current_midi = 0 + multiplier_power = 1 + + i = 0 while i < get_xsize( scl_list ) { + + scl_multiplier = 0 + current_midi = 0 + + if ( strstr( scl_list[ i ], "." ) != -1 ) { + + scl_multiplier = pow( 2, ( ( str_to_num( scl_list[ i ] ) / 100 ) / 12 ) ) + + } else { + + numerator = new( 256, 1, INT8 ) + clean( numerator, 0 ) + + denominator = new( 256, 1, INT8 ) + clean ( denominator, 0 ) + + slash_offset = 0 + no_denominator = 0 + + j = 0 while 1 { + + if ( scl_list[ i ][ j ] == '/' || scl_list[ i ][ j ] == '\\' ) { + + break + + } + + if ( scl_list[ i ][ j ] == ' ' || scl_list[ i ][ j ] == 0 ) { + + no_denominator = 1 + break + + } + + numerator[ j ] = scl_list[ i ][ j ] + + j = j + 1 + + slash_offset = slash_offset + 1 + + } + + if ( no_denominator == 0 ) { + + j = 0 while 1 { + + if ( scl_list[ i ][ j + slash_offset + 1 ] == 0 || scl_list[ i ][ j + slash_offset + 1 ] == ' ' ) { + + break; + + } + + denominator[ j ] = scl_list[ i ][ j + slash_offset + 1 ] + + j = j + 1 + } + + } else { + denominator[ 0 ] = '1' + } + + + scl_multiplier = str_to_num( numerator ) / str_to_num( denominator ) + } + + scl_multipliers[ i ] = scl_multiplier + + // logf( "multiplier written to %d: %f\n", i, scl_multipliers[ i ] ) + + i = i + 1 + + } + + i = 0 while i < get_xsize( scl_multipliers ) { + + i = i + 1 + } + + i = 0 while i < 128 { + + multiplier_power = floor( ( i - tuning_center_midi ) / scl_size ) + // logf( "multiplier power: %d\n", multiplier_power ) + + center_offset = pow( scl_multipliers[ scl_size - 1 ], multiplier_power ) + // logf( "center offset: %d\n", center_offset ) + + if ( i < tuning_center_midi ) { + scl_pitches[ i ] = freq_to_pitch( tuning_center_freq * scl_multipliers[ ( ( ( i - tuning_center_midi ) % scl_size ) - 1 ) + scl_size ] * center_offset ) + } + if ( i > tuning_center_midi ) { + scl_pitches[ i ] = freq_to_pitch( tuning_center_freq * scl_multipliers[ ( i - tuning_center_midi ) % ( scl_size ) - 1 ] * center_offset ) + } + if ( ( tuning_center_midi - i ) % scl_size == 0 ) { + scl_pitches[ i ] = freq_to_pitch( tuning_center_freq * center_offset ) + } + + // logf( "pitch written to %d: %d\n", i, scl_pitches[ i ] ) + + i = i + 1 + + } + +} + +fn freq_to_pitch( $freq ) { + + ret( log2( $freq / 16.333984375 ) * 3072 + 16384 ) + +} + +fn scl_save( $name, $pitches ) +{ + + $name = sanitize( $name ) + // logf( "sanitized filename: %s\n", $name ) + + strcat( $name, ".curve16bit" ) + + if save( $pitches, $name, FORMAT_RAW ) == 0 { logf( "file saved!\n" ) } + +} + +fn scl_map( $scale, $tuning, $tuning_length ) +{ + + $tuning_dest = clone( $tuning ) + + $scale_length = get_size( $scale ) + + if $scale_length > $tuning_length + { + + logf( "ERROR: scale length is greater than tuning size\n" ) + + ret( -1 ) + + } + + if $scale_length > 12 + { + + logf( "ERROR: scale length is greater than 12\n" ) + + ret( -1 ) + + } + + if $scale[ $scale_length - 1 ] > ( $tuning_length - 1 ) + { + + logf( "ERROR: scale degrees are greater than tuning size\n" ) + + ret( -1 ) + + } + + $i = 0 while $i < 128 { + + // logf( "i: %d\n", $i ) + + $mod_keyboard_index = ( ( $i + 12 ) - ( ( tuning_center_midi ) % 12 ) ) % 12 + // logf( "mod keyboard index: %f\n", $mod_keyboard_index ) + + $mod_period = floor( ( $i - ( tuning_center_midi ) ) / 12 ) + // logf( "mod period: %f\n", $mod_period ) + + $mod_tuning_index = brensenham( 0, 0, 11, $scale_length - 1 , $mod_keyboard_index ) + // logf( "mod tuning index: %f\n", $mod_tuning_index ) + + $mod_scale_index = $scale[ $mod_tuning_index ] + // logf( "mod scale index: %f\n", $mod_scale_index ) + + $mod_tuning_lookup = $mod_scale_index + ( ( $tuning_length ) * $mod_period ) + tuning_center_midi + // logf( "mod tuning lookup: %f\n", $mod_tuning_lookup ) + + $tuning_dest[ $i ] = $tuning[ $mod_tuning_lookup ] + + $i = $i + 1 + + } + + ret( $tuning_dest ) + +} + +fn sanitize( $input ) +{ + + $output = clone( $input ) + + i = 0 while i < get_xsize( $input ) { + + + if $input[ i ] == '/' { $output[ i ] = '_' } + if $input[ i ] == '\\' { $output[ i ] = '_' } + if $input[ i ] == '\'' { $output[ i ] = '_' } + if $input[ i ] == '\"' { $output[ i ] = '_' } + if $input[ i ] == '\ ' { $output[ i ] = '_' } + if $input[ i ] == ':' { $output[ i ] = '_' } + if $input[ i ] == ',' { $output[ i ] = '_' } + if $input[ i ] == '.' { $output[ i ] = '_' } + + i = i + 1 + + } + + ret( $output ) + +} + +fn strrchr( $s, $c ) +{ + + $i = 0 + $k = 0 + + for( $i = strlen( $s ); $i >= 0; $i - 1 ) + { + + if $s[ $i ] == $c { + + $k = 1 + break + + } + + } + + if $k { ret( $i ) } else { ret( -1 ) } + +} + +fn basename( $p, $x ) +{ + + $n = "" + strcat( $n, 0, $p, strrchr( $p, '/' ) + 1 ) + + if $x + { + + ret( $n ) + + } else { + + $nnxl = strrchr( $n, '.' ) + $nnx = new( $nnxl, 1, INT8 ) + + $i = 0 while $i < get_size( $nnx ) + { + + $nnx[ $i ] = $n[ $i ] + + $i = $i + 1 + + } + + ret( $nnx ) + + } + + +} + +fn brensenham( $x0, $y0, $x1, $y1, $xi ) +{ + + $dx = $x1 - $x0 + $dy = $y1 - $y0 + + $d = 2 * $dy - $dx + + $y = $y0 + + $x = $x0 while $x < $xi { + + if $d > 0 + { + + $y = $y + 1 + $d = $d - 2 * $dx + + } + + $d = $d + 2 * $dy + + $x = $x + 1 + + } + + ret( $y ) + +} + +fn show_log() +{ + + $l = get_system_log() + clear() + print( $l, 0, 0, WHITE, CENTER | CENTER ) + frame() + remove( $l ) + + while 1 + { + while( get_event() ) { if EVT[ EVT_TYPE ] == EVT_QUIT { halt } } + frame() + } + +} diff --git a/scl_to_set_pitch.pixi b/scl_to_set_pitch.pixi new file mode 100644 index 0000000..261a43b --- /dev/null +++ b/scl_to_set_pitch.pixi @@ -0,0 +1,414 @@ +// .scl to sunvox set pitch (sp) converter +// by acheney +// under mit license + +// NOTE: BE SURE THERE ARE NO DUPLICATE LINE ENDINGS IN YOUR .SCL FILE, OR ELSE THE SCRIPT WILL FAIL! +// .scl files generated using sevish's scale workshop should work fine, as well as .scl files saved locally on your system +// but if you download an .scl file from anywhere else, be sure to convert the line endings to those of your os +// on linux/osx you can use dos2unix, and on windows unix2dos, or for both, any text editor that lets you convert line endings + +// - BEGIN CONFIG - + +// the frequency of the tuning center in hertz +tuning_center_freq = 440 + +// the midi note to use as the tuning center. 57 = A4 +tuning_center_midi = 57 + +// - END CONFIG - + +sv = sv_new() + +scl_filename = file_dialog( "load .scl file", "scl", "file.scl" ) + +scl_name = "" +scl_size = 0 +scl_list = new( 1024, 1, INT8 ) +scl_pitches = new( 120, 1, INT16 ) + +clean( scl_list, 0 ) +clean( scl_pitches, 0 ) + +scl_read( scl_filename ) + +logf( "name: %s\n", scl_name ) +logf( "size: %d\n", scl_size ) + +resize( scl_list, scl_size ) + +if (scl_size > 0 ) { + + scl_write( ) + + sv_set_name( sv, scl_name ) + + sv_lock( sv ) + + sv_new_module( sv, "Analog generator", "Preview", 256, 512, 0 ) + sv_connect_module( sv, 1, 0 ) + + pat = sv_new_pattern( sv, -1, 0, 0, 1, 120, 0, scl_name ) + + sv_unlock( sv ) + + i = 0 while i < 120 + { + sv_lock( sv ) + sv_set_pattern_event( sv, pat, 0, i, NOTECMD_SET_PITCH, 00, 02, 0000, scl_pitches[ i ] ) + sv_unlock( sv ) + + i = i + 1 + } + + scl_name = sanitize( scl_name ) + // logf( "sanitized filename: %s\n", scl_name ) + + strcat( scl_name, ".sunvox" ) + + if sv_save( sv, scl_name ) == 0 { logf( "file saved!\n" ) } + +} + +sv_remove( sv ) + +logf( "\n" ) + +show_log() + +fn scl_read( $scl_filename ) { + + $temp_string = new( 1024, 1, INT8 ) + $length = 0 + $metadata_counter = 0 + $list_counter = 0 + $is_comment = 0 + + logf( "file to load: %s\n", $scl_filename ) + $scl_file = fopen( $scl_filename, "r" ) + + if $scl_file == 0 { ret( -1 ) } + + while 1 { + + $length = fgets( $temp_string, 2048, $scl_file ) + + if ( $length == -1 ) { + + break + + } + + if ( $length >= 0 ) { + + // logf( "read string: %s\n", $temp_string ) + + if ( $temp_string[ 0 ] == '!' ) { + + $is_comment = 1 + + } else { + + $is_comment = 0 + + } + + if ( $is_comment == 0 ) { + + if ( $metadata_counter >= 2 ) { + + scl_list[ $list_counter ] = strip_character( $temp_string, ' ' ) + $list_counter = $list_counter + 1 + + } + + if ( $metadata_counter == 1 ) { + + $metadata_counter = 2 + + if ($length == 0) { + + logf("ERROR: no size specified\n") + + breakall + + } else { + + // logf( "number conversion: %d\n", str_to_num( strip_character( $temp_string, ' ' ) ) ) + scl_size = str_to_num( strip_character( $temp_string, ' ' ) ) + + } + + + + } + + if ( $metadata_counter == 0 ) { + + $metadata_counter = 1 + + if ($length == 0) { + + logf("WARNING: no name found. defaulting to filename...\n") + + strcat( scl_name, basename( $scl_filename, 0 ) ) + + } else { + + strcat( scl_name, $temp_string ) + + } + + + + } + + } else { + + // logf( "comment found, skipping...\n" ) + + } + + + + } + + } + + remove( $temp_string ) + fclose( $scl_file ) +} + +fn strip_character( $input, $c ) { + + $non_char_count = 0 + $dest = clone( $input ) + + $i = 0 while $i < get_xsize( $input ) + 1 { + + if ( $input[ $i ] != $c ) { + + $dest[ $non_char_count ] = $input[ $i ] + $non_char_count = $non_char_count + 1 + + } + + $i = $i + 1 + + } + + ret( $dest ) + +} + +fn scl_write( ) { + + scl_multipliers = new( scl_size, 1, FLOAT ) + current_midi = 0 + multiplier_power = 1 + + i = 0 while i < get_xsize( scl_list ) { + + scl_multiplier = 0 + current_midi = 0 + + if ( strstr( scl_list[ i ], "." ) != -1 ) { + + scl_multiplier = pow( 2, ( ( str_to_num( scl_list[ i ] ) / 100 ) / 12 ) ) + + } else { + + numerator = new( 256, 1, INT8 ) + clean( numerator, 0 ) + + denominator = new( 256, 1, INT8 ) + clean ( denominator, 0 ) + + slash_offset = 0 + no_denominator = 0 + + j = 0 while 1 { + + if ( scl_list[ i ][ j ] == '/' || scl_list[ i ][ j ] == '\\' ) { + + break + + } + + if ( scl_list[ i ][ j ] == ' ' || scl_list[ i ][ j ] == 0 ) { + + no_denominator = 1 + break + + } + + numerator[ j ] = scl_list[ i ][ j ] + + j = j + 1 + + slash_offset = slash_offset + 1 + + } + + if ( no_denominator == 0 ) { + + j = 0 while 1 { + + if ( scl_list[ i ][ j + slash_offset + 1 ] == 0 || scl_list[ i ][ j + slash_offset + 1 ] == ' ' ) { + + break; + + } + + denominator[ j ] = scl_list[ i ][ j + slash_offset + 1 ] + + j = j + 1 + } + + } else { + denominator[ 0 ] = '1' + } + + + scl_multiplier = str_to_num( numerator ) / str_to_num( denominator ) + } + + scl_multipliers[ i ] = scl_multiplier + + // logf( "multiplier written to %d: %f\n", i, scl_multipliers[ i ] ) + + i = i + 1 + + } + + i = 0 while i < get_xsize( scl_multipliers ) { + + i = i + 1 + } + + i = 0 while i < 120 { + + multiplier_power = floor( ( i - tuning_center_midi ) / scl_size ) + // logf( "multiplier power: %d\n", multiplier_power ) + + center_offset = pow( scl_multipliers[ scl_size - 1 ], multiplier_power ) + // logf( "center offset: %d\n", center_offset ) + + if ( i < tuning_center_midi ) { + scl_pitches[ i ] = freq_to_sp( tuning_center_freq * scl_multipliers[ ( ( ( i - tuning_center_midi ) % scl_size ) - 1 ) + scl_size ] * center_offset ) + } + if ( i > tuning_center_midi ) { + scl_pitches[ i ] = freq_to_sp( tuning_center_freq * scl_multipliers[ ( i - tuning_center_midi ) % ( scl_size ) - 1 ] * center_offset ) + } + if ( ( tuning_center_midi - i ) % scl_size == 0 ) { + scl_pitches[ i ] = freq_to_sp( tuning_center_freq * center_offset ) + } + + // logf( "pitch written to %d: %d\n", i, scl_pitches[ i ] ) + + i = i + 1 + + } + +} + +fn freq_to_sp( $freq ) { + + ret( 30720 - log2( $freq / 16.3339 ) * 3072 ) + +} + +fn sanitize( $input ) +{ + + $output = clone( $input ) + + i = 0 while i < get_xsize( $input ) { + + + if $input[ i ] == '/' { $output[ i ] = '_' } + if $input[ i ] == '\\' { $output[ i ] = '_' } + if $input[ i ] == '\'' { $output[ i ] = '_' } + if $input[ i ] == '\"' { $output[ i ] = '_' } + if $input[ i ] == '\ ' { $output[ i ] = '_' } + if $input[ i ] == ':' { $output[ i ] = '_' } + if $input[ i ] == ',' { $output[ i ] = '_' } + if $input[ i ] == '.' { $output[ i ] = '_' } + + i = i + 1 + + } + + ret( $output ) + +} + +fn strrchr( $s, $c ) +{ + + $i = 0 + $k = 0 + + for( $i = strlen( $s ); $i >= 0; $i - 1 ) + { + + if $s[ $i ] == $c { + + $k = 1 + break + + } + + } + + if $k { ret( $i ) } else { ret( -1 ) } + +} + +fn basename( $p, $x ) +{ + + $n = "" + strcat( $n, 0, $p, strrchr( $p, '/' ) + 1 ) + + if $x + { + + ret( $n ) + + } else { + + $nnxl = strrchr( $n, '.' ) + $nnx = new( $nnxl, 1, INT8 ) + + $i = 0 while $i < get_size( $nnx ) + { + + $nnx[ $i ] = $n[ $i ] + + $i = $i + 1 + + } + + ret( $nnx ) + + } + + +} + +fn show_log() +{ + + $l = get_system_log() + clear() + print( $l, 0, 0, WHITE, CENTER | CENTER ) + frame() + remove( $l ) + + while 1 + { + while( get_event() ) { if EVT[ EVT_TYPE ] == EVT_QUIT { halt } } + frame() + } + +}