Generating MIDI with abcjs

It’s been a while since I worked on the MIDI part of abcjs, and it shows! When Greg first developed it, he put in a widget that would use QuickTime. Pretty clever at the time. But that doesn’t work anywhere any more, so I removed it. I have some plans for generating MIDI right on the web page, but that will have to wait until the next version.

For this version I concentrated on making the MIDI generation better.

I refactored the generation to use three passes: 1) identify the parts of the abcjs object that affect sound and put them in chronological order, 2) Turn those conceptual events into specific events, 3) generate MIDI. That made it much easier to add features: all of the “artistic” stuff is in the second step.

The two main features I added were guitar chord generation and grace notes. Both of those required some artistic license.

I tried to find a rule of thumb for how long a grace note is, but there really isn’t one. It depends on the style of music whether the grace note is before the beat or on the beat. The length of a grace note is variable. It is unclear whether it should be a fraction of a beat or a fixed amount of time, independent of the tempo. I just picked something that seemed to work for me.

For guitar chords, in general I wanted to do a boom-chick pattern, but that depended on where the chords were specified in time. This can be vague and differs for different meters. So if boom-chick didn’t work, I played the full chord on the beat. If a chord appears between beats, I pulled it back to the previous beat. This seems to have worked passably for the sample tunes I tried. It’s just that the specification is not very exact, so there’s no way of telling for some of the border cases.

In any case, the result was surprisingly good on some tunes! There is room for improvement and there are many decorations, MIDI commands, and rhythm designations to support to keep me busy for the next version.

And, when it comes down to it, MIDI is never going to sound “good”. The purpose of MIDI is as a proof-reader of the music you create. It’s just hard to stop refining it once I get going.

Money Lost:

Sheet Music in WordPress!

I created a WordPress plugin called “ABC Notation” that makes it easy to put sheet music in your blog. It uses the abcjs JavaScript library developed by Greg Dyke and me. Here’s an example of how it works:

The above music was created with this shortcode:

[abcjs engraver="{ staffwidth:600 }"]
X: 36
T:Money Lost
M:3/4
L:1/8
Q:3/4=40
C:Paul Rosen
S:Copyright 2007, Paul Rosen
R:Klezmer
K:Dm
Ade|:"Dm"(f2d)e gf|"A7"e2^c4|"Gm"B>>^c BA BG|"A"A3Ade|"Dm"(f2d)e gf|
"A7"e2^c4|"Gm"A>>B "A7"AG FE|1"Dm"D3Ade:|2"Dm"D3DEF||:"Gm"(G2D)E FG|
"Dm"A2F4|"Gm"B>>c "A7"BA BG|"Dm"A3 DEF|"Gm"(G2D)EFG|"Dm"A2F4|
"A7;(Edim)"E>>Fx "(A7)"ED^C2|1"Dm"D3DEF:|2"Dm"D6||
[/abcjs]

Integrating Rails and WordPress

I do most of my development in Rails, but many sites need a tab that contains a blog, so I’d like to install WordPress in my Rails app. Here’s how I do it.
(I’m using Passenger for deployment.) We first need to create a space for the blog, and we do that by creating a symbolic link in the Rails’ app’s public folder:

cd path/to/rails/app/public
ln -s /path/to/wordpress/installation news

This separates the WordPress from the Rails app physically, so it makes deployment easier. With the symbolic link, when we go to http:/mydomain.com/news, it should go to the blog.
However, Passenger is going to grab that URL and print out a Rails error message. This needs to be put in the Apache config file to get Passenger to ignore the blog:

<Directory "/path/to/rails/app/public/news">

    PassengerEnabled off
    AllowOverride all
</Directory>



Now, theoretically, that’s all you’d need to do, but the theme for the blog should be the same as the theme for your app. To do that, we need to get WordPress to use our stylesheets, and the code for our header and footer. That means that we should put most of the code in the layout file in partials, and create routes for them. After doing that, my layout file looks like this:

<!DOCTYPE html>

<html>

<head>

    <%= render :partial => '/layouts/dependencies' %>

    <title>Site Title</title>

</head>

<body>

    <%= render :partial => '/layouts/header' %>

    <%= yield %>
    
<%= render :partial => '/layouts/footer' %>

</body>
</html>

and my routes.rb file contains:

get "/styles" => "home#styles"

get "/header" => "home#header"

get "/footer" => "home#footer"

and my home_controller.rb file contains:

def styles
    render :partial => "/layouts/dependencies"

end


def header

    render :partial => "/layouts/header"
end


def footer

    render :partial => "/layouts/footer"

end



And, to use it, I created a little plugin for WordPress. I haven’t packaged it up, but here is the entire code for it. Just put this in the plugins folder, then activate it and look under “settings” for “Rails Theme”.  Put in the URL of the Rails app, and it should use your theme.
Note that because html ids and classes are global, it is easy to have a conflict, so you will probably need to be careful in the names of the css selectors you use. I use the Hybrid WordPress Theme, which doesn’t impose much and has lots of functionality. I had to rename a class that was named “content” because it conflicted. I also needed to add these two styles to override Hybrid styles:

/* for wordpress */
#body-container {
margin: 0;
}
#header-container {
display:none;
}

Here is the entire code of the plugin:

<?php
/*
Plugin Name: Rails Theme
Plugin URI: http://paulrosen.net
Description: This plugin causes WP to call into a Rails app to get stylesheets, javascripts, header, and footer info. This allows WP to seamlessly be integrated into a rails app.
Version: 1.0
Author: Paul Rosen
Author URI: http://paulrosen.net
License: Private
*/

/////////////////////////////////////////////////////
// options in the admin section
////////////////////////////////////////////////////

//------------- Add the admin menu -------------
function rails_admin_options() {
if (!current_user_can('manage_options'))  {
wp_die( __('You do not have sufficient permissions to access this page.') );
}
echo '<div>';
echo '<h2>Rails Theme Options</h2>';
echo 'Options for the Rails Theme Plugin';
echo '<form action="options.php" method="post">';
echo settings_fields("rails_theme_options");
echo do_settings_sections("rails_theme");
echo '<input name="Submit" type="submit" value="Save Changes" />';
echo '</form></div>';
}

function rails_modify_menu() {
add_options_page('Rails Theme', 'Rails Theme', 'manage_options', __FILE__, 'rails_admin_options');
}

add_action('admin_menu', 'rails_modify_menu');

//----------------- Add the settings for this plugin ---------------------
function rails_theme_admin_init(){
register_setting( 'rails_theme_options', 'rails_theme_options', 'rails_theme_options_validate' );
add_settings_section('rails_theme_main', 'Settings', 'rails_theme_section_text', 'rails_theme');
add_settings_field('rails_theme_text_string', 'Base URL (e.g. http://example.com)', 'rails_theme_setting_string', 'rails_theme', 'rails_theme_main');
}

add_action('admin_init', 'rails_theme_admin_init');

function rails_theme_section_text() {
echo '<p>Set the location of the rails web service that will respond to the urls: /styles, /header, and /footer.</p>';
}

function rails_theme_setting_string() {
$options = get_option('rails_theme_options');
echo "<input id='rails_theme_text_string' name='rails_theme_options[url]' size='40' type='text' value='" . $options['url'] . "' />";
}

function rails_theme_options_validate($input) {
$newinput['url'] = trim($input['url']);
return $newinput;
}

/////////////////////////////////////////////////////
// Calling rails app
/////////////////////////////////////////////////////

add_action ( 'wp_head', 'load_stylesheets');

function load_stylesheets() {
$options = get_option('rails_theme_options');
$base_url = $options['url'];

$style = file_get_contents($base_url . "/styles");
$style = str_replace("/stylesheets", $base_url . "/stylesheets", $style);
$style = str_replace("/javascripts", $base_url . "/javascripts", $style);
echo $style;
}

add_action ( 'hybrid_before_html', 'load_header');

function load_header() {
$options = get_option('rails_theme_options');
$base_url = $options['url'];

$contents = file_get_contents($base_url . "/header");
$contents = str_replace("href='/", "href='" . $base_url . "/", $contents);
echo $contents;
}

add_action( 'wp_footer', 'load_footer' );

function load_footer() {
$options = get_option('rails_theme_options');
$base_url = $options['url'];

echo file_get_contents($base_url . "/footer");
}

?>

Creating Custom Input Forms in WordPress

There are three Actors in a typical WordPress site (at least the typical ones I create). There is, of course, the Public, who needs to see a well-formatted and clear site, and there is, of course, the Administrator (that is, me), who needs to have an easy way to set up the site and make large changes to it, and there is a third user: the Editor, who is going to be adding the information to the site on a day-to-day basis. The Editor doesn’t have technical skills and should be shown a clean interface where it is obvious how to create and modify rows in the database.

Unfortunately, it is not straightforward to do this in WordPress; this post is a possible path to getting there. Conceptually,  the Administrator would like to create a custom table and specify whatever fields are necessary, then show only those fields to the Editor, who can easily manipulate that table. This example creates a Gigs table, with the fields: Title, Date, End Date, URL, Place, Time, and Price.

First, add the plugin, Custom Post Types UI, and use it to create a Gigs type with Title and Custom Fields checked as the fields. Then add the plugin Advanced Custom Fields and create a Gigs type with the fields you want. Under “Post types” check the gig type.

Then, to get the new columns to show up when adding gigs, add this code to functions.php:

/* Custom Edit Columns */
add_filter("manage_edit-gigs_columns", "gigs_columns");

// rearrange the columns on the Edit screens
function gigs_columns($defaults) {
	// preserve the default date and comment columns
	$date = $defaults['date'];

	// remove default date and comments
	unset($defaults['comments']);
	unset($defaults['date']);

	// insert custom columns
	$defaults['gig_date'] = __('Date');
	$defaults['gig_end_date'] = __('End Date');
	$defaults['url'] = __('URL');
	$defaults['place'] = __('Place');
	$defaults['time'] = __('Time');
	$defaults['price'] = __('Price');

	// restore default date and comments
	$defaults['date'] = $date;

return $defaults;
}

add_action("manage_posts_custom_column", "gigs_custom_column");

// put new columns in admin view
function gigs_custom_column($column) {
	global $post;
	if ($post->post_type == 'gigs') {
		switch ($column) {
		case 'gig_date': echo get_field('date'); break;
		case 'gig_end_date': echo get_field('end_date'); break;
		case 'url': echo get_field('url'); break;
		case 'place': echo get_field('place'); break;
		case 'time': echo get_field('time'); break;
		case 'price': echo get_field('price'); break;
		}
	}
}

Now, when the Editor goes to the Gigs to add one, they will see the fields you created in a way that is easy to update.

But, they won’t show up yet. You need to create a custom page template and associate it with a page. This is slightly different depending on your theme, but here is some code for the Hybrid theme:

<?php 
/* 
Template Name: Custom - Gigs 
*/ 
$this_type = 'gigs'; 
get_header(); ?> 
<div id="content"> 
<?php do_atomic( 'before_content' ); // hybrid_before_content 
?> 
<?php 
global $post; 
$pg = get_page($post->ID); 
setup_postdata($pg); 
echo "<div class='page-content'>"; 
the_content(); 
echo "</div>"; ?> 
<?php 
$orig_query = $wp_query; 
$wp_query = set_custom_query($this_type); 
?> 
<?php 
if ($wp_query->have_posts() ) : while ($wp_query->have_posts() ) : $wp_query->the_post(); ?> 
<div id="post-<?php the_ID(); ?>" 
<?php post_class(); ?>> 
<?php the_custom_post($this_type); ?> 
</div>
<!-- #post-<?php the_ID(); ?> --> 
<?php endwhile; endif; ?> 
<?php wp_reset_query(); 
$wp_query = $orig_query; ?> 
<?php do_atomic( 'after_content' ); // hybrid_after_content ?> 
</div>
<!-- #content --> 
<?php get_footer(); 
?>

I’ve abstracted the parts that are likely to change, so you then need to create the following functions in functions.php:

    function set_custom_query($ty) {
        switch ($ty) {
        case 'gigs':
            $wp_query = new WP_Query();
            $query_args = 'post_type=reviews';
            $wp_query-&gt;query($query_args);
            return $wp_query;
        }
    }

    function format_date($d) {
        if ($d == "")
            return "";
        //$date = DateTime::createFromFormat('Y-m-d', $d);
        //return date_format($date, 'D M d, Y');
        return date("D M d, Y", strtotime($d));
    }

    // Call this inside the loop to output the entire post
    function the_custom_post($ty) {
        switch ($ty) {
        case 'gigs':
            $url = get_field('url');
            if ($url && $url != "")
                the_title( "<div class='entry-title'><a href='" . $url . "' title='" . the_title_attribute( 'echo=0' ) . "' >", "</a></div>" );
            else
                the_title( "<div class='entry-title'>", "</div>" );

            // other meta info
            echo "<div class='gig_details'>";
            echo format_date(get_field('date'));
            $end_date = format_date(get_field('end_date'));
            if ($end_date != "")
                echo " - " . $end_date;
            //echo get_date_from_gmt(get_post_meta(get_the_ID(), "date", true), 'M d, Y');
            echo " ";

            $string = get_field('time');
            if ($string !== "") {
                echo $string;
            }

            echo " ";
            echo get_field('price');
            echo " ";
            echo get_field('place');
            echo "</div>";

            echo '<div>';
            the_excerpt();
            echo '</div>';
            break;
        }
    }

Now, create a page named Gigs, and set its template to “Custom – Gigs” and the gigs that the Editor creates will appear on that page.