Skip to content

Commit

Permalink
Add composition versions + some wavetable optimizations
Browse files Browse the repository at this point in the history
 * Add support for new compositions to be saved as a version of an old composition.  This makes requests for the old composition resolve to the new one.
   * Backend support and migrations
   * Frontend support with dedicated button to save as new version (if editing an old version)
 * Optimize wavetable sampling codepaths to reduce lookups in the case of only having a single dimension
 * Some other fixes + refactoring
  • Loading branch information
Ameobea committed Jul 28, 2024
1 parent 5c9c369 commit f654bf0
Show file tree
Hide file tree
Showing 18 changed files with 332 additions and 178 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"importOrderCaseInsensitive": true,
"importOrderMergeDuplicateImports": true,
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
"importOrderBuiltinModulesToTop": true
"importOrderBuiltinModulesToTop": true,
"parser": "babel-ts"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE compositions DROP COLUMN IF EXISTS composition_version;
ALTER TABLE compositions DROP COLUMN IF EXISTS parent_id;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE compositions ADD COLUMN IF NOT EXISTS composition_version INT NOT NULL DEFAULT 0;
ALTER TABLE compositions ADD COLUMN IF NOT EXISTS parent_id BIGINT NULL DEFAULT NULL;

ALTER TABLE compositions ADD CONSTRAINT IF NOT EXISTS parent_id_composition_version UNIQUE (parent_id, composition_version);
2 changes: 2 additions & 0 deletions backend/rustfmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ match_arm_blocks = false
overflow_delimited_expr = true
edition = "2018"
normalize_doc_attributes = true
# todo: enable \/ and format project
# tab_spaces = 2
2 changes: 1 addition & 1 deletion backend/src/db_util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod private_sample_libraries;
// Facilitate getting the primary key of the last inserted item
//
// https://github.com/diesel-rs/diesel/issues/1011#issuecomment-315536931
sql_function! {
define_sql_function! {
fn last_insert_id() -> BigInt;
}

Expand Down
17 changes: 17 additions & 0 deletions backend/src/models/compositions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub struct NewCompositionRequest {
pub content: Map<String, Value>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
#[serde(rename = "parentID")]
pub parent_id: Option<i64>,
}

#[derive(Insertable)]
Expand All @@ -18,6 +21,17 @@ pub struct NewComposition {
pub description: String,
pub content: String,
pub user_id: Option<i64>,
pub composition_version: i32,
pub parent_id: Option<i64>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompositionVersion {
pub id: i64,
pub title: String,
pub description: String,
pub composition_version: i32,
}

#[derive(Serialize)]
Expand All @@ -29,6 +43,7 @@ pub struct CompositionDescriptor {
pub tags: Vec<String>,
pub user_id: Option<i64>,
pub user_name: Option<String>,
pub versions: Vec<CompositionVersion>,
}

#[derive(Serialize, Queryable)]
Expand All @@ -39,6 +54,8 @@ pub struct Composition {
pub description: String,
pub content: String,
pub user_id: Option<i64>,
pub composition_version: i32,
pub parent_id: Option<i64>,
}

#[derive(Insertable)]
Expand Down
175 changes: 139 additions & 36 deletions backend/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::{
},
models::{
compositions::{
Composition, CompositionDescriptor, NewComposition, NewCompositionRequest,
NewCompositionTag,
Composition, CompositionDescriptor, CompositionVersion, NewComposition,
NewCompositionRequest, NewCompositionTag,
},
effects::{Effect, InsertableEffect},
synth_preset::{
Expand Down Expand Up @@ -95,6 +95,65 @@ pub async fn save_composition(
login_token: MaybeLoginToken,
) -> Result<Json<i64>, String> {
let user_id = get_logged_in_user_id(&conn, login_token).await;

// if adding a version to an existing composition, the user must be logged in and own the parent
let (parent_id, composition_version) = if let Some(parent_id) = composition.0.parent_id {
let Some(user_id) = user_id else {
return Err("User must be logged in to save a new version of a composition".to_owned());
};

let (id, parent_id) = match conn
.run(
move |conn| -> QueryResult<Option<(i64, Option<i64>, Option<i64>)>> {
schema::compositions::table
.filter(schema::compositions::dsl::id.eq(parent_id))
.select((
schema::compositions::dsl::id,
schema::compositions::dsl::parent_id,
schema::compositions::dsl::user_id,
))
.order_by(schema::compositions::dsl::composition_version.asc())
.first(conn)
.optional()
},
)
.await
{
Ok(Some((id, parent_id, parent_user_id))) => {
if parent_user_id != Some(user_id) {
return Err("User does not own the parent composition".to_owned());
}

(id, parent_id)
},
Ok(None) => return Err("Parent composition not found".to_owned()),
Err(err) => {
error!("Error querying parent composition: {:?}", err);
return Err("Error querying parent composition from the database".to_owned());
},
};

let root_parent_id = parent_id.unwrap_or(id);
let latest_version: Option<i32> = conn
.run(move |conn| {
schema::compositions::table
.filter(schema::compositions::dsl::parent_id.eq(Some(root_parent_id)))
.select(diesel::dsl::max(
schema::compositions::dsl::composition_version,
))
.first::<Option<i32>>(conn)
})
.await
.map_err(|err| {
error!("Error querying composition version: {:?}", err);
"Error querying composition version from the database".to_owned()
})?;

(Some(root_parent_id), latest_version.unwrap_or(0) + 1)
} else {
(None, 0)
};

let new_composition = NewComposition {
title: composition.0.title,
description: composition.0.description,
Expand All @@ -103,8 +162,14 @@ pub async fn save_composition(
format!("Failed to serialize composition to JSON string")
})?,
user_id,
composition_version,
parent_id,
};
let tags: Vec<String> = if new_composition.parent_id.is_none() {
std::mem::take(&mut composition.0.tags)
} else {
Vec::new()
};
let tags: Vec<String> = std::mem::take(&mut composition.0.tags);

let saved_composition_id = conn
.run(move |conn| {
Expand Down Expand Up @@ -148,26 +213,38 @@ pub async fn save_composition(
Ok(Json(saved_composition_id))
}

async fn query_composition_by_id(
conn: WebSynthDbConn,
composition_id: i64,
) -> QueryResult<Option<Composition>> {
use crate::schema::compositions;

conn.run(move |conn| {
compositions::table
.filter(
compositions::dsl::id
.eq(composition_id)
.or(compositions::dsl::parent_id.eq(composition_id)),
)
.order_by(compositions::dsl::composition_version.desc())
.first::<Composition>(conn)
.optional()
})
.await
}

#[get("/compositions/<composition_id>")]
pub async fn get_composition_by_id(
conn: WebSynthDbConn,
composition_id: i64,
) -> Result<Option<Json<Composition>>, String> {
use crate::schema::compositions::dsl::*;

let composition_opt = match conn
.run(move |conn| compositions.find(composition_id).first::<Composition>(conn))
query_composition_by_id(conn, composition_id)
.await
{
Ok(composition) => Some(Json(composition)),
Err(diesel::NotFound) => None,
Err(err) => {
error!("Error querying composition by id: {:?}", err);
return Err("Error querying composition by id from the database".to_string());
},
};

Ok(composition_opt)
.map_err(|err| {
error!("Error querying composition: {:?}", err);
"Error querying composition from the database".to_string()
})
.map(|comp| comp.map(Json))
}

#[get("/compositions")]
Expand All @@ -178,7 +255,7 @@ pub async fn get_compositions(

let (all_compos, all_compos_tags) = conn
.run(
|conn| -> QueryResult<(Vec<(_, _, _, _, _)>, Vec<EntityIdTag>)> {
|conn| -> QueryResult<(Vec<(_, _, _, _, Option<i64>, i32, _)>, Vec<EntityIdTag>)> {
let all_compos = compositions::table
.left_join(
users::table.on(compositions::dsl::user_id.eq(users::dsl::id.nullable())),
Expand All @@ -188,8 +265,11 @@ pub async fn get_compositions(
compositions::dsl::title,
compositions::dsl::description,
compositions::dsl::user_id,
compositions::dsl::parent_id,
compositions::dsl::composition_version,
users::dsl::username.nullable(),
))
.order_by(compositions::dsl::id.asc())
.load(conn)?;

let all_compos_tags: Vec<EntityIdTag> = compositions_tags::table
Expand All @@ -201,34 +281,57 @@ pub async fn get_compositions(
)
.await
.map_err(|err| {
error!("Error querying compositions: {:?}", err);
error!("Error querying compositions: {err:?}");
"Error querying compositions from the database".to_string()
})?;

let mut tags_by_compo_id = all_compos_tags
.into_iter()
.into_group_map_by(|tag| tag.entity_id);

let all_compos = all_compos
.into_iter()
.map(|(id, title, description, user_id, user_name)| {
let tags = tags_by_compo_id
.remove(&id)
.unwrap_or_default()
.iter()
.map(|tag| tag.tag.clone())
.collect_vec();

CompositionDescriptor {
let mut compositions_by_root_id: FxHashMap<i64, CompositionDescriptor> = FxHashMap::default();

for (id, title, description, user_id, parent_id, composition_version, user_name) in all_compos {
if let Some(parent_id) = parent_id {
let Some(parent) = compositions_by_root_id.get_mut(&parent_id) else {
error!(
"Composition with parent_id={parent_id} not found; versions should have been \
ordered after the parent"
);
continue;
};

parent.versions.push(CompositionVersion {
id,
title,
description,
tags,
user_id,
user_name,
}
})
.collect_vec();
composition_version,
});

continue;
}

let tags = tags_by_compo_id
.remove(&id)
.unwrap_or_default()
.iter()
.map(|tag| tag.tag.clone())
.collect_vec();

let descriptor = CompositionDescriptor {
id,
title,
description,
tags,
user_id,
user_name,
versions: Vec::new(),
};
compositions_by_root_id.insert(id, descriptor);
}

let mut all_compos = compositions_by_root_id.into_values().collect_vec();
all_compos.sort_unstable_by_key(|comp| comp.id);

Ok(Json(all_compos))
}
Expand Down
2 changes: 2 additions & 0 deletions backend/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ diesel::table! {
description -> Text,
content -> Longtext,
user_id -> Nullable<Bigint>,
composition_version -> Integer,
parent_id -> Nullable<Bigint>,
}
}

Expand Down
Loading

0 comments on commit f654bf0

Please sign in to comment.