[Sounds::] Sound Effects. To register the names associated with sound resource numbers, which are defined to allow the final story file to play sound effects. @ The following is called to activate the feature: = void Sounds::start(void) { PluginCalls::plug(MAKE_SPECIAL_MEANINGS_PLUG, Sounds::make_special_meanings); PluginCalls::plug(NEW_BASE_KIND_NOTIFY_PLUG, Sounds::new_base_kind_notify); PluginCalls::plug(NEW_INSTANCE_NOTIFY_PLUG, Sounds::new_named_instance_notify); PluginCalls::plug(PRODUCTION_LINE_PLUG, Sounds::production_line); } int Sounds::production_line(int stage, int debugging, stopwatch_timer *sequence_timer) { if (stage == INTER1_CSEQ) { BENCH(RTMultimedia::compile_sounds); } return FALSE; } @h One special meaning. We add one special meaning for assertions, to catch sentences with the shape "Sound... is the file...". = int Sounds::make_special_meanings(void) { SpecialMeanings::declare(Sounds::new_sound_SMF, I"new-sound", 2); return FALSE; } int Sounds::new_sound_SMF(int task, parse_node *V, wording *NPs) { wording SW = (NPs)?(NPs[0]):EMPTY_WORDING; wording OW = (NPs)?(NPs[1]):EMPTY_WORDING; switch (task) { case ACCEPT_SMFT: if (((SW)) && ((OW))) { parse_node *O = <>; (SW); V->next = <>; V->next->next = O; return TRUE; } break; case PASS_1_SMFT: Sounds::register_sound(Node::get_text(V->next), Node::get_text(V->next->next)); break; } return FALSE; } @ And this is the Preform grammar needed: = ::= | ==> { pass 2 } ==> { pass 1 } ::= file ==> { TRUE, RP[1] } ::= sound ... ==> { 0, Diagrams::new_UNPARSED_NOUN(W) } @ The syntax for sound effects allows for alt-texts, exactly as for figures. = ::= ( ) | ==> { R[1], -, <> = R[2] } ==> { pass 1 } ::= | ==> { pass 1 } ... ==> @; @ = StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundNotTextual), "a sound effect can only be declared as a quoted file name", "which should be the name of an AIFF or OGG file inside the Sounds " "subfolder of the project's .materials folder. For instance, 'Sound " "of Swordplay is the file \"Crossed Swords.aiff\".'"); ==> { 0, - }; @ In assertion pass 1, then, the following is called on any sentence which has been found to create a sound: = void Sounds::register_sound(wording W, wording FN) { <> = -1; if ((FN)) { int wn = <>; if (wn > 0) Word::dequote(wn); if (<> > 0) Word::dequote(<>); @; int id = Task::get_next_free_blorb_resource_ID(); TEMPORARY_TEXT(leaf) WRITE_TO(leaf, "%N", wn); if (Str::is_whitespace(leaf)) { StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundWhiteSpace), "this is not a filename I can use", "because it is either empty or contains only spaces."); return; } filename *sound_file = ResourceFinder::find_resource(Task::sounds_department(), leaf, FN); DISCARD_TEXT(leaf) if (sound_file) { Sounds::sounds_create(W, id, sound_file, <>); LOGIF(MULTIMEDIA_CREATIONS, "Created sound effect <%W> = filename '%N' = resource ID %d\n", W, wn, id); } } } @ = Assertions::Creator::vet_name_for_noun(W); if (((W)) && (Rvalues::is_CONSTANT_of_kind(<>, K_sound_name))) { StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundDuplicate), "this is already the name of a sound effect", "so there must be some duplication somewhere."); return; } @h One significant kind. = (early code) kind *K_sound_name = NULL; @ This is created by an Inter kit early in Inform's run; the function below detects that this has happened, and sets |K_sound_name| to point to it. = int Sounds::new_base_kind_notify(kind *new_base, text_stream *name, wording W) { if (Str::eq_wide_string(name, L"SOUND_NAME_TY")) { K_sound_name = new_base; return TRUE; } return FALSE; } @h Significant new instances. This structure of additional data is attached to each sound instance: = typedef struct sounds_data { struct wording name; /* text of name */ struct filename *filename_of_sound_file; /* relative to the Resources folder */ int sound_number; /* resource number of this picture inside Blorb */ int alt_description; /* word number of double-quoted description */ struct instance *as_instance; CLASS_DEFINITION } sounds_data; @ We allow instances of "sound name" to be created only through the above code calling //Sounds::sounds_create//. If any other proposition somehow manages to make a sound, a problem message is thrown. = int allow_sound_creations = FALSE; instance *Sounds::sounds_create(wording W, int id, filename *sound_file, int alt) { allow_sound_creations = TRUE; Assert::true(Propositions::Abstract::to_create_something(K_sound_name, W), CERTAIN_CE); allow_sound_creations = FALSE; instance *I = Instances::latest(); sounds_data *sd = FEATURE_DATA_ON_INSTANCE(sounds, I); sd->filename_of_sound_file = sound_file; sd->name = W; sd->sound_number = id; sd->alt_description = alt; sd->as_instance = I; return I; } int Sounds::new_named_instance_notify(instance *I) { if ((K_sound_name) && (Kinds::eq(Instances::to_kind(I), K_sound_name))) { if (allow_sound_creations == FALSE) StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_BackdoorSoundCreation), "this is not the way to create a new sound name", "which should be done with a special 'Sound ... is the file ...' " "sentence."); ATTACH_FEATURE_DATA_TO_SUBJECT(sounds, I->as_subject, CREATE(sounds_data)); return TRUE; } return FALSE; } @h Blurb and manifest. The sounds manifest is used by the implementation of Glulx within the Inform application to connect picture ID numbers with filenames relative to the |.materials| folder for its project. (It's part of the XML manifest file created from |Figures.w|.) = void Sounds::write_sounds_manifest(OUTPUT_STREAM) { if (FEATURE_INACTIVE(sounds)) return; sounds_data *sd; if (NUMBER_CREATED(sounds_data) == 0) return; WRITE("Sounds\n"); WRITE("\n"); INDENT; LOOP_OVER(sd, sounds_data) { WRITE("%d\n", sd->sound_number); TEMPORARY_TEXT(rel) Filenames::to_text_relative(rel, sd->filename_of_sound_file, Projects::materials_path(Task::project())); WRITE("%S\n", rel); DISCARD_TEXT(rel) } OUTDENT; WRITE("\n"); } @ The following writes Blurb commands for all of the sounds. = void Sounds::write_blurb_commands(OUTPUT_STREAM) { if (FEATURE_INACTIVE(sounds)) return; sounds_data *sd; LOOP_OVER(sd, sounds_data) { wchar_t *desc = L""; if (sd->alt_description >= 0) desc = Lexer::word_text(sd->alt_description); if (Wide::len(desc) > 0) WRITE("sound %d \"%f\" \"%N\"\n", sd->sound_number, sd->filename_of_sound_file, sd->alt_description); else WRITE("sound %d \"%f\"\n", sd->sound_number, sd->filename_of_sound_file); } } @ The following is used only with the "separate figures" release option. = void Sounds::write_copy_commands(release_instructions *rel) { if (FEATURE_INACTIVE(sounds)) return; sounds_data *sd; LOOP_OVER(sd, sounds_data) ReleaseInstructions::add_aux_file(rel, sd->filename_of_sound_file, Task::released_sounds_path(), L"--", SEPARATE_SOUNDS_PAYLOAD); }